Merge "Update internal gradle plugin." am: 66c78e3f88 am: cc321c9422
am: 1b7fc2999a
* commit '1b7fc2999a1608f93fba7cf97c60fda8d5c9a6d5':
diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml
index 471d956..bc1b5d4 100755
--- a/.idea/codeStyleSettings.xml
+++ b/.idea/codeStyleSettings.xml
@@ -61,6 +61,10 @@
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
+ <GroovyCodeStyleSettings>
+ <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
+ <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
+ </GroovyCodeStyleSettings>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
@@ -115,206 +119,6 @@
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
- <arrangement>
- <groups>
- <group>
- <type>GETTERS_AND_SETTERS</type>
- <order>KEEP</order>
- </group>
- </groups>
- <rules>
- <rule>
- <match>
- <AND>
- <FIELD />
- <FINAL />
- <PUBLIC />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <FINAL />
- <PROTECTED />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <FINAL />
- <PACKAGE_PRIVATE />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <FINAL />
- <PRIVATE />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <PUBLIC />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <PROTECTED />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <PACKAGE_PRIVATE />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <PRIVATE />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <FINAL />
- <PUBLIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <FINAL />
- <PROTECTED />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <FINAL />
- <PACKAGE_PRIVATE />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <FINAL />
- <PRIVATE />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <PUBLIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <PROTECTED />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <PACKAGE_PRIVATE />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <FIELD />
- <PRIVATE />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <FIELD />
- </match>
- </rule>
- <rule>
- <match>
- <CONSTRUCTOR />
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <METHOD />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <METHOD />
- </match>
- </rule>
- <rule>
- <match>
- <ENUM />
- </match>
- </rule>
- <rule>
- <match>
- <INTERFACE />
- </match>
- </rule>
- <rule>
- <match>
- <AND>
- <CLASS />
- <STATIC />
- </AND>
- </match>
- </rule>
- <rule>
- <match>
- <CLASS />
- </match>
- </rule>
- </rules>
- </arrangement>
</codeStyleSettings>
</value>
</option>
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index ac27d9f..56c69bf 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -88,6 +88,9 @@
</inspection_tool>
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="insideTryAllowed" value="false" />
+ </inspection_tool>
<inspection_tool class="CheckDtdRefs" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CheckEmptyScriptTag" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CheckTagEmptyBody" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -131,6 +134,10 @@
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
+ <option name="insideTryAllowed" value="false" />
+ </inspection_tool>
<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]*" />
diff --git a/.idea/libraries/ant.xml b/.idea/libraries/ant.xml
index df52e12..d6acead 100644
--- a/.idea/libraries/ant.xml
+++ b/.idea/libraries/ant.xml
@@ -1,7 +1,7 @@
<component name="libraryTable">
<library name="ant">
<CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/apache/ant/ant/1.8.0/ant-1.8.0.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/ant/ant/1.8.0/ant-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
diff --git a/.idea/libraries/bouncy_castle.xml b/.idea/libraries/bouncy_castle.xml
index 70ec93b..cf5a14f 100644
--- a/.idea/libraries/bouncy_castle.xml
+++ b/.idea/libraries/bouncy_castle.xml
@@ -1,13 +1,13 @@
<component name="libraryTable">
<library name="bouncy-castle">
<CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcpkix-jdk15on/1.48/bcpkix-jdk15on-1.48.jar!/" />
<root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcpkix-jdk15on/1.48/bcpkix-jdk15on-1.48.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcpkix-jdk15on/1.48/bcpkix-jdk15on-1.48-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48-sources.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcpkix-jdk15on/1.48/bcpkix-jdk15on-1.48-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
diff --git a/.idea/libraries/builder_model.xml b/.idea/libraries/builder_model.xml
index 6ed964d..5eb367b 100644
--- a/.idea/libraries/builder_model.xml
+++ b/.idea/libraries/builder_model.xml
@@ -1,11 +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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/builder-model/builder-model-0.11.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/builder-model/builder-model-0.7.0-sources.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/builder-model/builder-model-0.11.0-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
diff --git a/.idea/libraries/easymock_tools.xml b/.idea/libraries/easymock_tools.xml
index 5520660..11f89ea 100644
--- a/.idea/libraries/easymock_tools.xml
+++ b/.idea/libraries/easymock_tools.xml
@@ -1,11 +1,11 @@
<component name="libraryTable">
<library name="easymock-tools">
<CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/easymock/easymock/3.1/easymock-3.1.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/easymock/easymock/3.1/easymock-3.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/easymock/easymock/3.1/easymock-3.1-sources.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/easymock/easymock/3.1/easymock-3.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
diff --git a/.idea/libraries/ecj.xml b/.idea/libraries/ecj.xml
new file mode 100644
index 0000000..0e436aa
--- /dev/null
+++ b/.idea/libraries/ecj.xml
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+ <library name="ecj">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/eclipse/jdt/core/compiler/ecj/4.2.2/ecj-4.2.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/eclipse/jdt/core/compiler/ecj/4.2.2/ecj-4.2.2-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
index 5b6c54a..964794b 100644
--- a/.idea/libraries/gradle_tooling_api_1_9.xml
+++ b/.idea/libraries/gradle_tooling_api_1_9.xml
@@ -1,11 +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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/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/guava_tools.xml b/.idea/libraries/guava_tools.xml
index 2067a7a..66c3d35 100644
--- a/.idea/libraries/guava_tools.xml
+++ b/.idea/libraries/guava_tools.xml
@@ -1,11 +1,14 @@
<component name="libraryTable">
<library name="guava-tools">
+ <ANNOTATIONS>
+ <root url="file://$PROJECT_DIR$/../../prebuilts/tools/common/guava-tools/annotations" />
+ </ANNOTATIONS>
<CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/guava/guava/13.0.1/guava-13.0.1.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/guava/guava/13.0.1/guava-13.0.1-sources.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
diff --git a/.idea/libraries/javawriter.xml b/.idea/libraries/javawriter.xml
index 14d3470..a62619d 100644
--- a/.idea/libraries/javawriter.xml
+++ b/.idea/libraries/javawriter.xml
@@ -1,11 +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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/squareup/javawriter/2.5.0/javawriter-2.5.0.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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/squareup/javawriter/2.5.0/javawriter-2.5.0-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 e92068d..ef9fe6d 100644
--- a/.idea/libraries/lombok_ast.xml
+++ b/.idea/libraries/lombok_ast.xml
@@ -1,18 +1,18 @@
<component name="libraryTable">
<library name="lombok-ast">
<CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.2/lombok-ast-0.2.2.jar!/" />
</CLASSES>
<JAVADOC>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-javadoc.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.2/lombok-ast-0.2.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
- <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" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.2/lombok-ast-0.2.2-sources.jar!/src/main" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.2/lombok-ast-0.2.2-sources.jar!/src/printer" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.2/lombok-ast-0.2.2-sources.jar!/src/template" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.2/lombok-ast-0.2.2-sources.jar!/src/ecjTransformer" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.2/lombok-ast-0.2.2-sources.jar!/src/javacTransformer" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.2/lombok-ast-0.2.2-sources.jar!/build/lombok.ast_generatedSource" />
</SOURCES>
</library>
</component>
\ No newline at end of file
diff --git a/.idea/libraries/mockito.xml b/.idea/libraries/mockito.xml
new file mode 100644
index 0000000..7113f6f
--- /dev/null
+++ b/.idea/libraries/mockito.xml
@@ -0,0 +1,12 @@
+<component name="libraryTable">
+ <library name="mockito">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/mockito/mockito-all/1.9.5/mockito-all-1.9.5.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/mockito/mockito-all/1.9.5/mockito-all-1.9.5-sources.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/mockito/mockito-all/1.9.5/mockito-all-1.9.5.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
diff --git a/.idea/libraries/proguard_gradle.xml b/.idea/libraries/proguard_gradle.xml
index b7d046f..ed8a594 100644
--- a/.idea/libraries/proguard_gradle.xml
+++ b/.idea/libraries/proguard_gradle.xml
@@ -1,13 +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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.10/proguard-gradle-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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.10/proguard-gradle-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
index 69c282f..730c553 100644
--- a/.idea/libraries/slf4j_api.xml
+++ b/.idea/libraries/slf4j_api.xml
@@ -1,11 +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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/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
index 16e76fa..2181dac 100644
--- a/.idea/libraries/slf4j_simple.xml
+++ b/.idea/libraries/slf4j_simple.xml
@@ -1,11 +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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/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!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/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/misc.xml b/.idea/misc.xml
index b6b92c7..47720a9 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,6 +3,9 @@
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
+ <component name="FrameworkDetectionExcludesConfiguration">
+ <type id="android" />
+ </component>
<component name="IdProvider" IDEtalkID="DCEF6D5FAEEAA0E5B2200920077168C7" />
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="com.android.annotations.Nullable" />
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 28423eb..39cbc68 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -13,6 +13,7 @@
<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$/gradle-import/gradle-import.iml" filepath="$PROJECT_DIR$/gradle-import/gradle-import.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" />
diff --git a/asset-studio/build.gradle b/asset-studio/build.gradle
index 2b95089..73eed12 100644
--- a/asset-studio/build.gradle
+++ b/asset-studio/build.gradle
@@ -1,11 +1,12 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools'
archivesBaseName = 'asset-studio'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':layoutlib-api')
+ compile project(':base:layoutlib-api')
testCompile 'junit:junit:3.8.1'
}
@@ -14,5 +15,3 @@
main.resources.srcDir 'src/main/java'
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/GraphicGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java
index 2b948f4..28b95aa 100644
--- a/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java
+++ b/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java
@@ -205,7 +205,6 @@
* @return the image, or null
* @throws IOException if an unexpected I/O error occurs
*/
- @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) {
@@ -214,7 +213,7 @@
try {
return ImageIO.read(is);
} finally {
- Closeables.closeQuietly(is);
+ Closeables.close(is, true /* swallowIOException */);
}
}
@@ -226,14 +225,13 @@
* @return the icon image
* @throws IOException if the image cannot be loaded
*/
- @SuppressWarnings("resource") // Eclipse doesn't know about Closeables#closeQuietly yet
public static BufferedImage getClipartIcon(String name) throws IOException {
InputStream is = GraphicGenerator.class.getResourceAsStream(
"/images/clipart/small/" + name);
try {
return ImageIO.read(is);
} finally {
- Closeables.closeQuietly(is);
+ Closeables.close(is, true /* swallowIOException */);
}
}
@@ -245,14 +243,13 @@
* @return the clip art image
* @throws IOException if the image cannot be loaded
*/
- @SuppressWarnings("resource") // Eclipse doesn't know about Closeables#closeQuietly yet
public static BufferedImage getClipartImage(String name) throws IOException {
InputStream is = GraphicGenerator.class.getResourceAsStream(
"/images/clipart/big/" + name);
try {
return ImageIO.read(is);
} finally {
- Closeables.closeQuietly(is);
+ Closeables.close(is, true /* swallowIOException */);
}
}
diff --git a/asset-studio/src/main/java/images/clipart/big/1-navigation-drawer.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-drawer.png
new file mode 100644
index 0000000..c6e16a1
--- /dev/null
+++ b/asset-studio/src/main/java/images/clipart/big/1-navigation-drawer.png
Binary files differ
diff --git a/asset-studio/src/main/java/images/clipart/big/2-action-overflow.png b/asset-studio/src/main/java/images/clipart/big/2-action-overflow.png
new file mode 100644
index 0000000..573b33f
--- /dev/null
+++ b/asset-studio/src/main/java/images/clipart/big/2-action-overflow.png
Binary files differ
diff --git a/asset-studio/src/main/java/images/clipart/small/1-navigation-drawer.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-drawer.png
new file mode 100644
index 0000000..ba027f7
--- /dev/null
+++ b/asset-studio/src/main/java/images/clipart/small/1-navigation-drawer.png
Binary files differ
diff --git a/asset-studio/src/main/java/images/clipart/small/2-action-overflow.png b/asset-studio/src/main/java/images/clipart/small/2-action-overflow.png
new file mode 100644
index 0000000..cda7b2a
--- /dev/null
+++ b/asset-studio/src/main/java/images/clipart/small/2-action-overflow.png
Binary files differ
diff --git a/base.gradle b/base.gradle
deleted file mode 100644
index ea96d17..0000000
--- a/base.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-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
deleted file mode 100644
index c6cfacb..0000000
--- a/baseVersion.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-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
index 4c266ae..7ddc829 100644
--- a/build-system/.gitignore
+++ b/build-system/.gitignore
@@ -1,22 +1,31 @@
local.properties
+.idea
tests/*/build
tests/api/*/build
tests/applibtest/*/build
tests/assets/*/build
tests/attrOrder/*/build
+tests/basic/lint-report*
+tests/customArtifactDep/*/build
tests/3rdPartyTests/*/build
tests/dependencies/jarProject/build
+tests/dependencies/jarProject2/build
+tests/embedded/*/build
tests/flavorlib/*/build
+tests/flavoredlib/*/build
tests/flavorlibWithFailedTests/*/build
tests/libProguardJarDep/*/build
tests/libProguardLibDep/*/build
tests/libsTest/*/build
tests/multiproject/*/build
+tests/localAarTest/*/build
tests/localJars/*/build
tests/ndkJniLib/*/build
+tests/packagingOptions/jars/*/build
tests/proguardLib/*/build
tests/renderscriptInLib/*/build
tests/repo/*/build
+tests/mavenLocal/*/build
tests/sameNamedLibs/*/build
tests/sameNamedLibs/*/*/build
tests/tictactoe/*/build
diff --git a/build-system/builder-model/build.gradle b/build-system/builder-model/build.gradle
index 9906989..2833da4 100644
--- a/build-system/builder-model/build.gradle
+++ b/build-system/builder-model/build.gradle
@@ -1,18 +1,20 @@
apply plugin: 'java'
apply plugin: 'clone-artifacts'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
dependencies {
- compile project(':common')
+ compile project(':base:common')
}
group = 'com.android.tools.build'
archivesBaseName = 'builder-model'
+version = rootProject.ext.buildVersion
+
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'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
jar.manifest.attributes("Model-Version": "$version")
+sdkJar.manifest.attributes("Model-Version": "$version")
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
index 4cb9b36..fb5783f 100644
--- 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
@@ -31,4 +31,6 @@
* Returns the list of values for the -0 (disabled compression) option, or null
*/
Collection<String> getNoCompress();
+
+ boolean getUseAaptPngCruncher();
}
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
index 3cfd2f2..4e5c2bc 100644
--- 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
@@ -23,20 +23,17 @@
import java.util.Collection;
/**
- * The information for a generated Android artifact.
+ * An Android Artifact.
+ *
+ * This is the entry point for the output of a {@link Variant}. This can be more than one
+ * output in the case of multi-apk where more than one APKs are generated from the same set
+ * of sources.
+ *
*/
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();
+ Collection<AndroidArtifactOutput> getOutputs();
/**
* Returns whether the output file is signed. This is always false for the main artifact
@@ -56,12 +53,12 @@
String getSigningConfigName();
/**
- * Returns the package name of this artifact.
+ * Returns the application id of this artifact.
*
- * @return the package name.
+ * @return the application id.
*/
@NonNull
- String getPackageName();
+ String getApplicationId();
/**
* Returns the name of the task used to generate the source code. The actual value might
@@ -73,12 +70,6 @@
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.
*
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifactOutput.java b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifactOutput.java
new file mode 100644
index 0000000..7778458
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifactOutput.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 Actual output for a {@link AndroidArtifact}
+ */
+public interface AndroidArtifactOutput {
+
+ /**
+ * Returns the output file for this artifact's output.
+ * 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 the name of the task used to generate this artifact output.
+ *
+ * @return the name of the task.
+ */
+ @NonNull
+ String getAssembleTaskName();
+
+ /**
+ * The generated manifest for this variant's artifact's output.
+ */
+ @NonNull
+ File getGeneratedManifest();
+
+ /**
+ * The output versionCode.
+ *
+ * In case of multi-apk, the version code of each apk is different.
+ *
+ * @return the versionCode
+ */
+ int versionCode();
+
+ /**
+ * The density filter if applicable.
+ * @return the density filter or null if not applicable.
+ */
+ @Nullable
+ String densityFilter();
+
+ /**
+ * The ABI filter if applicable.
+ * @return the ABI filter or null if not applicable.
+ */
+ @Nullable
+ String abiFilter();
+}
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
index a46cb7e..ed27a69 100644
--- 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
@@ -26,10 +26,10 @@
/**
* Represents an Android Library dependency, its content and its own dependencies
*/
-public interface AndroidLibrary {
+public interface AndroidLibrary extends Library {
/**
- * Returns the project identifier if the library is output
+ * Returns an optional project identifier if the library is output
* by a module.
*
* @return the project identifier
@@ -38,6 +38,13 @@
String getProject();
/**
+ * Returns an optional configuration name if the library is output by a module
+ * that publishes more than one variant.
+ */
+ @Nullable
+ String getProjectVariant();
+
+ /**
* Returns the location of the library aar bundle.
*/
@NonNull
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
index 9a630d3..2c7136f 100644
--- 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
@@ -17,6 +17,7 @@
package com.android.builder.model;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import java.io.File;
import java.util.Collection;
@@ -26,10 +27,25 @@
* the module is an app project or a library project.
*/
public interface AndroidProject {
- String BUILD_MODEL_ONLY_SYSTEM_PROPERTY = "android.build.model.only";
+ // Injectable properties to use with -P
+ String PROPERTY_BUILD_MODEL_ONLY = "android.injected.build.model.only";
+ String PROPERTY_INVOKED_FROM_IDE = "android.injected.invoked.from.ide";
- public static final String ARTIFACT_MAIN = "_main_";
- public static final String ARTIFACT_INSTRUMENT_TEST = "_instrument_test_";
+ String PROPERTY_SIGNING_STORE_FILE = "android.injected.signing.store.file";
+ String PROPERTY_SIGNING_STORE_PASSWORD = "android.injected.signing.store.password";
+ String PROPERTY_SIGNING_KEY_ALIAS = "android.injected.signing.key.alias";
+ String PROPERTY_SIGNING_KEY_PASSWORD = "android.injected.signing.key.password";
+ String PROPERTY_SIGNING_STORE_TYPE = "android.injected.signing.store.type";
+
+ String PROPERTY_APK_LOCATION = "android.injected.apk.location";
+
+ String ARTIFACT_MAIN = "_main_";
+ String ARTIFACT_ANDROID_TEST = "_android_test_";
+
+ String FD_INTERMEDIATES = "intermediates";
+ String FD_OUTPUTS = "outputs";
+ String FD_GENERATED = "generated";
+
/**
* Returns the model version. This is a string in the format X.Y.Z
@@ -147,7 +163,7 @@
/**
* 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
+ * populated only if the system property {@link #PROPERTY_BUILD_MODEL_ONLY} has been
* set to {@code true}.
* <p>
* Each value of the collection has the format group:name:version, for example:
@@ -163,4 +179,22 @@
*/
@NonNull
JavaCompileOptions getJavaCompileOptions();
+
+ /**
+ * @return the build folder of this project.
+ */
+ @NonNull
+ File getBuildFolder();
+
+ /**
+ * Returns the resource prefix to use, if any. This is an optional prefix which can
+ * be set and which is used by the defaults to automatically choose new resources
+ * with a certain prefix, warn if resources are not using the given prefix, etc.
+ * This helps work with resources in the app namespace where there could otherwise
+ * be unintentional duplicated resource names between unrelated libraries.
+ *
+ * @return the optional resource prefix, or null if not set
+ */
+ @Nullable
+ String getResourcePrefix();
}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/ApiVersion.java b/build-system/builder-model/src/main/java/com/android/builder/model/ApiVersion.java
new file mode 100644
index 0000000..1b1c8db
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/ApiVersion.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Represents the version of an Android Platform.
+ *
+ * A version is defined by an API level and an optional code name.
+ *
+ * Release versions of the Android platform are identified by their API level (integer),
+ * (technically the code name for release version is "REL" but this class will return
+ * <code>null<code> instead.)
+ *
+ * Preview versions of the platform are identified by a code name. Their API level
+ * is usually set to the value of the previous platform.
+ */
+public interface ApiVersion {
+
+ /**
+ * Returns the api level as an integer.
+ * <p/>For target that are in preview mode, this can be superseded by
+ * {@link #getCodename()}.
+ *
+ * @see #getCodename()
+ */
+ int getApiLevel();
+
+ /**
+ * Returns the version code name if applicable, null otherwise.
+ * <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
+ String getCodename();
+
+ /**
+ * Returns the API value as a string.
+ *
+ * If there's a codename, this returns it, otherwise this returns the string version
+ * of the integer api level.
+ * @return a String.
+ */
+ @NonNull
+ String getApiString();
+}
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
index fd1a7b7..304c1f1 100644
--- 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
@@ -39,7 +39,7 @@
String getJavaCompileTaskName();
/**
- * Returns the name of the task used to generate the artifact.
+ * Returns the name of the task used to generate the artifact output(s).
*
* @return the name of the task.
*/
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
index d4e22c1..e5ab92e 100644
--- 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
@@ -20,18 +20,31 @@
import java.io.File;
import java.util.Collection;
+import java.util.Map;
/**
* Base config object for Build Type and Product flavor.
*/
public interface BaseConfig {
+ @NonNull
+ String getName();
+
/**
- * List of Build Config Fields
- * @return a non-null list of class fields (possibly empty)
+ * Map of Build Config Fields where the key is the field name.
+ *
+ * @return a non-null map of class fields (possibly empty).
*/
@NonNull
- Collection<ClassField> getBuildConfigFields();
+ Map<String, ClassField> getBuildConfigFields();
+
+ /**
+ * Map of generated res values where the key is the res name.
+ *
+ * @return a non-null map of class fields (possibly empty).
+ */
+ @NonNull
+ Map<String, ClassField> getResValues();
/**
* Returns the list of proguard rule files.
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
index c8cf61e..68e4d40 100644
--- 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
@@ -35,6 +35,7 @@
*
* @return the name of the build type.
*/
+ @Override
@NonNull
String getName();
@@ -46,6 +47,13 @@
boolean isDebuggable();
/**
+ * Returns whether the build type is configured to be build with support for code coverage.
+ *
+ * @return true if code coverage is enabled.
+ */
+ boolean isTestCoverageEnabled();
+
+ /**
* Returns whether the build type is configured to generate an apk with debuggable native code.
*
* @return true if the apk is debuggable
@@ -68,13 +76,13 @@
int getRenderscriptOptimLevel();
/**
- * Returns the package name suffix applied to this build type.
- * To get the final package name, use {@link AndroidArtifact#getPackageName()}.
+ * Returns the application id suffix applied to this build type.
+ * To get the final application id, use {@link AndroidArtifact#getApplicationId()}.
*
- * @return the package name suffix.
+ * @return the application id
*/
@Nullable
- String getPackageNameSuffix();
+ String getApplicationIdSuffix();
/**
* Returns the version name suffix.
@@ -104,4 +112,9 @@
*/
@Nullable
NdkConfig getNdkConfig();
+
+ /**
+ * Returns whether the variant embeds the micro app.
+ */
+ boolean isEmbedMicroApp();
}
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
index f28b648..2adf663 100644
--- 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
@@ -37,12 +37,12 @@
List<AndroidLibrary> getLibraries();
/**
- * The list of jar dependencies. This only includes external dependencies.
+ * The list of Java library dependencies. This only includes external dependencies.
*
- * @return the list of jar files.
+ * @return the list of Java library dependencies.
*/
@NonNull
- Collection<File> getJars();
+ Collection<JavaLibrary> getJavaLibraries();
/**
* The list of project dependencies. This is only for non Android module dependencies (which
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/JavaLibrary.java b/build-system/builder-model/src/main/java/com/android/builder/model/JavaLibrary.java
new file mode 100644
index 0000000..8eb3864
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/JavaLibrary.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.List;
+
+/**
+ * A Java library.
+ */
+public interface JavaLibrary extends Library {
+ /**
+ * Returns the library's jar file.
+ */
+ @NonNull
+ File getJarFile();
+
+ /**
+ * Returns the direct dependencies of this library.
+ */
+ @NonNull
+ List<? extends JavaLibrary> getDependencies();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/Library.java b/build-system/builder-model/src/main/java/com/android/builder/model/Library.java
new file mode 100644
index 0000000..828deed
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/Library.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+public interface Library {
+ /**
+ * Returns this library's Maven coordinates, as requested in the build file.
+ */
+ @Nullable
+ MavenCoordinates getRequestedCoordinates();
+
+ /**
+ * Returns this library's Maven coordinates after all the project's dependencies have been
+ * resolved. This coordinate may be different than {@link #getRequestedCoordinates()}.
+ */
+ @Nullable
+ MavenCoordinates getResolvedCoordinates();
+}
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
index c2ae6de..6e26b58 100644
--- 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
@@ -20,6 +20,7 @@
import com.android.annotations.Nullable;
import java.io.File;
+import java.util.Map;
import java.util.Set;
/**
@@ -34,6 +35,9 @@
* quiet true
* // if true, stop the gradle build if errors are found
* abortOnError false
+ * // set to true to have all release builds run lint on issues with severity=fatal
+ * // and abort the build (controlled by abortOnError above) if fatal issues are found
+ * checkReleaseBuilds true
* // if true, only report errors
* ignoreWarnings true
* // if true, emit full/absolute paths to files with errors (true by default)
@@ -52,6 +56,8 @@
* noLines true
* // if true, show all locations for an error, do not truncate lists, etc.
* showAll true
+ * // whether lint should include full issue explanations in the text error output
+ * explainIssues false
* // Fallback lint configuration (default severities, etc.)
* lintConfig file("default-lint.xml")
* // if true, generate a text report of issues (false by default)
@@ -67,6 +73,15 @@
* htmlReport true
* // optional path to report (default will be lint-results.html in the builddir)
* htmlOutput file("lint-report.html")
+ * // Set the severity of the given issues to fatal (which means they will be
+ * // checked during release builds (even if the lint target is not included)
+ * fatal 'NewApi', 'InlineApi'
+ * // Set the severity of the given issues to error
+ * error 'Wakelock', 'TextViewEdits'
+ * // Set the severity of the given issues to warning
+ * warning 'ResourceAsColor'
+ * // Set the severity of the given issues to ignore (same as disabling the check)
+ * ignore 'TypographyQuotes'
* }
* }
* </pre>
@@ -124,6 +139,10 @@
/** Returns whether lint should treat all warnings as errors */
public boolean isWarningsAsErrors();
+ /** Returns whether lint should include explanations for issue errors. (Note that
+ * HTML and XML reports intentionally do this unconditionally, ignoring this setting.) */
+ public boolean isExplainIssues();
+
/**
* Returns whether lint should include all output (e.g. include all alternate
* locations, not truncating long messages, etc.)
@@ -163,4 +182,31 @@
@Nullable
public File getXmlOutput();
+ /**
+ * Returns whether lint should check for fatal errors during release builds. Default is true.
+ * If issues with severity "fatal" are found, the release build is aborted.
+ */
+ public boolean isCheckReleaseBuilds();
+
+ /**
+ * An optional map of severity overrides. The map maps from issue id's to the corresponding
+ * severity to use, which must be "fatal", "error", "warning", or "ignore".
+ *
+ * @return a map of severity overrides, or null. The severities are one of the constants
+ * {@link #SEVERITY_FATAL}, {@link #SEVERITY_ERROR}, {@link #SEVERITY_WARNING},
+ * {@link #SEVERITY_INFORMATIONAL}, {@link #SEVERITY_IGNORE}
+ */
+ @Nullable
+ public Map<String, Integer> getSeverityOverrides();
+
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#FATAL */
+ public static final int SEVERITY_FATAL = 1;
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#ERROR */
+ public static final int SEVERITY_ERROR = 2;
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#WARNING */
+ public static final int SEVERITY_WARNING = 3;
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#INFORMATIONAL */
+ public static final int SEVERITY_INFORMATIONAL = 4;
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#IGNORE */
+ public static final int SEVERITY_IGNORE = 5;
}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/MavenCoordinates.java b/build-system/builder-model/src/main/java/com/android/builder/model/MavenCoordinates.java
new file mode 100644
index 0000000..8b248f5
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/MavenCoordinates.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Coordinates that uniquely identifies a project in a Maven repository.
+ */
+public interface MavenCoordinates {
+ /**
+ * Returns the name of the project's group, similar to the Java packaging structure.
+ */
+ @NonNull
+ String getGroupId();
+
+ /**
+ * Returns the name that the project is known by.
+ */
+ @NonNull
+ String getArtifactId();
+
+ /**
+ * Returns the version of the project.
+ */
+ @NonNull
+ String getVersion();
+
+ /**
+ * Returns the project's artifact type. It defaults to "jar" if not explicitly set.
+ */
+ @NonNull
+ String getPackaging();
+
+ /**
+ * Returns the project's classifier. The classifier allows to distinguish artifacts that were
+ * built from the same POM but differ in their content.
+ */
+ @Nullable
+ String getClassifier();
+}
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
index 25056c7..6075f28 100644
--- 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
@@ -19,6 +19,7 @@
import com.android.annotations.Nullable;
import java.util.Collection;
+import java.util.Set;
/**
* Base class for NDK config file.
@@ -47,7 +48,7 @@
* The ABI Filters
*/
@Nullable
- public Collection<String> getAbiFilters();
+ public Set<String> getAbiFilters();
/**
* The APP_STL value
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/PackagingOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/PackagingOptions.java
new file mode 100644
index 0000000..b69e00a
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/PackagingOptions.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.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.util.Set;
+
+/**
+ * Options for APK Packaging.
+ */
+public interface PackagingOptions {
+ @NonNull
+ Set<String> getExcludes();
+
+ @NonNull
+ Set<String> getPickFirsts();
+}
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
index 2e5eb81..d3f1506 100644
--- 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
@@ -20,6 +20,7 @@
import com.android.annotations.Nullable;
import java.util.Collection;
+import java.util.Map;
/**
* a Product Flavor. This is only the configuration of the flavor.
@@ -37,23 +38,24 @@
*
* @return the name of the flavor.
*/
+ @Override
@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()}.
+ * To get the final application id name, use {@link AndroidArtifact#getApplicationId()}.
*
- * @return the package name.
+ * @return the application id.
*/
@Nullable
- String getPackageName();
+ String getApplicationId();
/**
* 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
+ * @return the version code, or -1 if not specified
*/
int getVersionCode();
@@ -69,25 +71,25 @@
/**
* Returns the minSdkVersion. This is only the value set on this product flavor.
- * TODO: make final minSdkVersion available through the model
*
- * @return the minSdkVersion
+ * @return the minSdkVersion, or null if not specified
*/
- int getMinSdkVersion();
+ @Nullable
+ ApiVersion 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
+ * @return the targetSdkVersion, or null if not specified
*/
- int getTargetSdkVersion();
+ @Nullable
+ ApiVersion 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
+ * @return the renderscript target api, or -1 if not specified
*/
int getRenderscriptTargetApi();
@@ -106,17 +108,18 @@
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()}
+ * Returns the test application id. This is only the value set on this product flavor.
+ * To get the final value, use {@link Variant#getExtraAndroidArtifacts()} with
+ * {@link AndroidProject#ARTIFACT_ANDROID_TEST} and then
+ * {@link AndroidArtifact#getApplicationId()}
*
* @return the test package name.
*/
@Nullable
- String getTestPackageName();
+ String getTestApplicationId();
/**
- * Returns the test package name. This is only the value set on this product flavor.
+ * Returns the test instrumentation runner. This is only the value set on this product flavor.
* TODO: make test instrumentation runner available through the model.
*
* @return the test package name.
@@ -157,4 +160,13 @@
*/
@NonNull
Collection<String> getResourceConfigurations();
+
+ /**
+ * Returns the map of key value pairs for placeholder substitution in the android manifest file.
+ *
+ * This map will be used by the manifest merger.
+ * @return the map of key value pairs.
+ */
+ @NonNull
+ Map<String, String> getManifestPlaceholders();
}
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
index 6d58e64..f1e025d 100644
--- 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
@@ -28,6 +28,14 @@
public interface SourceProvider {
/**
+ * Returns the name of this source set.
+ *
+ * @return The name. Never returns null.
+ */
+ @NonNull
+ String getName();
+
+ /**
* Returns the manifest file.
*
* @return the manifest file. It may not exist.
@@ -90,4 +98,12 @@
*/
@NonNull
Collection<File> getAssetsDirectories();
+
+ /**
+ * Returns the native libs folders.
+ *
+ * @return a list of folders. They may not all exist.
+ */
+ @NonNull
+ Collection<File> getJniLibsDirectories();
}
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
index 67813ae..c285897 100644
--- 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
@@ -23,6 +23,19 @@
/**
* A build Variant.
+ *
+ * This is the combination of a Build Type and 0+ Product Flavors (exactly one for each existing
+ * Flavor Dimension).
+ *
+ * Build Types and Flavors both contribute source folders, so this Variant is the direct
+ * representation of a set of source folders (and configuration parameters) used to build something.
+ *
+ * However the output of a Variant is not a single item.
+ *
+ * First there can be several artifacts.
+ * - Main Artifact: this is the main Android output(s). The app or the library being generated.
+ * - Extra Android Artifacts: these are ancillary artifacts, most likely test app(s).
+ * - Extra Java artifacts: these are pure-Java ancillary artifacts (junit support for instance).
*/
public interface Variant {
@@ -77,7 +90,7 @@
* 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
+ * This is directly a ProductFlavor instance of a ProductFlavorContainer since this a composite
* of existing ProductFlavors.
*
* @return the merged flavors.
diff --git a/build-system/builder-test-api/build.gradle b/build-system/builder-test-api/build.gradle
index 2c4c8ae..bf23fe8 100644
--- a/build-system/builder-test-api/build.gradle
+++ b/build-system/builder-test-api/build.gradle
@@ -2,17 +2,16 @@
apply plugin: 'clone-artifacts'
dependencies {
- compile project(':ddmlib')
+ compile project(':base:ddmlib')
}
group = 'com.android.tools.build'
archivesBaseName = 'builder-test-api'
+version = rootProject.ext.buildVersion
+
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 from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
-apply plugin: 'distrib'
-shipping.isShipping = false
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
index 48208e8..f7eace8 100644
--- 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
@@ -17,12 +17,14 @@
package com.android.builder.testing.api;
import com.android.annotations.NonNull;
+import com.android.ddmlib.IDevice;
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.io.IOException;
import java.util.List;
/**
@@ -62,12 +64,35 @@
public abstract void uninstallPackage(@NonNull String packageName, int timeout, ILogger logger) throws DeviceException;
/**
+ * Pulls a single file.
+ *
+ * @param remote the full path to the remote file
+ * @param local The local destination.
+ *
+ * @throws IOException in case of an IO exception.
+ */
+ public abstract void pullFile(String remote, String local) throws IOException;
+
+ /**
* Returns the API level of the device, or 0 if it could not be queried.
* @return the api level
*/
public abstract int getApiLevel();
/**
+ * Returns the API codename for the device, or null if it's a release device.
+ * @return the API codename
+ */
+ public abstract String getApiCodeName();
+
+ /**
+ * Returns the {@link com.android.ddmlib.IDevice.DeviceState} for the device, or null
+ * if if cannot determined.
+ * @return the device state.
+ */
+ public abstract IDevice.DeviceState getState();
+
+ /**
* The device supported ABIs. This is in preferred order.
* @return the list of supported ABIs
*/
diff --git a/build-system/builder/build.gradle b/build-system/builder/build.gradle
index 42aa673..1dbf165 100644
--- a/build-system/builder/build.gradle
+++ b/build-system/builder/build.gradle
@@ -1,37 +1,34 @@
apply plugin: 'java'
apply plugin: 'clone-artifacts'
-evaluationDependsOn(':builder-model')
-evaluationDependsOn(':builder-test-api')
+evaluationDependsOn(':base:builder-model')
+evaluationDependsOn(':base:builder-test-api')
dependencies {
- compile project(':builder-model')
- compile project(':builder-test-api')
+ compile project(':base:builder-model')
+ compile project(':base:builder-test-api')
- compile project(':sdklib')
- compile project(':sdk-common')
- compile project(':common')
- compile project(':manifest-merger')
- compile project(':ddmlib')
+ compile project(':base:sdklib')
+ compile project(':base:sdk-common')
+ compile project(':base:common')
+ compile project(':base:manifest-merger')
+ compile project(':base:ddmlib')
- compile 'com.squareup:javawriter:2.2.1'
+ compile 'com.squareup:javawriter:2.5.0'
compile 'org.bouncycastle:bcpkix-jdk15on:1.48'
testCompile 'junit:junit:3.8.1'
- testCompile project(':testutils')
+ testCompile project(':base:testutils')
}
group = 'com.android.tools.build'
archivesBaseName = 'builder'
+version = rootProject.ext.buildVersion
+
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'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/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/src/main/java/com/android/builder/AndroidBuilder.java b/build-system/builder/src/main/java/com/android/builder/AndroidBuilder.java
deleted file mode 100644
index 0e12242..0000000
--- a/build-system/builder/src/main/java/com/android/builder/AndroidBuilder.java
+++ /dev/null
@@ -1,1191 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index 327d797..0000000
--- a/build-system/builder/src/main/java/com/android/builder/BuilderConstants.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index 63abf00..0000000
--- a/build-system/builder/src/main/java/com/android/builder/DefaultBuildType.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index e8d70d6..0000000
--- a/build-system/builder/src/main/java/com/android/builder/DefaultManifestParser.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index 2dbdd81..0000000
--- a/build-system/builder/src/main/java/com/android/builder/DefaultProductFlavor.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index 6244fb9..0000000
--- a/build-system/builder/src/main/java/com/android/builder/DefaultSdkParser.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index 1db13be..0000000
--- a/build-system/builder/src/main/java/com/android/builder/DexOptions.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index 3b8e4a3..0000000
--- a/build-system/builder/src/main/java/com/android/builder/ManifestParser.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index d0e1828..0000000
--- a/build-system/builder/src/main/java/com/android/builder/PlatformSdkParser.java
+++ /dev/null
@@ -1,174 +0,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.
- */
-
-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
deleted file mode 100644
index c247694..0000000
--- a/build-system/builder/src/main/java/com/android/builder/SdkParser.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index 80df454..0000000
--- a/build-system/builder/src/main/java/com/android/builder/VariantConfiguration.java
+++ /dev/null
@@ -1,1378 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
index 1e298b4..4b8a02a 100644
--- 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
@@ -15,14 +15,16 @@
*/
package com.android.builder.compiling;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.builder.AndroidBuilder;
+import com.android.builder.core.AndroidBuilder;
import com.android.builder.model.ClassField;
import com.google.common.collect.Lists;
+import com.google.common.io.Closer;
import com.squareup.javawriter.JavaWriter;
-import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
@@ -31,17 +33,17 @@
import java.util.List;
import java.util.Set;
-import static com.google.common.base.Preconditions.checkNotNull;
+import javax.lang.model.element.Modifier;
/**
- * Class able to generate a BuildConfig class in Android project.
+ * Class able to generate a BuildConfig class in an Android project.
* The BuildConfig class contains constants related to the build target.
*/
public class BuildConfigGenerator {
- public final static String BUILD_CONFIG_NAME = "BuildConfig.java";
+ public static final String BUILD_CONFIG_NAME = "BuildConfig.java";
- private final String mGenFolder;
+ private final File mGenFolder;
private final String mBuildConfigPackageName;
private final List<ClassField> mFields = Lists.newArrayList();
@@ -52,7 +54,7 @@
* @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) {
+ public BuildConfigGenerator(@NonNull File genFolder, @NonNull String buildConfigPackageName) {
mGenFolder = checkNotNull(genFolder);
mBuildConfigPackageName = checkNotNull(buildConfigPackageName);
}
@@ -74,8 +76,7 @@
* 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));
+ return new File(mGenFolder, mBuildConfigPackageName.replace('.', File.separatorChar));
}
public File getBuildConfigFile() {
@@ -95,41 +96,45 @@
}
File buildConfigJava = new File(pkgFolder, BUILD_CONFIG_NAME);
- FileWriter out = new FileWriter(buildConfigJava);
- JavaWriter writer = new JavaWriter(out);
+ Closer closer = Closer.create();
+ try {
+ FileWriter out = closer.register(new FileWriter(buildConfigJava));
+ JavaWriter writer = closer.register(new JavaWriter(out));
+ Set<Modifier> publicFinal = EnumSet.of(Modifier.PUBLIC, Modifier.FINAL);
+ Set<Modifier> publicFinalStatic = EnumSet.of(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
- 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);
- 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;
+ for (ClassField field : mFields) {
writer.emitField(
field.getType(),
field.getName(),
publicFinalStatic,
field.getValue());
-
- } else if (item instanceof String) {
- writer.emitSingleLineComment((String) item);
}
+
+ 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();
+ } catch (Throwable e) {
+ throw closer.rethrow(e);
+ } finally {
+ closer.close();
}
-
- 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
index cc2fd35..fa62aeb 100644
--- 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
@@ -17,31 +17,29 @@
package com.android.builder.compiling;
import com.android.annotations.NonNull;
+import com.android.builder.internal.incremental.DependencyData;
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
+ * During compilation of aidl, 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.
+ * The instance will be called for each dependency file that is created during compilation.
*
- * @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)
+ * @see com.android.builder.core.AndroidBuilder#compileAllAidlFiles(java.util.List, java.io.File, java.io.File, java.util.List, DependencyFileProcessor)
+ * @see com.android.builder.core.AndroidBuilder#compileAidlFile(java.io.File, java.io.File, java.io.File, java.io.File, java.util.List, DependencyFileProcessor)
*/
public interface DependencyFileProcessor {
/**
* Processes the dependency file.
* @param dependencyFile the dependency file.
- * @return true if the dependency file can be deleted by the caller.
+ * @return the dependency data that was created.
*/
- boolean processFile(@NonNull File dependencyFile);
+ DependencyData processFile(@NonNull File dependencyFile);
}
diff --git a/build-system/builder/src/main/java/com/android/builder/compiling/ResValueGenerator.java b/build-system/builder/src/main/java/com/android/builder/compiling/ResValueGenerator.java
new file mode 100644
index 0000000..c5a55c2
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/compiling/ResValueGenerator.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_RESOURCES;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.model.ClassField;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Class able to generate a res value file in an Android project.
+ */
+public class ResValueGenerator {
+
+ public static final String RES_VALUE_FILENAME_XML = "generated.xml";
+
+ private final File mGenFolder;
+
+ private final List<ClassField> mFields = Lists.newArrayList();
+ private List<Object> mItems = Lists.newArrayList();
+
+ /**
+ * Creates a generator
+ * @param genFolder the gen folder of the project
+ */
+ public ResValueGenerator(@NonNull File genFolder) {
+ mGenFolder = checkNotNull(genFolder);
+ }
+
+ public ResValueGenerator addResource(
+ @NonNull String type, @NonNull String name, @NonNull String value) {
+ mFields.add(AndroidBuilder.createClassField(type, name, value));
+ return this;
+ }
+
+ public ResValueGenerator 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() {
+ return new File(mGenFolder, "values");
+ }
+
+ /**
+ * Generates the resource files
+ */
+ public void generate() throws IOException, ParserConfigurationException {
+ File pkgFolder = getFolderPath();
+ if (!pkgFolder.isDirectory()) {
+ if (!pkgFolder.mkdirs()) {
+ throw new RuntimeException("Failed to create " + pkgFolder.getAbsolutePath());
+ }
+ }
+
+ File resFile = new File(pkgFolder, RES_VALUE_FILENAME_XML);
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringComments(true);
+ DocumentBuilder builder;
+
+ builder = factory.newDocumentBuilder();
+ Document document = builder.newDocument();
+
+ Node rootNode = document.createElement(TAG_RESOURCES);
+ document.appendChild(rootNode);
+
+ rootNode.appendChild(document.createTextNode("\n"));
+ rootNode.appendChild(document.createComment("Automatically generated file. DO NOT MODIFY"));
+ rootNode.appendChild(document.createTextNode("\n\n"));
+
+ for (Object item : mItems) {
+ if (item instanceof ClassField) {
+ ClassField field = (ClassField)item;
+
+ Node itemNode = document.createElement(TAG_ITEM);
+
+ Attr nameAttr = document.createAttribute(ATTR_NAME);
+ nameAttr.setValue(field.getName());
+ itemNode.getAttributes().setNamedItem(nameAttr);
+
+ Attr typeAttr = document.createAttribute(ATTR_TYPE);
+ typeAttr.setValue(field.getType());
+ itemNode.getAttributes().setNamedItem(typeAttr);
+
+ itemNode.appendChild(document.createTextNode(field.getValue()));
+
+ rootNode.appendChild(itemNode);
+ } else if (item instanceof String) {
+ rootNode.appendChild(document.createTextNode("\n"));
+ rootNode.appendChild(document.createComment((String) item));
+ rootNode.appendChild(document.createTextNode("\n"));
+ }
+ }
+
+ String content;
+ try {
+ content = XmlPrettyPrinter.prettyPrint(document, true);
+ } catch (Throwable t) {
+ content = XmlUtils.toXml(document, false);
+ }
+
+ Files.write(content, resFile, Charsets.UTF_8);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java b/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java
new file mode 100644
index 0000000..b883326
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java
@@ -0,0 +1,1745 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.FD_RES_XML;
+import static com.android.builder.core.BuilderConstants.ANDROID_WEAR;
+import static com.android.builder.core.BuilderConstants.ANDROID_WEAR_MICRO_APK;
+import static com.android.manifmerger.ManifestMerger2.Invoker;
+import static com.android.manifmerger.ManifestMerger2.SystemProperty;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+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.PreDexCache;
+import com.android.builder.internal.compiler.RenderScriptProcessor;
+import com.android.builder.internal.compiler.SourceSearcher;
+import com.android.builder.internal.incremental.DependencyData;
+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.PackagingOptions;
+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.sdk.SdkInfo;
+import com.android.builder.sdk.TargetInfo;
+import com.android.ide.common.internal.AaptCruncher;
+import com.android.ide.common.internal.CommandLineRunner;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.PngCruncher;
+import com.android.ide.common.signing.CertificateInfo;
+import com.android.ide.common.signing.KeystoreHelper;
+import com.android.ide.common.signing.KeytoolException;
+import com.android.manifmerger.ICallback;
+import com.android.manifmerger.ManifestMerger;
+import com.android.manifmerger.ManifestMerger2;
+import com.android.manifmerger.MergerLog;
+import com.android.manifmerger.MergingReport;
+import com.android.manifmerger.XmlDocument;
+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.Pair;
+import com.android.utils.SdkUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+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.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Files;
+
+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 java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 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(String, String, ILogger, boolean)}
+ *
+ * then build steps can be done with
+ * {@link #mergeManifests(java.io.File, java.util.List, java.util.List, String, int, String, String, String, String, com.android.manifmerger.ManifestMerger2.MergeType, java.util.Map)}
+ * {@link #processTestManifest2(String, String, String, String, String, Boolean, Boolean, java.util.List, java.io.File)}
+ * {@link #processResources(java.io.File, java.io.File, java.io.File, java.util.List, String, String, String, String, String, com.android.builder.core.VariantConfiguration.Type, boolean, com.android.builder.model.AaptOptions, java.util.Collection, boolean)}
+ * {@link #compileAllAidlFiles(java.util.List, java.io.File, java.io.File, java.util.List, com.android.builder.compiling.DependencyFileProcessor)}
+ * {@link #convertByteCode(Iterable, Iterable, java.io.File, DexOptions, java.util.List, boolean)}
+ * {@link #packageApk(String, java.io.File, java.util.Collection, String, java.util.Collection, java.util.Set, boolean, com.android.builder.model.SigningConfig, com.android.builder.model.PackagingOptions, String)}
+ *
+ * Java compilation is not handled but the builder provides the bootclasspath with
+ * {@link #getBootClasspath()}.
+ */
+public class AndroidBuilder {
+
+ private static final FullRevision MIN_BUILD_TOOLS_REV = new FullRevision(19, 1, 0);
+
+ private static final DependencyFileProcessor sNoOpDependencyFileProcessor = new DependencyFileProcessor() {
+ @Override
+ public DependencyData processFile(@NonNull File dependencyFile) {
+ return null;
+ }
+ };
+
+ private final String mProjectId;
+ private final ILogger mLogger;
+ private final CommandLineRunner mCmdLineRunner;
+ private final boolean mVerboseExec;
+
+ private String mCreatedBy;
+
+ private SdkInfo mSdkInfo;
+ private TargetInfo mTargetInfo;
+
+ /**
+ * Creates an AndroidBuilder.
+ * <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 createdBy the createdBy String for the apk manifest.
+ * @param logger the Logger
+ * @param verboseExec whether external tools are launched in verbose mode
+ */
+ public AndroidBuilder(
+ @NonNull String projectId,
+ @Nullable String createdBy,
+ @NonNull ILogger logger,
+ boolean verboseExec) {
+ mProjectId = projectId;
+ mCreatedBy = createdBy;
+ mLogger = checkNotNull(logger);
+ mVerboseExec = verboseExec;
+ mCmdLineRunner = new CommandLineRunner(mLogger);
+ }
+
+ @VisibleForTesting
+ AndroidBuilder(
+ @NonNull String projectId,
+ @NonNull CommandLineRunner cmdLineRunner,
+ @NonNull ILogger logger,
+ boolean verboseExec) {
+ mProjectId = projectId;
+ mCmdLineRunner = checkNotNull(cmdLineRunner);
+ mLogger = checkNotNull(logger);
+ mVerboseExec = verboseExec;
+ }
+
+ /**
+ * Sets the SdkInfo and the targetInfo on the builder. This is required to actually
+ * build (some of the steps).
+ *
+ * @param sdkInfo the SdkInfo
+ * @param targetInfo the TargetInfo
+ *
+ * @see com.android.builder.sdk.SdkLoader
+ */
+ public void setTargetInfo(@NonNull SdkInfo sdkInfo, @NonNull TargetInfo targetInfo) {
+ mSdkInfo = sdkInfo;
+ mTargetInfo = targetInfo;
+
+ if (mTargetInfo.getBuildTools().getRevision().compareTo(MIN_BUILD_TOOLS_REV) < 0) {
+ throw new IllegalArgumentException(String.format(
+ "The SDK Build Tools revision (%1$s) is too low for project '%2$s'. Minimum required is %3$s",
+ mTargetInfo.getBuildTools().getRevision(), mProjectId, MIN_BUILD_TOOLS_REV));
+ }
+ }
+
+ /**
+ * Returns the SdkInfo, if set.
+ */
+ @Nullable
+ public SdkInfo getSdkInfo() {
+ return mSdkInfo;
+ }
+
+ /**
+ * Returns the TargetInfo, if set.
+ */
+ @Nullable
+ public TargetInfo getTargetInfo() {
+ return mTargetInfo;
+ }
+
+ /**
+ * Returns the compilation target, if set.
+ */
+ @Nullable
+ public IAndroidTarget getTarget() {
+ checkState(mTargetInfo != null,
+ "Cannot call getTarget() before setTargetInfo() is called.");
+ return mTargetInfo.getTarget();
+ }
+
+ /**
+ * Returns whether the compilation target is a preview.
+ */
+ public boolean isPreviewTarget() {
+ checkState(mTargetInfo != null,
+ "Cannot call isTargetAPreview() before setTargetInfo() is called.");
+ return mTargetInfo.getTarget().getVersion().isPreview();
+ }
+
+ public String getTargetCodename() {
+ checkState(mTargetInfo != null,
+ "Cannot call getTargetCodename() before setTargetInfo() is called.");
+ return mTargetInfo.getTarget().getVersion().getCodename();
+ }
+
+ /**
+ * Helper method to get the boot classpath to be used during compilation.
+ */
+ @NonNull
+ public List<String> getBootClasspath() {
+ checkState(mTargetInfo != null,
+ "Cannot call getBootClasspath() before setTargetInfo() is called.");
+
+ List<String> classpath = Lists.newArrayList();
+
+ IAndroidTarget target = mTargetInfo.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(mSdkInfo.getAnnotationsJar().getPath());
+ }
+
+ return classpath;
+ }
+
+ /**
+ * Returns the jar file for the renderscript mode.
+ *
+ * This may return null if the SDK has not been loaded yet.
+ *
+ * @return the jar file, or null.
+ *
+ * @see #setTargetInfo(com.android.builder.sdk.SdkInfo, com.android.builder.sdk.TargetInfo)
+ */
+ @Nullable
+ public File getRenderScriptSupportJar() {
+ if (mTargetInfo != null) {
+ return RenderScriptProcessor.getSupportJar(
+ mTargetInfo.getBuildTools().getLocation().getAbsolutePath());
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the compile classpath for this config. If the config tests a library, this
+ * will include the classpath of the tested config.
+ *
+ * If the SDK was loaded, this may include the renderscript support jar.
+ *
+ * @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 = getRenderScriptSupportJar();
+
+ Set<File> fullJars = Sets.newHashSetWithExpectedSize(compileClasspath.size() + 1);
+ fullJars.addAll(compileClasspath);
+ if (renderScriptSupportJar != null) {
+ 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
+ *
+ * If the SDK was loaded, this may include the renderscript support jar.
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public Set<File> getPackagedJars(@NonNull VariantConfiguration variantConfiguration) {
+ Set<File> packagedJars = variantConfiguration.getPackagedJars();
+
+ ProductFlavor mergedFlavor = variantConfiguration.getMergedFlavor();
+
+ if (mergedFlavor.getRenderscriptSupportMode()) {
+ File renderScriptSupportJar = getRenderScriptSupportJar();
+
+ Set<File> fullJars = Sets.newHashSetWithExpectedSize(packagedJars.size() + 1);
+ fullJars.addAll(packagedJars);
+ if (renderScriptSupportJar != null) {
+ fullJars.add(renderScriptSupportJar);
+ }
+ packagedJars = fullJars;
+ }
+
+ return packagedJars;
+ }
+
+ /**
+ * Returns the native lib folder for the renderscript mode.
+ *
+ * This may return null if the SDK has not been loaded yet.
+ *
+ * @return the folder, or null.
+ *
+ * @see #setTargetInfo(com.android.builder.sdk.SdkInfo, com.android.builder.sdk.TargetInfo)
+ */
+ @Nullable
+ public File getSupportNativeLibFolder() {
+ if (mTargetInfo != null) {
+ return RenderScriptProcessor.getSupportNativeLibFolder(
+ mTargetInfo.getBuildTools().getLocation().getAbsolutePath());
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an {@link PngCruncher} using aapt underneath
+ * @return an PngCruncher object
+ */
+ @NonNull
+ public PngCruncher getAaptCruncher() {
+ checkState(mTargetInfo != null,
+ "Cannot call getAaptCruncher() before setTargetInfo() is called.");
+ return new AaptCruncher(
+ mTargetInfo.getBuildTools().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);
+ }
+
+ /**
+ * Invoke the Manifest Merger version 2.
+ */
+ public void mergeManifests(
+ @NonNull File mainManifest,
+ @NonNull List<File> manifestOverlays,
+ @NonNull List<? extends ManifestDependency> libraries,
+ String packageOverride,
+ int versionCode,
+ String versionName,
+ @Nullable String minSdkVersion,
+ @Nullable String targetSdkVersion,
+ @NonNull String outManifestLocation,
+ ManifestMerger2.MergeType mergeType,
+ Map<String, String> placeHolders) {
+
+ try {
+ Invoker manifestMergerInvoker =
+ ManifestMerger2.newMerger(mainManifest, mLogger, mergeType)
+ .setPlaceHolderValues(placeHolders)
+ .addFlavorAndBuildTypeManifests(
+ manifestOverlays.toArray(new File[manifestOverlays.size()]))
+ .addLibraryManifests(collectLibraries(libraries));
+
+ setInjectableValues(manifestMergerInvoker,
+ packageOverride, versionCode, versionName, minSdkVersion, targetSdkVersion);
+
+ MergingReport mergingReport = manifestMergerInvoker.merge();
+ mLogger.info("Merging result:" + mergingReport.getResult());
+ switch (mergingReport.getResult()) {
+ case WARNING:
+ mergingReport.log(mLogger);
+ // fall through since these are just warnings.
+ case SUCCESS:
+ XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
+ try {
+ String annotatedDocument = mergingReport.getActions().blame(xmlDocument);
+ mLogger.verbose(annotatedDocument);
+ } catch (Exception e) {
+ mLogger.error(e, "cannot print resulting xml");
+ }
+ save(xmlDocument, new File(outManifestLocation));
+ mLogger.info("Merged manifest saved to " + outManifestLocation);
+ break;
+ case ERROR:
+ mergingReport.log(mLogger);
+ throw new RuntimeException(mergingReport.getReportString());
+ default:
+ throw new RuntimeException("Unhandled result type : "
+ + mergingReport.getResult());
+ }
+ } catch (ManifestMerger2.MergeFailureException e) {
+ // TODO: unacceptable.
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the {@link com.android.manifmerger.ManifestMerger2.SystemProperty} that can be injected
+ * in the manifest file.
+ */
+ private static void setInjectableValues(
+ ManifestMerger2.Invoker<?> invoker,
+ String packageOverride,
+ int versionCode,
+ String versionName,
+ @Nullable String minSdkVersion,
+ @Nullable String targetSdkVersion) {
+
+ if (!Strings.isNullOrEmpty(packageOverride)) {
+ invoker.setOverride(SystemProperty.PACKAGE, packageOverride);
+ }
+ if (versionCode > 0) {
+ invoker.setOverride(SystemProperty.VERSION_CODE,
+ String.valueOf(versionCode));
+ }
+ if (!Strings.isNullOrEmpty(versionName)) {
+ invoker.setOverride(SystemProperty.VERSION_NAME, versionName);
+ }
+ if (!Strings.isNullOrEmpty(minSdkVersion)) {
+ invoker.setOverride(SystemProperty.MIN_SDK_VERSION, minSdkVersion);
+ }
+ if (!Strings.isNullOrEmpty(targetSdkVersion)) {
+ invoker.setOverride(SystemProperty.TARGET_SDK_VERSION, targetSdkVersion);
+ }
+ }
+
+ /**
+ * Saves the {@link com.android.manifmerger.XmlDocument} to a file in UTF-8 encoding.
+ * @param xmlDocument xml document to save.
+ * @param out file to save to.
+ */
+ private void save(XmlDocument xmlDocument, File out) {
+ try {
+ Files.write(xmlDocument.prettyPrint(), out, Charsets.UTF_8);
+ } catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Collect the list of libraries' manifest files.
+ * @param libraries declared dependencies
+ * @return a list of files and names for the libraries' manifest files.
+ */
+ private static ImmutableList<Pair<String, File>> collectLibraries(
+ List<? extends ManifestDependency> libraries) {
+
+ ImmutableList.Builder<Pair<String, File>> manifestFiles = ImmutableList.builder();
+ if (libraries != null) {
+ collectLibraries(libraries, manifestFiles);
+ }
+ return manifestFiles.build();
+ }
+
+ /**
+ * recursively calculate the list of libraries to merge the manifests files from.
+ * @param libraries the dependencies
+ * @param manifestFiles list of files and names identifiers for the libraries' manifest files.
+ */
+ private static void collectLibraries(List<? extends ManifestDependency> libraries,
+ ImmutableList.Builder<Pair<String, File>> manifestFiles) {
+
+ for (ManifestDependency library : libraries) {
+ manifestFiles.add(Pair.of(library.getName(), library.getManifest()));
+ List<? extends ManifestDependency> manifestDependencies = library
+ .getManifestDependencies();
+ if (!manifestDependencies.isEmpty()) {
+ collectLibraries(manifestDependencies, manifestFiles);
+ }
+ }
+ }
+
+ /**
+ * 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 VariantConfiguration#getMainManifest()
+ * @see VariantConfiguration#getManifestOverlays()
+ * @see VariantConfiguration#getDirectLibraries()
+ * @see 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,
+ @Nullable String minSdkVersion,
+ @Nullable String 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.");
+ checkState(mTargetInfo != null,
+ "Cannot call processManifest() before setTargetInfo() is called.");
+
+ final IAndroidTarget target = mTargetInfo.getTarget();
+
+ ICallback callback = new ICallback() {
+ @Override
+ public int queryCodenameApiLevel(@NonNull String codename) {
+ if (codename.equals(target.getVersion().getCodename())) {
+ return target.getVersion().getApiLevel();
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+ };
+
+ 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),
+ callback);
+ 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),
+ callback);
+ 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,
+ callback);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates the manifest for a test variant
+ *
+ * @param testApplicationId the application id of the test application
+ * @param minSdkVersion the minSdkVersion of the test application
+ * @param targetSdkVersion the targetSdkVersion of the test application
+ * @param testedApplicationId the application id 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 outManifest the output location for the merged manifest
+ *
+ * @see VariantConfiguration#getApplicationId()
+ * @see VariantConfiguration#getTestedConfig()
+ * @see VariantConfiguration#getMinSdkVersion()
+ * @see VariantConfiguration#getTestedApplicationId()
+ * @see VariantConfiguration#getInstrumentationRunner()
+ * @see VariantConfiguration#getHandleProfiling()
+ * @see VariantConfiguration#getFunctionalTest()
+ * @see VariantConfiguration#getDirectLibraries()
+ */
+ public void processTestManifest(
+ @NonNull String testApplicationId,
+ @Nullable String minSdkVersion,
+ @Nullable String targetSdkVersion,
+ @NonNull String testedApplicationId,
+ @NonNull String instrumentationRunner,
+ @NonNull Boolean handleProfiling,
+ @NonNull Boolean functionalTest,
+ @NonNull List<? extends ManifestDependency> libraries,
+ @NonNull File outManifest) {
+ checkNotNull(testApplicationId, "testApplicationId cannot be null.");
+ checkNotNull(testedApplicationId, "testedApplicationId 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(outManifest, "outManifestLocation cannot be null.");
+ checkState(mTargetInfo != null,
+ "Cannot call processTestManifest() before setTargetInfo() is called.");
+
+ final IAndroidTarget target = mTargetInfo.getTarget();
+
+ ICallback callback = new ICallback() {
+ @Override
+ public int queryCodenameApiLevel(@NonNull String codename) {
+ if (codename.equals(target.getVersion().getCodename())) {
+ return target.getVersion().getApiLevel();
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+ };
+
+ if (!libraries.isEmpty()) {
+ try {
+ // create the test manifest, merge the libraries in it
+ File generatedTestManifest = File.createTempFile("manifestMerge", ".xml");
+
+ generateTestManifest(
+ testApplicationId,
+ minSdkVersion,
+ targetSdkVersion,
+ testedApplicationId,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest,
+ generatedTestManifest);
+
+ mergeLibraryManifests(
+ generatedTestManifest,
+ libraries,
+ outManifest,
+ null, null,
+ callback);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ generateTestManifest(
+ testApplicationId,
+ minSdkVersion,
+ targetSdkVersion,
+ testedApplicationId,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest,
+ outManifest);
+ }
+ }
+
+ /**
+ * Creates the manifest for a test variant
+ *
+ * @param testApplicationId the application id of the test application
+ * @param minSdkVersion the minSdkVersion of the test application
+ * @param targetSdkVersion the targetSdkVersion of the test application
+ * @param testedApplicationId the application id 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 outManifest the output location for the merged manifest
+ *
+ * @see VariantConfiguration#getApplicationId()
+ * @see VariantConfiguration#getTestedConfig()
+ * @see VariantConfiguration#getMinSdkVersion()
+ * @see VariantConfiguration#getTestedApplicationId()
+ * @see VariantConfiguration#getInstrumentationRunner()
+ * @see VariantConfiguration#getHandleProfiling()
+ * @see VariantConfiguration#getFunctionalTest()
+ * @see VariantConfiguration#getDirectLibraries()
+ */
+ public void processTestManifest2(
+ @NonNull String testApplicationId,
+ @Nullable String minSdkVersion,
+ @Nullable String targetSdkVersion,
+ @NonNull String testedApplicationId,
+ @NonNull String instrumentationRunner,
+ @NonNull Boolean handleProfiling,
+ @NonNull Boolean functionalTest,
+ @NonNull List<? extends ManifestDependency> libraries,
+ @NonNull File outManifest) {
+ checkNotNull(testApplicationId, "testApplicationId cannot be null.");
+ checkNotNull(testedApplicationId, "testedApplicationId 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(outManifest, "outManifestLocation cannot be null.");
+
+ if (!libraries.isEmpty()) {
+ try {
+ // create the test manifest, merge the libraries in it
+ File generatedTestManifest = File.createTempFile("manifestMerge", ".xml");
+
+ generateTestManifest(
+ testApplicationId,
+ minSdkVersion,
+ targetSdkVersion,
+ testedApplicationId,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest,
+ generatedTestManifest);
+
+ MergingReport mergingReport = ManifestMerger2.newMerger(
+ generatedTestManifest, mLogger, ManifestMerger2.MergeType.APPLICATION)
+ .setOverride(SystemProperty.PACKAGE, testApplicationId)
+ .addLibraryManifests(collectLibraries(libraries))
+ .merge();
+
+ mLogger.info("Merging result:" + mergingReport.getResult());
+ switch (mergingReport.getResult()) {
+ case WARNING:
+ mergingReport.log(mLogger);
+ // fall through since these are just warnings.
+ case SUCCESS:
+ XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
+ try {
+ String annotatedDocument = mergingReport.getActions().blame(xmlDocument);
+ mLogger.verbose(annotatedDocument);
+ } catch (Exception e) {
+ mLogger.error(e, "cannot print resulting xml");
+ }
+ save(xmlDocument, outManifest);
+ mLogger.info("Merged manifest saved to " + outManifest);
+ break;
+ case ERROR:
+ mergingReport.log(mLogger);
+ throw new RuntimeException(mergingReport.getReportString());
+ default:
+ throw new RuntimeException("Unhandled result type : "
+ + mergingReport.getResult());
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ generateTestManifest(
+ testApplicationId,
+ minSdkVersion,
+ targetSdkVersion,
+ testedApplicationId,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest,
+ outManifest);
+ }
+ }
+
+ private static void generateTestManifest(
+ @NonNull String testApplicationId,
+ @Nullable String minSdkVersion,
+ @Nullable String targetSdkVersion,
+ @NonNull String testedApplicationId,
+ @NonNull String instrumentationRunner,
+ @NonNull Boolean handleProfiling,
+ @NonNull Boolean functionalTest,
+ @NonNull File outManifestLocation) {
+ TestManifestGenerator generator = new TestManifestGenerator(
+ outManifestLocation,
+ testApplicationId,
+ minSdkVersion,
+ targetSdkVersion,
+ testedApplicationId,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest);
+ try {
+ generator.generate();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ private static Map<String, String> getAttributeInjectionMap(
+ int versionCode,
+ @Nullable String versionName,
+ @Nullable String minSdkVersion,
+ @Nullable String 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 != null) {
+ attributeInjection.put(
+ "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion",
+ minSdkVersion);
+ }
+
+ if (targetSdkVersion != null) {
+ attributeInjection.put(
+ "/manifest/uses-sdk|http://schemas.android.com/apk/res/android targetSdkVersion",
+ 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,
+ @NonNull ICallback callback)
+ 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, callback);
+
+ manifests.add(mergeLibManifest);
+ }
+ }
+
+ ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(mLogger), callback);
+ doMerge(merger, outManifest, mainManifest, manifests, attributeInjection, packageOverride);
+ }
+
+ 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}
+ * @param resourceConfigs a list of resource config filters to pass to aapt.
+ * @param enforceUniquePackageName if true method will fail if some libraries share the same
+ * package name
+ *
+ *
+ * @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,
+ boolean enforceUniquePackageName)
+ 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");
+ checkState(mTargetInfo != null,
+ "Cannot call processResources() before setTargetInfo() is called.");
+
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+ IAndroidTarget target = mTargetInfo.getTarget();
+
+ // launch aapt: create the command line
+ ArrayList<String> command = Lists.newArrayList();
+
+ String aapt = buildToolInfo.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(target.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.TEST) {
+ 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);
+ }
+
+ // never compress apks.
+ command.add("-0");
+ command.add("apk");
+
+ // add custom no-compress extensions
+ 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) {
+ String packageName = VariantConfiguration.getManifestPackage(lib.getManifest());
+ if (appPackageName == null) {
+ continue;
+ }
+
+ if (appPackageName.equals(packageName)) {
+ if (enforceUniquePackageName) {
+ String msg = String.format(
+ "Error: A library uses the same package as this project: %s\n" +
+ "You can temporarily disable this error with android.enforceUniquePackageName=false\n" +
+ "However, this is temporary and will be enforced in 1.0",
+ packageName);
+ throw new RuntimeException(msg);
+ }
+
+ // ignore libraries that have the same package name as the app
+ continue;
+ }
+
+ File rFile = lib.getSymbolFile();
+ // if the library has no resource, this file won't exist.
+ if (rFile.isFile()) {
+
+
+ // 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);
+
+ if (enforceUniquePackageName && symbols.size() > 1) {
+ String msg = String.format(
+ "Error: more than one library with package name '%s'\n" +
+ "You can temporarily disable this error with android.enforceUniquePackageName=false\n" +
+ "However, this is temporary and will be enforced in 1.0", packageName);
+ throw new RuntimeException(msg);
+ }
+
+ SymbolWriter writer = new SymbolWriter(sourceOutputDir, packageName,
+ fullSymbolValues);
+ for (SymbolLoader symbolLoader : symbols) {
+ writer.addSymbolsToWrite(symbolLoader);
+ }
+ writer.write();
+ }
+ }
+ }
+
+ public void generateApkData(@NonNull File apkFile,
+ @NonNull File outResFolder,
+ @NonNull String mainPkgName)
+ throws InterruptedException, LoggedErrorException, IOException {
+
+ // need to run aapt to get apk information
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ // launch aapt: create the command line
+ ArrayList<String> command = Lists.newArrayList();
+
+ String aapt = buildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
+ if (aapt == null || !new File(aapt).isFile()) {
+ throw new IllegalStateException("aapt is missing");
+ }
+
+ command.add(aapt);
+ command.add("dump");
+ command.add("badging");
+ command.add(apkFile.getPath());
+
+ final List<String> aaptOutput = Lists.newArrayList();
+
+ mCmdLineRunner.runCmdLine(command, new CommandLineRunner.CommandLineOutput() {
+ @Override
+ public void out(@Nullable String line) {
+ if (line != null) {
+ aaptOutput.add(line);
+ }
+ }
+ @Override
+ public void err(@Nullable String line) {
+ super.err(line);
+
+ }
+ }, null /*env vars*/);
+
+ Pattern p = Pattern.compile("^package: name='(.+)' versionCode='([0-9]*)' versionName='(.*)'$");
+
+ String pkgName = null, versionCode = null, versionName = null;
+
+ for (String line : aaptOutput) {
+ Matcher m = p.matcher(line);
+ if (m.matches()) {
+ pkgName = m.group(1);
+ versionCode = m.group(2);
+ versionName = m.group(3);
+ break;
+ }
+ }
+
+ if (pkgName == null) {
+ throw new RuntimeException("Failed to find apk information with aapt");
+ }
+
+ if (!pkgName.equals(mainPkgName)) {
+ throw new RuntimeException("The main and the micro apps do not have the same package name.");
+ }
+
+ String content = String.format(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<wearableApp package=\"%1$s\">\n" +
+ " <versionCode>%2$s</versionCode>\n" +
+ " <versionName>%3$s</versionName>\n" +
+ " <path>%4$s</path>\n" +
+ "</wearableApp>", pkgName, versionCode, versionName, apkFile.getName());
+
+ // xml folder
+ File resXmlFile = new File(outResFolder, FD_RES_XML);
+ resXmlFile.mkdirs();
+
+ Files.write(content,
+ new File(resXmlFile, ANDROID_WEAR_MICRO_APK + DOT_XML),
+ Charsets.UTF_8);
+ }
+
+ public void generateApkDataEntryInManifest(@NonNull File manifestFile)
+ throws InterruptedException, LoggedErrorException, IOException {
+
+ String content =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<manifest package=\"\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" +
+ " <application>\n" +
+ " <meta-data android:name=\"" + ANDROID_WEAR + "\"\n" +
+ " android:resource=\"@xml/" + ANDROID_WEAR_MICRO_APK + "\" />\n" +
+ " </application>\n" +
+ "</manifest>\n";
+
+ Files.write(content, manifestFile, Charsets.UTF_8);
+ }
+
+ /**
+ * 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,
+ @Nullable File parcelableOutputDir,
+ @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.");
+ checkState(mTargetInfo != null,
+ "Cannot call compileAllAidlFiles() before setTargetInfo() is called.");
+
+ IAndroidTarget target = mTargetInfo.getTarget();
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ String aidl = buildToolInfo.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,
+ target.getPath(IAndroidTarget.ANDROID_AIDL),
+ fullImportList,
+ sourceOutputDir,
+ parcelableOutputDir,
+ 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 sourceFolder,
+ @NonNull File aidlFile,
+ @NonNull File sourceOutputDir,
+ @Nullable File parcelableOutputDir,
+ @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.");
+ checkState(mTargetInfo != null,
+ "Cannot call compileAidlFile() before setTargetInfo() is called.");
+
+ IAndroidTarget target = mTargetInfo.getTarget();
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ String aidl = buildToolInfo.getPath(BuildToolInfo.PathId.AIDL);
+ if (aidl == null || !new File(aidl).isFile()) {
+ throw new IllegalStateException("aidl is missing");
+ }
+
+ AidlProcessor processor = new AidlProcessor(
+ aidl,
+ target.getPath(IAndroidTarget.ANDROID_AIDL),
+ importFolders,
+ sourceOutputDir,
+ parcelableOutputDir,
+ dependencyFileProcessor != null ?
+ dependencyFileProcessor : sNoOpDependencyFileProcessor,
+ mCmdLineRunner);
+
+ processor.processFile(sourceFolder, 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
+ * @param ndkMode
+ * @param supportMode support mode flag to generate .so files.
+ * @param abiFilters ABI filters in case of support mode
+ *
+ * @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.");
+ checkState(mTargetInfo != null,
+ "Cannot call compileAllRenderscriptFiles() before setTargetInfo() is called.");
+
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ String renderscript = buildToolInfo.getPath(BuildToolInfo.PathId.LLVM_RS_CC);
+ if (renderscript == null || !new File(renderscript).isFile()) {
+ throw new IllegalStateException("llvm-rs-cc is missing");
+ }
+
+ RenderScriptProcessor processor = new RenderScriptProcessor(
+ sourceFolders,
+ importFolders,
+ sourceOutputDir,
+ resOutputDir,
+ objOutputDir,
+ libOutputDir,
+ buildToolInfo,
+ 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 outDexFolder the location of the output folder
+ * @param dexOptions dex options
+ * @param additionalParameters
+ * @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 outDexFolder,
+ @NonNull DexOptions dexOptions,
+ @Nullable List<String> additionalParameters,
+ boolean incremental) throws IOException, InterruptedException, LoggedErrorException {
+ checkNotNull(inputs, "inputs cannot be null.");
+ checkNotNull(preDexedLibraries, "preDexedLibraries cannot be null.");
+ checkNotNull(outDexFolder, "outDexFolder cannot be null.");
+ checkNotNull(dexOptions, "dexOptions cannot be null.");
+ checkArgument(outDexFolder.isDirectory(), "outDexFolder must be a folder");
+ checkState(mTargetInfo != null,
+ "Cannot call convertByteCode() before setTargetInfo() is called.");
+
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ // launch dx: create the command line
+ ArrayList<String> command = Lists.newArrayList();
+
+ String dx = buildToolInfo.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.getJumboMode()) {
+ command.add("--force-jumbo");
+ }
+
+ if (incremental) {
+ command.add("--incremental");
+ command.add("--no-strict");
+ }
+
+ if (dexOptions.getThreadCount() > 1) {
+ command.add("--num-threads=" + dexOptions.getThreadCount());
+ }
+
+ if (additionalParameters != null) {
+ for (String arg : additionalParameters) {
+ command.add(arg);
+ }
+ }
+
+ command.add("--output");
+ command.add(outDexFolder.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 {
+ checkState(mTargetInfo != null,
+ "Cannot call preDexLibrary() before setTargetInfo() is called.");
+
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ PreDexCache.getCache().preDexLibrary(inputFile, outFile, dexOptions, buildToolInfo,
+ mVerboseExec, mCmdLineRunner);
+ }
+
+ public static void preDexLibrary(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ @NonNull DexOptions dexOptions,
+ @NonNull BuildToolInfo buildToolInfo,
+ boolean verbose,
+ @NonNull CommandLineRunner commandLineRunner)
+ 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 = buildToolInfo.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 (verbose) {
+ command.add("--verbose");
+ }
+
+ if (dexOptions.getJumboMode()) {
+ command.add("--force-jumbo");
+ }
+
+ command.add("--output");
+ command.add(outFile.getAbsolutePath());
+
+ command.add(inputFile.getAbsolutePath());
+
+ commandLineRunner.runCmdLine(command, null);
+ }
+
+ /**
+ * Packages the apk.
+ *
+ * @param androidResPkgLocation the location of the packaged resource file
+ * @param dexFolder the folder with the 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 packagingOptions the packaging options
+ * @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 VariantConfiguration#getPackagedJars()
+ */
+ public void packageApk(
+ @NonNull String androidResPkgLocation,
+ @NonNull File dexFolder,
+ @NonNull Collection<File> packagedJars,
+ @Nullable String javaResourcesLocation,
+ @Nullable Collection<File> jniLibsFolders,
+ @Nullable Set<String> abiFilters,
+ boolean jniDebugBuild,
+ @Nullable SigningConfig signingConfig,
+ @Nullable PackagingOptions packagingOptions,
+ @NonNull String outApkLocation)
+ throws DuplicateFileException, FileNotFoundException,
+ KeytoolException, PackagerException, SigningException {
+ checkNotNull(androidResPkgLocation, "androidResPkgLocation cannot be null.");
+ checkNotNull(dexFolder, "dexFolder cannot be null.");
+ checkArgument(dexFolder.isDirectory(), "dexFolder is not a directory");
+ checkNotNull(outApkLocation, "outApkLocation cannot be null.");
+
+ CertificateInfo certificateInfo = null;
+ if (signingConfig != null && signingConfig.isSigningReady()) {
+ certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig.getStoreType(),
+ signingConfig.getStoreFile(), signingConfig.getStorePassword(),
+ signingConfig.getKeyPassword(), signingConfig.getKeyAlias());
+ if (certificateInfo == null) {
+ throw new SigningException("Failed to read key from keystore");
+ }
+ }
+
+ try {
+ Packager packager = new Packager(
+ outApkLocation, androidResPkgLocation, dexFolder,
+ certificateInfo, mCreatedBy, packagingOptions, 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.
+ Set<String> hashs = Sets.newHashSet();
+
+ for (File jar : packagedJars) {
+ // TODO remove once we can properly add a library as a dependency of its test.
+ String hash = getFileHash(jar);
+ if (hash == null) {
+ throw new PackagerException("Unable to compute hash of " + jar.getAbsolutePath());
+ }
+ if (hashs.contains(hash)) {
+ continue;
+ }
+
+ hashs.add(hash);
+
+ packager.addResourcesFromJar(jar);
+ }
+
+ // also add resources from library projects and jars
+ if (jniLibsFolders != null) {
+ for (File jniFolder : jniLibsFolders) {
+ if (jniFolder.isDirectory()) {
+ 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);
+ }
+ }
+
+ /**
+ * Returns the hash of a file.
+ * @param file the file to hash
+ * @return the hash or null if an error happened
+ */
+ @Nullable
+ private static String getFileHash(@NonNull File file) {
+ try {
+ HashCode hashCode = Files.hash(file, Hashing.sha1());
+ return hashCode.toString();
+ } catch (IOException ignored) {
+
+ }
+
+ return null;
+ }
+
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/BuilderConstants.java b/build-system/builder/src/main/java/com/android/builder/core/BuilderConstants.java
new file mode 100644
index 0000000..240fe74
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/BuilderConstants.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.builder.core;
+
+/**
+ * Generic constants.
+ */
+public class BuilderConstants {
+
+ /**
+ * Extension for library packages.
+ */
+ public static final String EXT_LIB_ARCHIVE = "aar";
+
+ /**
+ * The name of the default config.
+ */
+ public static final String MAIN = "main";
+
+ public static final String DEBUG = "debug";
+ public static final String RELEASE = "release";
+
+ public static final String LINT = "lint";
+
+ public static final String FD_REPORTS = "reports";
+
+ public static final String CONNECTED = "connected";
+ public static final String DEVICE = "device";
+
+ public static final String ANDROID_TEST = "androidTest";
+ public static final String FD_ANDROID_TESTS = "androidTests";
+ public static final String FD_ANDROID_RESULTS = ANDROID_TEST + "-results";
+
+ public static final String UI_TEST = "uiTest";
+ public static final String FD_UI_TESTS = "uiTests";
+ public static final String FD_UI_RESULTS = UI_TEST + "-results";
+
+ public static final String FD_FLAVORS = "flavors";
+ public static final String FD_FLAVORS_ALL = "all";
+
+ public static final String ANDROID_WEAR_MICRO_APK = "android_wear_micro_apk";
+
+ public static final String ANDROID_WEAR = "com.google.android.wearable.beta.app";
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DefaultApiVersion.java b/build-system/builder/src/main/java/com/android/builder/core/DefaultApiVersion.java
new file mode 100644
index 0000000..1840ffc
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DefaultApiVersion.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.ApiVersion;
+
+/**
+ * Basic implementation of ApiVersion
+ */
+public class DefaultApiVersion implements ApiVersion {
+
+ private final int mApiLevel;
+
+ @Nullable
+ private final String mCodename;
+
+ public DefaultApiVersion(int apiLevel, @Nullable String codename) {
+ mApiLevel = apiLevel;
+ mCodename = codename;
+ }
+
+ public DefaultApiVersion(int apiLevel) {
+ this(apiLevel, null);
+ }
+
+ public DefaultApiVersion(@NonNull String codename) {
+ this(1, codename);
+ }
+
+ public static ApiVersion create(@NonNull Object value) {
+ if (value instanceof Integer) {
+ return new DefaultApiVersion((Integer) value, null);
+ } else if (value instanceof String) {
+ return new DefaultApiVersion(1, (String) value);
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getApiLevel() {
+ return mApiLevel;
+ }
+
+ @Nullable
+ @Override
+ public String getCodename() {
+ return mCodename;
+ }
+
+ @NonNull
+ @Override
+ public String getApiString() {
+ return mCodename != null ? mCodename : Integer.toString(mApiLevel);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DefaultApiVersion that = (DefaultApiVersion) o;
+
+ if (mApiLevel != that.mApiLevel) {
+ return false;
+ }
+ if (mCodename != null ? !mCodename.equals(that.mCodename) : that.mCodename != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mApiLevel;
+ result = 31 * result + (mCodename != null ? mCodename.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ApiVersionImpl{" +
+ "mApiLevel=" + mApiLevel +
+ ", mCodename='" + mCodename + '\'' +
+ '}';
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DefaultBuildType.java b/build-system/builder/src/main/java/com/android/builder/core/DefaultBuildType.java
new file mode 100644
index 0000000..75efd55
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DefaultBuildType.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+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 mTestCoverageEnabled = false;
+ private boolean mJniDebugBuild = false;
+ private boolean mRenderscriptDebugBuild = false;
+ private int mRenderscriptOptimLevel = 3;
+ private String mApplicationIdSuffix = null;
+ private String mVersionNameSuffix = null;
+ private boolean mRunProguard = false;
+ private SigningConfig mSigningConfig = null;
+ private boolean mEmbedMicroApp = true;
+
+ private boolean mZipAlign = true;
+
+ public DefaultBuildType(@NonNull String name) {
+ mName = name;
+ }
+
+ public DefaultBuildType initWith(DefaultBuildType that) {
+ _initWith(that);
+
+ setDebuggable(that.isDebuggable());
+ setTestCoverageEnabled(that.isTestCoverageEnabled());
+ setJniDebugBuild(that.isJniDebugBuild());
+ setRenderscriptDebugBuild(that.isRenderscriptDebugBuild());
+ setRenderscriptOptimLevel(that.getRenderscriptOptimLevel());
+ setApplicationIdSuffix(that.getApplicationIdSuffix());
+ setVersionNameSuffix(that.getVersionNameSuffix());
+ setRunProguard(that.isRunProguard());
+ setZipAlign(that.isZipAlign());
+ setSigningConfig(that.getSigningConfig());
+ setEmbedMicroApp(that.isEmbedMicroApp());
+
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /** Whether this build type should generate a debuggable apk. */
+ @NonNull
+ public BuildType setDebuggable(boolean debuggable) {
+ mDebuggable = debuggable;
+ return this;
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return mDebuggable;
+ }
+
+
+ public void setTestCoverageEnabled(boolean testCoverageEnabled) {
+ mTestCoverageEnabled = testCoverageEnabled;
+ }
+
+ @Override
+ public boolean isTestCoverageEnabled() {
+ return mTestCoverageEnabled;
+ }
+
+ /**
+ * Whether this build type is configured to generate an APK with debuggable native code.
+ */
+ @NonNull
+ public BuildType setJniDebugBuild(boolean jniDebugBuild) {
+ mJniDebugBuild = jniDebugBuild;
+ return this;
+ }
+
+ @Override
+ public boolean isJniDebugBuild() {
+ return mJniDebugBuild;
+ }
+
+ @Override
+ public boolean isRenderscriptDebugBuild() {
+ return mRenderscriptDebugBuild;
+ }
+
+ /**
+ * Whether the build type is configured to generate an apk with debuggable RenderScript code.
+ */
+ public void setRenderscriptDebugBuild(boolean renderscriptDebugBuild) {
+ mRenderscriptDebugBuild = renderscriptDebugBuild;
+ }
+
+ @Override
+ public int getRenderscriptOptimLevel() {
+ return mRenderscriptOptimLevel;
+ }
+
+ /** Optimization level to use by the renderscript compiler. */
+ public void setRenderscriptOptimLevel(int renderscriptOptimLevel) {
+ mRenderscriptOptimLevel = renderscriptOptimLevel;
+ }
+
+ /**
+ * Application id suffix applied to this build type.
+ */
+ @NonNull
+ public BuildType setApplicationIdSuffix(@Nullable String applicationIdSuffix) {
+ mApplicationIdSuffix = applicationIdSuffix;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getApplicationIdSuffix() {
+ return mApplicationIdSuffix;
+ }
+
+ /** Version name suffix. */
+ @NonNull
+ public BuildType setVersionNameSuffix(@Nullable String versionNameSuffix) {
+ mVersionNameSuffix = versionNameSuffix;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getVersionNameSuffix() {
+ return mVersionNameSuffix;
+ }
+
+ /** Whether ProGuard is enabled for this build type. */
+ @NonNull
+ public BuildType setRunProguard(boolean runProguard) {
+ mRunProguard = runProguard;
+ return this;
+ }
+
+ @Override
+ public boolean isRunProguard() {
+ return mRunProguard;
+ }
+
+ /** Whether zipalign is enabled for this build type. */
+ @NonNull
+ public BuildType setZipAlign(boolean zipAlign) {
+ mZipAlign = zipAlign;
+ return this;
+ }
+
+ @Override
+ public boolean isZipAlign() {
+ return mZipAlign;
+ }
+
+ /** Sets the signing configuration. e.g.: {@code signingConfig signingConfigs.myConfig} */
+ @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 isEmbedMicroApp() {
+ return mEmbedMicroApp;
+ }
+
+ public void setEmbedMicroApp(boolean embedMicroApp) {
+ mEmbedMicroApp = embedMicroApp;
+ }
+
+ @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 (mTestCoverageEnabled != buildType.mTestCoverageEnabled) 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 (mApplicationIdSuffix != null ?
+ !mApplicationIdSuffix.equals(buildType.mApplicationIdSuffix) :
+ buildType.mApplicationIdSuffix != 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;
+ if (mEmbedMicroApp != buildType.mEmbedMicroApp) 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 + (mTestCoverageEnabled ? 1 : 0);
+ result = 31 * result + (mJniDebugBuild ? 1 : 0);
+ result = 31 * result + (mRenderscriptDebugBuild ? 1 : 0);
+ result = 31 * result + mRenderscriptOptimLevel;
+ result = 31 * result + (mApplicationIdSuffix != null ? mApplicationIdSuffix.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);
+ result = 31 * result + (mEmbedMicroApp ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", mName)
+ .add("debuggable", mDebuggable)
+ .add("testCoverageEnabled", mTestCoverageEnabled)
+ .add("jniDebugBuild", mJniDebugBuild)
+ .add("renderscriptDebugBuild", mRenderscriptDebugBuild)
+ .add("renderscriptOptimLevel", mRenderscriptOptimLevel)
+ .add("applicationIdSuffix", mApplicationIdSuffix)
+ .add("versionNameSuffix", mVersionNameSuffix)
+ .add("runProguard", mRunProguard)
+ .add("zipAlign", mZipAlign)
+ .add("signingConfig", mSigningConfig)
+ .add("embedMicroApp", mEmbedMicroApp)
+ .toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DefaultManifestParser.java b/build-system/builder/src/main/java/com/android/builder/core/DefaultManifestParser.java
new file mode 100644
index 0000000..8155f08
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DefaultManifestParser.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.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.io.FileWrapper;
+import com.android.io.StreamException;
+import com.android.utils.XmlUtils;
+import com.android.xml.AndroidManifest;
+import com.android.xml.AndroidXPathFactory;
+
+import org.xml.sax.InputSource;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+
+public class DefaultManifestParser implements ManifestParser {
+
+ @Nullable
+ @Override
+ public String getPackage(@NonNull File manifestFile) {
+ return getStringValue(manifestFile, "/manifest/@package");
+ }
+
+ @Nullable
+ @Override
+ public String getVersionName(@NonNull File manifestFile) {
+ return getStringValue(manifestFile, "/manifest/@android:versionName");
+ }
+
+ @Override
+ public int getVersionCode(@NonNull File manifestFile) {
+ try {
+ String value = getStringValue(manifestFile, "/manifest/@android:versionCode");
+ if (value != null) {
+ return Integer.valueOf(value);
+ }
+ } catch (NumberFormatException ignored) {
+ // return -1 below.
+ }
+
+ return -1;
+ }
+
+ @Override
+ public Object getMinSdkVersion(@NonNull File manifestFile) {
+ try {
+ return AndroidManifest.getMinSdkVersion(new FileWrapper(manifestFile));
+ } catch (XPathExpressionException e) {
+ // won't happen.
+ } catch (StreamException e) {
+ throw new RuntimeException(e);
+ }
+
+ return 1;
+ }
+
+ @Override
+ public Object getTargetSdkVersion(@NonNull File manifestFile) {
+ try {
+ return AndroidManifest.getTargetSdkVersion(new FileWrapper(manifestFile));
+ } catch (XPathExpressionException e) {
+ // won't happen.
+ } catch (StreamException e) {
+ throw new RuntimeException(e);
+ }
+
+ return -1;
+ }
+
+ private static String getStringValue(@NonNull File file, @NonNull String xPath) {
+ XPath xpath = AndroidXPathFactory.newXPath();
+
+ try {
+ InputSource source = new InputSource(XmlUtils.getUtfReader(file));
+ return xpath.evaluate(xPath, source);
+ } catch (XPathExpressionException e) {
+ // won't happen.
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return null;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DefaultProductFlavor.java b/build-system/builder/src/main/java/com/android/builder/core/DefaultProductFlavor.java
new file mode 100644
index 0000000..3b768f5
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DefaultProductFlavor.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.BaseConfigImpl;
+import com.android.builder.model.ApiVersion;
+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.Maps;
+import com.google.common.collect.Sets;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+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 ApiVersion mMinSdkVersion = null;
+ private ApiVersion mTargetSdkVersion = null;
+ private int mRenderscriptTargetApi = -1;
+ private Boolean mRenderscriptSupportMode;
+ private Boolean mRenderscriptNdkMode;
+ private int mVersionCode = -1;
+ private String mVersionName = null;
+ private String mApplicationId = null;
+ private String mTestApplicationId = null;
+ private String mTestInstrumentationRunner = null;
+ private Boolean mTestHandleProfiling = null;
+ private Boolean mTestFunctionalTest = null;
+ private SigningConfig mSigningConfig = null;
+ private Set<String> mResourceConfiguration = null;
+ private Map<String, String> mManifestPlaceholders = 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 application id.
+ *
+ * @param applicationId the application id
+ * @return the flavor object
+ */
+ @NonNull
+ public ProductFlavor setApplicationId(String applicationId) {
+ mApplicationId = applicationId;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getApplicationId() {
+ return mApplicationId;
+ }
+
+ /**
+ * 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;
+ }
+
+ /** Sets the minSdkVersion to the given value. */
+ @NonNull
+ public ProductFlavor setMinSdkVersion(ApiVersion minSdkVersion) {
+ mMinSdkVersion = minSdkVersion;
+ return this;
+ }
+
+ @Override
+ public ApiVersion getMinSdkVersion() {
+ return mMinSdkVersion;
+ }
+
+ /** Sets the targetSdkVersion to the given value. */
+ @NonNull
+ public ProductFlavor setTargetSdkVersion(ApiVersion targetSdkVersion) {
+ mTargetSdkVersion = targetSdkVersion;
+ return this;
+ }
+
+ @Override
+ public ApiVersion getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ @Override
+ public int getRenderscriptTargetApi() {
+ return mRenderscriptTargetApi;
+ }
+
+ /** Sets the renderscript target API to the given value. */
+ public void setRenderscriptTargetApi(int renderscriptTargetApi) {
+ mRenderscriptTargetApi = renderscriptTargetApi;
+ }
+
+ @Override
+ public boolean getRenderscriptSupportMode() {
+ // default is false
+ return mRenderscriptSupportMode != null && mRenderscriptSupportMode.booleanValue();
+ }
+
+ /**
+ * Sets whether the renderscript code should be compiled in support mode to make it compatible
+ * with older versions of Android.
+ */
+ public void setRenderscriptSupportMode(boolean renderscriptSupportMode) {
+ mRenderscriptSupportMode = renderscriptSupportMode;
+ }
+
+ @Override
+ public boolean getRenderscriptNdkMode() {
+ // default is false
+ return mRenderscriptNdkMode != null && mRenderscriptNdkMode.booleanValue();
+ }
+
+ /** Sets whether the renderscript code should be compiled to generate C/C++ bindings. */
+ public void setRenderscriptNdkMode(boolean renderscriptNdkMode) {
+ mRenderscriptNdkMode = renderscriptNdkMode;
+ }
+
+ /** Sets the test package name. */
+ @NonNull
+ public ProductFlavor setTestApplicationId(String applicationId) {
+ mTestApplicationId = applicationId;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getTestApplicationId() {
+ return mTestApplicationId;
+ }
+
+ /** Sets the test instrumentation runner to the given value. */
+ @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;
+ }
+
+ /** Sets the signing configuration. e.g.: {@code signingConfig signingConfigs.myConfig} */
+ @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;
+ }
+
+ @NonNull
+ public Map<String, String> getManifestPlaceholders() {
+ if (mManifestPlaceholders == null) {
+ mManifestPlaceholders = Maps.newHashMap();
+ }
+ return mManifestPlaceholders;
+ }
+
+ public void addManifestPlaceHolders(@NonNull Map<String, String> manifestPlaceholders) {
+ if (mManifestPlaceholders == null) {
+ mManifestPlaceholders = Maps.newHashMap();
+ }
+ mManifestPlaceholders.putAll(manifestPlaceholders);
+ }
+
+ public void setManifestPlaceholders(Map<String, String> manifestPlaceholders) {
+ this.mManifestPlaceholders = manifestPlaceholders;
+ }
+
+ /**
+ * 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 =
+ mMinSdkVersion != null ? mMinSdkVersion : base.mMinSdkVersion;
+ flavor.mTargetSdkVersion =
+ mTargetSdkVersion != null ? 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.mApplicationId = chooseString(mApplicationId, base.mApplicationId);
+
+ flavor.mTestApplicationId = chooseString(mTestApplicationId, base.mTestApplicationId);
+ 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(getResourceConfigurations());
+ flavor.addResourceConfigurations(base.getResourceConfigurations());
+
+ flavor.addManifestPlaceHolders(base.getManifestPlaceholders());
+ flavor.addManifestPlaceHolders(getManifestPlaceholders());
+
+ 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 != null ? !mMinSdkVersion.equals(that.mMinSdkVersion) : that.mMinSdkVersion != null)
+ return false;
+ if (mRenderscriptTargetApi != that.mRenderscriptTargetApi) return false;
+ if (mTargetSdkVersion != null ? !mTargetSdkVersion.equals(that.mTargetSdkVersion) : that.mTargetSdkVersion != null)
+ return false;
+ if (mVersionCode != that.mVersionCode) return false;
+ if (!mName.equals(that.mName)) return false;
+ if (mApplicationId != null ? !mApplicationId.equals(that.mApplicationId) : that.mApplicationId != 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 (mTestApplicationId != null ? !mTestApplicationId.equals(that.mTestApplicationId) : that.mTestApplicationId != 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 != null ? mMinSdkVersion.hashCode() : 0);
+ result = 31 * result + (mTargetSdkVersion != null ? mTargetSdkVersion.hashCode() : 0);
+ 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 + (mApplicationId != null ? mApplicationId.hashCode() : 0);
+ result = 31 * result + (mTestApplicationId != null ? mTestApplicationId.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("applicationId", mApplicationId)
+ .add("testApplicationId", mTestApplicationId)
+ .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/core/DexOptions.java b/build-system/builder/src/main/java/com/android/builder/core/DexOptions.java
new file mode 100644
index 0000000..eebaa27
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DexOptions.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+public interface DexOptions {
+
+ boolean getIncremental();
+ boolean getPreDexLibraries();
+ boolean getJumboMode();
+ String getJavaMaxHeapSize();
+
+ int getThreadCount();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/ManifestParser.java b/build-system/builder/src/main/java/com/android/builder/core/ManifestParser.java
new file mode 100644
index 0000000..979f316
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/ManifestParser.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+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.
+ * The returned value can be an Integer or a String
+ *
+ * @param manifestFile the manifest file to parse
+ *
+ * @return the minSdkVersion or null if not found.
+ */
+ Object getMinSdkVersion(@NonNull File manifestFile);
+
+ /**
+ * Returns the targetSdkVersion parsed from the given manifest file.
+ * The returned value can be an Integer or a String
+ *
+ * @param manifestFile the manifest file to parse
+ *
+ * @return the targetSdkVersion or null if not found
+ */
+ Object 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/core/VariantConfiguration.java b/build-system/builder/src/main/java/com/android/builder/core/VariantConfiguration.java
new file mode 100644
index 0000000..7244942
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/VariantConfiguration.java
@@ -0,0 +1,1614 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+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.ApiVersion;
+import com.android.builder.model.BaseConfig;
+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.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.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 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();
+
+ /**
+ * Signing Override to be used instead of any signing config provided by Build Type or
+ * Product Flavors.
+ */
+ private final SigningConfig mSigningConfigOverride;
+
+ 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,
+ @Nullable SigningConfig signingConfigOverride) {
+ this(
+ defaultConfig, defaultSourceProvider,
+ buildType, buildTypeSourceProvider,
+ Type.DEFAULT, null /*testedConfig*/, signingConfigOverride);
+ }
+
+ /**
+ * 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.
+ * @param signingConfigOverride an optional Signing override to be used for signing.
+ */
+ public VariantConfiguration(
+ @NonNull DefaultProductFlavor defaultConfig,
+ @NonNull SourceProvider defaultSourceProvider,
+ @NonNull DefaultBuildType buildType,
+ @Nullable SourceProvider buildTypeSourceProvider,
+ @NonNull Type type,
+ @Nullable SigningConfig signingConfigOverride) {
+ this(
+ defaultConfig, defaultSourceProvider,
+ buildType, buildTypeSourceProvider,
+ type, null /*testedConfig*/, signingConfigOverride);
+ }
+
+ /**
+ * 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
+ * @param signingConfigOverride an optional Signing override to be used for signing.
+ */
+ public VariantConfiguration(
+ @NonNull DefaultProductFlavor defaultConfig,
+ @NonNull SourceProvider defaultSourceProvider,
+ @NonNull DefaultBuildType buildType,
+ @Nullable SourceProvider buildTypeSourceProvider,
+ @NonNull Type type,
+ @Nullable VariantConfiguration testedConfig,
+ @Nullable SigningConfig signingConfigOverride) {
+ mDefaultConfig = checkNotNull(defaultConfig);
+ mDefaultSourceProvider = checkNotNull(defaultSourceProvider);
+ mBuildType = checkNotNull(buildType);
+ mBuildTypeSourceProvider = buildTypeSourceProvider;
+ mType = checkNotNull(type);
+ mTestedConfig = testedConfig;
+ mSigningConfigOverride = signingConfigOverride;
+ checkState(mType != Type.TEST || mTestedConfig != null);
+
+ mMergedFlavor = mDefaultConfig;
+ computeNdkConfig();
+
+ if (testedConfig != null &&
+ testedConfig.mType == Type.LIBRARY &&
+ testedConfig.mOutput != null) {
+ mDirectLibraries.add(testedConfig.mOutput);
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Checks the SourceProviders to ensure that they don't use the same folders.
+ *
+ * This can't be allowed as it can break incremental support, or
+ * generate dup files in resulting APK.
+ *
+ * @throws java.lang.RuntimeException if a dup is found.
+ */
+ public void checkSourceProviders() {
+ List<SourceProvider> providers = Lists.newArrayListWithExpectedSize(4 + mFlavorSourceProviders.size());
+
+ providers.add(mDefaultSourceProvider);
+ if (mBuildTypeSourceProvider != null) {
+ providers.add(mBuildTypeSourceProvider);
+ }
+
+ providers.addAll(mFlavorSourceProviders);
+ if (mVariantSourceProvider != null) {
+ providers.add(mVariantSourceProvider);
+ }
+ if (mMultiFlavorSourceProvider != null) {
+ providers.add(mMultiFlavorSourceProvider);
+ }
+
+ try {
+ checkFileSourceSet(providers, SourceProvider.class.getMethod("getManifestFile"), "manifest");
+ checkFileCollectionSourceSet(providers,
+ SourceProvider.class.getMethod("getJavaDirectories"), "java");
+ checkFileCollectionSourceSet(providers,
+ SourceProvider.class.getMethod("getResourcesDirectories"), "resources");
+ checkFileCollectionSourceSet(providers,
+ SourceProvider.class.getMethod("getAidlDirectories"), "aidl");
+ checkFileCollectionSourceSet(providers,
+ SourceProvider.class.getMethod("getRenderscriptDirectories"), "rs");
+ checkFileCollectionSourceSet(providers,
+ SourceProvider.class.getMethod("getJniDirectories"), "jni");
+ checkFileCollectionSourceSet(providers,
+ SourceProvider.class.getMethod("getResDirectories"), "res");
+ checkFileCollectionSourceSet(providers,
+ SourceProvider.class.getMethod("getAssetsDirectories"), "assets");
+ checkFileCollectionSourceSet(providers,
+ SourceProvider.class.getMethod("getJniLibsDirectories"), "jniLibs");
+
+ } catch (NoSuchMethodException ignored) {
+ } catch (InvocationTargetException ignored) {
+ } catch (IllegalAccessException ignored) {
+ }
+ }
+
+ private static void checkFileSourceSet(
+ List<SourceProvider> providers,
+ Method m,
+ @NonNull String name)
+ throws IllegalAccessException, InvocationTargetException {
+ Map<String, File> map = Maps.newHashMap();
+ for (SourceProvider sourceProvider : providers) {
+ File file = (File) m.invoke(sourceProvider);
+
+ if (!map.isEmpty()) {
+ for (Map.Entry<String, File> entry : map.entrySet()) {
+ if (file.equals(entry.getValue())) {
+ throw new RuntimeException(String.format(
+ "SourceSets '%s' and '%s' use the same file for '%s': %s",
+ sourceProvider.getName(),
+ entry.getKey(),
+ name,
+ file));
+ }
+ }
+ }
+
+ map.put(sourceProvider.getName(), file);
+ }
+ }
+
+ private static void checkFileCollectionSourceSet(
+ List<SourceProvider> providers,
+ Method m,
+ @NonNull String name)
+ throws IllegalAccessException, InvocationTargetException {
+ Multimap<String, File> map = ArrayListMultimap.create();
+ for (SourceProvider sourceProvider : providers) {
+ //noinspection unchecked
+ Collection<File> list = (Collection<File>) m.invoke(sourceProvider);
+
+ if (!map.isEmpty()) {
+ for (Map.Entry<String, Collection<File>> entry : map.asMap().entrySet()) {
+ Collection<File> existingFiles = entry.getValue();
+ for (File f : list) {
+ if (existingFiles.contains(f)) {
+ throw new RuntimeException(String.format(
+ "SourceSets '%s' and '%s' use the same file/folder for '%s': %s",
+ sourceProvider.getName(),
+ entry.getKey(),
+ name,
+ f));
+ }
+ }
+ }
+ }
+
+ map.putAll(sourceProvider.getName(), list);
+ }
+ }
+
+ /**
+ * 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 application ID before any overrides from flavors.
+ * If the variant is a test variant, then the application ID is the one coming from the
+ * configuration of the tested variant, and this call is similar to {@link #getApplicationId()}
+ * @return the original application ID
+ */
+ @Nullable
+ public String getOriginalApplicationId() {
+ if (mType == VariantConfiguration.Type.TEST) {
+ return getApplicationId();
+ }
+
+ return getPackageFromManifest();
+ }
+
+ /**
+ * Returns the application ID 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 application ID
+ */
+ @Override
+ @NonNull
+ public String getApplicationId() {
+ String id;
+
+ if (mType == Type.TEST) {
+ assert mTestedConfig != null;
+
+ id = mMergedFlavor.getTestApplicationId();
+ if (id == null) {
+ String testedPackage = mTestedConfig.getApplicationId();
+ id = testedPackage + ".test";
+ }
+ } else {
+ // first get package override.
+ id = getIdOverride();
+ // if it's null, this means we just need the default package
+ // from the manifest since both flavor and build type do nothing.
+ if (id == null) {
+ id = getPackageFromManifest();
+ }
+ }
+
+ if (id == null) {
+ throw new RuntimeException("Failed to get application id for " + getFullName());
+ }
+
+ return id;
+ }
+
+ @Override
+ @Nullable
+ public String getTestedApplicationId() {
+ if (mType == Type.TEST) {
+ assert mTestedConfig != null;
+ if (mTestedConfig.mType == Type.LIBRARY) {
+ return getApplicationId();
+ } else {
+ return mTestedConfig.getApplicationId();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the application id override value coming from the Product Flavor and/or the
+ * Build Type. If the package/id is not overridden then this returns null.
+ *
+ * @return the id override or null
+ */
+ @Nullable
+ public String getIdOverride() {
+ String idName = mMergedFlavor.getApplicationId();
+ String idSuffix = mBuildType.getApplicationIdSuffix();
+
+ if (idSuffix != null && !idSuffix.isEmpty()) {
+ if (idName == null) {
+ idName = getPackageFromManifest();
+ }
+
+ if (idSuffix.charAt(0) == '.') {
+ idName = idName + idSuffix;
+ } else {
+ idName = idName + '.' + idSuffix;
+ }
+ }
+
+ return idName;
+ }
+
+ /**
+ * 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.isEmpty()) {
+ 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 static final String DEFAULT_TEST_RUNNER = "android.test.InstrumentationTestRunner";
+ private static final Boolean DEFAULT_HANDLE_PROFILING = false;
+ private static final 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 ApiVersion getMinSdkVersion() {
+ if (mTestedConfig != null) {
+ return mTestedConfig.getMinSdkVersion();
+ }
+ ApiVersion minSdkVersion = mMergedFlavor.getMinSdkVersion();
+ if (minSdkVersion == null) {
+ // read it from the main manifest
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ minSdkVersion = DefaultApiVersion.create(
+ 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 ApiVersion getTargetSdkVersion() {
+ if (mTestedConfig != null) {
+ return mTestedConfig.getTargetSdkVersion();
+ }
+ ApiVersion targetSdkVersion = mMergedFlavor.getTargetSdkVersion();
+ if (targetSdkVersion == null) {
+ // read it from the main manifest
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ targetSdkVersion = DefaultApiVersion.create(
+ 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;
+ }
+
+ /**
+ * Returns a list of sorted SourceProvider in order of ascending order, meaning, the earlier
+ * items are meant to be overridden by later items.
+ *
+ * @return a list of source provider
+ */
+ @NonNull
+ public List<SourceProvider> getSortedSourceProviders() {
+ List<SourceProvider> providers = Lists.newArrayList();
+
+ // first the default source provider
+ providers.add(mDefaultSourceProvider);
+
+ // the list of flavor must be reversed to use the right overlay order.
+ for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
+ providers.add(mFlavorSourceProviders.get(n));
+ }
+
+ // multiflavor specific overrides flavor
+ if (mMultiFlavorSourceProvider != null) {
+ providers.add(mMultiFlavorSourceProvider);
+ }
+
+ // build type overrides flavors
+ if (mType != Type.TEST && mBuildTypeSourceProvider != null) {
+ providers.add(mBuildTypeSourceProvider);
+ }
+
+ // variant specific overrides all
+ if (mVariantSourceProvider != null) {
+ providers.add(mVariantSourceProvider);
+ }
+
+ return providers;
+ }
+
+ @NonNull
+ public List<BaseConfig> getSortedBaseConfigs() {
+ List<BaseConfig> configs = Lists.newArrayList();
+
+ configs.add(mDefaultConfig);
+
+ // the list of flavor must be reversed to use the right overlay order.
+ for (int n = mFlavorConfigs.size() - 1; n >= 0 ; n--) {
+ configs.add(mFlavorConfigs.get(n));
+ }
+
+ // build type overrides flavors
+ if (mType != Type.TEST) {
+ configs.add(mBuildType);
+ }
+
+ return configs;
+ }
+
+ @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 generatedResFolders a list of generated res folders
+ * @param includeDependencies whether to include in the result the resources of the dependencies
+ *
+ * @return a list ResourceSet.
+ */
+ @NonNull
+ public List<ResourceSet> getResourceSets(@NonNull List<File> generatedResFolders,
+ 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();
+
+ // the main + generated res folders are in the same ResourceSet
+ ResourceSet resourceSet = new ResourceSet(BuilderConstants.MAIN);
+ resourceSet.addSources(mainResDirs);
+ if (!generatedResFolders.isEmpty()) {
+ for (File generatedResFolder : generatedResFolders) {
+ 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(sourceProvider.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(@NonNull List<File> generatedResFolders,
+ 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();
+
+ // the main + generated asset folders are in the same AssetSet
+ AssetSet assetSet = new AssetSet(BuilderConstants.MAIN);
+ assetSet.addSources(mainResDirs);
+ if (!generatedResFolders.isEmpty()) {
+ for (File generatedResFolder : generatedResFolders) {
+ assetSet.addSource(generatedResFolder);
+ }
+ }
+ 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<SourceProvider> providers = getSortedSourceProviders();
+
+ List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
+
+ for (SourceProvider provider : providers) {
+ sourceList.addAll(provider.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<SourceProvider> providers = getSortedSourceProviders();
+
+ List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
+
+ for (SourceProvider provider : providers) {
+ sourceList.addAll(provider.getAidlDirectories());
+ }
+
+ return sourceList;
+ }
+
+ @NonNull
+ public List<File> getJniSourceList() {
+ List<SourceProvider> providers = getSortedSourceProviders();
+
+ List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
+
+ for (SourceProvider provider : providers) {
+ sourceList.addAll(provider.getJniDirectories());
+ }
+
+ return sourceList;
+ }
+
+ @NonNull
+ public List<File> getJniLibsList() {
+ List<SourceProvider> providers = getSortedSourceProviders();
+
+ List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
+
+ for (SourceProvider provider : providers) {
+ sourceList.addAll(provider.getJniLibsDirectories());
+ }
+
+ 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.newHashSetWithExpectedSize(mJars.size() + mFlatLibraries.size());
+
+ 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 Set<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 jars;
+ }
+
+ /**
+ * Returns the list of provided-only jars for this config.
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public List<File> getProvidedOnlyJars() {
+ Set<File> jars = Sets.newHashSetWithExpectedSize(mJars.size());
+
+ for (JarDependency jar : mJars) {
+ File jarFile = jar.getJarFile();
+ if (jar.isCompiled() && !jar.isPackaged() && jarFile.exists()) {
+ 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();
+
+ Collection<ClassField> list = mBuildType.getBuildConfigFields().values();
+ 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().values();
+ 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().values();
+ 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;
+ }
+
+ /**
+ * Returns a list of generated resource values.
+ *
+ * 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> getResValues() {
+ List<Object> fullList = Lists.newArrayList();
+
+ Set<String> usedFieldNames = Sets.newHashSet();
+
+ Collection<ClassField> list = mBuildType.getResValues().values();
+ if (!list.isEmpty()) {
+ fullList.add("Values from build type: " + mBuildType.getName());
+ for (ClassField f : list) {
+ usedFieldNames.add(f.getName());
+ fullList.add(f);
+ }
+ }
+
+ for (DefaultProductFlavor flavor : mFlavorConfigs) {
+ list = flavor.getResValues().values();
+ if (!list.isEmpty()) {
+ fullList.add("Values 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.getResValues().values();
+ if (!list.isEmpty()) {
+ fullList.add("Values 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() {
+ if (mSigningConfigOverride != null) {
+ return mSigningConfigOverride;
+ }
+
+ 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;
+ }
+
+ @NonNull
+ public NdkConfig getNdkConfig() {
+ return mMergedNdkConfig;
+ }
+
+ @Nullable
+ @Override
+ public Set<String> getSupportedAbis() {
+ if (mMergedNdkConfig != null) {
+ return mMergedNdkConfig.getAbiFilters();
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isTestCoverageEnabled() {
+ return mBuildType.isTestCoverageEnabled();
+ }
+}
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
index d2831a0..a28fb4e 100644
--- 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
@@ -27,7 +27,7 @@
private final File mJarFile;
private final boolean mCompiled;
- private final boolean mPackaged;
+ private boolean mPackaged;
private final boolean mProguarded;
public JarDependency(@NonNull File jarFile, boolean compiled, boolean packaged,
@@ -38,9 +38,6 @@
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);
@@ -59,7 +56,21 @@
return mPackaged;
}
+ public void setPackaged(boolean packaged) {
+ mPackaged = packaged;
+ }
+
public boolean isProguarded() {
return mProguarded;
}
+
+ @Override
+ public String toString() {
+ return "JarDependency{" +
+ "mJarFile=" + mJarFile +
+ ", mCompiled=" + mCompiled +
+ ", mPackaged=" + mPackaged +
+ ", mProguarded=" + 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
index f6c8ed4..93dab7c 100644
--- 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
@@ -19,6 +19,7 @@
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
@@ -29,6 +30,7 @@
* Default implementation of the LibraryDependency interface that handles a default bundle project
* structure.
*/
+@Immutable
public abstract class LibraryBundle implements LibraryDependency {
public static final String FN_PROGUARD_TXT = "proguard.txt";
@@ -52,10 +54,8 @@
mName = name;
}
- protected LibraryBundle(@NonNull File bundle, @NonNull File bundleFolder) {
- this(bundle, bundleFolder, null);
- }
-
+ @Override
+ @Nullable
public String getName() {
return mName;
}
@@ -71,6 +71,12 @@
return null;
}
+ @Nullable
+ @Override
+ public String getProjectVariant() {
+ return null;
+ }
+
@Override
@NonNull
public File getManifest() {
@@ -107,7 +113,7 @@
List<File> jars = getLocalJars();
List<JarDependency> localDependencies = Lists.newArrayListWithCapacity(jars.size());
for (File jar : jars) {
- localDependencies.add(new JarDependency(jar));
+ localDependencies.add(new JarDependency(jar, true, true));
}
return localDependencies;
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
index 4618a5a..ce533b4 100644
--- 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
@@ -17,6 +17,7 @@
package com.android.builder.dependency;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import java.util.List;
@@ -26,6 +27,12 @@
public interface ManifestDependency extends ManifestProvider {
/**
+ * Returns a user friendly name.
+ */
+ @Nullable
+ String getName();
+
+ /**
* Returns the direct dependency of this dependency.
*/
@NonNull
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
index 62653d3..a5bd374 100644
--- 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
@@ -20,43 +20,44 @@
import com.android.builder.model.BaseConfig;
import com.android.builder.model.ClassField;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import java.io.File;
import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
+import java.util.Map;
/**
* An object that contain a BuildConfig configuration
*/
-public class BaseConfigImpl implements Serializable, BaseConfig {
+public abstract class BaseConfigImpl implements Serializable, BaseConfig {
private static final long serialVersionUID = 1L;
- private final List<ClassField> mBuildConfigFields = Lists.newArrayList();
+ private final Map<String, ClassField> mBuildConfigFields = Maps.newTreeMap();
+ private final Map<String, ClassField> mResValues = Maps.newTreeMap();
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);
+ mBuildConfigFields.put(field.getName(), field);
+ }
+
+ public void addResValue(@NonNull ClassField field) {
+ mResValues.put(field.getName(), field);
}
@Override
@NonNull
- public List<ClassField> getBuildConfigFields() {
+ public Map<String, ClassField> getBuildConfigFields() {
return mBuildConfigFields;
}
+ @NonNull
+ @Override
+ public Map<String, ClassField> getResValues() {
+ return mResValues;
+ }
+
@Override
@NonNull
public List<File> getProguardFiles() {
@@ -69,8 +70,10 @@
return mConsumerProguardFiles;
}
- protected void _initWith(BaseConfig that) {
+
+ protected void _initWith(@NonNull BaseConfig that) {
setBuildConfigFields(that.getBuildConfigFields());
+ setResValues(that.getResValues());
mProguardFiles.clear();
mProguardFiles.addAll(that.getProguardFiles());
@@ -79,6 +82,16 @@
mConsumerProguardFiles.addAll(that.getConsumerProguardFiles());
}
+ private void setBuildConfigFields(@NonNull Map<String, ClassField> fields) {
+ mBuildConfigFields.clear();
+ mBuildConfigFields.putAll(fields);
+ }
+
+ private void setResValues(@NonNull Map<String, ClassField> fields) {
+ mResValues.clear();
+ mResValues.putAll(fields);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -87,6 +100,7 @@
BaseConfigImpl that = (BaseConfigImpl) o;
if (!mBuildConfigFields.equals(that.mBuildConfigFields)) return false;
+ if (!mResValues.equals(that.mResValues)) return false;
if (!mProguardFiles.equals(that.mProguardFiles)) return false;
if (!mConsumerProguardFiles.equals(that.mConsumerProguardFiles)) return false;
@@ -96,6 +110,7 @@
@Override
public int hashCode() {
int result = mBuildConfigFields.hashCode();
+ result = 31 * result + mResValues.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/FakeAndroidTarget.java b/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java
index 434b8d2..a9a7edc 100644
--- 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
@@ -22,9 +22,11 @@
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISystemImage;
+import com.android.sdklib.repository.descriptors.IdDisplay;
import com.android.utils.SparseArray;
import com.google.common.collect.Lists;
+import java.io.File;
import java.util.List;
import java.util.Map;
@@ -86,6 +88,11 @@
}
@Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+ @Override
public BuildToolInfo getBuildToolInfo() {
// this is not used internally since we properly query for the right Build Tools from
// the SdkManager.
@@ -163,12 +170,12 @@
}
@Override
- public String[] getSkins() {
- return new String[0];
+ public File[] getSkins() {
+ return new File[0];
}
@Override
- public String getDefaultSkin() {
+ public File getDefaultSkin() {
return null;
}
@@ -213,7 +220,7 @@
}
@Override
- public ISystemImage getSystemImage(String abiType) {
+ public ISystemImage getSystemImage(IdDisplay tag, String abiType) {
return null;
}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/JavaPngCruncher.java b/build-system/builder/src/main/java/com/android/builder/internal/JavaPngCruncher.java
new file mode 100644
index 0000000..d4dba8b
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/JavaPngCruncher.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png.NinePatchException;
+import com.android.builder.png.PngProcessor;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.PngCruncher;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Implementation of the PngCruncher using Java code
+ * only (intead of calling out to aapt).
+ */
+public class JavaPngCruncher implements PngCruncher {
+
+ @Override
+ public void crunchPng(@NonNull File from, @NonNull File to)
+ throws InterruptedException, LoggedErrorException, IOException {
+ try {
+ PngProcessor.process(from, to);
+ } catch (NinePatchException e) {
+ throw new IOException(e);
+ }
+ }
+}
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
index a45f650..a3472d5 100644
--- 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
@@ -23,7 +23,7 @@
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.Closer;
import com.google.common.io.Files;
import java.io.BufferedWriter;
@@ -76,9 +76,9 @@
file.mkdirs();
file = new File(file, SdkConstants.FN_RESOURCE_CLASS);
- BufferedWriter writer = null;
+ Closer closer = Closer.create();
try {
- writer = Files.newWriter(file, Charsets.UTF_8);
+ BufferedWriter writer = closer.register(Files.newWriter(file, Charsets.UTF_8));
writer.write("/* AUTO-GENERATED FILE. DO NOT MODIFY.\n");
writer.write(" *\n");
@@ -126,8 +126,10 @@
}
writer.write("}\n");
+ } catch (Throwable e) {
+ throw closer.rethrow(e);
} finally {
- Closeables.closeQuietly(writer);
+ closer.close();
}
}
}
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
index 9e443a7..6859da0 100644
--- 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
@@ -16,6 +16,7 @@
package com.android.builder.internal;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import java.io.File;
import java.io.IOException;
@@ -27,36 +28,37 @@
*/
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 static final String TEMPLATE = "AndroidManifest.template";
+ private static final String PH_PACKAGE = "#PACKAGE#";
+ private static final String PH_MIN_SDK_VERSION = "#MINSDKVERSION#";
+ private static final String PH_TARGET_SDK_VERSION = "#TARGETSDKVERSION#";
+ private static final String PH_TESTED_PACKAGE = "#TESTEDPACKAGE#";
+ private static final String PH_TEST_RUNNER = "#TESTRUNNER#";
+ private static final String PH_HANDLE_PROFILING = "#HANDLEPROFILING#";
+ private static final String PH_FUNCTIONAL_TEST = "#FUNCTIONALTEST#";
- private final String mOutputFile;
+ private final File mOutputFile;
private final String mPackageName;
- private final int mMinSdkVersion;
- private final int mTargetSdkVersion;
+ private final String mMinSdkVersion;
+ private final String 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) {
+ public TestManifestGenerator(
+ @NonNull File outputFile,
+ @NonNull String packageName,
+ @Nullable String minSdkVersion,
+ @Nullable String targetSdkVersion,
+ @NonNull String testedPackageName,
+ @NonNull String testRunnerName,
+ @NonNull Boolean handleProfiling,
+ @NonNull Boolean functionalTest) {
mOutputFile = outputFile;
mPackageName = packageName;
mMinSdkVersion = minSdkVersion;
- mTargetSdkVersion = targetSdkVersion != -1 ? targetSdkVersion : minSdkVersion;
+ mTargetSdkVersion = targetSdkVersion != null ? targetSdkVersion : minSdkVersion;
mTestedPackageName = testedPackageName;
mTestRunnerName = testRunnerName;
mHandleProfiling = handleProfiling;
@@ -66,8 +68,8 @@
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_MIN_SDK_VERSION, mMinSdkVersion != null ? mMinSdkVersion : "1");
+ map.put(PH_TARGET_SDK_VERSION, mTargetSdkVersion != null ? mTargetSdkVersion : map.get(PH_MIN_SDK_VERSION));
map.put(PH_TESTED_PACKAGE, mTestedPackageName);
map.put(PH_TEST_RUNNER, mTestRunnerName);
map.put(PH_HANDLE_PROFILING, Boolean.toString(mHandleProfiling));
@@ -77,6 +79,6 @@
TestManifestGenerator.class.getResourceAsStream(TEMPLATE),
map);
- processor.generate(new File(mOutputFile));
+ processor.generate(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
index a50029e..fd773b8 100644
--- 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
@@ -17,10 +17,14 @@
package com.android.builder.internal.compiler;
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.ide.common.internal.CommandLineRunner;
import com.android.ide.common.internal.LoggedErrorException;
+import com.android.sdklib.io.FileOp;
import com.google.common.collect.Lists;
+import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
@@ -40,6 +44,8 @@
private final List<File> mImportFolders;
@NonNull
private final File mSourceOutputDir;
+ @Nullable
+ private final File mParcelableOutputDir;
@NonNull
private final DependencyFileProcessor mDependencyFileProcessor;
@NonNull
@@ -49,18 +55,21 @@
@NonNull String frameworkLocation,
@NonNull List<File> importFolders,
@NonNull File sourceOutputDir,
+ @Nullable File parcelableOutputDir,
@NonNull DependencyFileProcessor dependencyFileProcessor,
@NonNull CommandLineRunner runner) {
mAidlExecutable = aidlExecutable;
mFrameworkLocation = frameworkLocation;
mImportFolders = importFolders;
mSourceOutputDir = sourceOutputDir;
+ mParcelableOutputDir = parcelableOutputDir;
mDependencyFileProcessor = dependencyFileProcessor;
mRunner = runner;
}
@Override
- public void processFile(File sourceFile) throws IOException, InterruptedException, LoggedErrorException {
+ public void processFile(@NonNull File sourceFolder, @NonNull File sourceFile)
+ throws IOException, InterruptedException, LoggedErrorException {
ArrayList<String> command = Lists.newArrayList();
command.add(mAidlExecutable);
@@ -82,8 +91,19 @@
mRunner.runCmdLine(command, null);
// send the dependency file to the processor.
- if (mDependencyFileProcessor.processFile(depFile)) {
- depFile.delete();
+ DependencyData data = mDependencyFileProcessor.processFile(depFile);
+
+ if (mParcelableOutputDir != null && data != null && data.getOutputFiles().isEmpty()) {
+ // looks like a parcelable. Store it in the 2ndary output of the DependencyData object.
+
+ String relative = FileOp.makeRelative(sourceFolder, sourceFile);
+
+ File destFile = new File(mParcelableOutputDir, relative);
+ destFile.getParentFile().mkdirs();
+ Files.copy(sourceFile, destFile);
+ data.addSecondaryOutputFile(destFile.getPath());
}
+
+ 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
index 1028617..074adc2 100644
--- 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
@@ -16,6 +16,7 @@
package com.android.builder.internal.compiler;
+import com.android.annotations.NonNull;
import com.google.common.collect.Lists;
import java.io.File;
@@ -26,13 +27,16 @@
* 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(File sourceFile) throws IOException, InterruptedException {
+ public void processFile(@NonNull File sourceFolder, @NonNull File sourceFile)
+ throws IOException, InterruptedException {
mFiles.add(sourceFile);
}
+ @NonNull
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
index 42d2bf3..0fa2731 100644
--- 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
@@ -16,6 +16,7 @@
package com.android.builder.internal.compiler;
+import com.android.annotations.NonNull;
import com.android.ide.common.internal.LoggedErrorException;
import com.google.common.collect.Sets;
@@ -28,13 +29,16 @@
*/
public class LeafFolderGatherer implements SourceSearcher.SourceFileProcessor {
+ @NonNull
private final Set<File> mFolders = Sets.newHashSet();
@Override
- public void processFile(File sourceFile) throws IOException, InterruptedException, LoggedErrorException {
+ public void processFile(@NonNull File sourceFolder, @NonNull File sourceFile)
+ throws IOException, InterruptedException, LoggedErrorException {
mFolders.add(sourceFile.getParentFile());
}
+ @NonNull
public Set<File> getFolders() {
return mFolders;
}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreDexCache.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreDexCache.java
new file mode 100644
index 0000000..755a4c3
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreDexCache.java
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.annotations.concurrency.Immutable;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.DexOptions;
+import com.android.ide.common.internal.CommandLineRunner;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.repository.FullRevision;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Objects;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Pre Dexing cache.
+ *
+ * Since we cannot yet have a single task for each library that needs to be pre-dexed (because
+ * there is no task-level parallelization), this class allows reusing the output of the pre-dexing
+ * of a library in a project to write the output of the pre-dexing of the same library in
+ * a different project.
+ *
+ * Because different project could use different build-tools, both the library to pre-dex and the
+ * version of the build tools are used as keys in the cache.
+ *
+ * The API is fairly simple, just call {@link #preDexLibrary(java.io.File, java.io.File, com.android.builder.core.DexOptions, com.android.sdklib.BuildToolInfo, boolean, com.android.ide.common.internal.CommandLineRunner)}
+ *
+ * The call will be blocking until the pre-dexing happened, either through actual pre-dexing or
+ * through copying the output of a previous pre-dex run.
+ *
+ * After a build a call to {@link #clear(java.io.File, com.android.utils.ILogger)} with a file
+ * will allow saving the known pre-dexed libraries for future reuse.
+ */
+public class PreDexCache {
+
+ private static final String NODE_ITEMS = "pre-dex-items";
+ private static final String NODE_ITEM = "item";
+ private static final String ATTR_JUMBO_MODE = "jumboMode";
+ private static final String ATTR_REVISION = "revision";
+ private static final String ATTR_JAR = "jar";
+ private static final String ATTR_DEX = "dex";
+ private static final String ATTR_SHA1 = "sha1";
+
+ /**
+ * Items representing jar/dex files that have been processed during a build.
+ */
+ @Immutable
+ private static class Item {
+ @NonNull
+ private final File mSourceFile;
+ @NonNull
+ private final File mOutputFile;
+ @NonNull
+ private final CountDownLatch mLatch;
+
+ Item(
+ @NonNull File sourceFile,
+ @NonNull File outputFile,
+ @NonNull CountDownLatch latch) {
+ mSourceFile = sourceFile;
+ mOutputFile = outputFile;
+ mLatch = latch;
+ }
+
+ @NonNull
+ private File getSourceFile() {
+ return mSourceFile;
+ }
+
+ @NonNull
+ private File getOutputFile() {
+ return mOutputFile;
+ }
+
+ @NonNull
+ private CountDownLatch getLatch() {
+ return mLatch;
+ }
+ }
+
+ /**
+ * Items representing jar/dex files that have been processed in a previous build, then were
+ * stored in a cache file and then reloaded during the current build.
+ */
+ @Immutable
+ private static class StoredItem {
+ @NonNull
+ private final File mSourceFile;
+ @NonNull
+ private final File mOutputFile;
+ @NonNull
+ private final HashCode mSourceHash;
+
+ StoredItem(
+ @NonNull File sourceFile,
+ @NonNull File outputFile,
+ @NonNull HashCode sourceHash) {
+ mSourceFile = sourceFile;
+ mOutputFile = outputFile;
+ mSourceHash = sourceHash;
+ }
+
+ @NonNull
+ private File getSourceFile() {
+ return mSourceFile;
+ }
+
+ @NonNull
+ private File getOutputFile() {
+ return mOutputFile;
+ }
+
+ @NonNull
+ private HashCode getSourceHash() {
+ return mSourceHash;
+ }
+ }
+
+ /**
+ * Key to store Item/StoredItem in maps.
+ * The key contains the element that are used for the dex call:
+ * - source file
+ * - build tools revision
+ * - jumbo mode
+ */
+ @Immutable
+ private static class Key {
+ @NonNull
+ private final File mSourceFile;
+ @NonNull
+ private final FullRevision mBuildToolsRevision;
+ private final boolean mJumboMode;
+
+ private static Key of(@NonNull File sourceFile, @NonNull FullRevision buildToolsRevision,
+ boolean jumboMode) {
+ return new Key(sourceFile, buildToolsRevision, jumboMode);
+ }
+
+ private Key(@NonNull File sourceFile, @NonNull FullRevision buildToolsRevision,
+ boolean jumboMode) {
+ mSourceFile = sourceFile;
+ mBuildToolsRevision = buildToolsRevision;
+ mJumboMode = jumboMode;
+ }
+
+ @NonNull
+ private FullRevision getBuildToolsRevision() {
+ return mBuildToolsRevision;
+ }
+
+ public boolean isJumboMode() {
+ return mJumboMode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Key key = (Key) o;
+
+ if (mJumboMode != key.mJumboMode) {
+ return false;
+ }
+ if (!mBuildToolsRevision.equals(key.mBuildToolsRevision)) {
+ return false;
+ }
+ if (!mSourceFile.equals(key.mSourceFile)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mSourceFile, mBuildToolsRevision, mJumboMode);
+ }
+ }
+
+ private static final PreDexCache sSingleton = new PreDexCache();
+
+ public static PreDexCache getCache() {
+ return sSingleton;
+ }
+
+ @GuardedBy("this")
+ private boolean mLoaded = false;
+
+ @GuardedBy("this")
+ private final Map<Key, Item> mMap = Maps.newHashMap();
+ @GuardedBy("this")
+ private final Map<Key, StoredItem> mStoredItems = Maps.newHashMap();
+
+ @GuardedBy("this")
+ private int mMisses = 0;
+ @GuardedBy("this")
+ private int mHits = 0;
+
+ /**
+ * Loads the stored item. This can be called several times (per subproject), so only
+ * the first call should do something.
+ */
+ public synchronized void load(@NonNull File itemStorage) {
+ if (mLoaded) {
+ return;
+ }
+
+ loadItems(itemStorage);
+
+ mLoaded = true;
+ }
+
+ /**
+ * Pre-dex a given library to a given output with a specific version of the build-tools.
+ * @param inputFile the jar to pre-dex
+ * @param outFile the output file.
+ * @param dexOptions the dex options to run pre-dex
+ * @param buildToolInfo the build tools info
+ * @param verbose verbose flag
+ * @param commandLineRunner the command line runner.
+ * @throws IOException
+ * @throws LoggedErrorException
+ * @throws InterruptedException
+ */
+ public void preDexLibrary(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ @NonNull DexOptions dexOptions,
+ @NonNull BuildToolInfo buildToolInfo,
+ boolean verbose,
+ @NonNull CommandLineRunner commandLineRunner)
+ throws IOException, LoggedErrorException, InterruptedException {
+ Pair<Item, Boolean> pair = getItem(inputFile, outFile, buildToolInfo, dexOptions);
+
+ // if this is a new item
+ if (pair.getSecond()) {
+ try {
+ // haven't process this file yet so do it and record it.
+ AndroidBuilder.preDexLibrary(inputFile, outFile, dexOptions, buildToolInfo,
+ verbose, commandLineRunner);
+
+ synchronized (this) {
+ mMisses++;
+ }
+ } catch (IOException exception) {
+ // in case of error, delete (now obsolete) output file
+ outFile.delete();
+ // and rethrow the error
+ throw exception;
+ } catch (LoggedErrorException exception) {
+ // in case of error, delete (now obsolete) output file
+ outFile.delete();
+ // and rethrow the error
+ throw exception;
+ } catch (InterruptedException exception) {
+ // in case of error, delete (now obsolete) output file
+ outFile.delete();
+ // and rethrow the error
+ throw exception;
+ } finally {
+ // enable other threads to use the output of this pre-dex.
+ // if something was thrown they'll handle the missing output file.
+ pair.getFirst().getLatch().countDown();
+ }
+ } else {
+ // wait until the file is pre-dexed by the first thread.
+ pair.getFirst().getLatch().await();
+
+ // check that the generated file actually exists
+ File fromFile = pair.getFirst().getOutputFile();
+
+ if (fromFile.isFile()) {
+ // file already pre-dex, just copy the output.
+ Files.copy(pair.getFirst().getOutputFile(), outFile);
+ synchronized (this) {
+ mHits++;
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ /*package*/ synchronized int getMisses() {
+ return mMisses;
+ }
+
+ @VisibleForTesting
+ /*package*/ synchronized int getHits() {
+ return mHits;
+ }
+
+ /**
+ * Returns a Pair of {@link Item}, and a boolean which indicates whether the item is new (true)
+ * or if it already existed (false).
+ *
+ * @param inputFile the input file
+ * @param outFile the output file
+ * @param buildToolInfo the build tools info.
+ * @return a pair of item, boolean
+ * @throws IOException
+ */
+ private synchronized Pair<Item, Boolean> getItem(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ @NonNull BuildToolInfo buildToolInfo,
+ @NonNull DexOptions dexOptions) throws IOException {
+
+ Key itemKey = Key.of(inputFile, buildToolInfo.getRevision(), dexOptions.getJumboMode());
+
+ // get the item
+ Item item = mMap.get(itemKey);
+
+ boolean newItem = false;
+
+ if (item == null) {
+ // check if we have a stored version.
+ StoredItem storedItem = mStoredItems.get(itemKey);
+
+ if (storedItem != null) {
+ // check the sha1 is still valid, and the pre-dex file is still there.
+ File dexFile = storedItem.getOutputFile();
+ if (dexFile.isFile() &&
+ storedItem.getSourceHash().equals(Files.hash(inputFile, Hashing.sha1()))) {
+
+ // create an item where the outFile is the one stored since it
+ // represent the pre-dexed library already.
+ // Next time this lib needs to be pre-dexed, we'll use the item
+ // rather than the stored item, allowing us to not compute the sha1 again.
+ // Use a 0-count latch since there is nothing to do.
+ item = new Item(inputFile, dexFile, new CountDownLatch(0));
+ }
+ }
+
+ // if we didn't find a valid stored item, create a new one.
+ if (item == null) {
+ item = new Item(inputFile, outFile, new CountDownLatch(1));
+ newItem = true;
+ }
+
+ mMap.put(itemKey, item);
+ }
+
+ return Pair.of(item, newItem);
+ }
+
+ public synchronized void clear(@Nullable File itemStorage, @Nullable ILogger logger) throws IOException {
+ if (!mMap.isEmpty()) {
+ if (itemStorage != null) {
+ saveItems(itemStorage);
+ }
+
+ if (logger != null) {
+ logger.info("PREDEX CACHE HITS: " + mHits);
+ logger.info("PREDEX CACHE MISSES: " + mMisses);
+ }
+ }
+
+ mMap.clear();
+ mStoredItems.clear();
+ mHits = 0;
+ mMisses = 0;
+ }
+
+ private synchronized void loadItems(@NonNull File itemStorage) {
+ if (!itemStorage.isFile()) {
+ return;
+ }
+
+ try {
+ Document document = XmlUtils.parseUtfXmlFile(itemStorage, true);
+
+ // get the root node
+ Node rootNode = document.getDocumentElement();
+ if (rootNode == null || !NODE_ITEMS.equals(rootNode.getLocalName())) {
+ return;
+ }
+
+ NodeList nodes = rootNode.getChildNodes();
+
+ for (int i = 0, n = nodes.getLength(); i < n; i++) {
+ Node node = nodes.item(i);
+
+ if (node.getNodeType() != Node.ELEMENT_NODE ||
+ !NODE_ITEM.equals(node.getLocalName())) {
+ continue;
+ }
+
+ NamedNodeMap attrMap = node.getAttributes();
+
+ File sourceFile = new File(attrMap.getNamedItem(ATTR_JAR).getNodeValue());
+ FullRevision revision = FullRevision.parseRevision(attrMap.getNamedItem(
+ ATTR_REVISION).getNodeValue());
+
+ StoredItem item = new StoredItem(
+ sourceFile,
+ new File(attrMap.getNamedItem(ATTR_DEX).getNodeValue()),
+ HashCode.fromString(attrMap.getNamedItem(ATTR_SHA1).getNodeValue()));
+
+ Key key = Key.of(sourceFile, revision,
+ Boolean.parseBoolean(attrMap.getNamedItem(ATTR_JUMBO_MODE).getNodeValue()));
+
+ mStoredItems.put(key, item);
+ }
+ } catch (Exception ignored) {
+ // if we fail to read parts or any of the file, all it'll do is fail to reuse an
+ // already pre-dexed library, so that's not a super big deal.
+ }
+ }
+
+ private synchronized void saveItems(@NonNull File itemStorage) throws IOException {
+ // write "compact" blob
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringComments(true);
+ DocumentBuilder builder;
+
+ try {
+ builder = factory.newDocumentBuilder();
+ Document document = builder.newDocument();
+
+ Node rootNode = document.createElement(NODE_ITEMS);
+ document.appendChild(rootNode);
+
+ Set<Key> keys = Sets.newHashSetWithExpectedSize(mMap.size() + mStoredItems.size());
+ keys.addAll(mMap.keySet());
+ keys.addAll(mStoredItems.keySet());
+
+ for (Key key : keys) {
+ Item item = mMap.get(key);
+
+ if (item != null) {
+
+ Node itemNode = createItemNode(document,
+ item.getSourceFile(),
+ item.getOutputFile(),
+ key.getBuildToolsRevision(),
+ key.isJumboMode(),
+ Files.hash(item.getSourceFile(), Hashing.sha1()));
+ rootNode.appendChild(itemNode);
+
+ } else {
+ StoredItem storedItem = mStoredItems.get(key);
+ // check that the source file still exists in order to avoid
+ // storing libraries that are gone.
+ if (storedItem != null &&
+ storedItem.getSourceFile().isFile() &&
+ storedItem.getOutputFile().isFile()) {
+ Node itemNode = createItemNode(document,
+ storedItem.getSourceFile(),
+ storedItem.getOutputFile(),
+ key.getBuildToolsRevision(),
+ key.isJumboMode(),
+ storedItem.getSourceHash());
+ rootNode.appendChild(itemNode);
+ }
+ }
+ }
+
+ String content = XmlPrettyPrinter.prettyPrint(document, true);
+
+ itemStorage.getParentFile().mkdirs();
+ Files.write(content, itemStorage, Charsets.UTF_8);
+ } catch (ParserConfigurationException e) {
+ }
+ }
+
+ private static Node createItemNode(
+ @NonNull Document document,
+ @NonNull File sourceFile,
+ @NonNull File outputFile,
+ @NonNull FullRevision toolsRevision,
+ boolean jumboMode,
+ @NonNull HashCode hashCode) {
+ Node itemNode = document.createElement(NODE_ITEM);
+
+ Attr attr = document.createAttribute(ATTR_JAR);
+ attr.setValue(sourceFile.getPath());
+ itemNode.getAttributes().setNamedItem(attr);
+
+ attr = document.createAttribute(ATTR_DEX);
+ attr.setValue(outputFile.getPath());
+ itemNode.getAttributes().setNamedItem(attr);
+
+ attr = document.createAttribute(ATTR_REVISION);
+ attr.setValue(toolsRevision.toString());
+ itemNode.getAttributes().setNamedItem(attr);
+
+ attr = document.createAttribute(ATTR_JUMBO_MODE);
+ attr.setValue(Boolean.toString(jumboMode));
+ itemNode.getAttributes().setNamedItem(attr);
+
+ attr = document.createAttribute(ATTR_SHA1);
+ attr.setValue(hashCode.toString());
+ itemNode.getAttributes().setNamedItem(attr);
+
+ return itemNode;
+ }
+}
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
index 9b28743..6c87bd6 100644
--- 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
@@ -17,6 +17,9 @@
package com.android.builder.internal.compiler;
+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;
@@ -36,9 +39,6 @@
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.
*/
@@ -109,7 +109,7 @@
private final Set<String> mAbiFilters;
private final File mRsLib;
- private final File mLibClCore;
+ private final Map<String, File> mLibClCore = Maps.newHashMap();
public RenderScriptProcessor(
@NonNull List<File> sourceFolders,
@@ -142,9 +142,12 @@
if (supportMode) {
File rs = new File(mBuildToolInfo.getLocation(), "renderscript");
mRsLib = new File(rs, "lib");
- mLibClCore = new File(mRsLib, "libclcore.bc");
+ File bcFolder = new File(mRsLib, "bc");
+ for (Abi abi : ABIS) {
+ mLibClCore.put(abi.mDevice,
+ new File(bcFolder, abi.mDevice + File.separatorChar + "libclcore.bc"));
+ }
} else {
- mLibClCore = null;
mRsLib = null;
}
}
@@ -334,7 +337,7 @@
args.add("-shared");
args.add("-rt-path");
- args.add(mLibClCore.getAbsolutePath());
+ args.add(mLibClCore.get(abi.mDevice).getAbsolutePath());
args.add("-mtriple");
args.add(abi.mToolchain);
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
index fe031e9..0a680b0 100644
--- 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
@@ -16,6 +16,7 @@
package com.android.builder.internal.compiler;
+import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.internal.LoggedErrorException;
import com.android.ide.common.internal.WaitableExecutor;
@@ -30,16 +31,18 @@
*/
public class SourceSearcher {
+ @NonNull
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;
+ void processFile(@NonNull File sourceFolder, @NonNull File sourceFile)
+ throws IOException, InterruptedException, LoggedErrorException;
}
- public SourceSearcher(List<File> sourceFolders, String... extensions) {
+ public SourceSearcher(@NonNull List<File> sourceFolders, String... extensions) {
mSourceFolders = sourceFolders;
mExtensions = extensions;
}
@@ -52,10 +55,12 @@
}
}
- public void search(SourceFileProcessor processor)
+ public void search(@NonNull SourceFileProcessor processor)
throws IOException, InterruptedException, LoggedErrorException {
for (File file : mSourceFolders) {
- processFile(file, processor);
+ // pass both the root folder (the source folder) and the file/folder to process,
+ // in this case the source folder as well.
+ processFile(file, file, processor);
}
if (mExecutor != null) {
@@ -63,7 +68,10 @@
}
}
- private void processFile(final File file, final SourceFileProcessor processor)
+ private void processFile(
+ @NonNull final File rootFolder,
+ @NonNull final File file,
+ @NonNull final SourceFileProcessor processor)
throws IOException, InterruptedException, LoggedErrorException {
if (file.isFile()) {
// get the extension of the file.
@@ -72,19 +80,19 @@
mExecutor.execute(new Callable<Void>() {
@Override
public Void call() throws Exception {
- processor.processFile(file);
+ processor.processFile(rootFolder, file);
return null;
}
});
} else {
- processor.processFile(file);
+ processor.processFile(rootFolder, file);
}
}
} else if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
- processFile(child, processor);
+ processFile(rootFolder, child, processor);
}
}
}
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
index 575e50c..2648b6a 100644
--- 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
@@ -39,6 +39,7 @@
private List<String> mSecondaryFiles = Lists.newArrayList();
@NonNull
private List<String> mOutputFiles = Lists.newArrayList();
+ @NonNull List<String> mSecondaryOutputFiles = Lists.newArrayList();
DependencyData() {
}
@@ -48,7 +49,7 @@
return mMainFile;
}
- void setMainFile(String path) {
+ void setMainFile(@NonNull String path) {
mMainFile = path;
}
@@ -57,7 +58,7 @@
return mSecondaryFiles;
}
- void addSecondaryFile(String path) {
+ void addSecondaryFile(@NonNull String path) {
mSecondaryFiles.add(path);
}
@@ -66,10 +67,19 @@
return mOutputFiles;
}
- void addOutputFile(String path) {
+ void addOutputFile(@NonNull String path) {
mOutputFiles.add(path);
}
+ public void addSecondaryOutputFile(@NonNull String path) {
+ mSecondaryOutputFiles.add(path);
+ }
+
+ @NonNull
+ public List<String> getSecondaryOutputFiles() {
+ return mSecondaryOutputFiles;
+ }
+
/**
* Parses the given dependency file and returns the parsed data
*
@@ -89,7 +99,7 @@
}
private static enum ParseMode {
- OUTPUT, MAIN, SECONDARY
+ OUTPUT, MAIN, SECONDARY, DONE
}
@VisibleForTesting
@@ -123,10 +133,18 @@
// detect : at the end indicating a parse mode change *after* we process this line.
if (line.endsWith(":")) {
- nextMode = ParseMode.MAIN;
+ if (parseMode == ParseMode.SECONDARY) {
+ nextMode = ParseMode.DONE;
+ } else {
+ nextMode = ParseMode.MAIN;
+ }
line = line.substring(0, line.length() - 1).trim();
}
+ if (nextMode == ParseMode.DONE) {
+ break;
+ }
+
if (!line.isEmpty()) {
switch (parseMode) {
case OUTPUT:
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
index 482032a..0c66a09 100644
--- 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
@@ -22,7 +22,7 @@
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 com.google.common.io.Closer;
import java.io.File;
import java.io.FileInputStream;
@@ -40,8 +40,8 @@
* 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)...]
+ * (Start Tag)(Main File)[(2ndary Tag)(2ndary File)...][(Output tag)(output file)...][(2ndary Output tag)(output file)...]
+ * (Start Tag)(Main File)[(2ndary Tag)(2ndary File)...][(Output tag)(output file)...][(2ndary Output tag)(output file)...]
* ...
*
* All files are written as (size in int)(byte array, using UTF8 encoding).
@@ -52,6 +52,7 @@
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_2NDARY_OUTPUT = 0x74;
private static final byte TAG_END = 0x77;
private static final int CURRENT_VERSION = 1;
@@ -59,24 +60,23 @@
private final Map<String, DependencyData> mMainFileMap = Maps.newHashMap();
public DependencyDataStore() {
-
}
- public void addData(List<DependencyData> dataList) {
+ public void addData(@NonNull List<DependencyData> dataList) {
for (DependencyData data : dataList) {
mMainFileMap.put(data.getMainFile(), data);
}
}
- public void addData(DependencyData data) {
+ public void addData(@NonNull DependencyData data) {
mMainFileMap.put(data.getMainFile(), data);
}
- public void remove(DependencyData data) {
+ public void remove(@NonNull DependencyData data) {
mMainFileMap.remove(data.getMainFile());
}
- public void updateAll(List<DependencyData> dataList) {
+ public void updateAll(@NonNull List<DependencyData> dataList) {
for (DependencyData data : dataList) {
mMainFileMap.put(data.getMainFile(), data);
}
@@ -108,16 +108,18 @@
* @param file the file to save the data to.
* @throws IOException
*/
- public void saveTo(File file) throws IOException {
- FileOutputStream fos = new FileOutputStream(file);
+ public void saveTo(@NonNull File file) throws IOException {
+ Closer closer = Closer.create();
try {
+ FileOutputStream fos = closer.register(new FileOutputStream(file));
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);
@@ -127,9 +129,17 @@
fos.write(TAG_OUTPUT);
writePath(fos, path);
}
+
+ for (String path : data.getSecondaryOutputFiles()) {
+ fos.write(TAG_2NDARY_OUTPUT);
+ writePath(fos, path);
+ }
+
}
+ } catch (Throwable e) {
+ throw closer.rethrow(e);
} finally {
- Closeables.closeQuietly(fos);
+ closer.close();
}
}
@@ -145,10 +155,11 @@
* @return a map of file-> list of impacted dependency data.
* @throws IOException
*/
- public Multimap<String, DependencyData> loadFrom(File file) throws IOException {
+ public Multimap<String, DependencyData> loadFrom(@NonNull File file) throws IOException {
Multimap<String, DependencyData> inputMap = ArrayListMultimap.create();
- FileInputStream fis = new FileInputStream(file);
+ Closer closer = Closer.create();
+ FileInputStream fis = closer.register(new FileInputStream(file));
// reusable buffer
ReusableBuffer buffers = new ReusableBuffer();
@@ -189,6 +200,9 @@
case TAG_OUTPUT:
currentData.addOutputFile(path);
break;
+ case TAG_2NDARY_OUTPUT:
+ currentData.addSecondaryOutputFile(path);
+ break;
}
// read the next tag.
@@ -200,25 +214,28 @@
}
return inputMap;
+ } catch (Throwable e) {
+ throw closer.rethrow(e);
} finally {
- Closeables.closeQuietly(fis);
+ closer.close();
}
}
- private void writeInt(FileOutputStream fos, int value) throws IOException {
+ private static void writeInt(@NonNull 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 {
+ private static void writePath(@NonNull 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 {
+ private static byte readByte(@NonNull FileInputStream fis, @NonNull ReusableBuffer buffers)
+ throws IOException {
int read = fis.read(buffers.intBuffer, 0, 1);
if (read != 1) {
return TAG_END;
@@ -227,7 +244,8 @@
return buffers.intBuffer[0];
}
- private int readInt(FileInputStream fis, ReusableBuffer buffers) throws IOException {
+ private static int readInt(@NonNull FileInputStream fis, @NonNull ReusableBuffer buffers)
+ throws IOException {
int read = fis.read(buffers.intBuffer);
// there must always be 4 bytes for the path length
@@ -240,7 +258,8 @@
return b.getInt();
}
- private String readPath(FileInputStream fis, ReusableBuffer buffers) throws IOException {
+ private static String readPath(@NonNull FileInputStream fis, @NonNull ReusableBuffer buffers)
+ throws IOException {
int length = readInt(fis, buffers);
if (buffers.pathBuffer == null || buffers.pathBuffer.length < length) {
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
index 3231262..300150f 100644
--- 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
@@ -20,23 +20,28 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.internal.packaging.JavaResourceProcessor.IArchiveBuilder;
+import com.android.builder.model.PackagingOptions;
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.signing.CertificateInfo;
import com.android.ide.common.packaging.PackagingUtils;
import com.android.utils.ILogger;
+import com.google.common.collect.Sets;
+import com.google.common.io.Closeables;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
@@ -92,9 +97,29 @@
*/
private final class JavaAndNativeResourceFilter implements IZipEntryFilter {
private final List<String> mNativeLibs = new ArrayList<String>();
+ private Set<String> mUsedPickFirsts = null;
+
+ @Nullable
+ private final PackagingOptions mPackagingOptions;
+
+ @NonNull
+ private final Set<String> mExcludes;
+ @NonNull
+ private final Set<String> mPickFirsts;
+
private boolean mNativeLibsConflict = false;
private File mInputFile;
+ private JavaAndNativeResourceFilter(@Nullable PackagingOptions packagingOptions) {
+ mPackagingOptions = packagingOptions;
+
+ mExcludes = mPackagingOptions != null ? mPackagingOptions.getExcludes() :
+ Collections.<String>emptySet();
+ mPickFirsts = mPackagingOptions != null ? mPackagingOptions.getPickFirsts() :
+ Collections.<String>emptySet();
+
+ }
+
@Override
public boolean checkEntry(String archivePath) throws ZipAbortException {
// split the path into segments.
@@ -105,6 +130,25 @@
return false;
}
+ //noinspection VariableNotUsedInsideIf
+ if (mPackagingOptions != null) {
+ if (mExcludes.contains(archivePath)) {
+ return false;
+ }
+
+ if (mPickFirsts.contains(archivePath)) {
+ if (mUsedPickFirsts == null) {
+ mUsedPickFirsts = Sets.newHashSetWithExpectedSize(mPickFirsts.size());
+ }
+
+ if (mUsedPickFirsts.contains(archivePath)) {
+ return false;
+ } else {
+ mUsedPickFirsts.add(archivePath);
+ }
+ }
+ }
+
// 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
@@ -167,7 +211,7 @@
private boolean mIsSealed = false;
private final NullZipFilter mNullFilter = new NullZipFilter();
- private final JavaAndNativeResourceFilter mFilter = new JavaAndNativeResourceFilter();
+ private final JavaAndNativeResourceFilter mFilter;
private final HashMap<String, File> mAddedFiles = new HashMap<String, File>();
/**
@@ -223,7 +267,7 @@
*
* @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 dexFolder the folder containing the dex file.
* @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
@@ -231,10 +275,12 @@
public Packager(
@NonNull String apkLocation,
@NonNull String resLocation,
- @NonNull String dexLocation,
+ @NonNull File dexFolder,
CertificateInfo certificateInfo,
@Nullable String createdBy,
+ @Nullable PackagingOptions packagingOptions,
ILogger logger) throws PackagerException {
+ mFilter = new JavaAndNativeResourceFilter(packagingOptions);
try {
File apkFile = new File(apkLocation);
@@ -243,12 +289,6 @@
File resFile = new File(resLocation);
checkInputFile(resFile);
- File dexFile = null;
- if (dexLocation != null) {
- dexFile = new File(dexLocation);
- checkInputFile(dexFile);
- }
-
mLogger = logger;
mBuilder = new SignedJarBuilder(
@@ -264,9 +304,8 @@
addZipFile(resFile);
// add the class dex file at the root of the apk
- if (dexFile != null) {
- addFile(dexFile, SdkConstants.FN_APK_CLASSES_DEX);
- }
+
+ addDexFolder(dexFolder);
} catch (PackagerException e) {
if (mBuilder != null) {
@@ -281,6 +320,22 @@
}
}
+ private void addDexFolder(@NonNull File dexFolder)
+ throws DuplicateFileException, SealedPackageException, PackagerException {
+ File[] files = dexFolder.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String name) {
+ return name.endsWith(SdkConstants.DOT_DEX);
+ }
+ });
+
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ addFile(file, file.getName());
+ }
+ }
+ }
+
/**
* 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.
@@ -340,6 +395,7 @@
throw new SealedPackageException("APK is already sealed");
}
+ FileInputStream fis = null;
try {
mLogger.verbose("%s:", zipFile);
@@ -347,7 +403,7 @@
mNullFilter.reset(zipFile);
// ask the builder to add the content of the file.
- FileInputStream fis = new FileInputStream(zipFile);
+ fis = new FileInputStream(zipFile);
mBuilder.writeZip(fis, mNullFilter);
} catch (DuplicateFileException e) {
mBuilder.cleanUp();
@@ -355,6 +411,12 @@
} catch (Exception e) {
mBuilder.cleanUp();
throw new PackagerException(e, "Failed to add %s", zipFile);
+ } finally {
+ try {
+ Closeables.close(fis, true /* swallowIOException */);
+ } catch (IOException e) {
+ // ignore
+ }
}
}
@@ -374,6 +436,7 @@
throw new SealedPackageException("APK is already sealed");
}
+ FileInputStream fis = null;
try {
mLogger.verbose("%s:", jarFile);
@@ -382,7 +445,7 @@
// ask the builder to add the content of the file, filtered to only let through
// the java resources.
- FileInputStream fis = new FileInputStream(jarFile);
+ fis = new FileInputStream(jarFile);
mBuilder.writeZip(fis, mFilter);
// check if native libraries were found in the external library. This should
@@ -394,6 +457,12 @@
} catch (Exception e) {
mBuilder.cleanUp();
throw new PackagerException(e, "Failed to add %s", jarFile);
+ } finally {
+ try {
+ Closeables.close(fis, true /* swallowIOException */);
+ } catch (IOException e) {
+ // ignore.
+ }
}
}
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
index 9ddb355..5511dcc 100644
--- 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
@@ -21,6 +21,7 @@
import com.android.builder.testing.TestData;
import com.android.builder.testing.api.DeviceConnector;
import com.android.builder.testing.api.DeviceException;
+import com.android.ddmlib.NullOutputReceiver;
import com.android.ddmlib.testrunner.ITestRunListener;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
@@ -32,6 +33,7 @@
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
/**
* Basic Callable to run tests on a given {@link DeviceConnector} using
@@ -39,6 +41,8 @@
*/
public class SimpleTestCallable implements Callable<Boolean> {
+ public static final String FILE_COVERAGE_EC = "coverage.ec";
+
@NonNull
private final String projectName;
@NonNull
@@ -50,6 +54,8 @@
@NonNull
private final File resultsDir;
@NonNull
+ private final File coverageDir;
+ @NonNull
private final File testApk;
@Nullable
private final File testedApk;
@@ -65,12 +71,14 @@
@Nullable File testedApk,
@NonNull TestData testData,
@NonNull File resultsDir,
+ @NonNull File coverageDir,
int timeout,
@NonNull ILogger logger) {
this.projectName = projectName;
this.device = device;
this.flavorName = flavorName;
this.resultsDir = resultsDir;
+ this.coverageDir = coverageDir;
this.testApk = testApk;
this.testedApk = testedApk;
this.testData = testData;
@@ -88,6 +96,9 @@
runListener.setReportDir(resultsDir);
long time = System.currentTimeMillis();
+ boolean success = false;
+
+ String coverageFile = "/data/data/" + testData.getTestedApplicationId() + "/" + FILE_COVERAGE_EC;
try {
device.connect(timeout, logger);
@@ -102,16 +113,23 @@
isInstalled = true;
RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(
- testData.getPackageName(),
+ testData.getApplicationId(),
testData.getInstrumentationRunner(),
device);
+ if (testData.isTestCoverageEnabled()) {
+ runner.addInstrumentationArg("coverage", "true");
+ runner.addInstrumentationArg("coverageFile", coverageFile);
+ }
+
runner.setRunName(deviceName);
runner.setMaxtimeToOutputResponse(timeout);
runner.run(runListener);
- return runListener.getRunResult().hasFailedTests();
+ boolean result = runListener.getRunResult().hasFailedTests();
+ success = true;
+ return result;
} catch (Exception e) {
Map<String, String> emptyMetrics = Collections.emptyMap();
@@ -131,13 +149,24 @@
throw e;
} finally {
if (isInstalled) {
+ // Get the coverage if needed.
+ if (success && testData.isTestCoverageEnabled()) {
+ device.executeShellCommand(
+ "run-as " + testData.getTestedApplicationId() + " chmod 644 " + coverageFile,
+ new NullOutputReceiver(),
+ 30, TimeUnit.SECONDS);
+ device.pullFile(
+ coverageFile,
+ new File(coverageDir, FILE_COVERAGE_EC).getPath());
+ }
+
// uninstall the apps
// This should really not be null, because if it was the build
// would have broken before.
- uninstall(testApk, testData.getPackageName(), deviceName);
+ uninstall(testApk, testData.getApplicationId(), deviceName);
if (testedApk != null) {
- uninstall(testedApk, testData.getTestedPackageName(), deviceName);
+ uninstall(testedApk, testData.getTestedApplicationId(), deviceName);
}
}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/ByteUtils.java b/build-system/builder/src/main/java/com/android/builder/png/ByteUtils.java
new file mode 100644
index 0000000..65f3a16
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/ByteUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Maps;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Map;
+
+/**
+ * A small util class to do byte conversion.
+ *
+ * This class allocates a couple of buffers and reuse them. Each new instance
+ * gets new buffers.
+ *
+ * This is not thread-safe.
+ */
+class ByteUtils {
+
+ @NonNull
+ private final ByteBuffer mIntBuffer;
+ @NonNull
+ private final ByteBuffer mLongBuffer;
+
+ ByteUtils() {
+ // ByteBuffer for endian-ness conversion
+ mIntBuffer = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
+ mLongBuffer = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
+ }
+
+ @NonNull
+ byte[] getLongAsIntArray(long value) {
+ return ((ByteBuffer) mLongBuffer.rewind()).putLong(value).array();
+ }
+
+ @NonNull
+ byte[] getIntAsArray(int value) {
+ return ((ByteBuffer) mIntBuffer.rewind()).putInt(value).array();
+ }
+
+ static class Cache {
+
+ private static final Cache sPngCache = new Cache();
+
+ private final Map<Long, ByteUtils> map = Maps.newHashMap();
+
+ @NonNull
+ static Cache getCache() {
+ return sPngCache;
+ }
+
+ synchronized ByteUtils getUtils(long key) {
+ ByteUtils utils = map.get(key);
+ if (utils == null) {
+ utils = new ByteUtils();
+ map.put(key, utils);
+ }
+
+ return utils;
+ }
+
+ static ByteUtils get() {
+ return getCache().getUtils(Thread.currentThread().getId());
+ }
+
+ synchronized void clear() {
+ map.clear();
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/Chunk.java b/build-system/builder/src/main/java/com/android/builder/png/Chunk.java
new file mode 100644
index 0000000..c08b826
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/Chunk.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.zip.CRC32;
+
+/**
+ * A Png Chunk.
+ *
+ * A chunk contains a 4-byte length, a 4-byte type, a number of bytes for the payload, and finally
+ * a 4-byte CRC32.
+ *
+ * The length value is only the length of the payload. If there is no payload, it is 0, but
+ * the type and CRC32 are still there.
+ * Length is unsigned but max value is (2^31)-1.
+ *
+ * The CRC32 is computed on the type and payload but not length.
+ *
+ * Reference: http://tools.ietf.org/html/rfc2083#section-3.2
+ *
+ * The type of the chunk follows a very specific naming scheme.
+
+ * Reference: http://tools.ietf.org/html/rfc2083#section-3.3
+ *
+ */
+class Chunk {
+
+ @NonNull
+ private final byte[] mType;
+ @Nullable
+ private final byte[] mData;
+ private final long mCrc32;
+
+ @VisibleForTesting
+ Chunk(@NonNull byte[] type, @Nullable byte[] data, long crc32) {
+ checkNotNull(type);
+ checkArgument(type.length == 4);
+
+ mType = type;
+ mData = data;
+ mCrc32 = crc32;
+ }
+
+ Chunk(@NonNull byte[] type, @Nullable byte[] data) {
+ this(type, data, computeCrc32(type, data));
+ }
+
+ Chunk(@NonNull byte[] type) {
+ this(type, null);
+ }
+
+ /**
+ * Return the length info about the chunk. This is the length that
+ * is written in the PNG file.
+ */
+ int getDataLength() {
+ return mData != null ? mData.length : 0;
+ }
+
+ /**
+ * returns the size of the chunk in the file.
+ */
+ int size() {
+ // 4 bytes for each length, type and crc32.
+ // then add the data length.
+ return 12 + getDataLength();
+ }
+
+ @NonNull
+ byte[] getType() {
+ return mType;
+ }
+
+ String getTypeAsString() {
+ return new String(mType, Charsets.US_ASCII);
+ }
+
+ @Nullable
+ byte[] getData() {
+ return mData;
+ }
+
+ long getCrc32() {
+ return mCrc32;
+ }
+
+ private static long computeCrc32(@NonNull byte[] type, @Nullable byte[] data) {
+ CRC32 checksum = new CRC32();
+ checksum.update(type);
+ if (data != null) {
+ checksum.update(data);
+ }
+
+ return checksum.getValue();
+ }
+
+ void write(@NonNull OutputStream outStream) throws IOException {
+ ByteUtils utils = ByteUtils.Cache.get();
+
+ //write the length
+ outStream.write(utils.getIntAsArray(getDataLength()));
+
+ // write the type
+ outStream.write(mType);
+
+ // write the data if applicable
+ if (mData != null) {
+ outStream.write(mData);
+ }
+
+ // write the CRC32. This is a long converted to a 8 byte array,
+ // but we only care about the last 4 bytes.
+ outStream.write(utils.getLongAsIntArray(mCrc32), 4, 4);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Chunk chunk = (Chunk) o;
+
+ if (mCrc32 != chunk.mCrc32) {
+ return false;
+ }
+ if (!Arrays.equals(mData, chunk.mData)) {
+ return false;
+ }
+ if (!Arrays.equals(mType, chunk.mType)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(mType);
+ result = 31 * result + (mData != null ? Arrays.hashCode(mData) : 0);
+ result = 31 * result + (int) (mCrc32 ^ (mCrc32 >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ if (Arrays.equals(mType, PngWriter.IHDR)) {
+ ByteBuffer buffer = ByteBuffer.wrap(mData);
+ return "Chunk{" +
+ "mType=" + getTypeAsString() +
+ ", mData=" + buffer.getInt() + "x" + buffer.getInt() + ":" + buffer.get() +
+ "-" + buffer.get() + "-" + buffer.get() + "-" + buffer.get() +
+ "-" + buffer.get() +
+ '}';
+ }
+
+ return "Chunk{" +
+ "mType=" + getTypeAsString() +
+ (getDataLength() <= 200 ? ", mData=" + getArray() : "") +
+ ", mData-Length=" + getDataLength() +
+ '}';
+ }
+
+ private String getArray() {
+ int len = getDataLength();
+ StringBuilder sb = new StringBuilder(len * 2);
+ if (mData != null) {
+ for (int i = 0 ; i < len ; i++) {
+ sb.append(String.format("%02X", mData[i]));
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/ColorType.java b/build-system/builder/src/main/java/com/android/builder/png/ColorType.java
new file mode 100644
index 0000000..0efed25
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/ColorType.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+/**
+ * PNG Color type enum.
+ */
+public enum ColorType {
+
+ GRAY_SCALE(0),
+ RGB(2),
+ PLTE(3),
+ GRAY_SCALE_ALPHA(4),
+ RGBA(6);
+
+ private final byte mFlag;
+
+ ColorType(int flag) {
+ mFlag = (byte) flag;
+ }
+
+ public byte getFlag() {
+ return mFlag;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/LayoutBoundChunkBuilder.java b/build-system/builder/src/main/java/com/android/builder/png/LayoutBoundChunkBuilder.java
new file mode 100644
index 0000000..fef6999
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/LayoutBoundChunkBuilder.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import com.android.annotations.NonNull;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Builder for the Layout Bound chunk.
+ */
+class LayoutBoundChunkBuilder {
+
+ /**
+ * Chunk Type for the layout bound chunk.
+ * This is part of the 9-patch 'spec' (if there was one).
+ */
+ private static final byte[] sChunkType = new byte[] { 'n', 'p', 'L', 'b' };
+
+ private final int mLeft;
+ private final int mTop;
+ private final int mRight;
+ private final int mBottom;
+
+ LayoutBoundChunkBuilder(int left, int top, int right, int bottom) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ }
+
+ /**
+ * Creates and returns a {@link com.android.builder.png.Chunk}
+ */
+ @NonNull
+ Chunk getChunk() {
+ ByteBuffer buffer = ByteBuffer.allocate(4 * Integer.SIZE/8);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(mLeft);
+ buffer.putInt(mTop);
+ buffer.putInt(mRight);
+ buffer.putInt(mBottom);
+
+ return new Chunk(sChunkType, buffer.array());
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/NinePatchChunkBuilder.java b/build-system/builder/src/main/java/com/android/builder/png/NinePatchChunkBuilder.java
new file mode 100644
index 0000000..8900853
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/NinePatchChunkBuilder.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * Builder for the NinePatch chunk.
+ */
+class NinePatchChunkBuilder {
+
+ /**
+ * Chunk Type for the chunk containing the 9-patch info.
+ * This is part of the 9-patch 'spec' (if there was one).
+ */
+ private static final byte[] sChunkType = new byte[] { 'n', 'p', 'T', 'c' };
+
+ private final int mPaddingLeft;
+ private final int mPaddingRight;
+ private final int mPaddingTop;
+ private final int mPaddingBottom;
+
+ @NonNull
+ private final byte[] mXDivs;
+ @NonNull
+ private final byte[] mYDivs;
+ @NonNull
+ private final byte [] mColors;
+
+ NinePatchChunkBuilder(@NonNull int[] xDivs, int numXDivs, @NonNull int[] yDivs, int numYDivs,
+ @NonNull int[] colors,
+ int paddingLeft, int paddingRight, int paddingTop, int paddingBottom) {
+ // fill the bytes array from the int array
+ mXDivs = intToByteArray(xDivs, numXDivs);
+ mYDivs = intToByteArray(yDivs, numYDivs);
+ mColors = intToByteArray(colors, colors.length);
+
+ mPaddingLeft = paddingLeft;
+ mPaddingRight = paddingRight;
+ mPaddingTop = paddingTop;
+ mPaddingBottom = paddingBottom;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ static byte[] intToByteArray(@NonNull int[] array, int length) {
+ byte[] byteArray = new byte[length * 4];
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
+ IntBuffer intBuffer = byteBuffer.asIntBuffer();
+
+ intBuffer.put(array, 0, length);
+
+ return byteArray;
+ }
+
+ /**
+ * Creates and returns a {@link com.android.builder.png.Chunk}
+ */
+ @NonNull
+ Chunk getChunk() {
+ int size = computeSize();
+ ByteBuffer buffer = ByteBuffer.allocate(size);
+
+ buffer.put((byte) 0); // was deserialized
+ buffer.put((byte) (mXDivs.length / 4));
+ buffer.put((byte) (mYDivs.length / 4));
+ buffer.put((byte) (mColors.length / 4));
+
+ // skip the pointers.
+ buffer.putInt(0);
+ buffer.putInt(0);
+
+ buffer.putInt(mPaddingLeft);
+ buffer.putInt(mPaddingRight);
+ buffer.putInt(mPaddingTop);
+ buffer.putInt(mPaddingBottom);
+
+ // skip more pointers
+ buffer.putInt(0);
+
+ buffer.put(mXDivs);
+ buffer.put(mYDivs);
+ buffer.put(mColors);
+
+ return new Chunk(sChunkType, buffer.array());
+ }
+
+ private int computeSize() {
+ // The size of this struct is 32 bytes on the 32-bit target system
+ // 4 * int8_t
+ // 4 * int32_t
+ // 3 * pointer
+
+ return 32 + mXDivs.length + mYDivs.length + mColors.length;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/NinePatchException.java b/build-system/builder/src/main/java/com/android/builder/png/NinePatchException.java
new file mode 100644
index 0000000..5504a73
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/NinePatchException.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+
+import java.io.File;
+
+/**
+ */
+public class NinePatchException extends Exception {
+
+ @NonNull
+ private final File mFile;
+ private final TickException mTickException;
+ private final String mEdge;
+
+ NinePatchException(
+ @NonNull File file,
+ @NonNull TickException tickException,
+ @NonNull String edge) {
+
+ mFile = file;
+ mTickException = tickException;
+ mEdge = edge;
+ }
+
+ NinePatchException(@NonNull File file, String message) {
+ super(message);
+ mFile = file;
+ mTickException = null;
+ mEdge = null;
+ }
+
+ @VisibleForTesting
+ String getEdge() {
+ return mEdge;
+ }
+
+ @VisibleForTesting
+ TickException getTickException() {
+ return mTickException;
+ }
+
+ @Override
+ public String getMessage() {
+ if (mTickException != null) {
+ String info;
+ if (mTickException.getPixelColor() != null && mTickException.getPixelLocation() >= 0) {
+ info = String.format("Found at pixel #%d with color 0x%08X along %s edge",
+ mTickException.getPixelLocation(),
+ mTickException.getPixelColor(),
+ mEdge);
+ } else {
+ info = String.format("Found along %s edge", mEdge);
+ }
+
+ return String.format("%s: Error: 9-patch image malformed:\n %s\n %s",
+ mFile, mTickException.getMessage(), info);
+ }
+
+ return String.format("%s: Error: 9-patch image malformed: %s", mFile, super.getMessage());
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/PngProcessor.java b/build-system/builder/src/main/java/com/android/builder/png/PngProcessor.java
new file mode 100644
index 0000000..f1b9871
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/PngProcessor.java
@@ -0,0 +1,938 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.zip.Deflater;
+
+import javax.imageio.ImageIO;
+
+/**
+ * a PngProcessor.
+ *
+ * It reads a png file and write another png file that's been optimized
+ * and processed in case of a 9-patch.
+ */
+public class PngProcessor {
+
+ private static final int COLOR_WHITE = 0xFFFFFFFF;
+ private static final int COLOR_TICK = 0xFF000000;
+ private static final int COLOR_LAYOUT_BOUNDS_TICK = 0xFFFF0000;
+
+ private static final int PNG_9PATCH_NO_COLOR = 0x00000001;
+ private static final int PNG_9PATCH_TRANSPARENT_COLOR = 0x00000000;
+
+
+ @NonNull
+ private final File mFile;
+
+ private Chunk mIhdr;
+ private Chunk mIdat;
+ private List<Chunk> mOtherChunks = Lists.newArrayList();
+
+ /**
+ * Processes a given png and writes the resulting png file.
+ * @param from the input file
+ * @param to the destination file
+ * @throws IOException
+ * @throws NinePatchException
+ */
+ public static void process(@NonNull File from, @NonNull File to)
+ throws IOException, NinePatchException {
+ PngProcessor processor = new PngProcessor(from);
+ processor.read();
+
+ if (!processor.is9Patch() && processor.size() >= from.length()) {
+ Files.copy(from, to);
+ return;
+ }
+
+ PngWriter writer = new PngWriter(to);
+ writer.setIhdr(processor.getIhdr())
+ .setChunks(processor.getOtherChunks())
+ .setChunk(processor.getIdat());
+
+ writer.write();
+ }
+
+ public static void clearCache() {
+ ByteUtils.Cache.getCache().clear();
+ }
+
+ @VisibleForTesting
+ PngProcessor(@NonNull File file) {
+ checkNotNull(file);
+
+ mFile = file;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ Chunk getIhdr() {
+ return mIhdr;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ List<Chunk> getOtherChunks() {
+ return mOtherChunks;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ Chunk getIdat() {
+ return mIdat;
+ }
+
+ @VisibleForTesting
+ void read() throws IOException, NinePatchException {
+ BufferedImage image = ImageIO.read(mFile);
+
+ processImageContent(image);
+ }
+
+ private void addChunk(@NonNull Chunk chunk) {
+ mOtherChunks.add(chunk);
+ }
+
+ /**
+ * Returns the size of the generated png.
+ */
+ long size() {
+ long size = PngWriter.SIGNATURE.length;
+
+ size += mIhdr.size();
+ size += mIdat.size();
+ for (Chunk chunk : mOtherChunks) {
+ size += chunk.size();
+ }
+
+ return size;
+ }
+
+ private void processImageContent(@NonNull BufferedImage image) throws NinePatchException,
+ IOException {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ int[] content = new int[width * height];
+
+ image.getRGB(0, 0, width, height, content, 0, width);
+
+ int startX = 0;
+ int startY = 0;
+ int endX = width;
+ int endY = height;
+
+ if (is9Patch()) {
+ startX = 1;
+ startY = 1;
+ endX--;
+ endY--;
+
+ processBorder(content, width, height);
+ }
+
+ ColorType colorType = createImage(content, width, startX, endX, startY, endY, is9Patch());
+
+ mIhdr = computeIhdr(endX - startX, endY - startY, (byte) 8, colorType);
+ }
+
+ private void writeIDat(byte[] data) throws IOException {
+ // create a growing buffer for the result.
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
+
+ Deflater deflater = new Deflater(Deflater.DEFLATED);
+ deflater.setInput(data);
+ deflater.finish();
+
+ // temp buffer for compressed data.
+ byte[] tmpBuffer = new byte[1024];
+ while (!deflater.finished()) {
+ int compressedLen = deflater.deflate(tmpBuffer);
+ bos.write(tmpBuffer, 0, compressedLen);
+ }
+
+ bos.close();
+
+ byte[] compressedData = bos.toByteArray();
+
+ mIdat = new Chunk(PngWriter.IDAT, compressedData);
+ }
+
+ /**
+ * Creates the PNG image buffer to be encoded in IDAT.
+ *
+ * This figures out the smallest way to encode it, and creates the IDAT chunk (and other needed
+ * chunks like PLTE).
+ *
+ * @param content the image array in ARGB.
+ * @param scanline the scanline value for the image buffer
+ * @param startX the startX of the image we want to create from the buffer
+ * @param endX the endX of the image we want to create from the buffer
+ * @param startY the startY of the image we want to create from the buffer
+ * @param endY the endY of the image we want to create from the buffer
+ * @param is9Patch whether the image is a nine-patch
+ */
+ private ColorType createImage(@NonNull int[] content, int scanline,
+ int startX, int endX, int startY, int endY, boolean is9Patch) throws IOException {
+ int[] paletteColors = new int[256];
+ int paletteColorCount = 0;
+
+ int grayscaleTolerance = -1;
+ int maxGrayDeviation = 0;
+
+ boolean isOpaque = true;
+ boolean isPalette = true;
+ boolean isGrayscale = true;
+
+ int width = endX - startX;
+ int height = endY - startY;
+
+ // RGBA buffer, in case it's the best one.
+ int rgbaLen = (1 + width * 4) * height;
+ ByteBuffer rgbaBufer = ByteBuffer.allocate(rgbaLen);
+
+ // Store palette data optimistically in case we use palette mode.
+ // Better than regoing through content after.
+ int indexedLen = (1 + width) * height;
+ byte[] indexedContent = new byte[indexedLen];
+ int indexedContentIndex = 0;
+
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors
+ for (int y = startY ; y < endY ; y++) {
+ rgbaBufer.put((byte) 0);
+ indexedContent[indexedContentIndex++] = 0;
+
+ for (int x = startX ; x < endX ; x++) {
+ int argb = content[(scanline * y) + x];
+
+ int aa = argb >>> 24;
+ int rr = (argb >> 16) & 0x000000FF;
+ int gg = (argb >> 8) & 0x000000FF;
+ int bb = argb & 0x000000FF;
+
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = Math.max(Math.abs(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = Math.max(Math.abs(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = Math.max(Math.abs(bb - rr), maxGrayDeviation);
+
+ // Check if image is really grayscale
+ if (isGrayscale && (rr != gg || rr != bb)) {
+ isGrayscale = false;
+ }
+
+ // Check if image is really opaque
+ if (isOpaque && aa != 0xff) {
+ isOpaque = false;
+ }
+
+ // Check if image is really <= 256 colors
+ if (isPalette) {
+ int rgba = (argb << 8) | aa;
+
+ boolean match = false;
+ int idx;
+ for (idx = 0; idx < paletteColorCount; idx++) {
+ if (paletteColors[idx] == rgba) {
+ match = true;
+ break;
+ }
+ }
+
+ // Write the palette index for the pixel to outRows optimistically
+ // We might overwrite it later if we decide to encode as gray or
+ // gray + alpha
+ indexedContent[indexedContentIndex++] = (byte)idx;
+ if (!match) {
+ if (paletteColorCount == 256) {
+ isPalette = false;
+ } else {
+ paletteColors[paletteColorCount++] = rgba;
+ }
+ }
+ }
+
+ // write rgba optimistically
+ rgbaBufer.putInt((argb << 8) | aa);
+ }
+ }
+
+ boolean hasTransparency = !isOpaque;
+
+ int bpp = isOpaque ? 3 : 4;
+ int paletteSize = width * height + (isOpaque ? 3 : 4) * paletteColorCount;
+
+ ColorType colorType;
+
+ // Choose the best color type for the image.
+ // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+ // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
+ // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+ // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
+ // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+ if (isGrayscale) {
+ if (isOpaque) {
+ colorType = ColorType.GRAY_SCALE; // 1 byte/pixel
+ } else {
+ // Use a simple heuristic to determine whether using a palette will
+ // save space versus using gray + alpha for each pixel.
+ // This doesn't take into account chunk overhead, filtering, LZ
+ // compression, etc.
+ if (isPalette && paletteSize < 2 * width * height) {
+ colorType = ColorType.PLTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ colorType = ColorType.GRAY_SCALE_ALPHA; // 2 bytes per pixel
+ }
+ }
+ } else if (isPalette && paletteSize < bpp * width * height) {
+ colorType = ColorType.PLTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ colorType = isOpaque ? ColorType.GRAY_SCALE : ColorType.GRAY_SCALE_ALPHA;
+ } else {
+ colorType = isOpaque ? ColorType.RGB : ColorType.RGBA;
+ }
+ }
+
+ // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+ // sure the pixels will not be pre-dithered/clamped until we decide they are
+ if (is9Patch && (colorType == ColorType.RGB ||
+ colorType == ColorType.GRAY_SCALE || colorType == ColorType.PLTE)) {
+ colorType = ColorType.RGBA;
+ }
+
+ // Perform postprocessing of the image or palette data based on the final
+ // color type chosen
+ if (colorType == ColorType.PLTE) {
+ byte[] rgbPalette = new byte[paletteColorCount * 3];
+ byte[] alphaPalette = null;
+ if (hasTransparency) {
+ alphaPalette = new byte[paletteColorCount];
+ }
+
+ // Create the RGB and alpha palettes
+ for (int idx = 0; idx < paletteColorCount; idx++) {
+ int color = paletteColors[idx];
+ rgbPalette[idx * 3] = (byte) ( color >>> 24);
+ rgbPalette[idx * 3 + 1] = (byte) ((color >> 16) & 0xFF);
+ rgbPalette[idx * 3 + 2] = (byte) ((color >> 8) & 0xFF);
+ if (hasTransparency) {
+ alphaPalette[idx] = (byte) (color & 0xFF);
+ }
+ }
+
+ // create chunks.
+ addChunk(new Chunk(PngWriter.PLTE, rgbPalette));
+
+ if (hasTransparency) {
+ addChunk(new Chunk(PngWriter.TRNS, alphaPalette));
+ }
+
+ // create image data chunk
+ writeIDat(indexedContent);
+
+ } else if (colorType == ColorType.GRAY_SCALE || colorType == ColorType.GRAY_SCALE_ALPHA) {
+ int grayLen = (1 + width * (1 + (hasTransparency ? 1 : 0))) * height;
+ byte[] grayContent = new byte[grayLen];
+ int grayContentIndex = 0;
+
+ for (int y = startY ; y < endY ; y++) {
+ grayContent[grayContentIndex++] = 0;
+
+ for (int x = startX ; x < endX ; x++) {
+ int argb = content[(scanline * y) + x];
+
+ int rr = (argb >> 16) & 0x000000FF;
+
+ if (isGrayscale) {
+ grayContent[grayContentIndex++] = (byte) rr;
+ } else {
+ int gg = (argb >> 8) & 0x000000FF;
+ int bb = argb & 0x000000FF;
+
+ // convert RGB to Grayscale.
+ // Ref: http://en.wikipedia.org/wiki/Luma_(video)
+ grayContent[grayContentIndex++] = (byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
+
+ if (hasTransparency) {
+ int aa = argb >>> 24;
+ grayContent[grayContentIndex++] = (byte) aa;
+ }
+ }
+ }
+
+ // create image data chunk
+ writeIDat(grayContent);
+
+ } else if (colorType == ColorType.RGBA) {
+ writeIDat(rgbaBufer.array());
+ } else {
+ //RGB mode
+ int rgbLen = (1 + width * 3) * height;
+ byte[] rgbContent = new byte[rgbLen];
+ int rgbContentIndex = 0;
+
+ for (int y = startY ; y < endY ; y++) {
+ rgbContent[rgbContentIndex++] = 0;
+
+ for (int x = startX ; x < endX ; x++) {
+ int argb = content[(scanline * y) + x];
+
+ rgbContent[rgbContentIndex++] = (byte) ((argb >> 16) & 0x000000FF);
+ rgbContent[rgbContentIndex++] = (byte) ((argb >> 8) & 0x000000FF);
+ rgbContent[rgbContentIndex++] = (byte) ( argb & 0x000000FF);
+ }
+ }
+
+ // create image data chunk
+ writeIDat(rgbContent);
+ }
+
+ return colorType;
+ }
+
+ /**
+ * process the border of the image to find 9-patch info
+ * @param content the content of ARGB
+ * @param width the width
+ * @param height the height
+ * @throws NinePatchException
+ */
+ private void processBorder(int[] content, int width, int height)
+ throws NinePatchException {
+ // Validate size...
+ if (width < 3 || height < 3) {
+ throw new NinePatchException(mFile, "Image must be at least 3x3 (1x1 without frame) pixels");
+ }
+
+ int i, j;
+
+ int[] xDivs = new int[width];
+ int[] yDivs = new int[height];
+ int[] colors;
+ Arrays.fill(xDivs, -1);
+ Arrays.fill(yDivs, -1);
+
+ int numXDivs;
+ int numYDivs;
+ byte numColors;
+ int numRows, numCols;
+ int top, left, right, bottom;
+
+ int paddingLeft, paddingTop, paddingRight, paddingBottom;
+
+ boolean transparent = (content[0] & 0xFF000000) == 0;
+
+ int colorIndex = 0;
+
+ // Validate frame...
+ if (!transparent && content[0] != 0xFFFFFFFF) {
+ throw new NinePatchException(mFile,
+ "Must have one-pixel frame that is either transparent or white");
+ }
+
+ // Find left and right of sizing areas...
+ AtomicInteger outInt = new AtomicInteger(0);
+ try {
+ getHorizontalTicks(
+ content, 0, width,
+ transparent, true /*required*/,
+ xDivs, 0, 1, outInt,
+ true /*multipleAllowed*/);
+ numXDivs = outInt.get();
+ } catch (TickException e) {
+ throw new NinePatchException(mFile, e, "top");
+ }
+
+ // Find top and bottom of sizing areas...
+ outInt.set(0);
+ try {
+ getVerticalTicks(content, 0, width, height,
+ transparent, true /*required*/,
+ yDivs, 0, 1, outInt,
+ true /*multipleAllowed*/);
+ numYDivs = outInt.get();
+ } catch (TickException e) {
+ throw new NinePatchException(mFile, e, "left");
+ }
+
+ // Find left and right of padding area...
+ int[] values = new int[2];
+ try {
+ getHorizontalTicks(
+ content, width * (height - 1), width,
+ transparent, false /*required*/,
+ values, 0, 1, null,
+ false /*multipleAllowed*/);
+ paddingLeft = values[0];
+ paddingRight = values[1];
+ values[0] = values[1] = 0;
+ } catch (TickException e) {
+ throw new NinePatchException(mFile, e, "bottom");
+ }
+
+ // Find top and bottom of padding area...
+ try {
+ getVerticalTicks(
+ content, width - 1, width, height,
+ transparent, false /*required*/,
+ values, 0, 1, null,
+ false /*multipleAllowed*/);
+ paddingTop = values[0];
+ paddingBottom = values[1];
+ } catch (TickException e) {
+ throw new NinePatchException(mFile, e, "right");
+ }
+
+ try {
+ // Find left and right of layout padding...
+ getHorizontalLayoutBoundsTicks(content, width * (height - 1), width,
+ transparent, false, values);
+ } catch (TickException e) {
+ throw new NinePatchException(mFile, e, "bottom");
+ }
+
+ int[] values2 = new int[2];
+ try {
+ getVerticalLayoutBoundsTicks(content, width - 1, width, height,
+ transparent, false, values2);
+ } catch (TickException e) {
+ throw new NinePatchException(mFile, e, "right");
+ }
+
+ LayoutBoundChunkBuilder layoutBoundChunkBuilder = null;
+ if (values[0] != 0 || values[1] != 0 || values2[0] != 0 || values2[1] != 0) {
+ layoutBoundChunkBuilder = new LayoutBoundChunkBuilder(
+ values[0], values2[0], values[1], values2[1]);
+ }
+
+ // If padding is not yet specified, take values from size.
+ if (paddingLeft < 0) {
+ paddingLeft = xDivs[0];
+ paddingRight = width - 2 - xDivs[1];
+ } else {
+ // Adjust value to be correct!
+ paddingRight = width - 2 - paddingRight;
+ }
+ if (paddingTop < 0) {
+ paddingTop = yDivs[0];
+ paddingBottom = height - 2 - yDivs[1];
+ } else {
+ // Adjust value to be correct!
+ paddingBottom = height - 2 - paddingBottom;
+ }
+
+ // Remove frame from image.
+ width -= 2;
+ height -= 2;
+
+ // Figure out the number of rows and columns in the N-patch
+ numCols = numXDivs + 1;
+ if (xDivs[0] == 0) { // Column 1 is strechable
+ numCols--;
+ }
+ if (xDivs[numXDivs - 1] == width) {
+ numCols--;
+ }
+ numRows = numYDivs + 1;
+ if (yDivs[0] == 0) { // Row 1 is strechable
+ numRows--;
+ }
+ if (yDivs[numYDivs - 1] == height) {
+ numRows--;
+ }
+
+ // Make sure the amount of rows and columns will fit in the number of
+ // colors we can use in the 9-patch format.
+ if (numRows * numCols > 0x7F) {
+ throw new NinePatchException(mFile, "Too many rows and columns in 9-patch perimeter");
+ }
+
+ numColors = (byte) (numRows * numCols);
+ colors = new int[numColors];
+
+ // Fill in color information for each patch.
+
+ int c;
+ top = 0;
+
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
+
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = (yDivs[0] == 0 ? 1 : 0);
+ j <= numYDivs && top < height;
+ j++) {
+ if (j == numYDivs) {
+ bottom = height;
+ } else {
+ bottom = yDivs[j];
+ }
+ left = 0;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ for (i = xDivs[0] == 0 ? 1 : 0;
+ i <= numXDivs && left < width;
+ i++) {
+ if (i == numXDivs) {
+ right = width;
+ } else {
+ right = xDivs[i];
+ }
+ c = getColor(content, width + 2, left, top, right - 1, bottom - 1);
+ colors[colorIndex++] = c;
+ left = right;
+ }
+ top = bottom;
+ }
+
+ // Create the chunks.
+ NinePatchChunkBuilder ninePatchChunkBuilder = new NinePatchChunkBuilder(
+ xDivs, numXDivs, yDivs, numYDivs, colors,
+ paddingLeft, paddingRight, paddingTop, paddingBottom);
+
+ addChunk(ninePatchChunkBuilder.getChunk());
+ if (layoutBoundChunkBuilder != null) {
+ addChunk(layoutBoundChunkBuilder.getChunk());
+ }
+ }
+
+ /**
+ * returns a color. the top/left/right/bottom coordinate are in a subframe of content, starting
+ * in (1,1).
+ * @param content the image buffer
+ * @param width the width of the image buffer
+ * @param left left coordinate.
+ * @param top top coordinate.
+ * @param right right coordinate.
+ * @param bottom bottom coordinate.
+ * @return a color.
+ */
+ static int getColor(@NonNull int[] content, int width,
+ int left, int top, int right, int bottom) {
+ int color = content[(top + 1) * width + left + 1];
+ int alpha = color & 0xFF000000;
+
+ if (left > right || top > bottom) {
+ return PNG_9PATCH_TRANSPARENT_COLOR;
+ }
+
+ while (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ int c = content[(top + 1) * width + i + 1];
+ if (alpha == 0) {
+ if ((c & 0xFF000000) != 0) {
+ return PNG_9PATCH_NO_COLOR;
+ }
+ } else if (c != color) {
+ return PNG_9PATCH_NO_COLOR;
+ }
+ }
+ top++;
+ }
+
+ if (alpha == 0) {
+ return PNG_9PATCH_TRANSPARENT_COLOR;
+ }
+
+ return color;
+ }
+
+
+ private static enum TickType {
+ NONE, TICK, LAYOUT_BOUNDS, BOTH
+ }
+
+ @NonNull
+ private static TickType getTickType(int color, boolean transparent) throws TickException {
+
+ int alpha = color >>> 24;
+
+ if (transparent) {
+ if (alpha == 0) {
+ return TickType.NONE;
+ }
+ if (color == COLOR_LAYOUT_BOUNDS_TICK) {
+ return TickType.LAYOUT_BOUNDS;
+ }
+ if (color == COLOR_TICK) {
+ return TickType.TICK;
+ }
+
+ // Error cases
+ if (alpha != 0xFF) {
+ throw TickException.createWithColor(
+ "Frame pixels must be either solid or transparent (not intermediate alphas)",
+ color);
+ }
+ if ((color & 0x00FFFFFF) != 0) {
+ throw TickException.createWithColor("Ticks in transparent frame must be black or red",
+ color);
+ }
+ return TickType.TICK;
+ }
+
+ if (alpha != 0xFF) {
+ throw TickException.createWithColor("White frame must be a solid color (no alpha)",
+ color);
+ }
+ if (color == COLOR_WHITE) {
+ return TickType.NONE;
+ }
+ if (color == COLOR_TICK) {
+ return TickType.TICK;
+ }
+ if (color == COLOR_LAYOUT_BOUNDS_TICK) {
+ return TickType.LAYOUT_BOUNDS;
+ }
+
+ if ((color & 0x00FFFFFF) != 0) {
+ throw TickException.createWithColor("Ticks in transparent frame must be black or red",
+ color);
+ }
+
+ return TickType.TICK;
+ }
+
+
+ private static enum Tick {
+ START, INSIDE_1, OUTSIDE_1
+ }
+
+ private static void getHorizontalTicks(
+ @NonNull int[] content, int offset, int width,
+ boolean transparent, boolean required,
+ @NonNull int[] divs, int left, int right,
+ @Nullable AtomicInteger outDivs, boolean multipleAllowed) throws TickException {
+ int i;
+ divs[left] = divs[right] = -1;
+ Tick state = Tick.START;
+ boolean found = false;
+
+ for (i = 1; i < width - 1; i++) {
+ TickType tickType;
+ try {
+ tickType = getTickType(content[offset + i], transparent);
+ } catch (TickException e) {
+ throw new TickException(e, i);
+ }
+
+ if (TickType.TICK == tickType) {
+ if (state == Tick.START ||
+ (state == Tick.OUTSIDE_1 && multipleAllowed)) {
+ divs[left] = i - 1;
+ divs[right] = width - 2;
+ found = true;
+ if (outDivs != null) {
+ outDivs.addAndGet(2);
+ }
+ state = Tick.INSIDE_1;
+ } else if (state == Tick.OUTSIDE_1) {
+ throw new TickException("Can't have more than one marked region along edge");
+ }
+ } else {
+ if (state == Tick.INSIDE_1) {
+ // We're done with this div. Move on to the next.
+ divs[right] = i - 1;
+ right += 2;
+ left += 2;
+ state = Tick.OUTSIDE_1;
+ }
+ }
+
+ }
+
+ if (required && !found) {
+ throw new TickException("No marked region found along edge");
+ }
+ }
+
+ private static void getVerticalTicks(
+ @NonNull int[] content, int offset, int width, int height,
+ boolean transparent, boolean required,
+ @NonNull int[] divs, int top, int bottom,
+ @Nullable AtomicInteger outDivs, boolean multipleAllowed) throws TickException {
+
+ int i;
+ divs[top] = divs[bottom] = -1;
+ Tick state = Tick.START;
+ boolean found = false;
+
+ for (i = 1; i < height - 1; i++) {
+ TickType tickType;
+ try {
+ tickType = getTickType(content[offset + width * i], transparent);
+ } catch (TickException e) {
+ throw new TickException(e, i);
+ }
+
+ if (TickType.TICK == tickType) {
+ if (state == Tick.START ||
+ (state == Tick.OUTSIDE_1 && multipleAllowed)) {
+ divs[top] = i - 1;
+ divs[bottom] = height - 2;
+ found = true;
+ if (outDivs != null) {
+ outDivs.addAndGet(2);
+ }
+ state = Tick.INSIDE_1;
+ } else if (state == Tick.OUTSIDE_1) {
+ throw new TickException("Can't have more than one marked region along edge");
+ }
+ } else {
+ if (state == Tick.INSIDE_1) {
+ // We're done with this div. Move on to the next.
+ divs[bottom] = i - 1;
+ top += 2;
+ bottom += 2;
+ state = Tick.OUTSIDE_1;
+ }
+ }
+ }
+
+ if (required && !found) {
+ throw new TickException("No marked region found along edge");
+ }
+ }
+
+ private static void getHorizontalLayoutBoundsTicks(
+ @NonNull int[] content, int offset, int width, boolean transparent, boolean required,
+ @NonNull int[] outValues) throws TickException {
+
+ int i;
+ outValues[0] = outValues[1] = 0;
+
+ // Look for left tick
+ if (TickType.LAYOUT_BOUNDS == getTickType(content[offset + 1], transparent)) {
+ // Starting with a layout padding tick
+ i = 1;
+ while (i < width - 1) {
+ outValues[0]++;
+ i++;
+ TickType tick = getTickType(content[offset + i], transparent);
+ if (tick != TickType.LAYOUT_BOUNDS) {
+ break;
+ }
+ }
+ }
+
+ // Look for right tick
+ if (TickType.LAYOUT_BOUNDS == getTickType(content[offset + (width - 2)], transparent)) {
+ // Ending with a layout padding tick
+ i = width - 2;
+ while (i > 1) {
+ outValues[1]++;
+ i--;
+ TickType tick = getTickType(content[offset + i], transparent);
+ if (tick != TickType.LAYOUT_BOUNDS) {
+ break;
+ }
+ }
+ }
+ }
+
+ private static void getVerticalLayoutBoundsTicks(
+ @NonNull int[] content, int offset, int width, int height,
+ boolean transparent, boolean required,
+ @NonNull int[] outValues) throws TickException {
+ int i;
+ outValues[0] = outValues[1] = 0;
+
+ // Look for top tick
+ if (TickType.LAYOUT_BOUNDS == getTickType(content[offset + width], transparent)) {
+ // Starting with a layout padding tick
+ i = 1;
+ while (i < height - 1) {
+ outValues[0]++;
+ i++;
+ TickType tick = getTickType(content[offset + width * i], transparent);
+ if (tick != TickType.LAYOUT_BOUNDS) {
+ break;
+ }
+ }
+ }
+
+ // Look for bottom tick
+ if (TickType.LAYOUT_BOUNDS == getTickType(content[offset + width * (height - 2)],
+ transparent)) {
+ // Ending with a layout padding tick
+ i = height - 2;
+ while (i > 1) {
+ outValues[1]++;
+ i--;
+ TickType tick = getTickType(content[offset + width * i], transparent);
+ if (tick != TickType.LAYOUT_BOUNDS) {
+ break;
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ Chunk computeIhdr(int width, int height, byte bitDepth, @NonNull ColorType colorType) {
+ byte[] buffer = new byte[13];
+
+ ByteUtils utils = ByteUtils.Cache.get();
+
+ System.arraycopy(utils.getIntAsArray(width), 0, buffer, 0, 4);
+ System.arraycopy(utils.getIntAsArray(height), 0, buffer, 4, 4);
+ buffer[8] = bitDepth;
+ buffer[9] = colorType.getFlag();
+ buffer[10] = 0; // compressionMethod
+ buffer[11] = 0; // filterMethod;
+ buffer[12] = 0; // interlaceMethod
+
+ return new Chunk(PngWriter.IHDR, buffer);
+ }
+
+ boolean is9Patch() {
+ return mFile.getPath().endsWith(SdkConstants.DOT_9PNG);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/PngWriter.java b/build-system/builder/src/main/java/com/android/builder/png/PngWriter.java
new file mode 100644
index 0000000..fc151e0
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/PngWriter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * PNG Writer.
+ *
+ * A PNG file is simply a signature followed by a number of {@link Chunk}.
+ *
+ * PNG specification reference: http://tools.ietf.org/html/rfc2083
+ */
+public class PngWriter {
+
+ /** Chunk type for the chunk that ends the PNG file. */
+ private static final Chunk sIend = new Chunk(new byte[] { 'I', 'E', 'N', 'D' });
+
+ /**
+ * Signature of a PNG file.
+ */
+ public static final byte[] SIGNATURE = new byte[] {
+ (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
+
+
+ /** Chunk type for the Image-Data chunk. */
+ public static final byte[] IDAT = new byte[] { 'I', 'D', 'A', 'T' };
+ /** Chunk type for the Image-Header chunk. */
+ public static final byte[] IHDR = new byte[] { 'I', 'H', 'D', 'R' };
+ /** Chunk type for the palette chunk. */
+ public static final byte[] PLTE = new byte[] { 'P', 'L', 'T', 'E' };
+ /** Chunk type for the transparency data chunk. */
+ public static final byte[] TRNS = new byte[] { 't', 'R', 'N', 'S' };
+
+
+ @NonNull
+ private final File mToFile;
+
+ private Chunk mIhdr;
+ private final List<Chunk> mChunks = Lists.newArrayList();
+
+ public PngWriter(@NonNull File toFile) {
+ mToFile = toFile;
+ }
+
+ public PngWriter setIhdr(@NonNull Chunk chunk) {
+ mIhdr = chunk;
+ return this;
+ }
+
+ public PngWriter setChunk(@NonNull Chunk chunk) {
+ mChunks.add(chunk);
+ return this;
+ }
+
+ public PngWriter setChunks(@NonNull List<Chunk> chunks) {
+ mChunks.addAll(chunks);
+ return this;
+ }
+
+ public void write() throws IOException {
+ FileOutputStream fos = new FileOutputStream(mToFile);
+ try {
+ // copy the sig
+ fos.write(SIGNATURE);
+
+ mIhdr.write(fos);
+ for (Chunk chunk : mChunks) {
+ chunk.write(fos);
+ }
+
+ sIend.write(fos);
+ } finally {
+ fos.close();
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/TickException.java b/build-system/builder/src/main/java/com/android/builder/png/TickException.java
new file mode 100644
index 0000000..560aed5
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/TickException.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+
+/**
+ */
+class TickException extends Exception {
+
+ /**
+ * Bad pixel location. -1 if unknown or not relevant.
+ */
+ private final int mPixelLocation;
+
+ /**
+ * Bad pixel color. null if unknown or not relevant.
+ */
+ @Nullable
+ private final Integer mPixelColor;
+
+ static TickException createWithColor(@NonNull String message, int color) {
+ return new TickException(message, -1, color);
+ }
+
+ TickException(@NonNull String message, int pixelLocation, @Nullable Integer pixelColor) {
+ super(message);
+ mPixelLocation = pixelLocation;
+ mPixelColor = pixelColor;
+ }
+
+ TickException(@NonNull String message) {
+ this(message, -1, null);
+ }
+
+ TickException(@NonNull TickException tickException, int pixelLocation) {
+ this(tickException.getMessage(), pixelLocation, tickException.getPixelColor());
+ }
+
+ @VisibleForTesting
+ int getPixelLocation() {
+ return mPixelLocation;
+ }
+
+ @VisibleForTesting
+ @Nullable
+ Integer getPixelColor() {
+ return mPixelColor;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/sdk/DefaultSdkLoader.java b/build-system/builder/src/main/java/com/android/builder/sdk/DefaultSdkLoader.java
new file mode 100644
index 0000000..9e0b18c
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/sdk/DefaultSdkLoader.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sdk;
+
+import static com.android.SdkConstants.FD_EXTRAS;
+import static com.android.SdkConstants.FD_M2_REPOSITORY;
+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_ADB;
+import static com.android.SdkConstants.FN_ANNOTATIONS_JAR;
+import static com.android.SdkConstants.FN_SOURCE_PROP;
+
+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.ImmutableList;
+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;
+
+/**
+ * Singleton-based implementation of SdkLoader for a standard SDK
+ */
+public class DefaultSdkLoader implements SdkLoader {
+
+ private static DefaultSdkLoader sLoader;
+
+ @NonNull
+ private final File mSdkLocation;
+ private SdkManager mSdkManager;
+ private SdkInfo mSdkInfo;
+ private final ImmutableList<File> mRepositories;
+
+ public static synchronized SdkLoader getLoader(
+ @NonNull File sdkLocation) {
+ if (sLoader != null && !sdkLocation.equals(sLoader.mSdkLocation)) {
+ throw new IllegalStateException("Already created an SDK Loader with different SDK Path");
+ }
+
+ return sLoader = new DefaultSdkLoader(sdkLocation);
+ }
+
+ public static synchronized void unload() {
+ sLoader = null;
+ }
+
+ @Override
+ @NonNull
+ public TargetInfo getTargetInfo(
+ @NonNull String targetHash,
+ @NonNull FullRevision buildToolRevision,
+ @NonNull ILogger logger) {
+ init(logger);
+
+ IAndroidTarget target = mSdkManager.getTargetFromHashString(targetHash);
+ if (target == null) {
+ throw new IllegalStateException("failed to find target " + targetHash + " : " + mSdkLocation);
+ }
+
+ BuildToolInfo buildToolInfo = mSdkManager.getBuildTool(buildToolRevision);
+ if (buildToolInfo == null) {
+ throw new IllegalStateException("failed to find Build Tools revision "
+ + buildToolRevision.toString());
+ }
+
+ return new TargetInfo(target, buildToolInfo);
+ }
+
+ @Override
+ @NonNull
+ public SdkInfo getSdkInfo(@NonNull ILogger logger) {
+ init(logger);
+ return mSdkInfo;
+ }
+
+ @Override
+ @NonNull
+ public ImmutableList<File> getRepositories() {
+ return mRepositories;
+ }
+
+ private DefaultSdkLoader(@NonNull File sdkLocation) {
+ mSdkLocation = sdkLocation;
+ mRepositories = computeRepositories();
+ }
+
+ private synchronized void init(@NonNull ILogger logger) {
+ if (mSdkManager == null) {
+ mSdkManager = SdkManager.createManager(mSdkLocation.getPath(), logger);
+
+ if (mSdkManager == null) {
+ throw new IllegalStateException("failed to parse SDK! Check console for details");
+ }
+
+ File toolsFolder = new File(mSdkLocation, FD_TOOLS);
+ File supportToolsFolder = new File(toolsFolder, FD_SUPPORT);
+ File platformTools = new File(mSdkLocation, FD_PLATFORM_TOOLS);
+
+ mSdkInfo = new SdkInfo(
+ new File(supportToolsFolder, FN_ANNOTATIONS_JAR),
+ new File(platformTools, FN_ADB));
+ }
+ }
+
+ @Nullable
+ private FullRevision getPlatformToolsRevision(@NonNull File platformToolsFolder) {
+ if (!platformToolsFolder.isDirectory()) {
+ return null;
+ }
+
+ Reader reader = null;
+ try {
+ reader = new InputStreamReader(
+ new FileInputStream(new File(platformToolsFolder, 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 {
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public ImmutableList<File> computeRepositories() {
+ List<File> repositories = Lists.newArrayListWithExpectedSize(2);
+
+ File androidRepo = new File(mSdkLocation, FD_EXTRAS + File.separator + "android"
+ + File.separator + FD_M2_REPOSITORY);
+ if (androidRepo.isDirectory()) {
+ repositories.add(androidRepo);
+ }
+
+ File googleRepo = new File(mSdkLocation, FD_EXTRAS + File.separator + "google"
+ + File.separator + FD_M2_REPOSITORY);
+ if (googleRepo.isDirectory()) {
+ repositories.add(googleRepo);
+ }
+
+ return ImmutableList.copyOf(repositories);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/sdk/PlatformLoader.java b/build-system/builder/src/main/java/com/android/builder/sdk/PlatformLoader.java
new file mode 100644
index 0000000..c484d55
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/sdk/PlatformLoader.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sdk;
+
+import static com.android.SdkConstants.FN_AAPT;
+import static com.android.SdkConstants.FN_AIDL;
+import static com.android.SdkConstants.FN_BCC_COMPAT;
+import static com.android.SdkConstants.FN_RENDERSCRIPT;
+import static com.android.SdkConstants.FN_ZIPALIGN;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+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.ImmutableList;
+
+import java.io.File;
+
+/**
+ * Singleton-based implementation of SdkLoader for a platform-based SDK.
+ *
+ * Platform-based SDK are in the Android source tree in AOSP, using a different
+ * folder layout for all the files.
+ */
+public class PlatformLoader implements SdkLoader {
+
+ private static PlatformLoader sLoader;
+
+ @NonNull
+ private final File mTreeLocation;
+
+ private File mHostToolsFolder;
+ private SdkInfo mSdkInfo;
+ @NonNull
+ private final ImmutableList<File> mRepositories;
+
+ public static synchronized SdkLoader getLoader(
+ @NonNull File treeLocation) {
+ if (sLoader != null && !treeLocation.equals(sLoader.mTreeLocation)) {
+ throw new IllegalStateException("Already created an SDK Loader with different SDK Path");
+ }
+
+ return sLoader = new PlatformLoader(treeLocation);
+ }
+
+ public static synchronized void unload() {
+ sLoader = null;
+ }
+
+ @NonNull
+ @Override
+ public TargetInfo getTargetInfo(@NonNull String targetHash,
+ @NonNull FullRevision buildToolRevision, @NonNull ILogger logger) {
+ init(logger);
+
+ IAndroidTarget androidTarget = new FakeAndroidTarget(mTreeLocation.getPath(), targetHash);
+
+ File hostTools = getHostToolsFolder();
+
+ BuildToolInfo buildToolInfo = new BuildToolInfo(
+ buildToolRevision,
+ mTreeLocation,
+ new File(hostTools, FN_AAPT),
+ new File(hostTools, FN_AIDL),
+ new File(mTreeLocation, "prebuilts/sdk/tools/dx"),
+ new File(mTreeLocation, "prebuilts/sdk/tools/lib/dx.jar"),
+ new File(hostTools, FN_RENDERSCRIPT),
+ new File(mTreeLocation, "prebuilts/sdk/renderscript/include"),
+ new File(mTreeLocation, "prebuilts/sdk/renderscript/clang-include"),
+ new File(hostTools, FN_BCC_COMPAT),
+ new File(hostTools, "arm-linux-androideabi-ld"),
+ new File(hostTools, "i686-linux-android-ld"),
+ new File(hostTools, "mipsel-linux-android-ld"),
+ new File(hostTools, FN_ZIPALIGN));
+
+ return new TargetInfo(androidTarget, buildToolInfo);
+ }
+
+ @NonNull
+ @Override
+ public SdkInfo getSdkInfo(@NonNull ILogger logger) {
+ init(logger);
+ return mSdkInfo;
+ }
+
+ @Override
+ @NonNull
+ public ImmutableList<File> getRepositories() {
+ return mRepositories;
+ }
+
+ private PlatformLoader(@NonNull File treeLocation) {
+ mTreeLocation = treeLocation;
+ mRepositories = ImmutableList.of(new File(mTreeLocation + "/prebuilts/sdk/m2repository"));
+ }
+
+ private synchronized void init(@NonNull ILogger logger) {
+ if (mSdkInfo == null) {
+ 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");
+ }
+
+ mSdkInfo = new SdkInfo(
+ new File(mTreeLocation, "out/host/" + host + "/framework/annotations.jar"),
+ new File(mTreeLocation, "out/host/" + host + "/bin/adb"));
+ }
+ }
+
+ @NonNull
+ private synchronized File getHostToolsFolder() {
+ if (mHostToolsFolder == null) {
+ File tools = new File(mTreeLocation, "prebuilts/sdk/tools");
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ mHostToolsFolder = new File(tools, "darwin");
+ } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
+ mHostToolsFolder = new File(tools, "linux");
+ } else {
+ throw new IllegalStateException(
+ "Windows is not supported for platform development");
+ }
+
+ if (!mHostToolsFolder.isDirectory()) {
+ throw new IllegalStateException("Host tools folder missing: " +
+ mHostToolsFolder.getAbsolutePath());
+ }
+ }
+
+ return mHostToolsFolder;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/sdk/SdkInfo.java b/build-system/builder/src/main/java/com/android/builder/sdk/SdkInfo.java
new file mode 100644
index 0000000..e169fc1
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/sdk/SdkInfo.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sdk;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+
+/**
+ * General information about the SDK.
+ */
+public class SdkInfo {
+
+ @NonNull
+ private final File mAnnotationJar;
+ @NonNull
+ private final File mAdb;
+
+ SdkInfo(@NonNull File annotationJar,
+ @NonNull File adb) {
+ mAnnotationJar = annotationJar;
+ mAdb = adb;
+ }
+
+ /**
+ * Returns the location of the annotations jar for compilation targets that are <= 15.
+ */
+ @NonNull
+ public File getAnnotationsJar() {
+ return mAnnotationJar;
+ }
+
+ /**
+ * Returns the revision of the installed platform tools component.
+ *
+ * @return the FullRevision or null if the revision couldn't not be found
+ */
+// @Nullable
+// public FullRevision getPlatformToolsRevision() {
+//
+// }
+
+ /**
+ * Returns the location of the adb tool.
+ */
+ @NonNull
+ public File getAdb() {
+ return mAdb;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/sdk/SdkLoader.java b/build-system/builder/src/main/java/com/android/builder/sdk/SdkLoader.java
new file mode 100644
index 0000000..0fadfc5
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/sdk/SdkLoader.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sdk;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.repository.FullRevision;
+import com.android.utils.ILogger;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+
+/**
+ * A loader for the SDK. It's able to provide general SDK information
+ * ({@link #getSdkInfo(com.android.utils.ILogger)}, or {@link #getRepositories()}), or
+ * target-specific information
+ * ({@link #getTargetInfo(String, com.android.sdklib.repository.FullRevision, com.android.utils.ILogger)}).
+ */
+public interface SdkLoader {
+
+ /**
+ * Returns information about a build target.
+ *
+ * This requires loading/parsing the SDK.
+ *
+ * @param targetHash the compilation target hash string.
+ * @param buildToolRevision the build tools revision.
+ * @param logger a logger to output messages.
+ * @return the target info.
+ */
+ @NonNull
+ TargetInfo getTargetInfo(
+ @NonNull String targetHash,
+ @NonNull FullRevision buildToolRevision,
+ @NonNull ILogger logger);
+
+ /**
+ * Returns generic SDK information.
+ *
+ * This requires loading/parsing the SDK.
+ *
+ * @param logger a logger to output messages.
+ * @return the sdk info.
+ */
+ @NonNull
+ SdkInfo getSdkInfo(@NonNull ILogger logger);
+
+ /**
+ * Returns the location of artifact repositories built-in the SDK.
+ * @return a non null list of repository folders.
+ */
+ @NonNull
+ ImmutableList<File> getRepositories();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/sdk/TargetInfo.java b/build-system/builder/src/main/java/com/android/builder/sdk/TargetInfo.java
new file mode 100644
index 0000000..e51731f
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/sdk/TargetInfo.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sdk;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+
+/**
+ * The SDK Target information needed to build a project.
+ */
+public class TargetInfo {
+
+ @NonNull
+ private final IAndroidTarget mTarget;
+ @NonNull
+ private final BuildToolInfo mBuildToolInfo;
+
+ TargetInfo(
+ @NonNull IAndroidTarget target,
+ @NonNull BuildToolInfo buildToolInfo) {
+
+ mTarget = target;
+ mBuildToolInfo = buildToolInfo;
+ }
+
+ /**
+ * Returns the compilation target
+ * @return the target.
+ */
+ @NonNull
+ public IAndroidTarget getTarget() {
+ return mTarget;
+ }
+
+ /**
+ * Returns the BuildToolInfo
+ * @return the build tool info
+ */
+ @NonNull
+ public BuildToolInfo getBuildTools() {
+ return mBuildToolInfo;
+ }
+}
\ 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
deleted file mode 100644
index 6c34fde..0000000
--- a/build-system/builder/src/main/java/com/android/builder/signing/CertificateInfo.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
index ff47542..11e4756 100644
--- 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
@@ -19,6 +19,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.model.SigningConfig;
+import com.android.ide.common.signing.KeystoreHelper;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.google.common.base.Objects;
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
deleted file mode 100644
index 957f8f6..0000000
--- a/build-system/builder/src/main/java/com/android/builder/signing/KeystoreHelper.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index 890096c..0000000
--- a/build-system/builder/src/main/java/com/android/builder/signing/KeytoolException.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
index c3e2d84..6c851b3 100644
--- 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
@@ -16,6 +16,7 @@
package com.android.builder.signing;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
@@ -373,7 +374,7 @@
MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM);
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
- true, "UTF-8");
+ true, SdkConstants.UTF_8);
// Digest of the entire manifest
mManifest.write(print);
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
index 7e6ae77..c0ed271 100644
--- 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
@@ -17,12 +17,14 @@
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.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.SyncException;
import com.android.ddmlib.TimeoutException;
import com.android.utils.ILogger;
import com.google.common.collect.Lists;
@@ -101,6 +103,20 @@
}
@Override
+ public void pullFile(String remote, String local) throws IOException {
+ try {
+ iDevice.pullFile(remote, local);
+
+ } catch (TimeoutException e) {
+ throw new IOException(String.format("Failed to pull %s from device", remote), e);
+ } catch (AdbCommandRejectedException e) {
+ throw new IOException(String.format("Failed to pull %s from device", remote), e);
+ } catch (SyncException e) {
+ throw new IOException(String.format("Failed to pull %s from device", remote), e);
+ }
+ }
+
+ @Override
public int getApiLevel() {
String sdkVersion = iDevice.getProperty(IDevice.PROP_BUILD_API_LEVEL);
if (sdkVersion != null) {
@@ -115,6 +131,29 @@
return 0;
}
+ @Override
+ public String getApiCodeName() {
+ String codeName = iDevice.getProperty(IDevice.PROP_BUILD_CODENAME);
+ if (codeName != null) {
+ // if this is a release platform return null.
+ if ("REL".equals(codeName)) {
+ return null;
+ }
+
+ // else return the codename
+ return codeName;
+ }
+
+ // can't get it, return 0.
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public IDevice.DeviceState getState() {
+ return iDevice.getState();
+ }
+
@NonNull
@Override
public List<String> getAbis() {
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
index ae81aa2..c23ad2a 100644
--- 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
@@ -17,7 +17,6 @@
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;
@@ -25,6 +24,7 @@
import com.android.ddmlib.IDevice;
import com.google.common.collect.Lists;
+import java.io.File;
import java.util.List;
/**
@@ -35,13 +35,13 @@
@NonNull
- private final SdkParser sdkParser;
+ private final File adbLocation;
@NonNull
private final List<ConnectedDevice> localDevices = Lists.newArrayList();
- public ConnectedDeviceProvider(@NonNull SdkParser sdkParser) {
- this.sdkParser = sdkParser;
+ public ConnectedDeviceProvider(@NonNull File adbLocation) {
+ this.adbLocation = adbLocation;
}
@Override
@@ -62,7 +62,7 @@
AndroidDebugBridge.initIfNeeded(false /*clientSupport*/);
AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
- sdkParser.getAdb().getAbsolutePath(), false /*forceNewBridge*/);
+ adbLocation.getAbsolutePath(), false /*forceNewBridge*/);
long timeOut = 30000; // 30 sec
int sleepTime = 1000;
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
index 237d871..e16f3ec 100644
--- 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
@@ -19,9 +19,12 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.internal.testing.SimpleTestCallable;
+import com.android.builder.model.ApiVersion;
import com.android.builder.testing.api.DeviceConnector;
import com.android.builder.testing.api.TestException;
+import com.android.ddmlib.IDevice;
import com.android.ide.common.internal.WaitableExecutor;
+import com.android.ide.common.sdk.SdkVersionInfo;
import com.android.utils.ILogger;
import java.io.File;
@@ -44,18 +47,26 @@
int maxThreads,
int timeout,
@NonNull File resultsDir,
- @NonNull ILogger logger) throws TestException, InterruptedException {
+ @NonNull File coverageDir,
+ @NonNull ILogger logger) throws TestException, NoAuthorizedDeviceFoundException, InterruptedException {
WaitableExecutor<Boolean> executor = new WaitableExecutor<Boolean>(maxThreads);
+ boolean foundAtLeastOneAuthorizedDevice = false;
for (DeviceConnector device : deviceList) {
- if (filterOutDevice(device, testData, logger, projectName, variantName)) {
- executor.execute(new SimpleTestCallable(device, projectName, variantName,
- testApk, testedApk, testData,
- resultsDir, timeout, logger));
+ if (device.getState() != IDevice.DeviceState.UNAUTHORIZED) {
+ foundAtLeastOneAuthorizedDevice = true;
+ if (filterOutDevice(device, testData, logger, projectName, variantName)) {
+ executor.execute(new SimpleTestCallable(device, projectName, variantName,
+ testApk, testedApk, testData,
+ resultsDir, coverageDir, timeout, logger));
+ }
}
}
+ if (!foundAtLeastOneAuthorizedDevice) {
+ throw new NoAuthorizedDeviceFoundException();
+ }
List<WaitableExecutor.TaskResult<Boolean>> results = executor.waitForAllTasks();
boolean success = true;
@@ -79,22 +90,45 @@
@NonNull String projectName, @NonNull String variantName) {
int deviceApiLevel = device.getApiLevel();
if (deviceApiLevel == 0) {
- logger.info("Skipping device '%s' for '%s:%s': Unknown API Level",
+ logger.info("Skipping device '%1$s' for '%2$s:%3$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);
+ ApiVersion apiVersion = testData.getMinSdkVersion();
+ int minSdkVersion = apiVersion == null ? 1 : apiVersion.getApiLevel();
+ if (apiVersion != null && apiVersion.getCodename() != null) {
+ String deviceCodeName = device.getApiCodeName();
+ if (deviceCodeName != null) {
+ if (deviceCodeName.equals(apiVersion.getCodename())) {
+ logger.info("Skipping device '%1$s', due to different API preview '%2$s' and '%3$s'",
+ device.getName(), deviceCodeName, apiVersion.getCodename());
+ return false;
+ }
+ } else {
+ minSdkVersion = SdkVersionInfo.getApiByBuildCode(apiVersion.getCodename(), true);
- return false;
+ if (minSdkVersion > deviceApiLevel) {
+ logger.info("Skipping device '%s' for '%s:%s'",
+ device.getName(), projectName, variantName);
+
+ return false;
+ }
+ }
+
+ } else {
+ if (minSdkVersion > deviceApiLevel) {
+ logger.info("Skipping device '%s' for '%s:%s'",
+ device.getName(), projectName, variantName);
+
+ return false;
+ }
}
Set<String> appAbis = testData.getSupportedAbis();
- if (appAbis != null) {
+ if (appAbis != null && !appAbis.isEmpty()) {
List<String> deviceAbis = device.getAbis();
- if (deviceAbis == null || deviceAbis.isEmpty()) {
+ if (deviceAbis.isEmpty()) {
logger.info("Skipping device '%s' for '%s:%s': Unknown ABI",
device.getName(), projectName, variantName);
return false;
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
index a736026..5f3311f 100644
--- 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
@@ -18,6 +18,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.builder.model.ApiVersion;
import java.util.Set;
@@ -26,20 +27,20 @@
public interface TestData {
/**
- * Returns the package name.
+ * Returns the application id.
*
- * @return the package name
+ * @return the id
*/
@NonNull
- String getPackageName();
+ String getApplicationId();
/**
- * Returns the tested package name. This can be empty if the test package is self-contained.
+ * Returns the tested application id. This can be empty if the test package is self-contained.
*
- * @return the package name or null.
+ * @return the id or null.
*/
@Nullable
- String getTestedPackageName();
+ String getTestedApplicationId();
@NonNull
String getInstrumentationRunner();
@@ -50,7 +51,15 @@
@NonNull
Boolean getFunctionalTest();
- int getMinSdkVersion();
+ /**
+ * Returns whether the tested app is enabled for code coverage
+ */
+ boolean isTestCoverageEnabled();
+
+ /**
+ * The min SDK version of the app
+ */
+ ApiVersion getMinSdkVersion();
/**
* List of supported ABIs. Null means all.
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
index e0dc29c..b686838 100644
--- 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
@@ -44,6 +44,7 @@
* @param maxThreads the max number of threads to run in parallel. 0 means unlimited.
* @param timeout
* @param resultsDir
+ * @param coverageDir
* @param logger
* @return true if the test succeed
*
@@ -60,5 +61,14 @@
int maxThreads,
int timeout,
@NonNull File resultsDir,
- @NonNull ILogger logger) throws TestException, InterruptedException;
+ @NonNull File coverageDir,
+ @NonNull ILogger logger)
+ throws TestException, NoAuthorizedDeviceFoundException, InterruptedException;
+
+ public class NoAuthorizedDeviceFoundException extends Exception {
+
+ public NoAuthorizedDeviceFoundException() {
+ super("No suitable device connected");
+ }
+ }
}
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
deleted file mode 100644
index f618085..0000000
--- a/build-system/builder/src/test/java/com/android/builder/DefaultProductFlavorTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index bb5fe39..0000000
--- a/build-system/builder/src/test/java/com/android/builder/MockSourceProvider.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100644
index e1ffc3d..0000000
--- a/build-system/builder/src/test/java/com/android/builder/VariantConfigurationTest.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
index 0bb2200..de394dd 100644
--- 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
@@ -16,7 +16,7 @@
package com.android.builder.compiling;
-import com.android.builder.AndroidBuilder;
+import com.android.builder.core.AndroidBuilder;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
@@ -29,8 +29,7 @@
public class BuildConfigGeneratorTest extends TestCase {
public void testFalse() throws Exception {
File tempDir = Files.createTempDir();
- BuildConfigGenerator generator = new BuildConfigGenerator(tempDir.getPath(),
- "my.app.pkg");
+ BuildConfigGenerator generator = new BuildConfigGenerator(tempDir, "my.app.pkg");
generator.addField("boolean", "DEBUG", "false").generate();
@@ -52,8 +51,7 @@
public void testTrue() throws Exception {
File tempDir = Files.createTempDir();
- BuildConfigGenerator generator = new BuildConfigGenerator(tempDir.getPath(),
- "my.app.pkg");
+ BuildConfigGenerator generator = new BuildConfigGenerator(tempDir, "my.app.pkg");
generator.addField("boolean", "DEBUG", "Boolean.parseBoolean(\"true\")").generate();
File file = generator.getBuildConfigFile();
@@ -74,8 +72,7 @@
public void testExtra() throws Exception {
File tempDir = Files.createTempDir();
- BuildConfigGenerator generator = new BuildConfigGenerator(tempDir.getPath(),
- "my.app.pkg");
+ BuildConfigGenerator generator = new BuildConfigGenerator(tempDir, "my.app.pkg");
List<Object> items = Lists.newArrayList();
items.add("Extra line");
diff --git a/build-system/builder/src/test/java/com/android/builder/core/DefaultProductFlavorTest.java b/build-system/builder/src/test/java/com/android/builder/core/DefaultProductFlavorTest.java
new file mode 100644
index 0000000..ca50fa7
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/core/DefaultProductFlavorTest.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.core;
+
+import com.android.builder.model.ProductFlavor;
+import com.google.common.collect.ImmutableMap;
+
+import junit.framework.TestCase;
+
+import java.util.Collection;
+import java.util.Map;
+
+public class DefaultProductFlavorTest extends TestCase {
+
+ private DefaultProductFlavor mDefault;
+ private DefaultProductFlavor mDefault2;
+ private DefaultProductFlavor mCustom;
+ private DefaultProductFlavor mCustom2;
+
+ @Override
+ protected void setUp() throws Exception {
+ mDefault = new DefaultProductFlavor("default");
+ mDefault2 = new DefaultProductFlavor("default2");
+
+ mCustom = new DefaultProductFlavor("custom");
+ mCustom.setMinSdkVersion(new DefaultApiVersion(42));
+ mCustom.setTargetSdkVersion(new DefaultApiVersion(43));
+ mCustom.setRenderscriptTargetApi(17);
+ mCustom.setVersionCode(44);
+ mCustom.setVersionName("42.0");
+ mCustom.setApplicationId("com.forty.two");
+ mCustom.setTestApplicationId("com.forty.two.test");
+ mCustom.setTestInstrumentationRunner("com.forty.two.test.Runner");
+ mCustom.setTestHandleProfiling(true);
+ mCustom.setTestFunctionalTest(true);
+ mCustom.addResourceConfiguration("hdpi");
+ mCustom.addManifestPlaceHolders(ImmutableMap.of("one", "oneValue", "two", "twoValue"));
+
+ mCustom2 = new DefaultProductFlavor("custom2");
+ mCustom2.addResourceConfigurations("ldpi", "hdpi");
+ mCustom2.addManifestPlaceHolders(
+ ImmutableMap.of("two","twoValueBis", "three", "threeValue"));
+ }
+
+ public void testMergeOnDefault() {
+ ProductFlavor flavor = mCustom.mergeOver(mDefault);
+
+ assertNotNull(flavor.getMinSdkVersion());
+ assertEquals(42, flavor.getMinSdkVersion().getApiLevel());
+ assertNotNull(flavor.getTargetSdkVersion());
+ assertEquals(43, flavor.getTargetSdkVersion().getApiLevel());
+ assertEquals(17, flavor.getRenderscriptTargetApi());
+ assertEquals(44, flavor.getVersionCode());
+ assertEquals("42.0", flavor.getVersionName());
+ assertEquals("com.forty.two", flavor.getApplicationId());
+ assertEquals("com.forty.two.test", flavor.getTestApplicationId());
+ 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);
+
+ assertNotNull(flavor.getMinSdkVersion());
+ assertEquals(42, flavor.getMinSdkVersion().getApiLevel());
+ assertNotNull(flavor.getTargetSdkVersion());
+ assertEquals(43, flavor.getTargetSdkVersion().getApiLevel());
+ assertEquals(17, flavor.getRenderscriptTargetApi());
+ assertEquals(44, flavor.getVersionCode());
+ assertEquals("42.0", flavor.getVersionName());
+ assertEquals("com.forty.two", flavor.getApplicationId());
+ assertEquals("com.forty.two.test", flavor.getTestApplicationId());
+ 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);
+
+ assertNull(flavor.getMinSdkVersion());
+ assertNull(flavor.getTargetSdkVersion());
+ assertEquals(-1, flavor.getRenderscriptTargetApi());
+ assertEquals(-1, flavor.getVersionCode());
+ assertNull(flavor.getVersionName());
+ assertNull(flavor.getApplicationId());
+ assertNull(flavor.getTestApplicationId());
+ assertNull(flavor.getTestInstrumentationRunner());
+ assertNull(flavor.getTestHandleProfiling());
+ assertNull(flavor.getTestFunctionalTest());
+ }
+
+ public void testResourceConfigMerge() {
+ ProductFlavor productflavor = mCustom.mergeOver(mCustom2);
+
+ Collection<String> configs = productflavor.getResourceConfigurations();
+ assertEquals(2, configs.size());
+ assertTrue(configs.contains("hdpi"));
+ assertTrue(configs.contains("ldpi"));
+ }
+
+ public void testManifestPlaceholdersMerge() {
+ ProductFlavor productFlavor = mCustom.mergeOver(mCustom2);
+
+ Map<String, String> manifestPlaceholders = productFlavor.getManifestPlaceholders();
+ assertEquals(3, manifestPlaceholders.size());
+ assertEquals("oneValue", manifestPlaceholders.get("one"));
+ assertEquals("twoValue", manifestPlaceholders.get("two"));
+ assertEquals("threeValue", manifestPlaceholders.get("three"));
+
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/core/MockSourceProvider.java b/build-system/builder/src/test/java/com/android/builder/core/MockSourceProvider.java
new file mode 100644
index 0000000..ca2e731
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/core/MockSourceProvider.java
@@ -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.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.SourceProvider;
+
+import java.io.File;
+import java.util.Collection;
+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 String getName() {
+ return 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"));
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getJniLibsDirectories() {
+ return Collections.singleton(new File(mRoot, "jniLibs"));
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/core/VariantConfigurationTest.java b/build-system/builder/src/test/java/com/android/builder/core/VariantConfigurationTest.java
new file mode 100644
index 0000000..1835c9a
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/core/VariantConfigurationTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.signing.DefaultSigningConfig;
+
+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 Object getMinSdkVersion(@NonNull File manifestFile) {
+ return null;
+ }
+
+ @Override
+ public Object getTargetSdkVersion(@NonNull File manifestFile) {
+ return null;
+ }
+
+ @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.getIdOverride());
+ }
+
+ public void testPackageOverridePackageFromFlavor() {
+ mFlavorConfig.setApplicationId("foo.bar");
+
+ VariantConfiguration variant = getVariant();
+
+ assertEquals("foo.bar", variant.getIdOverride());
+ }
+
+ public void testIdOverrideIdFromFlavor() {
+ mFlavorConfig.setApplicationId("foo.bar");
+
+ VariantConfiguration variant = getVariant();
+
+ assertEquals("foo.bar", variant.getIdOverride());
+ }
+
+ public void testPackageOverridePackageFromFlavorWithSuffix() {
+ mFlavorConfig.setApplicationId("foo.bar");
+ mBuildType.setApplicationIdSuffix(".fortytwo");
+
+ VariantConfiguration variant = getVariant();
+
+ assertEquals("foo.bar.fortytwo", variant.getIdOverride());
+ }
+
+ public void testPackageOverridePackageFromFlavorWithSuffix2() {
+ mFlavorConfig.setApplicationId("foo.bar");
+ mBuildType.setApplicationIdSuffix("fortytwo");
+
+ VariantConfiguration variant = getVariant();
+
+ assertEquals("foo.bar.fortytwo", variant.getIdOverride());
+ }
+
+ public void testPackageOverridePackageWithSuffixOnly() {
+
+ mBuildType.setApplicationIdSuffix("fortytwo");
+
+ VariantConfiguration variant = getVariantWithManifestPackage("fake.package.name");
+
+ assertEquals("fake.package.name.fortytwo", variant.getIdOverride());
+ }
+
+ 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());
+ }
+
+ public void testSigningBuildTypeOverride() {
+ // DefaultSigningConfig doesn't compare the name, so put some content.
+ DefaultSigningConfig debugSigning = new DefaultSigningConfig("debug");
+ debugSigning.setStorePassword("debug");
+ mBuildType.setSigningConfig(debugSigning);
+
+ DefaultSigningConfig override = new DefaultSigningConfig("override");
+ override.setStorePassword("override");
+
+ VariantConfiguration variant = getVariant(override);
+
+ assertEquals(override, variant.getSigningConfig());
+ }
+
+ public void testSigningProductFlavorOverride() {
+ // DefaultSigningConfig doesn't compare the name, so put some content.
+ DefaultSigningConfig defaultConfig = new DefaultSigningConfig("defaultConfig");
+ defaultConfig.setStorePassword("debug");
+ mDefaultConfig.setSigningConfig(defaultConfig);
+
+ DefaultSigningConfig override = new DefaultSigningConfig("override");
+ override.setStorePassword("override");
+
+ VariantConfiguration variant = getVariant(override);
+
+ assertEquals(override, variant.getSigningConfig());
+ }
+
+ private VariantConfiguration getVariant() {
+ return getVariant(null /*signingOverride*/);
+ }
+
+ private VariantConfiguration getVariant(SigningConfig signingOverride) {
+ VariantConfiguration variant = new VariantConfiguration(
+ mDefaultConfig, new MockSourceProvider("main"),
+ mBuildType, new MockSourceProvider("debug"),
+ VariantConfiguration.Type.DEFAULT,
+ signingOverride);
+
+ 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,
+ null /*signingConfigOverride*/) {
+ @Override
+ public String getPackageFromManifest() {
+ return packageName;
+ }
+ };
+
+ 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,
+ null /*signingConfigOverride*/) {
+ @Override
+ public String getVersionNameFromManifest() {
+ return versionName;
+ }
+ // don't do validation.
+ };
+
+ variant.addProductFlavor(mFlavorConfig, new MockSourceProvider("custom"), "");
+ return variant;
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/compiler/PreDexCacheTest.java b/build-system/builder/src/test/java/com/android/builder/internal/compiler/PreDexCacheTest.java
new file mode 100644
index 0000000..107db51
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/compiler/PreDexCacheTest.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.SdkConstants.FN_AAPT;
+import static com.android.SdkConstants.FN_AIDL;
+import static com.android.SdkConstants.FN_BCC_COMPAT;
+import static com.android.SdkConstants.FN_DX;
+import static com.android.SdkConstants.FN_DX_JAR;
+import static com.android.SdkConstants.FN_RENDERSCRIPT;
+import static com.android.SdkConstants.FN_ZIPALIGN;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.core.DexOptions;
+import com.android.ide.common.internal.CommandLineRunner;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.repository.FullRevision;
+import com.android.utils.ILogger;
+import com.android.utils.StdLogger;
+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 junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class PreDexCacheTest extends TestCase {
+
+ private static final String DEX_DATA = "**";
+
+ /**
+ * Override the command line runner to intercept the call to dex and replace it
+ * with something else.
+ */
+ private static class FakeCommandLineRunner extends CommandLineRunner {
+
+ public FakeCommandLineRunner(ILogger logger) {
+ super(logger);
+ }
+
+ @Override
+ public void runCmdLine(@NonNull String[] command,
+ @Nullable Map<String, String> envVariableMap)
+ throws IOException, InterruptedException, LoggedErrorException {
+ // small delay to test multi-threading.
+ Thread.sleep(1000);
+
+ // input file is the last file in the command
+ File input = new File(command[command.length - 1]);
+ if (!input.isFile()) {
+ throw new FileNotFoundException(input.getPath());
+ }
+
+ // loop on the command to find --output
+ String output = null;
+ for (int i = 0 ; i < command.length ; i++) {
+ if ("--output".equals(command[i])) {
+ output = command[i+1];
+ break;
+ }
+ }
+
+ if (output == null) {
+ throw new IOException("Failed to find output in dex commands");
+ }
+
+ // read the source content
+ List<String> lines = Files.readLines(input, Charsets.UTF_8);
+
+ // modify the lines
+ List<String> dexedLines = Lists.newArrayListWithCapacity(lines.size());
+ for (String line : lines) {
+ dexedLines.add(DEX_DATA + line + DEX_DATA);
+ }
+
+ // combine the lines
+ String content = Joiner.on('\n').join(dexedLines);
+
+ // write it
+ Files.write(content, new File(output), Charsets.UTF_8);
+ }
+ }
+
+ /**
+ * Override the command line runner to simulate error during the dexing
+ */
+ private static class FakeCommandLineRunner2 extends CommandLineRunner {
+
+ public FakeCommandLineRunner2(ILogger logger) {
+ super(logger);
+ }
+
+ @Override
+ public void runCmdLine(@NonNull String[] command,
+ @Nullable Map<String, String> envVariableMap)
+ throws IOException, InterruptedException, LoggedErrorException {
+ Thread.sleep(1000);
+ throw new IOException("foo");
+ }
+ }
+
+ private static class FakeDexOptions implements DexOptions {
+
+ @Override
+ public boolean getIncremental() {
+ return false;
+ }
+
+ @Override
+ public boolean getPreDexLibraries() {
+ return false;
+ }
+
+ @Override
+ public boolean getJumboMode() {
+ return false;
+ }
+
+ @Override
+ public String getJavaMaxHeapSize() {
+ return null;
+ }
+
+ @Override
+ public int getThreadCount() {
+ return 1;
+ }
+ }
+
+ private BuildToolInfo mBuildToolInfo;
+
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mBuildToolInfo = getBuildToolInfo();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ File toolFolder = mBuildToolInfo.getLocation();
+ deleteFolder(toolFolder);
+
+ PreDexCache.getCache().clear(null, null);
+
+ super.tearDown();
+ }
+
+ public void testSinglePreDexLibrary() throws IOException, LoggedErrorException, InterruptedException {
+ String content = "Some Content";
+ File input = createInputFile(content);
+
+ File output = File.createTempFile("predex", ".jar");
+ output.deleteOnExit();
+
+ PreDexCache.getCache().preDexLibrary(
+ input, output,
+ new FakeDexOptions(), mBuildToolInfo,
+ false /*verbose*/, new FakeCommandLineRunner(new StdLogger(StdLogger.Level.INFO)));
+
+ checkOutputFile(content, output);
+ }
+
+ public void testThreadedPreDexLibrary() throws IOException, InterruptedException {
+ String content = "Some Content";
+ final File input = createInputFile(content);
+ input.deleteOnExit();
+
+ Thread[] threads = new Thread[3];
+ final File[] outputFiles = new File[threads.length];
+
+ final CommandLineRunner clr = new FakeCommandLineRunner(new StdLogger(StdLogger.Level.INFO));
+ final DexOptions dexOptions = new FakeDexOptions();
+
+ for (int i = 0 ; i < threads.length ; i++) {
+ final int ii = i;
+ threads[i] = new Thread() {
+ @Override
+ public void run() {
+ try {
+ File output = File.createTempFile("predex", ".jar");
+ output.deleteOnExit();
+ outputFiles[ii] = output;
+
+ PreDexCache.getCache().preDexLibrary(
+ input, output,
+ dexOptions, mBuildToolInfo, false /*verbose*/, clr);
+ } catch (Exception ignored) {
+
+ }
+ }
+ };
+
+ threads[i].start();
+ }
+
+ // wait on the threads.
+ for (Thread thread : threads) {
+ thread.join();
+ }
+
+ // check the output.
+ for (File outputFile : outputFiles) {
+ checkOutputFile(content, outputFile);
+ }
+
+ // now check the cache
+ PreDexCache cache = PreDexCache.getCache();
+ assertEquals(1, cache.getMisses());
+ assertEquals(threads.length - 1, cache.getHits());
+ }
+
+ public void testThreadedPreDexLibraryWithError() throws IOException, InterruptedException {
+ String content = "Some Content";
+ final File input = createInputFile(content);
+ input.deleteOnExit();
+
+ Thread[] threads = new Thread[3];
+ final File[] outputFiles = new File[threads.length];
+
+ final CommandLineRunner clr = new FakeCommandLineRunner(new StdLogger(StdLogger.Level.INFO));
+ final CommandLineRunner clrWithError = new FakeCommandLineRunner2(new StdLogger(StdLogger.Level.INFO));
+ final DexOptions dexOptions = new FakeDexOptions();
+
+ final AtomicInteger threadDoneCount = new AtomicInteger();
+
+ for (int i = 0 ; i < threads.length ; i++) {
+ final int ii = i;
+ threads[i] = new Thread() {
+ @Override
+ public void run() {
+ try {
+ File output = File.createTempFile("predex", ".jar");
+ output.deleteOnExit();
+ outputFiles[ii] = output;
+
+ PreDexCache.getCache().preDexLibrary(
+ input, output,
+ dexOptions, mBuildToolInfo, false /*verbose*/,
+ ii == 0 ? clrWithError : clr);
+ } catch (Exception ignored) {
+
+ }
+ threadDoneCount.incrementAndGet();
+ }
+ };
+
+ threads[i].start();
+ }
+
+ // wait on the threads, long enough but stop after a while
+ for (Thread thread : threads) {
+ thread.join(5000);
+ }
+
+ // if the test fail, we'll have two threads still blocked on the countdownlatch.
+ assertEquals(3, threadDoneCount.get());
+ }
+
+
+ public void testReload() throws IOException, LoggedErrorException, InterruptedException {
+ final CommandLineRunner clr = new FakeCommandLineRunner(new StdLogger(StdLogger.Level.INFO));
+ final DexOptions dexOptions = new FakeDexOptions();
+
+ // convert one file.
+ String content = "Some Content";
+ File input = createInputFile(content);
+
+ File output = File.createTempFile("predex", ".jar");
+ output.deleteOnExit();
+
+ PreDexCache.getCache().preDexLibrary(
+ input, output,
+ dexOptions, mBuildToolInfo, false /*verbose*/, clr);
+
+ checkOutputFile(content, output);
+
+ // store the cache
+ File cacheXml = File.createTempFile("predex", ".xml");
+ cacheXml.deleteOnExit();
+ PreDexCache.getCache().clear(cacheXml, null);
+
+ // reload.
+ PreDexCache.getCache().load(cacheXml);
+
+ // re-pre-dex into another file.
+ File output2 = File.createTempFile("predex", ".jar");
+ output2.deleteOnExit();
+
+ PreDexCache.getCache().preDexLibrary(
+ input, output2,
+ dexOptions, mBuildToolInfo, false /*verbose*/, clr);
+
+ // check the output
+ checkOutputFile(content, output2);
+
+ // check the hit/miss
+ PreDexCache cache = PreDexCache.getCache();
+ assertEquals(0, cache.getMisses());
+ assertEquals(1, cache.getHits());
+ }
+
+ private static File createInputFile(String content) throws IOException {
+ File input = File.createTempFile("predex", ".jar");
+ input.deleteOnExit();
+
+ Files.write(content, input, Charsets.UTF_8);
+ return input;
+ }
+
+ private static void checkOutputFile(String content, File output) throws IOException {
+ List<String> lines = Files.readLines(output, Charsets.UTF_8);
+
+ assertEquals(1, lines.size());
+ assertEquals(DEX_DATA + content + DEX_DATA, lines.get(0));
+ }
+
+ /**
+ * Create a fake build tool info where the dx tool actually exists (even if it's not used).
+ */
+ private static BuildToolInfo getBuildToolInfo() throws IOException {
+ File toolDir = Files.createTempDir();
+
+ // create a dx file.
+ File dx = new File(toolDir, FN_DX);
+ Files.write("dx!", dx, Charsets.UTF_8);
+
+ return new BuildToolInfo(
+ new FullRevision(1),
+ toolDir,
+ new File(toolDir, FN_AAPT),
+ new File(toolDir, FN_AIDL),
+ dx,
+ new File(toolDir, FN_DX_JAR),
+ new File(toolDir, FN_RENDERSCRIPT),
+ new File(toolDir, "include"),
+ new File(toolDir, "clang-include"),
+ new File(toolDir, FN_BCC_COMPAT),
+ new File(toolDir, "arm-linux-androideabi-ld"),
+ new File(toolDir, "i686-linux-android-ld"),
+ new File(toolDir, "mipsel-linux-android-ld"),
+ new File(toolDir, FN_ZIPALIGN));
+ }
+
+ private static void deleteFolder(File folder) {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteFolder(file);
+ } else {
+ file.delete();
+ }
+ }
+ }
+
+ folder.delete();
+ }
+}
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
index cba11f1..2506ea4 100644
--- 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
@@ -67,6 +67,24 @@
assertEquals("/path/to/main input.bar", data.getMainFile());
}
+ public void testSecondaryFiles() throws Exception {
+ DependencyData data = getData("secondary_files.d");
+
+ assertEquals("/path/to/project/src/com/example/IService.aidl", data.getMainFile());
+
+ List<String> secondaryFiles = data.getSecondaryFiles();
+ assertEquals(4, secondaryFiles.size());
+ assertEquals("/path/to/project/src/com/example/ISecondaryFile1.aidl", secondaryFiles.get(0));
+ assertEquals("/path/to/project/src/com/example/ISecondaryFile2.aidl", secondaryFiles.get(1));
+ assertEquals("/path/to/project/src/com/example/ISecondaryFile3.aidl", secondaryFiles.get(2));
+ assertEquals("/path/to/project/src/com/example/ISecondaryFile4.aidl", secondaryFiles.get(3));
+
+ List<String> outputs = data.getOutputFiles();
+ assertEquals(1, outputs.size());
+
+ assertEquals("/path/to/project/build/source/aidl/debug/com/example/IService.java", outputs.get(0));
+ }
+
private DependencyData getData(String name) throws IOException {
File depFile = new File(TestUtils.getRoot("dependencyData"), name);
DependencyData data = DependencyData.parseDependencyFile(depFile);
diff --git a/build-system/builder/src/test/java/com/android/builder/png/BasePngTest.java b/build-system/builder/src/test/java/com/android/builder/png/BasePngTest.java
new file mode 100644
index 0000000..968a410
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/png/BasePngTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import com.android.annotations.NonNull;
+import com.android.testutils.TestUtils;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import junit.framework.TestCase;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+
+/**
+ */
+public abstract class BasePngTest extends TestCase {
+
+ @NonNull
+ protected static File crunch(@NonNull File file) throws IOException, NinePatchException {
+ File outFile = File.createTempFile("pngWriterTest", ".png");
+ outFile.deleteOnExit();
+
+ PngProcessor.process(file, outFile);
+ return outFile;
+ }
+
+ protected static void compareImageContent(@NonNull File originalFile, @NonNull File createdFile,
+ boolean is9Patch)
+ throws IOException {
+ BufferedImage originalImage = ImageIO.read(originalFile);
+ BufferedImage createdImage = ImageIO.read(createdFile);
+
+ int originalWidth = originalImage.getWidth();
+ int originalHeight = originalImage.getHeight();
+
+ int createdWidth = createdImage.getWidth();
+ int createdHeight = createdImage.getHeight();
+
+ // compare sizes taking into account if the image is a 9-patch
+ // in which case the original is bigger by 2 since it has the patch area still.
+ assertEquals(originalWidth, createdWidth + (is9Patch ? 2 : 0));
+ assertEquals(originalHeight, createdHeight + (is9Patch ? 2 : 0));
+
+ // get the file content
+ // always use the created Size. And for the original image, if 9-patch, just take
+ // the image minus the 1-pixel border all around.
+ int[] originalContent = new int[createdWidth * createdHeight];
+ if (is9Patch) {
+ originalImage.getRGB(1, 1, createdWidth, createdHeight, originalContent, 0, createdWidth);
+ } else {
+ originalImage.getRGB(0, 0, createdWidth, createdHeight, originalContent, 0, createdWidth);
+ }
+
+ int[] createdContent = new int[createdWidth * createdHeight];
+ createdImage.getRGB(0, 0, createdWidth, createdHeight, createdContent, 0, createdWidth);
+
+ for (int y = 0 ; y < createdHeight ; y++) {
+ for (int x = 0 ; x < createdWidth ; x++) {
+ int originalRGBA = originalContent[y * createdWidth + x];
+ int createdRGBA = createdContent[y * createdWidth + x];
+ assertEquals(
+ String.format("%dx%d: 0x%08x : 0x%08x", x, y, originalRGBA, createdRGBA),
+ originalRGBA,
+ createdRGBA);
+ }
+ }
+ }
+
+ @NonNull
+ protected static Map<String, Chunk> readChunks(@NonNull File file) throws IOException {
+ Map<String, Chunk> chunks = Maps.newHashMap();
+
+ byte[] fileBuffer = Files.toByteArray(file);
+ ByteBuffer buffer = ByteBuffer.wrap(fileBuffer);
+
+ byte[] sig = new byte[8];
+ buffer.get(sig);
+
+ assertTrue(Arrays.equals(sig, PngWriter.SIGNATURE));
+
+ byte[] data, type;
+ int len;
+ int crc32;
+
+ while (buffer.hasRemaining()) {
+ len = buffer.getInt();
+
+ type = new byte[4];
+ buffer.get(type);
+
+ data = new byte[len];
+ buffer.get(data);
+
+ // crc
+ crc32 = buffer.getInt();
+
+ Chunk chunk = new Chunk(type, data, crc32);
+ chunks.put(chunk.getTypeAsString(), chunk);
+ }
+
+ return chunks;
+ }
+
+
+ @NonNull
+ protected static File getFile(@NonNull String name) {
+ return new File(getPngFolder(), name);
+ }
+
+ @NonNull
+ protected static File getPngFolder() {
+ File folder = TestUtils.getRoot("png");
+ assertTrue(folder.isDirectory());
+ return folder;
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/png/NinePatchChunkBuilderTest.java b/build-system/builder/src/test/java/com/android/builder/png/NinePatchChunkBuilderTest.java
new file mode 100644
index 0000000..f7b9c45
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/png/NinePatchChunkBuilderTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import junit.framework.TestCase;
+
+import java.nio.ByteBuffer;
+
+/**
+ */
+public class NinePatchChunkBuilderTest extends TestCase {
+
+ public void testArrayConversion() {
+ int[] testArray = new int[] { -2, 42, 60000 };
+
+ byte[] result = NinePatchChunkBuilder.intToByteArray(testArray, testArray.length);
+
+ ByteBuffer buffer = ByteBuffer.wrap(result);
+
+ for (int i : testArray) {
+ assertEquals(i, buffer.getInt());
+ }
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/png/NinePatchProcessorTest.java b/build-system/builder/src/test/java/com/android/builder/png/NinePatchProcessorTest.java
new file mode 100644
index 0000000..f9b3daa
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/png/NinePatchProcessorTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.zip.DataFormatException;
+
+public class NinePatchProcessorTest extends BasePngTest {
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.setName("NinePatchProcessor");
+
+ for (File file : getNinePatches()) {
+ String testName = "process_" + file.getName();
+
+ NinePatchProcessorTest test = (NinePatchProcessorTest) TestSuite.createTest(
+ NinePatchProcessorTest.class, testName);
+
+ test.setFile(file);
+
+ suite.addTest(test);
+ }
+
+ return suite;
+ }
+
+ @NonNull
+ private File mFile;
+
+ protected void setFile(@NonNull File file) {
+ mFile = file;
+ }
+
+ @Override
+ protected void runTest() throws Throwable {
+ File outFile = crunch(mFile);
+ File crunched = new File(mFile.getParent(), mFile.getName() + ".crunched");
+
+ Map<String, Chunk> testedChunks = compareChunks(crunched, outFile);
+
+ try {
+ compareImageContent(crunched, outFile, false);
+ } catch (AssertionFailedError e) {
+ throw new RuntimeException("Failed with " + testedChunks.get("IHDR"), e);
+ }
+ }
+
+ private static Map<String, Chunk> compareChunks(@NonNull File original, @NonNull File tested) throws
+ IOException, DataFormatException {
+ Map<String, Chunk> originalChunks = readChunks(original);
+ Map<String, Chunk> testedChunks = readChunks(tested);
+
+ compareChunk(originalChunks, testedChunks, "IHDR");
+ compareChunk(originalChunks, testedChunks, "npLb");
+ compareChunk(originalChunks, testedChunks, "npTc");
+
+ return testedChunks;
+ }
+
+ private static void compareChunk(
+ @NonNull Map<String, Chunk> originalChunks,
+ @NonNull Map<String, Chunk> testedChunks,
+ @NonNull String chunkType) {
+ assertEquals(originalChunks.get(chunkType), testedChunks.get(chunkType));
+ }
+
+ @NonNull
+ private static File[] getNinePatches() {
+ File pngFolder = getPngFolder();
+ File ninePatchFolder = new File(pngFolder, "ninepatch");
+
+ File[] files = ninePatchFolder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.getPath().endsWith(SdkConstants.DOT_9PNG);
+ }
+ });
+ if (files != null) {
+ return files;
+ }
+
+ return new File[0];
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/png/PngProcessorTest.java b/build-system/builder/src/test/java/com/android/builder/png/PngProcessorTest.java
new file mode 100644
index 0000000..f8113f6
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/png/PngProcessorTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.png;
+
+import com.android.annotations.NonNull;
+import com.google.common.io.Files;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+public class PngProcessorTest extends BasePngTest {
+
+ public void testSimplePng() throws IOException, NinePatchException {
+ File fromFile = getFile("icon.png");
+ File outFile = crunch(fromFile);
+
+ compareImageContent(fromFile, outFile, false /*is9Patch*/);
+ }
+
+ public void testGrayscalePng() throws IOException, NinePatchException, DataFormatException {
+ File fromFile = getFile("grayscale.png");
+ File outFile = crunch(fromFile);
+
+ // compare to the aapt-crunched file since ImageIO applies some color correction
+ // when loading grayscale images.
+ File aaptFile = new File(fromFile.getParent(), fromFile.getName() + ".crunched");
+
+ compareImageContent(aaptFile, outFile, false /*is9Patch*/);
+ }
+
+ public void testBroken9PatchBlue() {
+ File fromFile = getFile("blue_patch.9.png");
+ try {
+ crunch(fromFile);
+ assertFalse("Crunching didn't fail", true);
+ } catch (NinePatchException e) {
+ TickException tickException = e.getTickException();
+ assertNotNull(tickException);
+
+ assertEquals(1, tickException.getPixelLocation());
+ assertNotNull(tickException.getPixelColor());
+ assertEquals(0xFF0000FF, tickException.getPixelColor().intValue());
+ assertEquals("left", e.getEdge());
+
+ } catch (IOException e) {
+ assertFalse("Got IOException instead of NinePatchException", true);
+ }
+ }
+
+ public void testMissingPatch() {
+ File fromFile = getFile("missing_patch.9.png");
+ try {
+ crunch(fromFile);
+ assertFalse("Crunching didn't fail", true);
+ } catch (NinePatchException e) {
+ TickException tickException = e.getTickException();
+ assertNotNull(tickException);
+
+ assertEquals(-1, tickException.getPixelLocation());
+ assertNull(tickException.getPixelColor());
+ assertEquals("top", e.getEdge());
+
+ } catch (IOException e) {
+ assertFalse("Got IOException instead of NinePatchException", true);
+ }
+ }
+
+ public void testAlreadyCrunchedFile() throws IOException, NinePatchException {
+ File fromFile = getFile("crunched.png");
+ File outFile = crunch(fromFile);
+
+ // check the files are the same.
+ assertEquals(fromFile.length(), outFile.length());
+
+ byte[] fromArray = Files.toByteArray(fromFile);
+ byte[] toArray = Files.toByteArray(outFile);
+ assertTrue(Arrays.equals(fromArray, toArray));
+ }
+
+ byte[] getRawImageData(@NonNull File file) throws DataFormatException, IOException {
+ Map<String, Chunk> chunks = readChunks(file);
+
+ Chunk idat = chunks.get("IDAT");
+
+ assertNotNull(idat);
+ assertNotNull(idat.getData());
+
+ // create a growing buffer for the result.
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(idat.getData().length);
+
+ Inflater inflater = new Inflater();
+ inflater.setInput(idat.getData());
+
+ // temp buffer for compressed data.
+ byte[] tmpBuffer = new byte[1024];
+ while (!inflater.finished()) {
+ int compressedLen = inflater.inflate(tmpBuffer);
+ bos.write(tmpBuffer, 0, compressedLen);
+ }
+
+ bos.close();
+
+ return bos.toByteArray();
+ }
+}
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
deleted file mode 100755
index 453230c..0000000
--- a/build-system/builder/src/test/java/com/android/builder/signing/KeyStoreHelperTest.java
+++ /dev/null
@@ -1,126 +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.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/secondary_files.d b/build-system/builder/src/test/resources/testData/dependencyData/secondary_files.d
new file mode 100644
index 0000000..8482357
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/dependencyData/secondary_files.d
@@ -0,0 +1,11 @@
+/path/to/project/build/source/aidl/debug/com/example/IService.java: \
+ /path/to/project/src/com/example/IService.aidl \
+ /path/to/project/src/com/example/ISecondaryFile1.aidl \
+ /path/to/project/src/com/example/ISecondaryFile2.aidl \
+ /path/to/project/src/com/example/ISecondaryFile3.aidl \
+ /path/to/project/src/com/example/ISecondaryFile4.aidl \
+
+/path/to/project/src/com/example/ISecondaryFile1.aidl :
+/path/to/project/src/com/example/ISecondaryFile2.aidl :
+/path/to/project/src/com/example/ISecondaryFile3.aidl :
+/path/to/project/src/com/example/ISecondaryFile4.aidl :
diff --git a/build-system/builder/src/test/resources/testData/png/blue_patch.9.png b/build-system/builder/src/test/resources/testData/png/blue_patch.9.png
new file mode 100644
index 0000000..df5b60f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/blue_patch.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/crunched.png b/build-system/builder/src/test/resources/testData/png/crunched.png
new file mode 100755
index 0000000..4bcb643
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/crunched.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/grayscale.png b/build-system/builder/src/test/resources/testData/png/grayscale.png
new file mode 100644
index 0000000..722373f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/grayscale.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/grayscale.png.crunched b/build-system/builder/src/test/resources/testData/png/grayscale.png.crunched
new file mode 100644
index 0000000..5170b5f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/grayscale.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/icon.png b/build-system/builder/src/test/resources/testData/png/icon.png
new file mode 100644
index 0000000..f31e571
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/icon.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/missing_patch.9.png b/build-system/builder/src/test/resources/testData/png/missing_patch.9.png
new file mode 100644
index 0000000..ba731c6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/missing_patch.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png
new file mode 100644
index 0000000..769463b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched
new file mode 100644
index 0000000..42bb366
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png
new file mode 100644
index 0000000..88f11dc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched
new file mode 100644
index 0000000..d12d923
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png
new file mode 100644
index 0000000..7305047
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched
new file mode 100644
index 0000000..612889a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png
new file mode 100644
index 0000000..712a551
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched
new file mode 100644
index 0000000..9a80fb7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png
new file mode 100644
index 0000000..bf3b943
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched
new file mode 100644
index 0000000..2b4a61a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png
new file mode 100644
index 0000000..6c14157
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched
new file mode 100644
index 0000000..75d3269
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png
new file mode 100644
index 0000000..f4ff16b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched
new file mode 100644
index 0000000..ca7df22
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png
new file mode 100644
index 0000000..cbbaec5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched
new file mode 100644
index 0000000..b110188
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png
new file mode 100644
index 0000000..af917e5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched
new file mode 100644
index 0000000..95c76e3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png
new file mode 100644
index 0000000..2d59f35
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched
new file mode 100644
index 0000000..d6b9634
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png
new file mode 100644
index 0000000..0520e5a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched
new file mode 100644
index 0000000..20d7080
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png
new file mode 100644
index 0000000..42528b1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched
new file mode 100644
index 0000000..c2b2667
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png
new file mode 100644
index 0000000..e3e3f93
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched
new file mode 100644
index 0000000..1dc40c9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png
new file mode 100644
index 0000000..1e39572
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched
new file mode 100644
index 0000000..48bc536
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png
new file mode 100644
index 0000000..a16db85
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched
new file mode 100644
index 0000000..46fcf71
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png
new file mode 100644
index 0000000..0eff695
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched
new file mode 100644
index 0000000..2adaa18
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png
new file mode 100644
index 0000000..219b170
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched
new file mode 100644
index 0000000..629911f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png
new file mode 100644
index 0000000..48d60c4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched
new file mode 100644
index 0000000..ae4e4ee
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png
new file mode 100644
index 0000000..b0dc31f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched
new file mode 100644
index 0000000..11c7940
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png
new file mode 100644
index 0000000..4bc2683
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched
new file mode 100644
index 0000000..720f52d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png
new file mode 100644
index 0000000..4af38fb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..6e4332f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png
new file mode 100644
index 0000000..d32f74c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..ac9f39b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png
new file mode 100644
index 0000000..99d60e3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..9ccd26e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png
new file mode 100644
index 0000000..45a0cf0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..29932f7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png
new file mode 100644
index 0000000..97e6806
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched
new file mode 100644
index 0000000..56badf5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png
new file mode 100644
index 0000000..28a1cba
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..80f71ef
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png
new file mode 100644
index 0000000..28a1cba
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..80f71ef
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png
new file mode 100644
index 0000000..0437c31
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched
new file mode 100644
index 0000000..a1c3c19
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png
new file mode 100644
index 0000000..72b0d42
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..fdcd971
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png
new file mode 100644
index 0000000..72b0d42
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..fdcd971
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png
new file mode 100644
index 0000000..882ed61
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched
new file mode 100644
index 0000000..7b87352
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png
new file mode 100644
index 0000000..eff3cc4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..4f49bc1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png
new file mode 100644
index 0000000..eff3cc4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..4f49bc1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png
new file mode 100644
index 0000000..803651b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched
new file mode 100644
index 0000000..1ba4933
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png
new file mode 100644
index 0000000..f4f01c7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched
new file mode 100644
index 0000000..acb8572
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png
new file mode 100644
index 0000000..5376db2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched
new file mode 100644
index 0000000..c2fcfd7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png
new file mode 100644
index 0000000..dbcede7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched
new file mode 100644
index 0000000..5e01284
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png
new file mode 100644
index 0000000..986f797
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched
new file mode 100644
index 0000000..51325a9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png
new file mode 100644
index 0000000..42fc83c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched
new file mode 100644
index 0000000..913064b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png
new file mode 100644
index 0000000..4312c27
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched
new file mode 100644
index 0000000..d98a519
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png
new file mode 100644
index 0000000..fd2b63a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched
new file mode 100644
index 0000000..d497da3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png
new file mode 100644
index 0000000..b7c125b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..741387b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png
new file mode 100644
index 0000000..bf09b6f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..ba84489
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png
new file mode 100644
index 0000000..06b7790
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched
new file mode 100644
index 0000000..782f35c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png
new file mode 100644
index 0000000..6d3ea9a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched
new file mode 100644
index 0000000..ae95f13
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png
new file mode 100644
index 0000000..2646ba0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched
new file mode 100644
index 0000000..23e49a7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png
new file mode 100644
index 0000000..013210c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched
new file mode 100644
index 0000000..15b1a09
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png
new file mode 100644
index 0000000..24cefd4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched
new file mode 100644
index 0000000..a11747b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png
new file mode 100644
index 0000000..bedbceb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched
new file mode 100644
index 0000000..17f6969
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png
new file mode 100644
index 0000000..617dba3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched
new file mode 100644
index 0000000..7fc4832
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png
new file mode 100644
index 0000000..0d25b6e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched
new file mode 100644
index 0000000..3e08062
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png
new file mode 100644
index 0000000..e21fd75
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched
new file mode 100644
index 0000000..f304f18
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png
new file mode 100644
index 0000000..f10402f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched
new file mode 100644
index 0000000..aec91d0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png
new file mode 100644
index 0000000..366c6e0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched
new file mode 100644
index 0000000..bcde8a9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png
new file mode 100644
index 0000000..f063c8d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched
new file mode 100644
index 0000000..2685554
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png
new file mode 100644
index 0000000..30984f4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched
new file mode 100644
index 0000000..b00a5b2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png
new file mode 100644
index 0000000..a8225e8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched
new file mode 100644
index 0000000..65637ae
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png
new file mode 100644
index 0000000..f020f77
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched
new file mode 100644
index 0000000..4773c06
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png
new file mode 100644
index 0000000..5bec4f8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched
new file mode 100644
index 0000000..83eaf80
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png
new file mode 100644
index 0000000..00e8f06
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..1935b4f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png
new file mode 100644
index 0000000..997ccb2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..bcbe2e3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png
new file mode 100644
index 0000000..824b45a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..f89ba49
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png
new file mode 100644
index 0000000..824b45a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..f89ba49
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png
new file mode 100644
index 0000000..b2120f4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched
new file mode 100644
index 0000000..776edea
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png
new file mode 100644
index 0000000..782d36b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched
new file mode 100644
index 0000000..65170bc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png
new file mode 100644
index 0000000..34ec825
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..df07405
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png
new file mode 100644
index 0000000..f7680ab
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..58a214b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png
new file mode 100644
index 0000000..5e6a9d6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched
new file mode 100644
index 0000000..2541a04
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png
new file mode 100644
index 0000000..eb9d740
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched
new file mode 100644
index 0000000..ae228f6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png
new file mode 100644
index 0000000..869a330
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched
new file mode 100644
index 0000000..ae54100
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png
new file mode 100644
index 0000000..7ec33dd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched
new file mode 100644
index 0000000..6fde961
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png
new file mode 100644
index 0000000..72d63da
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched
new file mode 100644
index 0000000..8efaab4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png
new file mode 100644
index 0000000..fcc5cac
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched
new file mode 100644
index 0000000..5a79ccb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png
new file mode 100644
index 0000000..b6c234c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched
new file mode 100644
index 0000000..1fda804
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png
new file mode 100644
index 0000000..9f3c087
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched
new file mode 100644
index 0000000..ef80f3f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png
new file mode 100644
index 0000000..4041342
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched
new file mode 100644
index 0000000..3d64cdd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png
new file mode 100644
index 0000000..73a8cd1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched
new file mode 100644
index 0000000..2c4f2e7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png
new file mode 100644
index 0000000..8473e8e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched
new file mode 100644
index 0000000..b5874d4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png
new file mode 100644
index 0000000..f4f59c0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched
new file mode 100644
index 0000000..d7cecd3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png
new file mode 100644
index 0000000..baff858
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched
new file mode 100644
index 0000000..6abbf9c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png
new file mode 100644
index 0000000..5612c51
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched
new file mode 100644
index 0000000..834057b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png
new file mode 100644
index 0000000..42c7c14
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched
new file mode 100644
index 0000000..9187e8a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png
new file mode 100644
index 0000000..01e2506
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched
new file mode 100644
index 0000000..3b86f74
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png
new file mode 100644
index 0000000..83c6eb3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched
new file mode 100644
index 0000000..3579c6f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png
new file mode 100644
index 0000000..e047eaf
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched
new file mode 100644
index 0000000..8842bef
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png
new file mode 100644
index 0000000..218a2d2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched
new file mode 100644
index 0000000..2c589da
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png
new file mode 100644
index 0000000..afe4951
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched
new file mode 100644
index 0000000..9ac0057
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png
new file mode 100644
index 0000000..9c7e483
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched
new file mode 100644
index 0000000..6a001f8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png
new file mode 100644
index 0000000..1508653
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched
new file mode 100644
index 0000000..791181b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png
new file mode 100644
index 0000000..66c231a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched
new file mode 100644
index 0000000..a57d761
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png
new file mode 100644
index 0000000..e01a49d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched
new file mode 100644
index 0000000..a87c15a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png
new file mode 100644
index 0000000..cdad182
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched
new file mode 100644
index 0000000..eab8ed4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png
new file mode 100644
index 0000000..e95f4cf
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched
new file mode 100644
index 0000000..9f283b1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png
new file mode 100644
index 0000000..544655e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched
new file mode 100644
index 0000000..d01000a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png
new file mode 100644
index 0000000..bf16315
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched
new file mode 100644
index 0000000..31bb632
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png
new file mode 100644
index 0000000..d7b8ed5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched
new file mode 100644
index 0000000..0de60c6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png
new file mode 100644
index 0000000..1a35c31
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched
new file mode 100644
index 0000000..492bd06
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png
new file mode 100644
index 0000000..17dd3fc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched
new file mode 100644
index 0000000..908fb2f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png
new file mode 100644
index 0000000..a146d8f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched
new file mode 100644
index 0000000..3a1b6f3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png
new file mode 100644
index 0000000..45c5c6a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched
new file mode 100644
index 0000000..cb51f3d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png
new file mode 100644
index 0000000..72faccf
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched
new file mode 100644
index 0000000..f155d26
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png
new file mode 100644
index 0000000..369be10
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched
new file mode 100644
index 0000000..e277e3c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png
new file mode 100644
index 0000000..7e996ec
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched
new file mode 100644
index 0000000..1b51bf8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png
new file mode 100644
index 0000000..eda6e16
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched
new file mode 100644
index 0000000..ad9cee7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png
new file mode 100644
index 0000000..4158ac4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched
new file mode 100644
index 0000000..0ae6a7c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png
new file mode 100644
index 0000000..6f68f25
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched
new file mode 100644
index 0000000..7d7b564
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png
new file mode 100644
index 0000000..6008067
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched
new file mode 100644
index 0000000..7271755
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png
new file mode 100644
index 0000000..9e141d8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched
new file mode 100644
index 0000000..01b2f47
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png
new file mode 100644
index 0000000..17acfc5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..a23528b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png
new file mode 100644
index 0000000..17acfc5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..a23528b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png
new file mode 100644
index 0000000..9b8ca22
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..372306c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png
new file mode 100644
index 0000000..9b8ca22
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..372306c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png
new file mode 100644
index 0000000..bc20f6c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..5970fe6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png
new file mode 100644
index 0000000..bc20f6c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..5970fe6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png
new file mode 100644
index 0000000..571819b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched
new file mode 100644
index 0000000..e257632
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png
new file mode 100644
index 0000000..571819b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched
new file mode 100644
index 0000000..e257632
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png
new file mode 100644
index 0000000..94c0ee7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..495605b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png
new file mode 100644
index 0000000..9bef909
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..48e87a2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png
new file mode 100644
index 0000000..dba2fa5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched
new file mode 100644
index 0000000..0b5cc87
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png
new file mode 100644
index 0000000..1f83b5a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..5ff5f88
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png
new file mode 100644
index 0000000..1f83b5a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..5ff5f88
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png
new file mode 100644
index 0000000..733cf45
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..6173e83
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png
new file mode 100644
index 0000000..733cf45
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..6173e83
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png
new file mode 100644
index 0000000..2265de4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..2e629e1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png
new file mode 100644
index 0000000..2265de4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..2e629e1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png
new file mode 100644
index 0000000..f3ada58
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched
new file mode 100644
index 0000000..ac8b136
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png
new file mode 100644
index 0000000..f3ada58
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched
new file mode 100644
index 0000000..ac8b136
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png
new file mode 100644
index 0000000..469ba9b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..b06a800
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png
new file mode 100644
index 0000000..40a61ca
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..281b153
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png
new file mode 100644
index 0000000..6441f4d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched
new file mode 100644
index 0000000..6fc8d50
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png
new file mode 100644
index 0000000..cfb0613
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched
new file mode 100644
index 0000000..12d2244
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png
new file mode 100644
index 0000000..b30f834
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched
new file mode 100644
index 0000000..4105dd3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png
new file mode 100644
index 0000000..4ff9910
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched
new file mode 100644
index 0000000..d109a4d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png
new file mode 100644
index 0000000..5542695
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched
new file mode 100644
index 0000000..ed9b858
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png
new file mode 100644
index 0000000..6a55903
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched
new file mode 100644
index 0000000..2d144b9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png
new file mode 100644
index 0000000..7adbae1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched
new file mode 100644
index 0000000..d4757e0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png
new file mode 100644
index 0000000..4631a32
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched
new file mode 100644
index 0000000..9cddba8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png
new file mode 100644
index 0000000..df75fec
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched
new file mode 100644
index 0000000..614e78f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png
new file mode 100644
index 0000000..bae522c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched
new file mode 100644
index 0000000..86b8ca2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png
new file mode 100644
index 0000000..1d836f6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched
new file mode 100644
index 0000000..fc2e90a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png
new file mode 100644
index 0000000..5818666
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched
new file mode 100644
index 0000000..2d8352b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png
new file mode 100644
index 0000000..564fb34
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched
new file mode 100644
index 0000000..365e83e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png
new file mode 100644
index 0000000..ae21b76
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched
new file mode 100644
index 0000000..0a42fb2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png
new file mode 100644
index 0000000..36fbfc8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched
new file mode 100644
index 0000000..7a5073f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png
new file mode 100644
index 0000000..e308382
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched
new file mode 100644
index 0000000..0534393
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png
new file mode 100644
index 0000000..4f9ca6f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched
new file mode 100644
index 0000000..888f5d7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png
new file mode 100644
index 0000000..e72d0f7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched
new file mode 100644
index 0000000..354f18c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png
new file mode 100644
index 0000000..76ff1f5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched
new file mode 100644
index 0000000..d529743
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png
new file mode 100644
index 0000000..20af255
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched
new file mode 100644
index 0000000..af43229
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png
new file mode 100644
index 0000000..981b2e9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched
new file mode 100644
index 0000000..3ac860f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png
new file mode 100644
index 0000000..8cd231b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched
new file mode 100644
index 0000000..aee4e7b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png
new file mode 100644
index 0000000..1deaad7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched
new file mode 100644
index 0000000..e70af97
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png
new file mode 100644
index 0000000..b23740c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched
new file mode 100644
index 0000000..4d84575
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png
new file mode 100644
index 0000000..44803d7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched
new file mode 100644
index 0000000..651cd76
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png
new file mode 100644
index 0000000..77b0999
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched
new file mode 100644
index 0000000..6fe7089
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png
new file mode 100644
index 0000000..3fde76e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched
new file mode 100644
index 0000000..02148c0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png
new file mode 100644
index 0000000..441ef32
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched
new file mode 100644
index 0000000..69d382c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png
new file mode 100644
index 0000000..911f3fe
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched
new file mode 100644
index 0000000..b668fe0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png
new file mode 100644
index 0000000..2129567
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched
new file mode 100644
index 0000000..461aa67
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png
new file mode 100644
index 0000000..dc5e79d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched
new file mode 100644
index 0000000..89fac30
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png
new file mode 100644
index 0000000..9ce7cfc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched
new file mode 100644
index 0000000..8755051
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png
new file mode 100644
index 0000000..396a0f2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched
new file mode 100644
index 0000000..2171a23
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png
new file mode 100644
index 0000000..22ca61f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched
new file mode 100644
index 0000000..da5ecf0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png
new file mode 100644
index 0000000..9b54cd5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched
new file mode 100644
index 0000000..a265691
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png
new file mode 100644
index 0000000..41b776b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched
new file mode 100644
index 0000000..a2722db
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png
new file mode 100644
index 0000000..eb75a22
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched
new file mode 100644
index 0000000..900713c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png
new file mode 100644
index 0000000..55a5e53
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched
new file mode 100644
index 0000000..5211814
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png
new file mode 100644
index 0000000..60e2cb2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched
new file mode 100644
index 0000000..5f606ff
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png
new file mode 100644
index 0000000..cf34613
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched
new file mode 100644
index 0000000..f0e8b46
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png
new file mode 100644
index 0000000..3dfe6c2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched
new file mode 100644
index 0000000..1c0195d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png
new file mode 100644
index 0000000..ea38ebb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched
new file mode 100644
index 0000000..4012c45
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png
new file mode 100644
index 0000000..23b0b51
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched
new file mode 100644
index 0000000..abc72c7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png
new file mode 100644
index 0000000..0758593
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched
new file mode 100644
index 0000000..e65d695
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png
new file mode 100644
index 0000000..41b776b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched
new file mode 100644
index 0000000..a2722db
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png
new file mode 100644
index 0000000..eb75a22
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched
new file mode 100644
index 0000000..900713c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png
new file mode 100644
index 0000000..55a5e53
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched
new file mode 100644
index 0000000..5211814
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png
new file mode 100644
index 0000000..60e2cb2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched
new file mode 100644
index 0000000..5f606ff
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png
new file mode 100644
index 0000000..c039428
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched
new file mode 100644
index 0000000..a85bdc1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png
new file mode 100644
index 0000000..7c4a29f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched
new file mode 100644
index 0000000..c02fa2f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png
new file mode 100644
index 0000000..519f522
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..d972430
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png
new file mode 100644
index 0000000..9c181d0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..5788062
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png
new file mode 100644
index 0000000..6ca975f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..b31fa43
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png
new file mode 100644
index 0000000..7a20af7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..ad48216
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png
new file mode 100644
index 0000000..a3dfb98
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..1cdd4f9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png
new file mode 100644
index 0000000..766543c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..8dbbdaf
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png
new file mode 100644
index 0000000..a4ac317
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched
new file mode 100644
index 0000000..cc562d7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png
new file mode 100644
index 0000000..b4ab9ad
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched
new file mode 100644
index 0000000..f4e3bed
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png
new file mode 100644
index 0000000..f6382c8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..595d4f4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png
new file mode 100644
index 0000000..c849e2f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..76508bc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png
new file mode 100644
index 0000000..c083129
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched
new file mode 100644
index 0000000..e9bd129
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png
new file mode 100644
index 0000000..41f3970
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched
new file mode 100644
index 0000000..63f964d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png
new file mode 100644
index 0000000..04a8d12
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched
new file mode 100644
index 0000000..b462267
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png
new file mode 100644
index 0000000..70cd52b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched
new file mode 100644
index 0000000..d5e5dd1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png
new file mode 100644
index 0000000..ce12b3b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched
new file mode 100644
index 0000000..00de08c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png
new file mode 100644
index 0000000..e7967d5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched
new file mode 100644
index 0000000..2b2c2ea
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png
new file mode 100644
index 0000000..4cce373
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched
new file mode 100644
index 0000000..c751899
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png
new file mode 100644
index 0000000..73ff79f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched
new file mode 100644
index 0000000..a3e4579
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png
new file mode 100644
index 0000000..290c24d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched
new file mode 100644
index 0000000..8199594
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png
new file mode 100644
index 0000000..2ec27af
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched
new file mode 100644
index 0000000..53ce473
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png
new file mode 100644
index 0000000..0c19bb7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched
new file mode 100644
index 0000000..8831c06
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png
new file mode 100644
index 0000000..754032e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched
new file mode 100644
index 0000000..d924426
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png
new file mode 100644
index 0000000..e32c7c7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched
new file mode 100644
index 0000000..e6c9889
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png
new file mode 100644
index 0000000..769cb12
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched
new file mode 100644
index 0000000..8dfb925
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png
new file mode 100644
index 0000000..c5372a8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched
new file mode 100644
index 0000000..03a22e9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png
new file mode 100644
index 0000000..1dee51b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched
new file mode 100644
index 0000000..71218be
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png
new file mode 100644
index 0000000..3c1e25a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched
new file mode 100644
index 0000000..341cd6d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png
new file mode 100644
index 0000000..707414d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched
new file mode 100644
index 0000000..6d3151d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png
new file mode 100644
index 0000000..707414d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched
new file mode 100644
index 0000000..6d3151d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png
new file mode 100644
index 0000000..4d810dd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..c7bae12
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png
new file mode 100644
index 0000000..64fa147
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..99d6c23
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png
new file mode 100644
index 0000000..8edbd3f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched
new file mode 100644
index 0000000..b8843e2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png
new file mode 100644
index 0000000..986e6d5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched
new file mode 100644
index 0000000..944d2e3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png
new file mode 100644
index 0000000..95f3ab5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched
new file mode 100644
index 0000000..5831ac7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png
new file mode 100644
index 0000000..99403aa
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched
new file mode 100644
index 0000000..c3b68aa
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png
new file mode 100644
index 0000000..3aa2e17
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched
new file mode 100644
index 0000000..fd3f650
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png
new file mode 100644
index 0000000..8f1e752
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched
new file mode 100644
index 0000000..ed9ace5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png
new file mode 100644
index 0000000..3d10b86
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched
new file mode 100644
index 0000000..ba7b89d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png
new file mode 100644
index 0000000..2fa7c46
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched
new file mode 100644
index 0000000..73979ec
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png
new file mode 100644
index 0000000..be2aeed
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched
new file mode 100644
index 0000000..7ca0560
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png
new file mode 100644
index 0000000..27e455a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched
new file mode 100644
index 0000000..ba49b36
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png
new file mode 100644
index 0000000..46f755d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched
new file mode 100644
index 0000000..953989f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png
new file mode 100644
index 0000000..91d7db1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched
new file mode 100644
index 0000000..126ce92
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png
new file mode 100644
index 0000000..6e92dd5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched
new file mode 100644
index 0000000..ede1fd5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png
new file mode 100644
index 0000000..744178f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched
new file mode 100644
index 0000000..c034a04
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png
new file mode 100644
index 0000000..a4da974
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched
new file mode 100644
index 0000000..0b073c7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png
new file mode 100644
index 0000000..6f9a442
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched
new file mode 100644
index 0000000..efaa7e5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png
new file mode 100644
index 0000000..abb91cc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched
new file mode 100644
index 0000000..1f318b2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png
new file mode 100644
index 0000000..7c4f40e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched
new file mode 100644
index 0000000..68194fd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png
new file mode 100644
index 0000000..6dbf925
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched
new file mode 100644
index 0000000..2e292c4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png
new file mode 100644
index 0000000..b05a49f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched
new file mode 100644
index 0000000..832c5b7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png
new file mode 100644
index 0000000..b9ec237
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched
new file mode 100644
index 0000000..f7b86dc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png
new file mode 100644
index 0000000..2800cab
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched
new file mode 100644
index 0000000..89511ff
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png
new file mode 100644
index 0000000..51cbfa6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched
new file mode 100644
index 0000000..ea21423
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png
new file mode 100644
index 0000000..ca51ccc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched
new file mode 100644
index 0000000..a049aef
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png
new file mode 100644
index 0000000..fd98571
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched
new file mode 100644
index 0000000..435e209
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png
new file mode 100644
index 0000000..723815b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched
new file mode 100644
index 0000000..f3622fb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png
new file mode 100644
index 0000000..30fcda5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched
new file mode 100644
index 0000000..a2c5d70
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png
new file mode 100644
index 0000000..ffc5433
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched
new file mode 100644
index 0000000..cb05a8f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png
new file mode 100644
index 0000000..5b0f6c5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched
new file mode 100644
index 0000000..3d398ba
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png
new file mode 100644
index 0000000..7a03b8e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched
new file mode 100644
index 0000000..32f0a57
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png
new file mode 100644
index 0000000..6ba42db
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched
new file mode 100644
index 0000000..4ca3226
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png
new file mode 100644
index 0000000..4d0b601
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched
new file mode 100644
index 0000000..92bd51f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png
new file mode 100644
index 0000000..8e2461b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched
new file mode 100644
index 0000000..ac09de0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png
new file mode 100644
index 0000000..fd7366e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched
new file mode 100644
index 0000000..adff9c5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png
new file mode 100644
index 0000000..6fc53ca
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched
new file mode 100644
index 0000000..c778238
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png
new file mode 100644
index 0000000..4ea7afa
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched
new file mode 100644
index 0000000..4da70ac
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png
new file mode 100644
index 0000000..986ab0b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched
new file mode 100644
index 0000000..a57e66a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png
new file mode 100644
index 0000000..0279e17
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched
new file mode 100644
index 0000000..75bd2d7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png
new file mode 100644
index 0000000..0a4347f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched
new file mode 100644
index 0000000..d0ce0ab
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png
new file mode 100644
index 0000000..5552708
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched
new file mode 100644
index 0000000..5fe1de8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png
new file mode 100644
index 0000000..4ea7afa
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched
new file mode 100644
index 0000000..4da70ac
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png
new file mode 100644
index 0000000..f5cc0ed
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..f9b4257
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png
new file mode 100644
index 0000000..e9afcc9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..69c1a31
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png
new file mode 100644
index 0000000..596accb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..24b157f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png
new file mode 100644
index 0000000..2054530
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..5f85799
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png
new file mode 100644
index 0000000..43a20ad
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched
new file mode 100644
index 0000000..cc8ad51
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png
new file mode 100644
index 0000000..b7b292e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched
new file mode 100644
index 0000000..48dc934
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png
new file mode 100644
index 0000000..2030d3b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched
new file mode 100644
index 0000000..1389c6e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png
new file mode 100644
index 0000000..4ca2773
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched
new file mode 100644
index 0000000..2223061
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png
new file mode 100644
index 0000000..1a0bf0d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched
new file mode 100644
index 0000000..ebb1634
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png
new file mode 100644
index 0000000..c9e662d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched
new file mode 100644
index 0000000..1f86a7a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png
new file mode 100644
index 0000000..1a516c1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched
new file mode 100644
index 0000000..16bfac2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png
new file mode 100644
index 0000000..f22217b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched
new file mode 100644
index 0000000..755a31e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png
new file mode 100644
index 0000000..25c6241
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched
new file mode 100644
index 0000000..9b10e0b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png
new file mode 100644
index 0000000..c3e69f0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched
new file mode 100644
index 0000000..ca30904
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png
new file mode 100644
index 0000000..05b1419
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched
new file mode 100644
index 0000000..d239cc4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png
new file mode 100644
index 0000000..8edc9cd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched
new file mode 100644
index 0000000..e6de17a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png
new file mode 100644
index 0000000..3a21d35
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched
new file mode 100644
index 0000000..7322a94
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png
new file mode 100644
index 0000000..60bb454
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched
new file mode 100644
index 0000000..c6a2b39
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png
new file mode 100644
index 0000000..60bb454
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched
new file mode 100644
index 0000000..c6a2b39
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png
new file mode 100644
index 0000000..b527da1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched
new file mode 100644
index 0000000..c9307c8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png
new file mode 100644
index 0000000..19558e9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched
new file mode 100644
index 0000000..6219921
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png
new file mode 100644
index 0000000..fc2ba2e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched
new file mode 100644
index 0000000..4e4806b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png
new file mode 100644
index 0000000..0f3b444
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched
new file mode 100644
index 0000000..6b45450
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png
new file mode 100644
index 0000000..00786e5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched
new file mode 100644
index 0000000..a10f808
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png
new file mode 100644
index 0000000..cbf50b3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched
new file mode 100644
index 0000000..3ebca45
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png
new file mode 100644
index 0000000..34ea86e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched
new file mode 100644
index 0000000..a4dfdb3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png
new file mode 100644
index 0000000..f6fd30d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..cb12896
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png
new file mode 100644
index 0000000..ca8e9a2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..233da8e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png
new file mode 100644
index 0000000..c1f3d7d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..0874c54
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png
new file mode 100644
index 0000000..99bb246
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..ca61114
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png
new file mode 100644
index 0000000..f702fc8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched
new file mode 100644
index 0000000..3509896
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png
new file mode 100644
index 0000000..e8f277d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched
new file mode 100644
index 0000000..9624855
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png
new file mode 100644
index 0000000..0ed5ba3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..e52c570
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png
new file mode 100644
index 0000000..1471c17
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..5a0ced9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png
new file mode 100644
index 0000000..29bdc42
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched
new file mode 100644
index 0000000..a19c287
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png
new file mode 100644
index 0000000..f4c9e08
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched
new file mode 100644
index 0000000..41fe75d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png
new file mode 100644
index 0000000..a3cec11
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched
new file mode 100644
index 0000000..3d3a662
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png
new file mode 100644
index 0000000..72ee35f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched
new file mode 100644
index 0000000..bbd0b0f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png
new file mode 100644
index 0000000..0d1f9bf
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched
new file mode 100644
index 0000000..b973eb1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png
new file mode 100644
index 0000000..465ee6d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched
new file mode 100644
index 0000000..c5bec2e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png
new file mode 100644
index 0000000..76a5c53
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched
new file mode 100644
index 0000000..e7dcc13
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png
new file mode 100644
index 0000000..e5ff886
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched
new file mode 100644
index 0000000..81ad3ce
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png
new file mode 100644
index 0000000..06d1653
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched
new file mode 100644
index 0000000..0d352cc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png
new file mode 100644
index 0000000..3685b4e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched
new file mode 100644
index 0000000..f999be4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png
new file mode 100644
index 0000000..7b7c8b2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched
new file mode 100644
index 0000000..2682ff8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png
new file mode 100644
index 0000000..d8e16b9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched
new file mode 100644
index 0000000..74e8564
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png
new file mode 100644
index 0000000..ba79cf7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched
new file mode 100644
index 0000000..fed1ea0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png
new file mode 100644
index 0000000..99dd9b1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched
new file mode 100644
index 0000000..715a37f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png
new file mode 100644
index 0000000..389063a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched
new file mode 100644
index 0000000..1713388
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png
new file mode 100644
index 0000000..3ba8376
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched
new file mode 100644
index 0000000..e0606a4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png
new file mode 100644
index 0000000..df226c7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched
new file mode 100644
index 0000000..6aa8c30
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png
new file mode 100644
index 0000000..bb417e6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched
new file mode 100644
index 0000000..7792736
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png
new file mode 100644
index 0000000..d9ef49e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched
new file mode 100644
index 0000000..606a08f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png
new file mode 100644
index 0000000..383c4fc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched
new file mode 100644
index 0000000..8283cf0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png
new file mode 100644
index 0000000..af91f5e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched
new file mode 100644
index 0000000..46527df
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png
new file mode 100644
index 0000000..9832ace
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched
new file mode 100644
index 0000000..5fd89d6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png
new file mode 100644
index 0000000..6ebed8b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched
new file mode 100644
index 0000000..01e7ba9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png
new file mode 100644
index 0000000..c271b11
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched
new file mode 100644
index 0000000..d3fa588
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png
new file mode 100644
index 0000000..73b6915
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched
new file mode 100644
index 0000000..a68aaea
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png
new file mode 100644
index 0000000..046e60f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched
new file mode 100644
index 0000000..e3003a0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png
new file mode 100644
index 0000000..9baf7cc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched
new file mode 100644
index 0000000..34c0c5e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png
new file mode 100644
index 0000000..d95fdd3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched
new file mode 100644
index 0000000..2bc69cc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png
new file mode 100644
index 0000000..a84448f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched
new file mode 100644
index 0000000..5f5f9e4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png
new file mode 100644
index 0000000..aa17a98
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched
new file mode 100644
index 0000000..e339788
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png
new file mode 100644
index 0000000..be78a58
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched
new file mode 100644
index 0000000..6498b65
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png
new file mode 100644
index 0000000..b28f66c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched
new file mode 100644
index 0000000..0fd0c2c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png
new file mode 100644
index 0000000..2e175e8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched
new file mode 100644
index 0000000..b4ebe92
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png
new file mode 100644
index 0000000..c9c72ba
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched
new file mode 100644
index 0000000..86296e2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png
new file mode 100644
index 0000000..348e48c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched
new file mode 100644
index 0000000..fe3a4f5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png
new file mode 100644
index 0000000..93cf3a0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched
new file mode 100644
index 0000000..46f444b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png
new file mode 100644
index 0000000..b4acced
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched
new file mode 100644
index 0000000..d2ef0ef
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png
new file mode 100644
index 0000000..bd29510
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched
new file mode 100644
index 0000000..356428b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png
new file mode 100644
index 0000000..a666998
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched
new file mode 100644
index 0000000..fd353a9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png
new file mode 100644
index 0000000..03175d4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched
new file mode 100644
index 0000000..f1eabe8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png
new file mode 100644
index 0000000..a5ac279
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched
new file mode 100644
index 0000000..3936a8a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png
new file mode 100644
index 0000000..583865e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched
new file mode 100644
index 0000000..3953dc9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png
new file mode 100644
index 0000000..fdafdf5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched
new file mode 100644
index 0000000..c211a57
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png
new file mode 100644
index 0000000..a654277
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched
new file mode 100644
index 0000000..01528bd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png
new file mode 100644
index 0000000..73162a5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched
new file mode 100644
index 0000000..fbd943c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png
new file mode 100644
index 0000000..2c424f0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched
new file mode 100644
index 0000000..713e34d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png
new file mode 100644
index 0000000..c56c704
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched
new file mode 100644
index 0000000..8fa6abf
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png
new file mode 100644
index 0000000..c038b2a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched
new file mode 100644
index 0000000..d56b088
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png
new file mode 100644
index 0000000..6e5fbb5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched
new file mode 100644
index 0000000..b68f0a7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png
new file mode 100644
index 0000000..3434b2d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched
new file mode 100644
index 0000000..21ce32a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png
new file mode 100644
index 0000000..673a509
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched
new file mode 100644
index 0000000..ef00533
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png
new file mode 100644
index 0000000..c2a739c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched
new file mode 100644
index 0000000..14c6f3d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png
new file mode 100644
index 0000000..9d2bfb1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched
new file mode 100644
index 0000000..ec41d3b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png
new file mode 100644
index 0000000..4375bf2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched
new file mode 100644
index 0000000..4837ab6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png
new file mode 100644
index 0000000..6b8aa9d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched
new file mode 100644
index 0000000..9c8070f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png
new file mode 100644
index 0000000..2884abe
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched
new file mode 100644
index 0000000..3c95bd0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png
new file mode 100644
index 0000000..3d4e8ba
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched
new file mode 100644
index 0000000..559ec85
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png
new file mode 100644
index 0000000..83b2bce
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..93e4612
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png
new file mode 100644
index 0000000..61ea2b0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..bd76be5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png
new file mode 100644
index 0000000..b188d81
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched
new file mode 100644
index 0000000..2554d3c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png
new file mode 100644
index 0000000..9f3060d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..998c0be
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png
new file mode 100644
index 0000000..84fbdac
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..ca653cc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png
new file mode 100644
index 0000000..76c35ec
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched
new file mode 100644
index 0000000..3d1865f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png
new file mode 100644
index 0000000..f317330
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched
new file mode 100644
index 0000000..231815e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png
new file mode 100644
index 0000000..a4c5b8b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched
new file mode 100644
index 0000000..6b7c234
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png
new file mode 100644
index 0000000..3f12166
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched
new file mode 100644
index 0000000..2a8c72e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png
new file mode 100644
index 0000000..b73abba
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched
new file mode 100644
index 0000000..20b656e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png
new file mode 100644
index 0000000..2f76a22
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched
new file mode 100644
index 0000000..26ffb33
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png
new file mode 100644
index 0000000..a75d0dd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched
new file mode 100644
index 0000000..86e0234
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png
new file mode 100644
index 0000000..955b708
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched
new file mode 100644
index 0000000..86e0234
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png
new file mode 100644
index 0000000..93a8417
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched
new file mode 100644
index 0000000..a59bc38
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png
new file mode 100644
index 0000000..61e856a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched
new file mode 100644
index 0000000..68bd3c2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png
new file mode 100644
index 0000000..7632a16
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched
new file mode 100644
index 0000000..66310d4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png
new file mode 100644
index 0000000..8e66ad1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched
new file mode 100644
index 0000000..f34d0f4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png
new file mode 100644
index 0000000..02618ca
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched
new file mode 100644
index 0000000..38e32b6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png
new file mode 100644
index 0000000..939050d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched
new file mode 100644
index 0000000..9269c99
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png
new file mode 100644
index 0000000..f5cf487
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched
new file mode 100644
index 0000000..9b105bb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png
new file mode 100644
index 0000000..1237f26
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched
new file mode 100644
index 0000000..9116ab7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png
new file mode 100644
index 0000000..cbd8c5c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched
new file mode 100644
index 0000000..457f5ab
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png
new file mode 100644
index 0000000..f7f4ba3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched
new file mode 100644
index 0000000..26bd74b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png
new file mode 100644
index 0000000..a82e7ac
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched
new file mode 100644
index 0000000..3ad08a3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png
new file mode 100644
index 0000000..db4ce80
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched
new file mode 100644
index 0000000..a5ac105
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png
new file mode 100644
index 0000000..4e40eda
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched
new file mode 100644
index 0000000..8996a57
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png
new file mode 100644
index 0000000..f1b7036
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched
new file mode 100644
index 0000000..8bac71e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png
new file mode 100644
index 0000000..bebcc40
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched
new file mode 100644
index 0000000..2ed1725
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png
new file mode 100644
index 0000000..6eddc3f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched
new file mode 100644
index 0000000..c4746bc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png
new file mode 100644
index 0000000..3c4a50e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched
new file mode 100644
index 0000000..4c5e4f8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png
new file mode 100644
index 0000000..222c776
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched
new file mode 100644
index 0000000..8712b83
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png
new file mode 100644
index 0000000..cd206e2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched
new file mode 100644
index 0000000..5394ec8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png
new file mode 100644
index 0000000..3ec0791
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched
new file mode 100644
index 0000000..9236f57
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png
new file mode 100644
index 0000000..260a0a5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched
new file mode 100644
index 0000000..de46685
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png
new file mode 100644
index 0000000..09f2d58
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched
new file mode 100644
index 0000000..4f4fc1a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png
new file mode 100644
index 0000000..0c0ccda
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched
new file mode 100644
index 0000000..ef227d5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png
new file mode 100644
index 0000000..90528b1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched
new file mode 100644
index 0000000..f6bda4d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png
new file mode 100644
index 0000000..e6945db
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched
new file mode 100644
index 0000000..94fdd92
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png
new file mode 100644
index 0000000..561c9fa
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched
new file mode 100644
index 0000000..b7d7be2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png
new file mode 100644
index 0000000..32c6dc3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched
new file mode 100644
index 0000000..c947bc8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png
new file mode 100644
index 0000000..6857c0f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched
new file mode 100644
index 0000000..ea082c5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png
new file mode 100644
index 0000000..88f8765
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..0ff233c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png
new file mode 100644
index 0000000..fa68a13
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..3b12c00
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png
new file mode 100644
index 0000000..78c63cb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..1e14a5f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png
new file mode 100644
index 0000000..e13a9f8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..3f17950
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png
new file mode 100644
index 0000000..26d2e16
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..ac10349
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png
new file mode 100644
index 0000000..00ae92a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..9d3d3a0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png
new file mode 100644
index 0000000..66f0d88
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..468166c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png
new file mode 100644
index 0000000..10af163
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..4580132
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png
new file mode 100644
index 0000000..78e583c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..67746ac
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png
new file mode 100644
index 0000000..fb54f22
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..c365d47
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png
new file mode 100644
index 0000000..210832c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..a1ed738
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png
new file mode 100644
index 0000000..d0d9419
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..af1c42f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png
new file mode 100644
index 0000000..566543d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched
new file mode 100644
index 0000000..ab54e2d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png
new file mode 100644
index 0000000..7b883cc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched
new file mode 100644
index 0000000..e84b433
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png
new file mode 100644
index 0000000..be365ec
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..657b4a4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png
new file mode 100644
index 0000000..cd7b803
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..af06fcd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png
new file mode 100644
index 0000000..b1f25f0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched
new file mode 100644
index 0000000..724698a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png
new file mode 100644
index 0000000..6aab271
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched
new file mode 100644
index 0000000..aa42586
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png
new file mode 100644
index 0000000..aca9435
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched
new file mode 100644
index 0000000..9708644
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png
new file mode 100644
index 0000000..eafd44a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched
new file mode 100644
index 0000000..d945bec
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png
new file mode 100644
index 0000000..9024d07
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched
new file mode 100644
index 0000000..df6179e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png
new file mode 100644
index 0000000..37b5fef
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched
new file mode 100644
index 0000000..6eb2508
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png
new file mode 100644
index 0000000..4fbfa4f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched
new file mode 100644
index 0000000..870bb69
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png
new file mode 100644
index 0000000..0876bc6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched
new file mode 100644
index 0000000..74e8564
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png
new file mode 100644
index 0000000..810c950
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched
new file mode 100644
index 0000000..b1d1d05
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png
new file mode 100644
index 0000000..343e4ca
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched
new file mode 100644
index 0000000..fed1ea0
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png
new file mode 100644
index 0000000..a4be298
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched
new file mode 100644
index 0000000..d9b5423
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png
new file mode 100644
index 0000000..f2196fd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..d3f494d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png
new file mode 100644
index 0000000..f111d82
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..29afafa
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png
new file mode 100644
index 0000000..4e2ae0f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..ccd45ba
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png
new file mode 100644
index 0000000..479e504
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..96ed79a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png
new file mode 100644
index 0000000..933d99b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched
new file mode 100644
index 0000000..513b312
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png
new file mode 100644
index 0000000..7abe99a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched
new file mode 100644
index 0000000..9979ba5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png
new file mode 100644
index 0000000..9c5147e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched
new file mode 100644
index 0000000..6bb7773
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png
new file mode 100644
index 0000000..9c5147e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched
new file mode 100644
index 0000000..6bb7773
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png
new file mode 100644
index 0000000..a257e26
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..5a5bfa2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png
new file mode 100644
index 0000000..a257e26
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..5a5bfa2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png
new file mode 100644
index 0000000..dd999d6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched
new file mode 100644
index 0000000..750d2d4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png
new file mode 100644
index 0000000..dd999d6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched
new file mode 100644
index 0000000..750d2d4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png
new file mode 100644
index 0000000..b6009e6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched
new file mode 100644
index 0000000..000c660
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png
new file mode 100644
index 0000000..54d813c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched
new file mode 100644
index 0000000..d1b6d89
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png
new file mode 100644
index 0000000..8abf2ba
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched
new file mode 100644
index 0000000..d3d5a10
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png
new file mode 100644
index 0000000..8862fc7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched
new file mode 100644
index 0000000..224153e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png
new file mode 100644
index 0000000..7f40d36
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched
new file mode 100644
index 0000000..a0e5a6f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png
new file mode 100644
index 0000000..7f40d36
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched
new file mode 100644
index 0000000..a0e5a6f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png
new file mode 100644
index 0000000..4650d68
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched
new file mode 100644
index 0000000..b3e83c1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png
new file mode 100644
index 0000000..b43c592
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched
new file mode 100644
index 0000000..7135c4f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png
new file mode 100644
index 0000000..b43c592
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched
new file mode 100644
index 0000000..7135c4f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png
new file mode 100644
index 0000000..6d05735
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched
new file mode 100644
index 0000000..48cfa36
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png
new file mode 100644
index 0000000..d5d3cee
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched
new file mode 100644
index 0000000..748743c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png
new file mode 100644
index 0000000..c1f950c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched
new file mode 100644
index 0000000..c9b36eb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png
new file mode 100644
index 0000000..e7a0725
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched
new file mode 100644
index 0000000..adad3fc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png
new file mode 100644
index 0000000..c1f950c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched
new file mode 100644
index 0000000..c9b36eb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png
new file mode 100644
index 0000000..e7a0725
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched
new file mode 100644
index 0000000..adad3fc
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..673e3bf
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched
new file mode 100644
index 0000000..1e6ab9a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png
new file mode 100644
index 0000000..d57df98
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched
new file mode 100644
index 0000000..4dfaac8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..956d3c4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched
new file mode 100644
index 0000000..742afb5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png
new file mode 100644
index 0000000..50fcb52
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched
new file mode 100644
index 0000000..8c48806
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png
new file mode 100644
index 0000000..cdc7a4a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched
new file mode 100644
index 0000000..f57ee4f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..294991d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched
new file mode 100644
index 0000000..8d03d51
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png
new file mode 100644
index 0000000..19532ab
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched
new file mode 100644
index 0000000..c251c6a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..57e57e1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched
new file mode 100644
index 0000000..278f683
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png
new file mode 100644
index 0000000..c56254c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched
new file mode 100644
index 0000000..e9fcc4e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png
new file mode 100644
index 0000000..8a64d36
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched
new file mode 100644
index 0000000..ed569c6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png
new file mode 100644
index 0000000..2b50efa
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched
new file mode 100644
index 0000000..8057528
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png
new file mode 100644
index 0000000..8a64d36
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched
new file mode 100644
index 0000000..ed569c6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png
new file mode 100644
index 0000000..b7c5dcd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched
new file mode 100644
index 0000000..31cf4b6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png
new file mode 100644
index 0000000..1aaa9ae
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched
new file mode 100644
index 0000000..31cf4b6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png
new file mode 100644
index 0000000..a233b0d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched
new file mode 100644
index 0000000..4a47501
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png
new file mode 100644
index 0000000..403f502
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched
new file mode 100644
index 0000000..48b1922
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png
new file mode 100644
index 0000000..0ded801
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..bbfee93
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png
new file mode 100644
index 0000000..27237b8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..80d0a7e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png
new file mode 100644
index 0000000..0e451f1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..900695b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png
new file mode 100644
index 0000000..f7b6e99
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched
new file mode 100644
index 0000000..757679c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png
new file mode 100644
index 0000000..e6ef296
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched
new file mode 100644
index 0000000..430fb0a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png
new file mode 100644
index 0000000..7368261
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched
new file mode 100644
index 0000000..db979bb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png
new file mode 100644
index 0000000..3011502
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched
new file mode 100644
index 0000000..9cbcac5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png
new file mode 100644
index 0000000..29e33e3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..5979d8f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png
new file mode 100644
index 0000000..b70db4e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..8a0ec36
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png
new file mode 100644
index 0000000..73ec37c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..d217976
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png
new file mode 100644
index 0000000..a77d66d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..fe9e353
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png
new file mode 100644
index 0000000..e0f82eb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched
new file mode 100644
index 0000000..c612813
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png
new file mode 100644
index 0000000..03a81d9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..318d92d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png
new file mode 100644
index 0000000..03a81d9
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..318d92d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png
new file mode 100644
index 0000000..2993b44
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched
new file mode 100644
index 0000000..bdc7882
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png
new file mode 100644
index 0000000..3141caf
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched
new file mode 100644
index 0000000..31cf4b6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png
new file mode 100644
index 0000000..df7f770
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched
new file mode 100644
index 0000000..31cf4b6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png
new file mode 100644
index 0000000..ab381a6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched
new file mode 100644
index 0000000..430fb0a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png
new file mode 100644
index 0000000..ed1306c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched
new file mode 100644
index 0000000..db979bb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png
new file mode 100644
index 0000000..269e6b3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..5979d8f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png
new file mode 100644
index 0000000..e0a83fe
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..8a0ec36
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png
new file mode 100644
index 0000000..9f14a06
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched
new file mode 100644
index 0000000..d217976
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png
new file mode 100644
index 0000000..c458698
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched
new file mode 100644
index 0000000..fe9e353
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png
new file mode 100644
index 0000000..180d85c
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched
new file mode 100644
index 0000000..5e1b6b5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png
new file mode 100644
index 0000000..e075149
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched
new file mode 100644
index 0000000..6505a3b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png
new file mode 100644
index 0000000..4aad237
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched
new file mode 100644
index 0000000..168f8a3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png
new file mode 100644
index 0000000..db64da1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched
new file mode 100644
index 0000000..479adb6
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png
new file mode 100644
index 0000000..70c0e73
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched
new file mode 100644
index 0000000..2671d27
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png
new file mode 100644
index 0000000..36e71d8
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched
new file mode 100644
index 0000000..95781dd
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png
new file mode 100644
index 0000000..c0b84da
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched
new file mode 100644
index 0000000..9ed572e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png
new file mode 100644
index 0000000..0a0fc6b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched
new file mode 100644
index 0000000..1a3ce77
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png
new file mode 100644
index 0000000..04813c2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched
new file mode 100644
index 0000000..2c65a87
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png
new file mode 100644
index 0000000..cde51e4
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched
new file mode 100644
index 0000000..7c0b57d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png
new file mode 100644
index 0000000..4be4af5
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched
new file mode 100644
index 0000000..2c879af
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png
new file mode 100644
index 0000000..e72193f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched
new file mode 100644
index 0000000..a4af865
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png
new file mode 100644
index 0000000..8f20b9d
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched
new file mode 100644
index 0000000..acae791
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png
new file mode 100644
index 0000000..04f657e
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched
new file mode 100644
index 0000000..e22bb17
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png
new file mode 100644
index 0000000..f4bf352
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched
new file mode 100644
index 0000000..7e1aa59
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png
new file mode 100644
index 0000000..99309ef
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched
new file mode 100644
index 0000000..984332a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png
new file mode 100644
index 0000000..9bde7fb
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched
new file mode 100644
index 0000000..984332a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png
new file mode 100644
index 0000000..cf2cae3
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched
new file mode 100644
index 0000000..ee94991
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png
new file mode 100644
index 0000000..311a54a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched
new file mode 100644
index 0000000..750c623
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png
new file mode 100644
index 0000000..70f7cc2
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched
new file mode 100644
index 0000000..ea07e41
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png
new file mode 100644
index 0000000..e6dab63
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched
new file mode 100644
index 0000000..482e68b
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png
new file mode 100644
index 0000000..5c1a69f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched
new file mode 100644
index 0000000..93896f7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png
new file mode 100644
index 0000000..a804a8a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched
new file mode 100644
index 0000000..b924660
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png
new file mode 100644
index 0000000..ebd6f8a
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched
new file mode 100644
index 0000000..eeb0e01
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png
new file mode 100644
index 0000000..e97dac1
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png
Binary files differ
diff --git a/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched
new file mode 100644
index 0000000..a587635
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched
Binary files differ
diff --git a/build-system/changelog.txt b/build-system/changelog.txt
index a07b919..9bcb2b1 100644
--- a/build-system/changelog.txt
+++ b/build-system/changelog.txt
@@ -1,3 +1,177 @@
+0.11.1:
+- Fix issue with artifact depending on android.jar artifact on MavenCentral.
+- Fix issue with missing custom namespace declaration in generated manifest.
+- Fix issue with validation of permission group in manifest merger.
+
+0.11.0:
+- Updated IDE model, requires Studio 0.6
+- New Manifest merger is now the default merger.
+ - lots of fixes
+ - added ability to add custom placeholders for merger.
+
+- Replaced the various DSL properties used to define the "package
+ name" with an "application ID", to decouple the persistent ID of the
+ application from the implementation package used to contain for
+ example the R and BuildConfig classes.
+ packageName => applicationId
+ packageNameSuffix => applicationIdSuffix
+ testPackageName => testApplicationId
+ testedPackageName => testedApplicationId
+- min/targetSdkVersion on ProductFlavor is now a ApiVersion which contains both an integer and a string.
+- DSL impact: cannot use setter: flavor {minSdkVersion = 9}, must use method: flavor { minSdkVersion 9}, due to a groovy limitation preventing overloaded setters.
+
+- Moved files and folders around in the buildDir for better IDE integration.
+- Generated APK can now be published. Same configuration as libraries with defaultPublishConfig and publishNonDefault flags.
+
+
+0.10.2:
+
+- More fixes on the Manifest merger, including better handling of minSdkVersion.
+- More lint fixes.
+- Fixed incremental dex support (still needs to be enabled)
+
+0.10.1:
+
+- fixed some issues with the new manifest merger. Please keep sending us feedback.
+- fixed issue with uninstall task.
+- lots of lint fixes and new checks. For instance you can use lint to enforce resource prefix in your library.
+
+0.10.0:
+- New manifest merger
+- test code coverage support with Jacoco
+- Pre-dex cache (in rootProject/build). Shared across modules and variants
+- Exploded aar are extracted in a single location (under rootProject/build) to share across all modules using it.
+- Upgraded to Proguard 4.11. Fixed incremental issues.
+- Fixed incremental issues with aidl files.
+
+0.9.2:
+- Aapt-based PNG processor is now default again while we investigate some issues with the old one.
+- flavorGroups have been renamed flavorDimensions and the DSL has been updated. The old DSL is still available until 1.0 at which time it'll be removed.
+
+0.9.1:
+- It's now possible to include a file when there's a conflict during packaging:
+ android.packagingOptions {
+ pickFirst 'META-INF/foo.txt'
+ }
+- New PNG processor.
+ * Should be much faster when processing many files
+ * Fix issue where crunched png are bigger than original file
+ * To revert to the old cruncher: android.aaptOptions.useAaptPngCruncher = true
+- The plugin now enforces that all library dependencies have a unique package name.
+ To disable this you can use android.enforceUniquePackageName = false
+ WARNING: The ability to disable enforcement will disappear in 1.0
+- Fixes:
+ * Generated POM files now have the proper dependencies even if the pom object is manipulated in build.gradle
+ * libraryVariant API now gives access to the list of flavors.
+ * fixed issue where changes to the manifests of libraries didn't trigger a new manifest merge.
+ * BuildConfig.VERSION_NAME is always generated even if the value is not set in the current variant.
+ * BuildConfig is now package in the library. This requires that all your libraries have a unique package name.
+ If you are disabling enforcement of package name, then you should disable packaging of BuildConfig with:
+ android.packageBuildConfig = false
+ WARNING: the ability to disable packaging will disappear in 1.0
+
+0.9.0:
+- Compatible with Gradle 1.10 and 1.11
+- BREAKING CHANGES:
+ * DSL for Library Projects is now the same as for app projects, meaning you can create more Build Types, as well as ProductFlavors.
+ * instrumentTest (both default folders and DSL objects) renamed androidTest
+
+- In preparation for a final variant publishing mechanism, flavors in Libraries can be published alongside the default configuration.
+ The default publishing configuration is configured with
+ android.defaultPublishConfig
+ Default Value is "release", but can be changed to be the name of any variant.
+ To enable publication all the variants, use:
+ android.publishNonDefault = true
+ To use from another project:
+ compile project(path: ':project', configuration: 'flavor1Debug')
+ See 'FlavoredLib' sample.
+ Note that this does not really solve the issue with library being published with 'release' mode always. This is because you would have to manually
+ specify which variant you want to reference in each of the configuration of the app project. A better mechanism will come later.
+- Ability to skip some variants. Create a closure to control which variants should be created.
+ android.variantFilter { variant ->
+ ...
+ }
+
+ The object passed to the closure implements the following methods:
+ public void setIgnore(boolean ignore);
+ @NonNull
+ public ProductFlavor getDefaultConfig();
+ @NonNull
+ public BuildType getBuildType();
+ @NonNull
+ public List<ProductFlavor> getFlavors();
+ To skip a variant, call setIgnore(false)
+- Library dependency scopes are now 'provided', 'compile', 'publish'.
+ The 'publish' and 'apk' configurations don't extend 'compile' anymore but the composite configurations are still properly setup.
+- Fix issue where variant specific source folders where not used for java compilation.
+- Fix for some Renderscript support mode compatibility issues. Requires Build Tools 19.0.3
+- Lots of misc fixes.
+
+0.8.3:
+
+- Fix Studio integration regression.
+
+0.8.2:
+- Fix incremental issue with build config fields and generated res values.
+
+0.8.1:
+- Added the ability to create resource values through the DSL.
+ You can now use 'resValue <type>,<name>,<value>' on build types and product flavors
+ the same way you can use buildConfigField.
+- Fixed package renaming in activity-alias:targetActivity
+- Variant API improvements:
+ * packageName returns the variant's package name
+ * versionCode returns the (app/test) variant's versionCode
+ * versionName returns the (app/test/) variant's versionName. Can return null.
+
+0.8.0
+
+- Support for Gradle 1.10
+- Requires Build-Tools 19.0.0+
+- Fixed issue 64302: Add renderscript support mode jar to the dependencies in the IDE model.
+- Fixed issue 64094: buildConfigField can now replace previous values inside the same type/flavors.
+- Add support for NDK prebuilts in library projects.
+- Parallelize pre-dexing to speed up clean builds.
+- Incremental dexing re-enabled (though it'll be automatically disabled in some builds for some cases that dx doesn't support yet.)
+- Added 'provided' dependency scope for compile only (not packaged) dependencies.
+ Additional scope per buildtype and flavors are also available (debugProvided, myFlavorProvided,etc...)
+- Fix NDK on windows.
+- Variant API improvements:
+ * getPreBuild() returns the prebuild task for the variant
+ * getSourceSets() returns the sorted sourcesets for the task, from lower to higher priority
+ * createZipAlignTask(String taskName, File inputFile, File outputFile)
+ This creates and return a new zipalign task. Useful if you have a custom plugin providing custom signing of APKs.
+ This also makes the assemble task depend on the new zipalign task, and wires variant.getOutputFile() to return the result of the zipalign task.
+ * project.android.registerJavaArtifact() now receives a Configuration object to pass the dependencies to the IDE. See artifactApi sample.
+- New "lintVital" task, run automatically as part of assembling release variants, which checks only fatal-severity issues
+- Replace Java parser in lint with ECJ; much faster and fixes bug where lint could hang on certain source constructs
+- Lint HTML report now writes links to source files and images as URLs relative to the report location
+
+0.7.3
+
+- Rebuild 0.7.2 to work with Java6
+
+0.7.2
+
+- Fix issue with Proguard.
+- Add packagingOptions support in Library projects.
+- Solve issue with local jar when testing library projects.
+- Fix bug with variant.addJavaSourceFoldersToModel
+- Add jniLibs folder to source sets for prebuilt .so files.
+- Lint fixes:
+ * fix RTL detector
+ * fix HTML report to have valid HTML
+
+0.7.1
+
+- DSL to exclude some files coming from jar dependencies
+ android {
+ packagingOptions {
+ exclude 'META-INF/LICENSE.txt'
+ }
+ }
+
+
0.7.0
- Requires Gradle 1.9
- You can now have a variant specific source folder if you have flavors.
diff --git a/build-system/gradle-model/build.gradle b/build-system/gradle-model/build.gradle
index 8567674..20e19dd 100644
--- a/build-system/gradle-model/build.gradle
+++ b/build-system/gradle-model/build.gradle
@@ -2,6 +2,13 @@
apply plugin: 'clone-artifacts'
apply plugin: 'idea'
+if (System.env.USE_GRADLE_REPO != null) {
+ repositories {
+ maven { url 'http://repo.gradle.org/gradle/libs-snapshots-local' }
+ maven { url 'http://repo.gradle.org/gradle/libs-releases-local' }
+ }
+}
+
def toolingApiVersion = gradle.gradleVersion
// Custom config that cloneArtifact will not look into, since this
@@ -11,10 +18,10 @@
}
dependencies {
- compile project(':builder-model')
+ compile project(':base:builder-model')
testCompile 'junit:junit:3.8.1'
- testCompile project(':builder')
+ testCompile project(':base:builder')
// Need an SLF4J implementation at runtime
testRuntime 'org.slf4j:slf4j-simple:1.7.2'
@@ -30,13 +37,10 @@
sourceSets.test.compileClasspath += configurations.gradleRepo
sourceSets.test.runtimeClasspath += configurations.gradleRepo
-test.dependsOn ':gradle:publishLocal'
+test.dependsOn ':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
index 12701a8..4db99f8 100644
--- a/build-system/gradle-model/gradle-model.iml
+++ b/build-system/gradle-model/gradle-model.iml
@@ -4,15 +4,72 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/../../../../out/host/gradle/tools/base/gradle-model" />
+ <excludeFolder url="file://$MODULE_DIR$/.gradle" />
</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" />
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/gradle/gradle-tooling-api/1.12/gradle-tooling-api-1.12.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/gradle/gradle-tooling-api/1.12/gradle-tooling-api-1.12-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-api/1.7.5/slf4j-api-1.7.5.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-api/1.7.5/slf4j-api-1.7.5-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module" module-name="builder-model" exported="" />
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/junit/junit/3.8.1/junit-3.8.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/junit/junit/3.8.1/junit-3.8.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-simple/1.7.2/slf4j-simple-1.7.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-simple/1.7.2/slf4j-simple-1.7.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-api/1.7.2/slf4j-api-1.7.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-api/1.7.2/slf4j-api-1.7.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module" module-name="builder" scope="TEST" />
</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
index 4da4114..9f55db7 100644
--- 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
@@ -16,26 +16,31 @@
package com.android.build.gradle.model;
-import static com.android.builder.model.AndroidProject.ARTIFACT_INSTRUMENT_TEST;
+import static com.android.builder.core.BuilderConstants.ANDROID_TEST;
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST;
+import com.android.SdkConstants;
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.AndroidArtifactOutput;
import com.android.builder.model.AndroidLibrary;
import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
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.JavaLibrary;
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.ide.common.signing.KeystoreHelper;
import com.android.prefs.AndroidLocation;
import com.google.common.collect.Maps;
@@ -52,12 +57,13 @@
import java.security.CodeSource;
import java.security.KeyStore;
import java.util.Collection;
+import java.util.List;
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 String MODEL_VERSION = "0.12.2";
private static final Map<String, ProjectData> sProjectModelMap = Maps.newHashMap();
@@ -74,6 +80,80 @@
}
}
+ private static final class DefaultApiVersion implements ApiVersion {
+ private final int mApiLevel;
+
+ @Nullable
+ private final String mCodename;
+
+ public DefaultApiVersion(int apiLevel, @Nullable String codename) {
+ mApiLevel = apiLevel;
+ mCodename = codename;
+ }
+
+ public DefaultApiVersion(int apiLevel) {
+ this(apiLevel, null);
+ }
+
+ public DefaultApiVersion(@NonNull String codename) {
+ this(1, codename);
+ }
+
+ public static ApiVersion create(@NonNull Object value) {
+ if (value instanceof Integer) {
+ return new DefaultApiVersion((Integer) value, null);
+ } else if (value instanceof String) {
+ return new DefaultApiVersion(1, (String) value);
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getApiLevel() {
+ return mApiLevel;
+ }
+
+ @Nullable
+ @Override
+ public String getCodename() {
+ return mCodename;
+ }
+
+ @NonNull
+ @Override
+ public String getApiString() {
+ return mCodename != null ? mCodename : Integer.toString(mApiLevel);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ /**
+ * Normally equals only test for the same exact class, but here me make it accept
+ * ApiVersion since we're comparing it against implementations that are serialized
+ * across Gradle's tooling api.
+ */
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ApiVersion)) {
+ return false;
+ }
+
+ ApiVersion that = (ApiVersion) o;
+
+ if (mApiLevel != that.getApiLevel()) {
+ return false;
+ }
+ if (mCodename != null ? !mCodename.equals(that.getCodename()) : that.getCodename() != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ }
+
private ProjectData getModelForProject(String projectName) {
ProjectData projectData = sProjectModelMap.get(projectName);
@@ -161,7 +241,7 @@
AndroidProject model = projectData.model;
assertFalse("Library Project", model.isLibrary());
- assertEquals("Compile Target", "android-15", model.getCompileTarget());
+ assertEquals("Compile Target", "android-19", model.getCompileTarget());
assertFalse("Non empty bootclasspath", model.getBootClasspath().isEmpty());
JavaCompileOptions javaCompileOptions = model.getJavaCompileOptions();
@@ -210,13 +290,13 @@
assertEquals(1, pfContainer.getExtraSourceProviders().size());
SourceProviderContainer container = getSourceProviderContainer(
- pfContainer.getExtraSourceProviders(), ARTIFACT_INSTRUMENT_TEST);
+ pfContainer.getExtraSourceProviders(), ARTIFACT_ANDROID_TEST);
assertNotNull(container);
new SourceProviderTester(
model.getName(),
projectDir,
- "instrumentTest" + StringHelper.capitalize(name),
+ ANDROID_TEST + StringHelper.capitalize(name),
container.getSourceProvider())
.test();
}
@@ -229,7 +309,8 @@
}
}
- private void testDefaultSourceSets(@NonNull AndroidProject model, @NonNull File projectDir) {
+ private static void testDefaultSourceSets(@NonNull AndroidProject model,
+ @NonNull File projectDir) {
ProductFlavorContainer defaultConfig = model.getDefaultConfig();
// test the main source provider
@@ -239,11 +320,11 @@
// test the main instrumentTest source provider
SourceProviderContainer testSourceProviders = getSourceProviderContainer(
- defaultConfig.getExtraSourceProviders(), ARTIFACT_INSTRUMENT_TEST);
+ defaultConfig.getExtraSourceProviders(), ARTIFACT_ANDROID_TEST);
assertNotNull("InstrumentTest source Providers null-check", testSourceProviders);
new SourceProviderTester(model.getName(), projectDir,
- "instrumentTest", testSourceProviders.getSourceProvider())
+ ANDROID_TEST, testSourceProviders.getSourceProvider())
.test();
// test the source provider for the build types
@@ -284,30 +365,46 @@
.setTestFunctionalTest(null)
.test();
+ // debug variant, tested.
AndroidArtifact debugMainInfo = debugVariant.getMainArtifact();
assertNotNull("Debug main info null-check", debugMainInfo);
assertEquals("Debug package name", "com.android.tests.basic.debug",
- debugMainInfo.getPackageName());
+ debugMainInfo.getApplicationId());
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();
-
+ Collection<AndroidArtifactOutput> debugMainOutputs = debugMainInfo.getOutputs();
+ assertNotNull("Debug main output null-check", debugMainOutputs);
+ assertEquals("Debug main output size", 1, debugMainOutputs.size());
+ AndroidArtifactOutput debugMainOutput = debugMainOutputs.iterator().next();
+ assertNotNull(debugMainOutput);
+ assertNotNull(debugMainOutput.getOutputFile());
+ assertNotNull(debugMainOutput.getAssembleTaskName());
+ assertNotNull(debugMainOutput.getGeneratedManifest());
// this variant is tested.
+ Collection<AndroidArtifact> debugExtraAndroidArtifacts = debugVariant.getExtraAndroidArtifacts();
AndroidArtifact debugTestInfo = getAndroidArtifact(debugExtraAndroidArtifacts,
- ARTIFACT_INSTRUMENT_TEST);
+ ARTIFACT_ANDROID_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());
+ debugTestInfo.getApplicationId());
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());
+ Collection<AndroidArtifactOutput> debugTestOutputs = debugTestInfo.getOutputs();
+ assertNotNull("Debug test output null-check", debugTestOutputs);
+ assertEquals("Debug test output size", 1, debugTestOutputs.size());
+ AndroidArtifactOutput debugTestOutput = debugTestOutputs.iterator().next();
+ assertNotNull(debugTestOutput);
+ assertNotNull(debugTestOutput.getOutputFile());
+ assertNotNull(debugTestOutput.getAssembleTaskName());
+ assertNotNull(debugTestOutput.getGeneratedManifest());
+
// release variant, not tested.
Variant releaseVariant = getVariant(variants, "release");
assertNotNull("release Variant null-check", releaseVariant);
@@ -315,20 +412,30 @@
AndroidArtifact relMainInfo = releaseVariant.getMainArtifact();
assertNotNull("Release main info null-check", relMainInfo);
assertEquals("Release package name", "com.android.tests.basic",
- relMainInfo.getPackageName());
+ relMainInfo.getApplicationId());
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<AndroidArtifactOutput> relMainOutputs = relMainInfo.getOutputs();
+ assertNotNull("Rel Main output null-check", relMainOutputs);
+ assertEquals("Rel Main output size", 1, relMainOutputs.size());
+ AndroidArtifactOutput relMainOutput = relMainOutputs.iterator().next();
+ assertNotNull(relMainOutput);
+ assertNotNull(relMainOutput.getOutputFile());
+ assertNotNull(relMainOutput.getAssembleTaskName());
+ assertNotNull(relMainOutput.getGeneratedManifest());
+
+
Collection<AndroidArtifact> releaseExtraAndroidArtifacts = releaseVariant.getExtraAndroidArtifacts();
- AndroidArtifact relTestInfo = getAndroidArtifact(releaseExtraAndroidArtifacts, ARTIFACT_INSTRUMENT_TEST);
+ AndroidArtifact relTestInfo = getAndroidArtifact(releaseExtraAndroidArtifacts, ARTIFACT_ANDROID_TEST);
assertNull("Release test info null-check", relTestInfo);
// check debug dependencies
Dependencies dependencies = debugMainInfo.getDependencies();
assertNotNull(dependencies);
- assertEquals(2, dependencies.getJars().size());
+ assertEquals(2, dependencies.getJavaLibraries().size());
assertEquals(1, dependencies.getLibraries().size());
AndroidLibrary lib = dependencies.getLibraries().iterator().next();
@@ -385,11 +492,11 @@
.test();
SourceProviderContainer testSourceProviderContainer = getSourceProviderContainer(
- defaultConfig.getExtraSourceProviders(), ARTIFACT_INSTRUMENT_TEST);
+ defaultConfig.getExtraSourceProviders(), ARTIFACT_ANDROID_TEST);
assertNotNull("InstrumentTest source Providers null-check", testSourceProviderContainer);
new SourceProviderTester(model.getName(), projectDir,
- "instrumentTest", testSourceProviderContainer.getSourceProvider())
+ ANDROID_TEST, testSourceProviderContainer.getSourceProvider())
.setJavaDir("tests/java")
.setResourcesDir("tests/resources")
.setAidlDir("tests/aidl")
@@ -422,9 +529,41 @@
"Null-check on mainArtifactInfo for " + variant.getDisplayName(),
mainInfo);
+ AndroidArtifactOutput output = mainInfo.getOutputs().iterator().next();
+
assertEquals("Output file for " + variant.getName(),
new File(buildDir, variant.getName() + ".apk"),
- mainInfo.getOutputFile());
+ output.getOutputFile());
+ }
+ }
+
+ public void testFilteredOutBuildType() {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("filteredOutBuildType");
+
+ AndroidProject model = projectData.model;
+
+ assertEquals("Variant Count", 1, model.getVariants().size());
+ Variant variant = model.getVariants().iterator().next();
+ assertEquals("Variant name", "release", variant.getBuildType());
+ }
+
+ public void testFilteredOutVariants() {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("filteredOutVariants");
+
+ AndroidProject model = projectData.model;
+
+ Collection<Variant> variants = model.getVariants();
+ // check we have the right number of variants:
+ // arm/cupcake, arm/gingerbread, x86/gingerbread, mips/gingerbread
+ // all 4 in release and debug
+ assertEquals("Variant Count", 8, variants.size());
+
+ for (Variant variant : variants) {
+ List<String> flavors = variant.getProductFlavors();
+ assertFalse("check ignored x86/cupcake", flavors.contains("x68") && flavors.contains("cupcake"));
+ assertFalse("check ignored mips/cupcake", flavors.contains("mips") && flavors.contains("cupcake"));
}
}
@@ -446,11 +585,11 @@
.test();
SourceProviderContainer testSourceProviderContainer = getSourceProviderContainer(
- defaultConfig.getExtraSourceProviders(), ARTIFACT_INSTRUMENT_TEST);
+ defaultConfig.getExtraSourceProviders(), ARTIFACT_ANDROID_TEST);
assertNotNull("InstrumentTest source Providers null-check", testSourceProviderContainer);
new SourceProviderTester(model.getName(), projectDir,
- "instrumentTest", testSourceProviderContainer.getSourceProvider())
+ ANDROID_TEST, testSourceProviderContainer.getSourceProvider())
.test();
Collection<BuildTypeContainer> buildTypes = model.getBuildTypes();
@@ -463,7 +602,7 @@
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();
+ new VariantTester(f1faDebugVariant, projectDir, "flavors-f1-fa-debug.apk").test();
}
public void testTicTacToe() throws Exception {
@@ -493,7 +632,7 @@
assertEquals("Dependency project path", ":lib", androidLibrary.getProject());
// TODO: right now we can only test the folder name efficiently
- assertEquals("TictactoeLibUnspecified.aar", androidLibrary.getFolder().getName());
+ assertTrue(androidLibrary.getFolder().getPath().endsWith("/tictactoe/lib/unspecified"));
}
public void testFlavorLib() throws Exception {
@@ -521,8 +660,9 @@
assertEquals(1, libs.size());
AndroidLibrary androidLibrary = libs.iterator().next();
assertNotNull(androidLibrary);
+ assertEquals(":lib1", androidLibrary.getProject());
// TODO: right now we can only test the folder name efficiently
- assertEquals("FlavorlibLib1Unspecified.aar", androidLibrary.getFolder().getName());
+ assertTrue(androidLibrary.getFolder().getPath(), androidLibrary.getFolder().getPath().endsWith("/flavorlib/lib1/unspecified"));
ProductFlavorContainer flavor2 = getProductFlavor(productFlavors, "flavor2");
assertNotNull(flavor2);
@@ -537,8 +677,58 @@
assertEquals(1, libs.size());
androidLibrary = libs.iterator().next();
assertNotNull(androidLibrary);
+ assertEquals(":lib2", androidLibrary.getProject());
// TODO: right now we can only test the folder name efficiently
- assertEquals("FlavorlibLib2Unspecified.aar", androidLibrary.getFolder().getName());
+ assertTrue(androidLibrary.getFolder().getPath(), androidLibrary.getFolder().getPath().endsWith("/flavorlib/lib2/unspecified"));
+ }
+
+ public void testFlavoredLib() throws Exception {
+ Map<String, ProjectData> map = getModelForMultiProject("flavoredlib");
+
+ 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);
+ assertEquals(":lib", androidLibrary.getProject());
+ assertEquals("flavor1Release", androidLibrary.getProjectVariant());
+ // TODO: right now we can only test the folder name efficiently
+ assertTrue(androidLibrary.getFolder().getPath(), androidLibrary.getFolder().getPath().endsWith("/flavoredlib/lib/unspecified/flavor1Release"));
+
+ 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);
+ assertEquals(":lib", androidLibrary.getProject());
+ assertEquals("flavor2Release", androidLibrary.getProjectVariant());
+ // TODO: right now we can only test the folder name efficiently
+ assertTrue(androidLibrary.getFolder().getPath(), androidLibrary.getFolder().getPath().endsWith("/flavoredlib/lib/unspecified/flavor2Release"));
}
public void testMultiproject() throws Exception {
@@ -565,10 +755,10 @@
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);
+ Collection<JavaLibrary> javaLibraries = dependencies.getJavaLibraries();
+ assertNotNull("jar dep list null-check", javaLibraries);
// TODO these are jars coming from ':util' They shouldn't be there.
- assertEquals("jar dep count", 2, jars.size());
+ assertEquals("jar dep count", 2, javaLibraries.size());
}
public void testTestWithDep() {
@@ -583,11 +773,62 @@
Collection<AndroidArtifact> extraAndroidArtifact = debugVariant.getExtraAndroidArtifacts();
AndroidArtifact testArtifact = getAndroidArtifact(extraAndroidArtifact,
- ARTIFACT_INSTRUMENT_TEST);
+ ARTIFACT_ANDROID_TEST);
assertNotNull(testArtifact);
Dependencies testDependencies = testArtifact.getDependencies();
- assertEquals(1, testDependencies.getJars().size());
+ assertEquals(1, testDependencies.getJavaLibraries().size());
+ }
+
+ public void testLibTestDep() {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("libTestDep");
+
+ 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_ANDROID_TEST);
+ assertNotNull(testArtifact);
+
+ Dependencies testDependencies = testArtifact.getDependencies();
+ Collection<JavaLibrary> javaLibraries = testDependencies.getJavaLibraries();
+ assertEquals(2, javaLibraries.size());
+ for (JavaLibrary lib : javaLibraries) {
+ File f = lib.getJarFile();
+ assertTrue(f.getName().equals("guava-11.0.2.jar") || f.getName().equals("jsr305-1.3.9.jar"));
+ }
+ }
+
+ public void testRsSupportMode() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("rsSupportMode");
+
+ AndroidProject model = projectData.model;
+ File projectDir = projectData.projectDir;
+
+ Variant debugVariant = getVariant(model.getVariants(), "x86Debug");
+ assertNotNull("x86Debug variant null-check", debugVariant);
+
+ AndroidArtifact mainArtifact = debugVariant.getMainArtifact();
+ Dependencies dependencies = mainArtifact.getDependencies();
+
+ assertFalse(dependencies.getJavaLibraries().isEmpty());
+
+ boolean foundSupportJar = false;
+ for (JavaLibrary lib : dependencies.getJavaLibraries()) {
+ File file = lib.getJarFile();
+ if (SdkConstants.FN_RENDERSCRIPT_V8_JAR.equals(file.getName())) {
+ foundSupportJar = true;
+ break;
+ }
+ }
+
+ assertTrue("Found suppport jar check", foundSupportJar);
}
@@ -624,6 +865,39 @@
}
}
+ public void testGenFolderApi2() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("genFolderApi2");
+
+ 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");
@@ -636,7 +910,7 @@
assertEquals("Extra artifact size check", 2, extraArtifacts.size());
assertNotNull("instrument test metadata null-check",
- getArtifactMetaData(extraArtifacts, ARTIFACT_INSTRUMENT_TEST));
+ getArtifactMetaData(extraArtifacts, ARTIFACT_ANDROID_TEST));
// get the custom one.
ArtifactMetaData extraArtifactMetaData = getArtifactMetaData(extraArtifacts, "__test__");
@@ -686,7 +960,7 @@
assertNotNull(
"Extra source provider container for product flavor '" + name + "': instTest check",
- getSourceProviderContainer(extraSourceProviderContainers, ARTIFACT_INSTRUMENT_TEST));
+ getSourceProviderContainer(extraSourceProviderContainers, ARTIFACT_ANDROID_TEST));
SourceProviderContainer sourceProviderContainer = getSourceProviderContainer(
@@ -720,9 +994,66 @@
SourceProvider variantSourceProvider = javaArtifact.getVariantSourceProvider();
assertNotNull(variantSourceProvider);
assertEquals("provider:" + name, variantSourceProvider.getManifestFile().getPath());
+
+ Dependencies deps = javaArtifact.getDependencies();
+ assertNotNull("java artifact deps null-check", deps);
+ assertFalse(deps.getJavaLibraries().isEmpty());
}
}
+ public void testCustomArtifact() throws Exception {
+ // Load the custom model for the projects
+ Map<String, ProjectData> map = getModelForMultiProject("customArtifactDep");
+
+ ProjectData appModelData = map.get(":app");
+ assertNotNull("Module app null-check", appModelData);
+ AndroidProject model = appModelData.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);
+ assertTrue("project dep empty check", projects.isEmpty());
+
+ Collection<JavaLibrary> javaLibraries = dependencies.getJavaLibraries();
+ assertNotNull("jar dep list null-check", javaLibraries);
+ assertEquals("jar dep count", 1, javaLibraries.size());
+ }
+
+ public void testLocalJarInLib() throws Exception {
+ Map<String, ProjectData> map = getModelForMultiProject("localJars");
+
+ ProjectData libModelData = map.get(":baseLibrary");
+ assertNotNull("Module app null-check", libModelData);
+ AndroidProject model = libModelData.model;
+
+ Collection<Variant> variants = model.getVariants();
+
+ Variant releaseVariant = getVariant(variants, "release");
+ assertNotNull(releaseVariant);
+
+ Dependencies dependencies = releaseVariant.getMainArtifact().getDependencies();
+ assertNotNull(dependencies);
+
+ Collection<JavaLibrary> javaLibraries = dependencies.getJavaLibraries();
+ assertNotNull(javaLibraries);
+
+ // com.google.guava:guava:11.0.2
+ // \--- com.google.code.findbugs:jsr305:1.3.9
+ // + the local jar
+ assertEquals(3, javaLibraries.size());
+ }
+
/**
* Returns the SDK folder as built from the Android source tree.
* @return the SDK
@@ -754,7 +1085,7 @@
if (System.getenv("IDE_MODE") != null) {
f = dir.getParentFile().getParentFile().getParentFile();
} else {
- f = dir.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile();
+ f = dir.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile();
f = new File(f, "tools" + File.separator + "base" + File.separator + "build-system");
}
return f;
@@ -868,13 +1199,13 @@
@NonNull private final ProductFlavor productFlavor;
@NonNull private final String name;
- private String packageName = null;
+ private String applicationId = null;
private int versionCode = -1;
private String versionName = null;
- private int minSdkVersion = -1;
- private int targetSdkVersion = -1;
+ private ApiVersion minSdkVersion = null;
+ private ApiVersion targetSdkVersion = null;
private int renderscriptTargetApi = -1;
- private String testPackageName = null;
+ private String testApplicationId = null;
private String testInstrumentationRunner = null;
private Boolean testHandleProfiling = null;
private Boolean testFunctionalTest = null;
@@ -884,8 +1215,8 @@
this.name = name;
}
- ProductFlavorTester setPackageName(String packageName) {
- this.packageName = packageName;
+ ProductFlavorTester setApplicationId(String applicationId) {
+ this.applicationId = applicationId;
return this;
}
@@ -900,12 +1231,12 @@
}
ProductFlavorTester setMinSdkVersion(int minSdkVersion) {
- this.minSdkVersion = minSdkVersion;
+ this.minSdkVersion = new DefaultApiVersion(minSdkVersion);
return this;
}
ProductFlavorTester setTargetSdkVersion(int targetSdkVersion) {
- this.targetSdkVersion = targetSdkVersion;
+ this.targetSdkVersion = new DefaultApiVersion(targetSdkVersion);
return this;
}
@@ -914,8 +1245,8 @@
return this;
}
- ProductFlavorTester setTestPackageName(String testPackageName) {
- this.testPackageName = testPackageName;
+ ProductFlavorTester setTestApplicationId(String testApplicationId) {
+ this.testApplicationId = testApplicationId;
return this;
}
@@ -935,7 +1266,7 @@
}
void test() {
- assertEquals(name + ":packageName", packageName, productFlavor.getPackageName());
+ assertEquals(name + ":applicationId", applicationId, productFlavor.getApplicationId());
assertEquals(name + ":VersionCode", versionCode, productFlavor.getVersionCode());
assertEquals(name + ":VersionName", versionName, productFlavor.getVersionName());
assertEquals(name + ":minSdkVersion", minSdkVersion, productFlavor.getMinSdkVersion());
@@ -943,8 +1274,8 @@
targetSdkVersion, productFlavor.getTargetSdkVersion());
assertEquals(name + ":renderscriptTargetApi",
renderscriptTargetApi, productFlavor.getRenderscriptTargetApi());
- assertEquals(name + ":testPackageName",
- testPackageName, productFlavor.getTestPackageName());
+ assertEquals(name + ":testApplicationId",
+ testApplicationId, productFlavor.getTestApplicationId());
assertEquals(name + ":testInstrumentationRunner",
testInstrumentationRunner, productFlavor.getTestInstrumentationRunner());
assertEquals(name + ":testHandleProfiling",
@@ -1070,13 +1401,18 @@
String variantName = variant.getName();
File build = new File(projectDir, "build");
- File apk = new File(build, "apk/" + outputFileName);
- assertEquals(variantName + " output", apk, artifact.getOutputFile());
+ File apk = new File(build, "outputs/apk/" + outputFileName);
Collection<File> sourceFolders = artifact.getGeneratedSourceFolders();
assertEquals("Gen src Folder count", 4, sourceFolders.size());
- File manifest = artifact.getGeneratedManifest();
+ Collection<AndroidArtifactOutput> outputs = artifact.getOutputs();
+ assertNotNull(outputs);
+ assertEquals(1, outputs.size());
+ AndroidArtifactOutput output = outputs.iterator().next();
+
+ assertEquals(variantName + " output", apk, output.getOutputFile());
+ File manifest = output.getGeneratedManifest();
assertNotNull(manifest);
}
}
diff --git a/build-system/gradle/build.gradle b/build-system/gradle/build.gradle
index b3d559a..3e5d2c7 100644
--- a/build-system/gradle/build.gradle
+++ b/build-system/gradle/build.gradle
@@ -2,16 +2,10 @@
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'
+ groovy.srcDirs = ['src/main/groovy', 'src/fromGradle/groovy']
+ resources.srcDirs = ['src/main/resources', 'src/fromGradle/resources']
}
buildTest {
groovy.srcDir file('src/build-test/groovy')
@@ -23,12 +17,14 @@
}
}
+ext.proguardVersion = "4.11"
+
dependencies {
- gradleApi gradleApi()
- groovy localGroovy()
- compile project(':builder')
- compile project(':lint')
- compile 'net.sf.proguard:proguard-gradle:4.10'
+ compile gradleApi()
+ compile localGroovy()
+ compile project(':base:builder')
+ compile project(':base:lint')
+ compile "net.sf.proguard:proguard-gradle:${project.ext.proguardVersion}"
testCompile 'junit:junit:3.8.1'
@@ -51,13 +47,12 @@
}
dependencies{
- provided 'net.sf.proguard:proguard-gradle:4.10'
+ provided "net.sf.proguard:proguard-gradle:${project.ext.proguardVersion}"
}
//Include provided for compilation
sourceSets.main.compileClasspath += configurations.provided
-/*
idea {
module {
testSourceDirs += files('src/build-test/groovy', 'src/device-test/groovy').files
@@ -65,20 +60,19 @@
scopes.COMPILE.plus += configurations.provided
}
}
-*/
group = 'com.android.tools.build'
archivesBaseName = 'gradle'
+version = rootProject.ext.buildVersion
+
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'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
jar.manifest.attributes("Plugin-Version": version)
-publishLocal.dependsOn ':builder:publishLocal'
-task buildTest(type: Test, dependsOn: publishLocal) {
+task buildTest(type: Test) {
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."
@@ -86,7 +80,10 @@
systemProperties['jar.path'] = jar.archivePath
}
-task deviceTest(type: Test, dependsOn: publishLocal) {
+buildTest.dependsOn ':publishLocal'
+check.dependsOn buildTest
+
+task deviceTest(type: Test) {
testClassesDir = sourceSets.deviceTest.output.classesDir
classpath = sourceSets.deviceTest.runtimeClasspath
description = "Runs the device tests. This requires a device."
@@ -94,7 +91,7 @@
systemProperties['jar.path'] = jar.archivePath
}
-check.dependsOn buildTest
+deviceTest.dependsOn ':publishLocal'
groovydoc {
@@ -116,7 +113,3 @@
artifacts {
archives javadocJar
}
-
-apply plugin: 'distrib'
-shipping.isShipping = false
-
diff --git a/build-system/gradle/gradle.iml b/build-system/gradle/gradle.iml
index 27b8d92..074df11 100644
--- a/build-system/gradle/gradle.iml
+++ b/build-system/gradle/gradle.iml
@@ -10,13 +10,39 @@
<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" />
+ <excludeFolder url="file://$MODULE_DIR$/../../../../out/host/gradle/tools/base/gradle" />
+ <excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" exported="" scope="TEST" name="JUnit3" level="project" />
+ <orderEntry type="library" exported="" name="proguard-gradle" level="project" />
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-core-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -25,7 +51,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/groovy-all-1.8.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -34,7 +60,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/asm-all-5.0_BETA.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -43,7 +69,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/ant-1.9.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -52,7 +78,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-collections-3.2.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -61,7 +87,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-io-1.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -70,7 +96,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-lang-2.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -79,7 +105,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/ivy-2.2.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -88,7 +114,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/logback-core-1.0.13.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -97,7 +123,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/logback-classic-1.0.13.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -106,7 +132,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/guava-jdk5-14.0.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -115,7 +141,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jcip-annotations-1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -124,7 +150,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jul-to-slf4j-1.7.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -133,7 +159,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jarjar-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -142,7 +168,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/javax.inject-1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -151,7 +177,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/bintray-client-java-impl-0.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -160,7 +186,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/httpclient-4.2.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -169,7 +195,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/slf4j-api-1.7.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -178,7 +204,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/log4j-over-slf4j-1.7.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -187,7 +213,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jcl-over-slf4j-1.7.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -196,7 +222,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/ant-launcher-1.9.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -205,7 +231,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jsch-0.1.51.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -214,7 +240,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/joda-time-2.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -223,7 +249,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/bintray-client-java-api-0.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -232,7 +258,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-logging-1.1.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -241,7 +267,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-beanutils-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -250,7 +276,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/ezmorph-1.0.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -259,7 +285,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/json-lib-2.3-jdk15.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -268,7 +294,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/xml-resolver-1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -277,7 +303,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/http-builder-0.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -286,7 +312,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-codec-1.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -295,7 +321,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/httpcore-4.2.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -304,7 +330,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jcifs-1.3.17.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -313,7 +339,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-docs-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -322,7 +348,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-base-services-groovy-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -331,7 +357,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-base-services-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -340,7 +366,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-resources-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -349,7 +375,97 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-cli-1.12.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-native-1.12.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jna-3.2.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/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/gradle-dist-link/lib/jansi-1.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-osx-i386-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-osx-amd64-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-linux-amd64-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-linux-i386-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-windows-amd64-0.10.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -358,7 +474,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-windows-i386-0.10.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -367,7 +483,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-messaging-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -376,7 +492,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/kryo-2.20.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -385,7 +501,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/asm-4.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -394,7 +510,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/reflectasm-1.07-shaded.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -403,7 +519,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/minlog-1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -412,7 +528,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/objenesis-1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -421,7 +537,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-core-impl-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -430,7 +546,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-settings-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -439,7 +555,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-repository-metadata-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -448,7 +564,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-container-default-1.5.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -457,7 +573,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-aether-provider-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -466,7 +582,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-wagon-provider-api-2.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -475,7 +591,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-cipher-1.7.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -484,7 +600,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-interpolation-1.14.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -493,7 +609,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-utils-2.0.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -502,7 +618,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-classworlds-2.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -511,7 +627,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-plugin-api-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -520,7 +636,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-model-builder-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -529,7 +645,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-sec-dispatcher-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -538,7 +654,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-component-annotations-1.5.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -547,7 +663,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-connector-wagon-1.13.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -556,7 +672,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-compat-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -565,7 +681,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-wagon-http-2.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -574,7 +690,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-api-1.13.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -583,7 +699,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-settings-builder-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -592,7 +708,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-spi-1.13.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -601,7 +717,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-core-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -610,7 +726,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-wagon-http-shared4-2.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -619,7 +735,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-util-1.13.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -628,7 +744,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-artifact-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -637,7 +753,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-model-3.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -646,7 +762,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-impl-1.13.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -655,7 +771,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/maven-ant-tasks-2.1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -664,7 +780,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/nekohtml-1.9.14.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -673,7 +789,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/xbean-reflect-3.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -682,7 +798,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/xml-apis-1.3.04.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -691,7 +807,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/xercesImpl-2.9.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -700,7 +816,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-tooling-api-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -709,7 +825,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-plugins-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -718,7 +834,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/junit-4.11.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -727,7 +843,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/testng-6.3.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -736,7 +852,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/commons-cli-1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -745,7 +861,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/bsh-2.0b4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -754,7 +870,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jcommander-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -763,7 +879,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/snakeyaml-1.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -772,7 +888,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/hamcrest-core-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -781,7 +897,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-code-quality-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -790,7 +906,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-jetty-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -799,7 +915,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-6.1.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -808,7 +924,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-util-6.1.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -817,7 +933,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/servlet-api-2.5-20081211.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -826,7 +942,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-plus-6.1.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -835,7 +951,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jsp-2.1-6.1.14.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -844,7 +960,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-annotations-6.1.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -853,7 +969,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/geronimo-annotation_1.0_spec-1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -862,7 +978,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-naming-6.1.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -871,7 +987,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/core-3.1.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -880,7 +996,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jsp-api-2.1-6.1.14.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -889,7 +1005,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-antlr-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -898,7 +1014,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/ant-antlr-1.9.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -907,7 +1023,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/antlr-2.7.7.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -916,7 +1032,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-wrapper-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -925,7 +1041,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-osgi-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -934,7 +1050,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/bndlib-2.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -943,7 +1059,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-maven-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -952,7 +1068,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/pmaven-common-0.8-20100325.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -961,7 +1077,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/pmaven-groovy-0.8-20100325.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -970,7 +1086,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/plexus-component-annotations-1.5.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -979,7 +1095,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-ide-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -988,7 +1104,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-announce-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -997,7 +1113,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-scala-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1006,7 +1122,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-sonar-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1015,7 +1131,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/sonar-runner-2.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1024,7 +1140,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/sonar-batch-bootstrapper-2.9.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1033,7 +1149,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-signing-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1042,7 +1158,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/bcpg-jdk15-1.46.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1051,7 +1167,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/bcprov-jdk15-1.46.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1060,7 +1176,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-cpp-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1069,7 +1185,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-ear-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1078,7 +1194,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-javascript-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1087,7 +1203,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/rhino-1.7R3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1096,7 +1212,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gson-2.2.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1105,7 +1221,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/simple-4.1.21.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1114,7 +1230,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-build-comparison-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1123,7 +1239,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-diagnostics-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1132,7 +1248,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-reporting-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1141,7 +1257,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jatl-0.2.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1150,7 +1266,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-publish-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1159,7 +1275,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-ivy-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1168,7 +1284,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-jacoco-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1177,7 +1293,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-build-init-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1186,7 +1302,7 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-language-jvm-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -1195,16 +1311,25 @@
<orderEntry type="module-library" exported="">
<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!/" />
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-language-base-1.12.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" />
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/junit/junit/3.8.1/junit-3.8.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/junit/junit/3.8.1/junit-3.8.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
</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
index 18e3277..a931a24 100644
--- 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
@@ -34,12 +34,46 @@
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"*/
+ "aidl",
+ "api",
+ "applibtest",
+ "assets",
+ "attrOrder",
+ "basic",
+ "dependencies",
+ "dependencyChecker",
+ "filteredOutBuildType",
+ "flavored",
+ "flavorlib",
+ "flavoredlib",
+ "flavors",
+ "genFolderApi",
+ "libProguardJarDep",
+ "libProguardLibDep",
+ "libTestDep",
+ "libsTest",
+ "localAarTest",
+ "localJars",
+ "migrated",
+ "multiproject",
+ "multires",
+ "ndkSanAngeles",
+ "ndkJniLib",
+ "ndkPrebuilts",
+ "ndkLibPrebuilts",
+ "noPreDex",
+ "overlay1",
+ "overlay2",
+ "pkgOverride",
+ "proguard",
+ "proguardLib",
+ "renderscript",
+ "renderscriptInLib",
+ "renderscriptMultiSrc",
+ "rsSupportMode",
+ "sameNamedLibs",
+ "tictactoe",
+ /*"autorepo"*/
};
private static final String[] sReportProjects = new String[] {
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
index c81d0db..7af7a39 100644
--- 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
@@ -16,11 +16,14 @@
package com.android.build.gradle;
+import com.android.annotations.NonNull;
import com.android.build.gradle.internal.test.BaseTest;
import com.google.common.collect.Lists;
import java.io.File;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
/**
* Base class for build tests.
@@ -29,7 +32,7 @@
* 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();
+ private static final Collection<String> IGNORED_GRADLE_VERSIONS = Lists.newArrayList("1.10", "1.11");
protected File testDir;
protected File sdkDir;
@@ -53,18 +56,26 @@
return IGNORED_GRADLE_VERSIONS.contains(gradleVersion);
}
- protected File buildProject(String name, String gradleVersion) {
- return runTasksOnProject(name, gradleVersion, "clean", "assembleDebug");
+ protected File buildProject(@NonNull String name, @NonNull String gradleVersion) {
+ return runTasksOnProject(
+ name,
+ gradleVersion,
+ Collections.<String>emptyList(),
+ "clean", "assembleDebug", "lint");
}
- protected File runTasksOnProject(String name, String gradleVersion, String... tasks) {
+ protected File runTasksOnProject(
+ @NonNull String name,
+ @NonNull String gradleVersion,
+ @NonNull List<String> arguments,
+ @NonNull String... tasks) {
File project = new File(testDir, name);
File buildGradle = new File(project, "build.gradle");
- assertTrue("Missing build.gradle for " + name, buildGradle.isFile());
+ assertTrue("Missing file: " + buildGradle, buildGradle.isFile());
// build the project
- runGradleTasks(sdkDir, ndkDir, gradleVersion, project, tasks);
+ runGradleTasks(sdkDir, ndkDir, gradleVersion, project, arguments, 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
index e0c985b..cc40578 100644
--- 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
@@ -16,13 +16,33 @@
package com.android.build.gradle;
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_KEY_ALIAS;
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_KEY_PASSWORD;
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_STORE_FILE;
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_STORE_PASSWORD;
+
import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
-import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.imageio.ImageIO;
/**
* Some manual tests for building projects.
@@ -32,14 +52,13 @@
*/
public class ManualBuildTest extends BuildTest {
- private final static int RED = 0xFFFF0000;
- private final static int GREEN = 0xFF00FF00;
- private final static int BLUE = 0xFF0000FF;
-
+ private static final int RED = 0xFFFF0000;
+ private static final int GREEN = 0xFF00FF00;
+ private static final 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");
+ File drawableOutput = new File(project, "build/" + FD_INTERMEDIATES + "/res/debug/drawable");
checkImageColor(drawableOutput, "no_overlay.png", GREEN);
checkImageColor(drawableOutput, "type_overlay.png", GREEN);
@@ -47,7 +66,7 @@
public void testOverlay2Content() throws Exception {
File project = buildProject("overlay2", BasePlugin.GRADLE_MIN_VERSION);
- File drawableOutput = new File(project, "build/res/all/one/debug/drawable");
+ File drawableOutput = new File(project, "build/" + FD_INTERMEDIATES + "/res/one/debug/drawable");
checkImageColor(drawableOutput, "no_overlay.png", GREEN);
checkImageColor(drawableOutput, "type_overlay.png", GREEN);
@@ -58,7 +77,7 @@
public void testOverlay3Content() throws Exception {
File project = buildProject("overlay3", BasePlugin.GRADLE_MIN_VERSION);
- File drawableOutput = new File(project, "build/res/all/freebeta/debug/drawable");
+ File drawableOutput = new File(project, "build/" + FD_INTERMEDIATES + "/res/freebeta/debug/drawable");
checkImageColor(drawableOutput, "no_overlay.png", GREEN);
checkImageColor(drawableOutput, "debug_overlay.png", GREEN);
@@ -68,7 +87,7 @@
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");
+ drawableOutput = new File(project, "build/" + FD_INTERMEDIATES + "/res/freenormal/debug/drawable");
checkImageColor(drawableOutput, "no_overlay.png", GREEN);
checkImageColor(drawableOutput, "debug_overlay.png", GREEN);
@@ -78,7 +97,7 @@
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");
+ drawableOutput = new File(project, "build/" + FD_INTERMEDIATES + "/res/paidbeta/debug/drawable");
checkImageColor(drawableOutput, "no_overlay.png", GREEN);
checkImageColor(drawableOutput, "debug_overlay.png", GREEN);
@@ -94,13 +113,21 @@
try {
runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
- new File(repo, "util"), "clean", "uploadArchives");
+ new File(repo, "util"),
+ Collections.<String>emptyList(),
+ "clean", "uploadArchives");
runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
- new File(repo, "baseLibrary"), "clean", "uploadArchives");
+ new File(repo, "baseLibrary"),
+ Collections.<String>emptyList(),
+ "clean", "uploadArchives");
runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
- new File(repo, "library"), "clean", "uploadArchives");
+ new File(repo, "library"),
+ Collections.<String>emptyList(),
+ "clean", "uploadArchives");
runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
- new File(repo, "app"), "clean", "assemble");
+ new File(repo, "app"),
+ Collections.<String>emptyList(),
+ "clean", "assemble");
} finally {
// clean up the test repository.
File testRepo = new File(repo, "testrepo");
@@ -108,13 +135,26 @@
}
}
+ public void testLibsManifestMerging() throws Exception {
+ File project = new File(testDir, "libsTest");
+ File fileOutput = new File(project, "libapp/build/" + FD_INTERMEDIATES + "/bundles/release/AndroidManifest.xml");
+
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ project,
+ Collections.<String>emptyList(),
+ "clean", "build");
+ assertTrue(fileOutput.exists());
+ }
+
// 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");
+ File fileOutput = new File(project, "build/" + FD_OUTPUTS + "/proguard/release");
runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
- project, "clean", "build");
+ project,
+ Collections.<String>emptyList(),
+ "clean", "build");
checkFile(fileOutput, "mapping.txt", new String[]{"int proguardInt -> a"});
}
@@ -122,23 +162,172 @@
// 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");
+ File debugFileOutput = new File(project, "build/" + FD_INTERMEDIATES + "/bundles/debug");
+ File releaseFileOutput = new File(project, "build/" + FD_INTERMEDIATES + "/bundles/release");
runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
- project, "clean", "build");
+ project,
+ Collections.<String>emptyList(),
+ "clean", "build");
checkFile(debugFileOutput, "proguard.txt", new String[]{"A"});
checkFile(releaseFileOutput, "proguard.txt", new String[]{"A", "B", "C"});
}
+ public void testAnnotations() throws Exception {
+ File project = new File(testDir, "extractAnnotations");
+ File debugFileOutput = new File(project, "build/" + FD_INTERMEDIATES + "/bundles/debug");
+
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ project,
+ Collections.<String>emptyList(),
+ "clean", "assembleDebug");
+ File file = new File(debugFileOutput, "annotations.zip");
+
+ Map<String, String> map = Maps.newHashMap();
+ //noinspection SpellCheckingInspection
+ map.put("com/android/tests/extractannotations/annotations.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest ExtractTest(int, java.lang.String) 0\">\n"
+ + " <annotation name=\"android.support.annotation.IdRes\" />\n"
+ + " </item>\n"
+ // This item should be removed when I start supporting @hide
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest int getHiddenMethod()\">\n"
+ + " <annotation name=\"android.support.annotation.IdRes\" />\n"
+ + " </item>\n"
+ // This item should be removed when I start supporting @hide
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest int getPrivate()\">\n"
+ + " <annotation name=\"android.support.annotation.IdRes\" />\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest int getVisibility()\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"value\" val=\"{com.android.tests.extractannotations.ExtractTest.VISIBLE, com.android.tests.extractannotations.ExtractTest.INVISIBLE, com.android.tests.extractannotations.ExtractTest.GONE, 5, 17, com.android.tests.extractannotations.Constants.CONSTANT_1}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest int resourceTypeMethod(int, int)\">\n"
+ + " <annotation name=\"android.support.annotation.StringRes\" />\n"
+ + " <annotation name=\"android.support.annotation.IdRes\" />\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest int resourceTypeMethod(int, int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.DrawableRes\" />\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest int resourceTypeMethod(int, int) 1\">\n"
+ + " <annotation name=\"android.support.annotation.IdRes\" />\n"
+ + " <annotation name=\"android.support.annotation.ColorRes\" />\n"
+ + " </item>\n"
+ // This item should be removed when I start supporting @hide
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest java.lang.Object getPackagePrivate()\">\n"
+ + " <annotation name=\"android.support.annotation.IdRes\" />\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest java.lang.String getStringMode(int)\">\n"
+ + " <annotation name=\"android.support.annotation.StringDef\">\n"
+ + " <val name=\"value\" val=\"{com.android.tests.extractannotations.ExtractTest.STRING_1, com.android.tests.extractannotations.ExtractTest.STRING_2, "literalValue", "concatenated"}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest java.lang.String getStringMode(int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"value\" val=\"{com.android.tests.extractannotations.ExtractTest.VISIBLE, com.android.tests.extractannotations.ExtractTest.INVISIBLE, com.android.tests.extractannotations.ExtractTest.GONE, 5, 17, com.android.tests.extractannotations.Constants.CONSTANT_1}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest void checkForeignTypeDef(int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"value\" val=\"{com.android.tests.extractannotations.Constants.CONSTANT_1, com.android.tests.extractannotations.Constants.CONSTANT_2}\" />\n"
+ + " <val name=\"flag\" val=\"true\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest void resourceTypeMethodWithTypeArgs(java.util.Map<java.lang.String,? extends java.lang.Number>, T, int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.StringRes\" />\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest void resourceTypeMethodWithTypeArgs(java.util.Map<java.lang.String,? extends java.lang.Number>, T, int) 1\">\n"
+ + " <annotation name=\"android.support.annotation.DrawableRes\" />\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest void resourceTypeMethodWithTypeArgs(java.util.Map<java.lang.String,? extends java.lang.Number>, T, int) 2\">\n"
+ + " <annotation name=\"android.support.annotation.IdRes\" />\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest void testMask(int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"value\" val=\"{0, com.android.tests.extractannotations.Constants.FLAG_VALUE_1, com.android.tests.extractannotations.Constants.FLAG_VALUE_2}\" />\n"
+ + " <val name=\"flag\" val=\"true\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest void testNonMask(int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"value\" val=\"{0, com.android.tests.extractannotations.Constants.CONSTANT_1, com.android.tests.extractannotations.Constants.CONSTANT_3}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ // This should be hidden when we start filtering out hidden classes on @hide!
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest.HiddenClass int getHiddenMember()\">\n"
+ + " <annotation name=\"android.support.annotation.IdRes\" />\n"
+ + " </item>\n"
+ + "</root>");
+
+ checkJar(file, map);
+
+ // check the resulting .aar file to ensure annotations.zip inclusion.
+ File archiveFile = new File(project, "build/outputs/aar/extractAnnotations.aar");
+ assertTrue(archiveFile.isFile());
+ ZipFile archive = null;
+ try {
+ archive = new ZipFile(archiveFile);
+ ZipEntry entry = archive.getEntry("annotations.zip");
+ assertNotNull(entry);
+ } finally {
+ if (archive != null) {
+ archive.close();
+ }
+ }
+ }
+
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");
+ new File(testDir, "3rdPartyTests"),
+ Collections.<String>emptyList(),
+ "clean", "deviceCheck");
}
+ public void testEmbedded() throws Exception {
+ File project = new File(testDir, "embedded");
+
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ project,
+ Collections.<String>emptyList(),
+ "clean", ":main:assembleRelease");
+
+ File mainApk = new File(project, "main/build/" + FD_OUTPUTS + "/apk/main-release-unsigned.apk");
+
+ checkJar(mainApk, Collections.<String,
+ String>singletonMap("assets/embedded-release-unsigned.apk", null));
+ }
+
+ public void testBasicWithSigningOverride() throws Exception {
+ File project = new File(testDir, "basic");
+
+ // add prop args for signing override.
+ List<String> args = Lists.newArrayListWithExpectedSize(4);
+ args.add("-P" + PROPERTY_SIGNING_STORE_FILE + "=" + new File(project, "debug.keystore").getPath());
+ args.add("-P" + PROPERTY_SIGNING_STORE_PASSWORD + "=android");
+ args.add("-P" + PROPERTY_SIGNING_KEY_ALIAS + "=AndroidDebugKey");
+ args.add("-P" + PROPERTY_SIGNING_KEY_PASSWORD + "=android");
+
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ project,
+ args,
+ "clean", ":assembleRelease");
+
+ // check that the output exist. Since the filename is tried to signing/zipaligning
+ // this gives us a fairly good idea about signing already.
+ File releaseApk = new File(project, "build/" + FD_OUTPUTS + "/apk/basic-release.apk");
+ assertTrue(releaseApk.isFile());
+
+ // now check for signing file inside the archive.
+ checkJar(releaseApk, Collections.<String,
+ String>singletonMap("META-INF/CERT.RSA", null));
+ }
+
+
private static void checkImageColor(File folder, String fileName, int expectedColor)
throws IOException {
File f = new File(folder, fileName);
@@ -162,4 +351,46 @@
contents.contains(expectedContent));
}
}
+
+ private static void checkJar(File jar, Map<String, String> pathToContents)
+ throws IOException {
+ assertTrue("File '" + jar.getPath() + "' does not exist.", jar.isFile());
+ JarInputStream zis = null;
+ FileInputStream fis;
+ Set<String> notFound = Sets.newHashSet();
+ notFound.addAll(pathToContents.keySet());
+ fis = new FileInputStream(jar);
+ try {
+ zis = new JarInputStream(fis);
+
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ String name = entry.getName();
+
+ String expected = pathToContents.get(name);
+ if (expected != null) {
+ notFound.remove(name);
+ if (!entry.isDirectory()) {
+ byte[] bytes = ByteStreams.toByteArray(zis);
+ if (bytes != null) {
+ String contents = new String(bytes, Charsets.UTF_8).trim();
+ assertEquals("Contents in " + name + " did not match",
+ expected, contents);
+ }
+ }
+ } else if (pathToContents.keySet().contains(name)) {
+ notFound.remove(name);
+ }
+ entry = zis.getNextEntry();
+ }
+ } finally {
+ fis.close();
+ if (zis != null) {
+ zis.close();
+ }
+ }
+
+ assertTrue("Did not find the following paths in the " + jar.getPath() + " file: " +
+ notFound, notFound.isEmpty());
+ }
}
diff --git a/build-system/gradle/src/build-test/groovy/com/android/build/gradle/PackagingBuildTest.java b/build-system/gradle/src/build-test/groovy/com/android/build/gradle/PackagingBuildTest.java
new file mode 100644
index 0000000..1622f01
--- /dev/null
+++ b/build-system/gradle/src/build-test/groovy/com/android/build/gradle/PackagingBuildTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static com.android.builder.model.AndroidProject.FD_OUTPUTS;
+
+import com.android.annotations.NonNull;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Automated tests building a set of projects using a set of gradle versions, and testing
+ * the packaging of the apk.
+ *
+ * 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 PackagingBuildTest extends BuildTest {
+
+ private String projectName;
+ private String[] packagedFiles;
+
+ private static final Object[] sBuiltProjects = new Object[] {
+ "packagingOptions", new String[] { "first_pick.txt" },
+ };
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.setName("AutomatedBuildTest");
+
+ // first the project we build on all available versions of Gradle
+ for (int i = 0, count = sBuiltProjects.length; i < count ; i += 2) {
+ String projectName = (String) sBuiltProjects[i];
+ String testName = "build_" + projectName;
+
+ PackagingBuildTest test = (PackagingBuildTest) TestSuite.createTest(
+ PackagingBuildTest.class, testName);
+ test.setProjectInfo(projectName, (String[]) sBuiltProjects[i+1]);
+ suite.addTest(test);
+ }
+
+ return suite;
+ }
+
+ private void setProjectInfo(String projectName, String[] packagedFiles) {
+ this.projectName = projectName;
+ this.packagedFiles = packagedFiles;
+ }
+
+ @Override
+ protected void runTest() throws Throwable {
+ File projectFolder = buildProject(projectName, BasePlugin.GRADLE_MIN_VERSION);
+
+ // TODO replace with model access.
+ File apkFolder = new File(projectFolder, "build/" + FD_OUTPUTS + "/apk");
+
+ File apk = new File(apkFolder, projectName + "-debug-unaligned.apk");
+
+ int found = findFilesInZip(apk, Arrays.asList(packagedFiles));
+ assertEquals(packagedFiles.length, found);
+ }
+
+ private static int findFilesInZip(@NonNull File zip, @NonNull Collection<String> files)
+ throws IOException {
+ int found = 0;
+
+ ZipInputStream zis = new ZipInputStream(new FileInputStream(zip));
+ 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;
+ }
+
+ if (files.contains(name)) {
+ found++;
+ }
+ }
+ } finally {
+ zis.close();
+ }
+
+ return found;
+ }
+}
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
index 0e410f1..5b8cbd3 100644
--- 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
@@ -19,6 +19,8 @@
import junit.framework.Test;
import junit.framework.TestSuite;
+import java.util.Collections;
+
/**
* DeviceConnector tests.
*
@@ -38,10 +40,32 @@
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"
+ "api",
+ "assets",
+ "applibtest",
+ "attrOrder",
+ "basic",
+ "dependencies",
+ "flavored",
+ "flavorlib",
+ "flavoredlib",
+ "flavors",
+ "libProguardJarDep",
+ "libProguardLibDep",
+ "libTestDep",
+ "libsTest",
+ "migrated",
+ "multires",
+ "ndkJniLib",
+ "ndkPrebuilts",
+ "ndkLibPrebuilts",
+ "overlay1",
+ "overlay2",
+ "packagingOptions",
+ "pkgOverride",
+ "proguard",
+ "proguardLib",
+ "sameNamedLibs"
};
public static Test suite() {
@@ -73,7 +97,9 @@
@Override
protected void runTest() throws Throwable {
try {
- runTasksOnProject(projectName, gradleVersion, "clean", "connectedCheck");
+ runTasksOnProject(projectName, gradleVersion,
+ Collections.<String>emptyList(),
+ "clean", "connectedCheck");
} finally {
// because runTasksOnProject will throw an exception if the gradle side fails, do this
// in the finally block.
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/VariantFilterImpl.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/VariantFilterImpl.java
new file mode 100644
index 0000000..ad192bc
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/VariantFilterImpl.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.api.VariantFilter;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.ProductFlavor;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Internal implementation of VariantFilter
+ */
+public class VariantFilterImpl implements VariantFilter {
+
+ private boolean ignore;
+
+ private ProductFlavor defaultConfig;
+ private BuildType buildType;
+ private List<ProductFlavor> flavors;
+
+ VariantFilterImpl() {
+ }
+
+ void reset(
+ @NonNull ProductFlavor defaultConfig,
+ @NonNull BuildType buildType,
+ @Nullable List<ProductFlavor> flavors) {
+ ignore = false;
+ this.defaultConfig = defaultConfig;
+ this.buildType = buildType;
+ this.flavors = flavors;
+ }
+
+ boolean isIgnore() {
+ return ignore;
+ }
+
+ @Override
+ public void setIgnore(boolean ignore) {
+ this.ignore = ignore;
+ }
+
+ @Override
+ @NonNull
+ public ProductFlavor getDefaultConfig() {
+ return defaultConfig;
+ }
+
+ @Override
+ @NonNull
+ public BuildType getBuildType() {
+ return buildType;
+ }
+
+ @NonNull
+ @Override
+ public List<ProductFlavor> getFlavors() {
+ return flavors != null ? flavors : Collections.<ProductFlavor>emptyList();
+ }
+}
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
index ee31146..d10e8dd 100644
--- 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
@@ -16,13 +16,11 @@
package com.android.build.gradle.internal.test.report;
+import static org.gradle.api.tasks.testing.TestResult.ResultType;
+
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;
@@ -30,8 +28,6 @@
import java.util.Map;
import java.util.Set;
-import static org.gradle.api.tasks.testing.TestResult.ResultType;
-
/**
* Custom ClassPageRenderer based on Gradle's ClassPageRenderer
*/
@@ -205,7 +201,7 @@
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()) {
+ for (TestResult.TestFailure failure : test.getFailures()) {
codePanelRenderer.render(failure.getStackTrace(), htmlWriter);
}
htmlWriter.endElement();
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CodePanelRenderer.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CodePanelRenderer.java
new file mode 100644
index 0000000..517f8a0
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CodePanelRenderer.java
@@ -0,0 +1,31 @@
+/*
+ * 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.reporting.ReportRenderer;
+
+import java.io.IOException;
+
+public class CodePanelRenderer extends ReportRenderer<String, SimpleHtmlWriter> {
+ @Override
+ public void render(String text, SimpleHtmlWriter htmlWriter) throws IOException {
+ // Wrap in a <span>, to work around CSS problem in IE
+ htmlWriter.startElement("span").attribute("class", "code")
+ .startElement("pre").characters(text).endElement()
+ .endElement();
+ }
+}
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
index aab9cc6..873ea52 100644
--- 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
@@ -16,8 +16,7 @@
package com.android.build.gradle.internal.test.report;
-import com.android.builder.BuilderConstants;
-import org.gradle.api.internal.tasks.testing.junit.report.TestResultModel;
+import com.android.builder.core.BuilderConstants;
import java.math.BigDecimal;
import java.util.Map;
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ErroringAction.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ErroringAction.java
new file mode 100644
index 0000000..b4061c0
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ErroringAction.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/**
+ * Action adapter/implementation for action code that may throw exceptions.
+ *
+ * Implementations implement doExecute() (instead of execute()) which is allowed to throw checked
+ * exceptions.
+ * Any checked exceptions thrown will be wrapped as unchecked exceptions and re-thrown.
+ *
+ * @param <T> The type of object which this action accepts.
+ */
+public abstract class ErroringAction<T> implements Action<T> {
+
+ @Override
+ public void execute(T thing) {
+ try {
+ doExecute(thing);
+ } catch (Exception e) {
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected abstract void doExecute(T thing) throws Exception;
+
+}
+
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/HtmlReportRenderer.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/HtmlReportRenderer.java
new file mode 100644
index 0000000..9e1b9fe
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/HtmlReportRenderer.java
@@ -0,0 +1,133 @@
+/*
+ * 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.base.Preconditions;
+import com.google.common.io.ByteStreams;
+
+import org.gradle.reporting.ReportRenderer;
+import org.gradle.reporting.TextReportRenderer;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class HtmlReportRenderer {
+ private final Set<URL> resources = new HashSet<URL>();
+
+ public void requireResource(URL resource) {
+ resources.add(resource);
+ }
+
+ public <T> TextReportRenderer<T> renderer(final ReportRenderer<T, SimpleHtmlWriter> renderer) {
+ return renderer(new TextReportRendererImpl<T>(renderer));
+ }
+
+ public <T> TextReportRenderer<T> renderer(final TextReportRendererImpl<T> renderer) {
+ return new TextReportRenderer<T>() {
+ @Override
+ protected void writeTo(T model, Writer out) throws Exception {
+ renderer.writeTo(model, out);
+ }
+
+ @Override
+ public void writeTo(T model, File file) {
+ super.writeTo(model, file);
+ for (URL resource : resources) {
+ String name = substringAfterLast(resource.getPath(), "/");
+ String type = substringAfterLast(resource.getPath(), ".");
+ File destFile = new File(file.getParentFile(), String.format("%s/%s", type, name));
+ if (!destFile.exists()) {
+ destFile.getParentFile().mkdirs();
+ try {
+ URLConnection urlConnection = resource.openConnection();
+ urlConnection.setUseCaches(false);
+ InputStream inputStream = null;
+ try {
+ inputStream = urlConnection.getInputStream();
+ OutputStream outputStream = null;
+ try {
+ outputStream = new BufferedOutputStream(
+ new FileOutputStream(destFile));
+ ByteStreams.copy(inputStream, outputStream);
+ } finally {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ }
+ } finally {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ }
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ };
+ }
+
+ private static class TextReportRendererImpl<T> extends TextReportRenderer<T> {
+ private final ReportRenderer<T, SimpleHtmlWriter> delegate;
+
+ private TextReportRendererImpl(ReportRenderer<T, SimpleHtmlWriter> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected void writeTo(T model, Writer writer) throws Exception {
+ SimpleHtmlWriter htmlWriter = new SimpleHtmlWriter(writer, "");
+ htmlWriter.startElement("html");
+ delegate.render(model, htmlWriter);
+ htmlWriter.endElement();
+ }
+ }
+
+ /**
+ * Returns the substring of a string that follows the last
+ * occurence of a separator.
+ *
+ * <p>Largely replicated and slightly updated from the
+ * {@code apache.commons.lang.StringUtils} method of the same name.
+ *
+ * @param string the String to get a substring from, may not be null
+ * @param separator the String to search for, may not be null
+ * @return the substring after the last occurrence of the separator or an
+ * empty string if not found.
+ */
+ public static String substringAfterLast(String string, String separator) {
+ Preconditions.checkNotNull(string);
+ Preconditions.checkNotNull(separator);
+ int pos = string.lastIndexOf(separator);
+ if (pos == -1 || pos == (string.length() - separator.length())) {
+ return "";
+ }
+ return string.substring(pos + separator.length());
+ }
+
+}
\ No newline at end of file
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
index f289179..a3526fe 100644
--- 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
@@ -15,9 +15,6 @@
*/
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;
/**
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
index 796617f..9dfc61a 100644
--- 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
@@ -15,9 +15,6 @@
*/
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;
/**
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
index f240cf9..39250bb 100644
--- 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
@@ -16,11 +16,7 @@
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;
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleHtmlWriter.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleHtmlWriter.java
new file mode 100644
index 0000000..f910901
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleHtmlWriter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.io.IOException;
+import java.io.Writer;
+
+/**
+ * <p>A streaming HTML writer.</p>
+ */
+public class SimpleHtmlWriter extends SimpleMarkupWriter {
+
+ public SimpleHtmlWriter(Writer writer) throws IOException {
+ this(writer, null);
+ }
+
+ public SimpleHtmlWriter(Writer writer, String indent) throws IOException {
+ super(writer, indent);
+ writeHtmlHeader();
+ }
+
+ private void writeHtmlHeader() throws IOException {
+ writeRaw("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">");
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleMarkupWriter.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleMarkupWriter.java
new file mode 100644
index 0000000..28dd1fb
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleMarkupWriter.java
@@ -0,0 +1,416 @@
+/*
+ * 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.io.IOException;
+import java.io.Writer;
+import java.util.LinkedList;
+
+/**
+ * <p>A streaming markup writer. Encodes characters and CDATA. Provides only basic state validation, and some simple indentation.</p>
+ *
+ * <p>This class also is-a {@link Writer}, and any characters written to this writer will be encoded as appropriate. Note, however, that
+ * calling {@link #close()} on this object does not close the backing stream.
+ * </p>
+ */
+public class SimpleMarkupWriter extends Writer {
+
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ private enum Context {
+ Outside, Text, CData, StartTag, ElementContent
+ }
+
+ private final Writer output;
+ private final LinkedList<String> elements = new LinkedList<String>();
+ private Context context = Context.Outside;
+ private int squareBrackets;
+ private final String indent;
+
+ protected SimpleMarkupWriter(Writer writer, String indent) throws IOException {
+ this.indent = indent;
+ this.output = writer;
+ }
+
+ @Override
+ public void write(char[] chars, int offset, int length) throws IOException {
+ characters(chars, offset, length);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ output.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ // Does nothing
+ }
+
+ public SimpleMarkupWriter characters(char[] characters) throws IOException {
+ characters(characters, 0, characters.length);
+ return this;
+ }
+
+ public SimpleMarkupWriter characters(char[] characters, int start, int count) throws IOException {
+ if (context == Context.CData) {
+ writeCDATA(characters, start, count);
+ } else {
+ maybeStartText();
+ writeXmlEncoded(characters, start, count);
+ }
+ return this;
+ }
+
+ public SimpleMarkupWriter characters(CharSequence characters) throws IOException {
+ if (context == Context.CData) {
+ writeCDATA(characters);
+ } else {
+ maybeStartText();
+ writeXmlEncoded(characters);
+ }
+ return this;
+ }
+
+ private void maybeStartText() throws IOException {
+ if (context == Context.Outside) {
+ throw new IllegalStateException("Cannot write text, as there are no started elements.");
+ }
+ if (context == Context.StartTag) {
+ writeRaw(">");
+ }
+ context = Context.Text;
+ }
+
+ private void maybeFinishStartTag() throws IOException {
+ if (context == Context.StartTag) {
+ writeRaw(">");
+ context = Context.ElementContent;
+ }
+ }
+
+ public SimpleMarkupWriter startElement(String name) throws IOException {
+ if (!isValidXmlName(name)) {
+ throw new IllegalArgumentException(String.format("Invalid element name: '%s'", name));
+ }
+ if (context == Context.CData) {
+ throw new IllegalStateException("Cannot start element, as current CDATA node has not been closed.");
+ }
+ maybeFinishStartTag();
+ if (indent != null) {
+ writeRaw(LINE_SEPARATOR);
+ for (int i = 0; i < elements.size(); i++) {
+ writeRaw(indent);
+ }
+ }
+ context = Context.StartTag;
+ elements.add(name);
+ writeRaw("<");
+ writeRaw(name);
+ return this;
+ }
+
+ public SimpleMarkupWriter endElement() throws IOException {
+ if (context == Context.Outside) {
+ throw new IllegalStateException("Cannot end element, as there are no started elements.");
+ }
+ if (context == Context.CData) {
+ throw new IllegalStateException("Cannot end element, as current CDATA node has not been closed.");
+ }
+ if (context == Context.StartTag) {
+ writeRaw("/>");
+ elements.removeLast();
+ } else {
+ if (context != Context.Text && indent != null) {
+ writeRaw(LINE_SEPARATOR);
+ for (int i = 1; i < elements.size(); i++) {
+ writeRaw(indent);
+ }
+ }
+ writeRaw("</");
+ writeRaw(elements.removeLast());
+ writeRaw(">");
+ }
+ if (elements.isEmpty()) {
+ if (indent != null) {
+ writeRaw(LINE_SEPARATOR);
+ }
+ output.flush();
+ context = Context.Outside;
+ } else {
+ context = Context.ElementContent;
+ }
+ return this;
+ }
+
+ private void writeCDATA(char[] cdata, int offset, int count) throws IOException {
+ int end = offset + count;
+ for (int i = offset; i < end; i++) {
+ writeCDATA(cdata[i]);
+ }
+ }
+
+ private void writeCDATA(CharSequence cdata) throws IOException {
+ int len = cdata.length();
+ for (int i = 0; i < len; i++) {
+ writeCDATA(cdata.charAt(i));
+ }
+ }
+
+ private void writeCDATA(char ch) throws IOException {
+ if (needsCDATAEscaping(ch)) {
+ writeRaw("]]><![CDATA[>");
+ } else if (!isLegalCharacter(ch)) {
+ writeRaw('?');
+ } else if (isRestrictedCharacter(ch)) {
+ writeRaw("]]>");
+ writeCharacterReference(ch);
+ writeRaw("<![CDATA[");
+ } else {
+ writeRaw(ch);
+ }
+ }
+
+ private void writeCharacterReference(char ch) throws IOException {
+ writeRaw("&#x");
+ writeRaw(Integer.toHexString(ch));
+ writeRaw(";");
+ }
+
+ private boolean needsCDATAEscaping(char ch) {
+ switch (ch) {
+ case ']':
+ squareBrackets++;
+ return false;
+ case '>':
+ if (squareBrackets >= 2) {
+ squareBrackets = 0;
+ return true;
+ }
+ return false;
+ default:
+ squareBrackets = 0;
+ return false;
+ }
+ }
+
+ public SimpleMarkupWriter startCDATA() throws IOException {
+ if (context == Context.CData) {
+ throw new IllegalStateException("Cannot start CDATA node, as current CDATA node has not been closed.");
+ }
+ maybeFinishStartTag();
+ writeRaw("<![CDATA[");
+ context = Context.CData;
+ squareBrackets = 0;
+ return this;
+ }
+
+ public SimpleMarkupWriter endCDATA() throws IOException {
+ if (context != Context.CData) {
+ throw new IllegalStateException("Cannot end CDATA node, as not currently in a CDATA node.");
+ }
+ writeRaw("]]>");
+ context = Context.Text;
+ return this;
+ }
+
+ public SimpleMarkupWriter attribute(String name, String value) throws IOException {
+ if (!isValidXmlName(name)) {
+ throw new IllegalArgumentException(String.format("Invalid attribute name: '%s'", name));
+ }
+ if (context != Context.StartTag) {
+ throw new IllegalStateException("Cannot write attribute [" + name + ":" + value + "]. You should write start element first.");
+ }
+
+ writeRaw(" ");
+ writeRaw(name);
+ writeRaw("=\"");
+ writeXmlAttributeEncoded(value);
+ writeRaw("\"");
+ return this;
+ }
+
+ private static boolean isValidXmlName(String name) {
+ int length = name.length();
+ if (length == 0) {
+ return false;
+ }
+ char ch = name.charAt(0);
+ if (!isValidNameStartChar(ch)) {
+ return false;
+ }
+ for (int i = 1; i < length; i++) {
+ ch = name.charAt(i);
+ if (!isValidNameChar(ch)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isValidNameChar(char ch) {
+ if (isValidNameStartChar(ch)) {
+ return true;
+ }
+ if (ch >= '0' && ch <= '9') {
+ return true;
+ }
+ if (ch == '-' || ch == '.' || ch == '\u00b7') {
+ return true;
+ }
+ if (ch >= '\u0300' && ch <= '\u036f') {
+ return true;
+ }
+ if (ch >= '\u203f' && ch <= '\u2040') {
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isValidNameStartChar(char ch) {
+ if (ch >= 'A' && ch <= 'Z') {
+ return true;
+ }
+ if (ch >= 'a' && ch <= 'z') {
+ return true;
+ }
+ if (ch == ':' || ch == '_') {
+ return true;
+ }
+ if (ch >= '\u00c0' && ch <= '\u00d6') {
+ return true;
+ }
+ if (ch >= '\u00d8' && ch <= '\u00f6') {
+ return true;
+ }
+ if (ch >= '\u00f8' && ch <= '\u02ff') {
+ return true;
+ }
+ if (ch >= '\u0370' && ch <= '\u037d') {
+ return true;
+ }
+ if (ch >= '\u037f' && ch <= '\u1fff') {
+ return true;
+ }
+ if (ch >= '\u200c' && ch <= '\u200d') {
+ return true;
+ }
+ if (ch >= '\u2070' && ch <= '\u218f') {
+ return true;
+ }
+ if (ch >= '\u2c00' && ch <= '\u2fef') {
+ return true;
+ }
+ if (ch >= '\u3001' && ch <= '\ud7ff') {
+ return true;
+ }
+ if (ch >= '\uf900' && ch <= '\ufdcf') {
+ return true;
+ }
+ if (ch >= '\ufdf0' && ch <= '\ufffd') {
+ return true;
+ }
+ return false;
+ }
+
+ private void writeRaw(char c) throws IOException {
+ output.write(c);
+ }
+
+ private boolean isLegalCharacter(final char c) {
+ if (c == 0) {
+ return false;
+ } else if (c <= 0xD7FF) {
+ return true;
+ } else if (c < 0xE000) {
+ return false;
+ } else if (c <= 0xFFFD) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isRestrictedCharacter(char c) {
+ if (c == 0x9 || c == 0xA || c == 0xD || c == 0x85) {
+ return false;
+ } else if (c <= 0x1F) {
+ return true;
+ } else if (c < 0x7F) {
+ return false;
+ } else if (c <= 0x9F) {
+ return true;
+ }
+ return false;
+ }
+
+ protected void writeRaw(String message) throws IOException {
+ output.write(message);
+ }
+
+ private void writeXmlEncoded(char[] message, int offset, int count) throws IOException {
+ int end = offset + count;
+ for (int i = offset; i < end; i++) {
+ writeXmlEncoded(message[i]);
+ }
+ }
+
+ private void writeXmlAttributeEncoded(CharSequence message) throws IOException {
+ assert message != null;
+ int len = message.length();
+ for (int i = 0; i < len; i++) {
+ writeXmlAttributeEncoded(message.charAt(i));
+ }
+ }
+
+ private void writeXmlAttributeEncoded(char ch) throws IOException {
+ if (ch == 9) {
+ writeRaw("	");
+ } else if (ch == 10) {
+ writeRaw(" ");
+ } else if (ch == 13) {
+ writeRaw(" ");
+ } else {
+ writeXmlEncoded(ch);
+ }
+ }
+
+ private void writeXmlEncoded(CharSequence message) throws IOException {
+ assert message != null;
+ int len = message.length();
+ for (int i = 0; i < len; i++) {
+ writeXmlEncoded(message.charAt(i));
+ }
+ }
+
+ private void writeXmlEncoded(char ch) throws IOException {
+ if (ch == '<') {
+ writeRaw("<");
+ } else if (ch == '>') {
+ writeRaw(">");
+ } else if (ch == '&') {
+ writeRaw("&");
+ } else if (ch == '"') {
+ writeRaw(""");
+ } else if (!isLegalCharacter(ch)) {
+ writeRaw('?');
+ } else if (isRestrictedCharacter(ch)) {
+ writeCharacterReference(ch);
+ } else {
+ writeRaw(ch);
+ }
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabbedPageRenderer.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabbedPageRenderer.java
new file mode 100644
index 0000000..b9c8790
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabbedPageRenderer.java
@@ -0,0 +1,68 @@
+/*
+ * 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.reporting.ReportRenderer;
+import org.gradle.util.GradleVersion;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.Date;
+
+public abstract class TabbedPageRenderer<T> extends ReportRenderer<T, SimpleHtmlWriter> {
+ private T model;
+
+ protected T getModel() {
+ return model;
+ }
+
+ protected abstract String getTitle();
+
+ protected abstract ReportRenderer<T, SimpleHtmlWriter> getHeaderRenderer();
+
+ protected abstract ReportRenderer<T, SimpleHtmlWriter> getContentRenderer();
+
+ protected String getPageTitle() {
+ return getTitle();
+ }
+
+ @Override
+ public void render(final T model, SimpleHtmlWriter htmlWriter) throws
+ IOException {
+ this.model = model;
+ htmlWriter.startElement("head")
+ .startElement("meta").attribute("http-equiv", "Content-Type").attribute("content", "text/html; charset=utf-8").endElement()
+ .startElement("title").characters(getPageTitle()).endElement()
+ .startElement("link").attribute("href", "css/base-style.css").attribute("rel", "stylesheet").attribute("type", "text/css").endElement()
+ .startElement("link").attribute("href", "css/style.css").attribute("rel", "stylesheet").attribute("type", "text/css").endElement()
+ .startElement("script").attribute("src", "js/report.js").attribute("type", "text/javascript").characters("").endElement() //html does not like <a name="..."/>
+ .endElement();
+
+ htmlWriter.startElement("body")
+ .startElement("div").attribute("id", "content")
+ .startElement("h1").characters(getTitle()).endElement();
+ getHeaderRenderer().render(model, htmlWriter);
+ getContentRenderer().render(model, htmlWriter);
+ htmlWriter.startElement("div").attribute("id", "footer")
+ .startElement("p").characters("Generated by ")
+ .startElement("a").attribute("href", "http://www.gradle.org").characters(String.format("Gradle %s", GradleVersion.current().getVersion())).endElement()
+ .characters(String.format(" at %s", DateFormat.getDateTimeInstance().format(new Date())))
+ .endElement()
+ .endElement()
+ .endElement();
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabsRenderer.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabsRenderer.java
new file mode 100644
index 0000000..aae8df9
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabsRenderer.java
@@ -0,0 +1,69 @@
+/*
+ * 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.reporting.ReportRenderer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TabsRenderer<T> extends ReportRenderer<T, SimpleHtmlWriter> {
+ private final List<TabDefinition> tabs = new ArrayList<TabDefinition>();
+
+ public void add(String title, ReportRenderer<T, SimpleHtmlWriter> contentRenderer) {
+ tabs.add(new TabDefinition(title, contentRenderer));
+ }
+
+ public void clear() {
+ tabs.clear();
+ }
+
+ @Override
+ public void render(T model, SimpleHtmlWriter htmlWriterWriter) throws IOException {
+ htmlWriterWriter.startElement("div").attribute("id", "tabs");
+ htmlWriterWriter.startElement("ul").attribute("class", "tabLinks");
+ for (int i = 0; i < this.tabs.size(); i++) {
+ TabDefinition tab = this.tabs.get(i);
+ String tabId = String.format("tab%s", i);
+ htmlWriterWriter.startElement("li");
+ htmlWriterWriter.startElement("a").attribute("href", "#" + tabId).characters(tab.title).endElement();
+ htmlWriterWriter.endElement();
+ }
+ htmlWriterWriter.endElement();
+
+ for (int i = 0; i < this.tabs.size(); i++) {
+ TabDefinition tab = this.tabs.get(i);
+ String tabId = String.format("tab%s", i);
+ htmlWriterWriter.startElement("div").attribute("id", tabId).attribute("class", "tab");
+ htmlWriterWriter.startElement("h2").characters(tab.title).endElement();
+ tab.renderer.render(model, htmlWriterWriter);
+ htmlWriterWriter.endElement();
+ }
+ htmlWriterWriter.endElement();
+ }
+
+ private class TabDefinition {
+ final String title;
+ final ReportRenderer<T, SimpleHtmlWriter> renderer;
+
+ private TabDefinition(String title, ReportRenderer<T, SimpleHtmlWriter> renderer) {
+ this.title = title;
+ this.renderer = renderer;
+ }
+ }
+}
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
index 57675e5..645e44c 100644
--- 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
@@ -15,19 +15,24 @@
*/
package com.android.build.gradle.internal.test.report;
+import com.google.common.io.Closeables;
+
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.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.ParseException;
+
+import javax.xml.parsers.DocumentBuilderFactory;
/**
* Custom test reporter based on Gradle's DefaultTestReport
@@ -55,9 +60,12 @@
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);
+ File[] files = resultDir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.getName().startsWith("TEST-") && file.getName().endsWith(".xml")) {
+ mergeFromFile(file, model);
+ }
}
}
}
@@ -65,8 +73,10 @@
}
private void mergeFromFile(File file, AllTestResults model) {
+ InputStream inputStream = null;
try {
- InputStream inputStream = new FileInputStream(file);
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ inputStream = new FileInputStream(file);
Document document;
try {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
@@ -91,8 +101,7 @@
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"));
+ BigDecimal duration = parse(testCase.getAttribute("time"));
duration = duration.multiply(BigDecimal.valueOf(1000));
NodeList failures = testCase.getElementsByTagName("failure");
TestResult testResult = model.addTest(className, testName, duration.longValue(),
@@ -123,6 +132,12 @@
}
} catch (Exception e) {
throw new GradleException(String.format("Could not load test results from '%s'.", file), e);
+ } finally {
+ try {
+ Closeables.close(inputStream, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
}
@@ -146,4 +161,20 @@
private <T extends CompositeTestResults> void generatePage(T model, PageRenderer<T> renderer,
File outputFile) throws Exception {
htmlRenderer.renderer(renderer).writeTo(model, outputFile);
- }}
+ }
+
+ /**
+ * Regardless of the default locale, comma ('.') is used as decimal separator
+ *
+ * @param source
+ * @return
+ * @throws java.text.ParseException
+ */
+ public BigDecimal parse(String source) throws ParseException {
+ DecimalFormatSymbols symbols = new DecimalFormatSymbols();
+ symbols.setDecimalSeparator('.');
+ DecimalFormat format = new DecimalFormat("#.#", symbols);
+ format.setParseBigDecimal(true);
+ return (BigDecimal) format.parse(source);
+ }
+}
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
index 90ecc8a..26e0386 100644
--- 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
@@ -15,9 +15,6 @@
*/
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;
@@ -134,4 +131,29 @@
int otherIdentity = System.identityHashCode(testResult);
return thisIdentity.compareTo(otherIdentity);
}
+
+ public static class TestFailure {
+ private final String message;
+ private final String stackTrace;
+ private final String exceptionType;
+
+ public TestFailure(String message, String stackTrace, String exceptionType) {
+ this.message = message;
+ this.stackTrace = stackTrace;
+ this.exceptionType = exceptionType;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String getStackTrace() {
+ return stackTrace;
+ }
+
+ public String getExceptionType() {
+ return exceptionType;
+ }
+ }
+
}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResultModel.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResultModel.java
new file mode 100644
index 0000000..4d8e167
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResultModel.java
@@ -0,0 +1,60 @@
+/*
+ * 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.tasks.testing.TestResult;
+import org.gradle.reporting.DurationFormatter;
+
+public abstract class TestResultModel {
+ public static final DurationFormatter DURATION_FORMATTER = new DurationFormatter();
+
+ public abstract TestResult.ResultType getResultType();
+
+ public abstract long getDuration();
+
+ public abstract String getTitle();
+
+ public String getFormattedDuration() {
+ return DURATION_FORMATTER.format(getDuration());
+ }
+
+ public String getStatusClass() {
+ switch (getResultType()) {
+ case SUCCESS:
+ return "success";
+ case FAILURE:
+ return "failures";
+ case SKIPPED:
+ return "skipped";
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ public String getFormattedResultType() {
+ switch (getResultType()) {
+ case SUCCESS:
+ return "passed";
+ case FAILURE:
+ return "failed";
+ case SKIPPED:
+ return "ignored";
+ default:
+ throw new IllegalStateException();
+ }
+ }
+}
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
index 521616b..5a4b59d 100644
--- 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
@@ -14,67 +14,37 @@
* 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.build.gradle.api.BaseVariant
+import com.android.builder.core.DefaultBuildType
+import com.android.builder.core.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)
+ NamedDomainObjectContainer<SigningConfig> signingConfigs,
+ boolean isLibrary) {
+ super(plugin, project, instantiator, buildTypes, productFlavors, signingConfigs, isLibrary)
}
public DefaultDomainObjectSet<ApplicationVariant> getApplicationVariants() {
return applicationVariantList
}
- void addApplicationVariant(ApplicationVariant applicationVariant) {
- applicationVariantList.add(applicationVariant)
+ @Override
+ void addVariant(BaseVariant variant) {
+ applicationVariantList.add((ApplicationVariant) variant)
}
}
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
index c65544b74..e24da47 100644
--- 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
@@ -15,67 +15,31 @@
*/
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 com.android.build.gradle.internal.variant.ApplicationVariantFactory
+import com.android.build.gradle.internal.variant.VariantFactory
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> {
+class AppPlugin extends 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
+ protected Class<? extends BaseExtension> getExtensionClass() {
+ return AppExtension.class
}
@Override
@@ -87,523 +51,12 @@
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.")
- }
+ // create the config to link a wear apk.
+ project.configurations.create(ApplicationVariantFactory.CONFIG_WEAR_APP)
}
- /**
- * 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)
+ protected VariantFactory getVariantFactory() {
+ return new ApplicationVariantFactory(this)
}
}
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
index ba72ffc..abc279e 100644
--- 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
@@ -14,7 +14,6 @@
* limitations under the License.
*/
package com.android.build.gradle
-
import com.android.SdkConstants
import com.android.annotations.NonNull
import com.android.annotations.Nullable
@@ -23,16 +22,20 @@
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.coverage.JacocoExtension
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.PackagingOptionsImpl
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.core.BuilderConstants
+import com.android.builder.core.DefaultBuildType
+import com.android.builder.core.DefaultProductFlavor
import com.android.builder.model.BuildType
import com.android.builder.model.ProductFlavor
+import com.android.builder.model.SigningConfig
import com.android.builder.model.SourceProvider
import com.android.builder.testing.api.DeviceProvider
import com.android.builder.testing.api.TestServer
@@ -61,6 +64,22 @@
final DexOptionsImpl dexOptions
final TestOptions testOptions
final CompileOptions compileOptions
+ final PackagingOptionsImpl packagingOptions
+ final JacocoExtension jacoco
+
+ final NamedDomainObjectContainer<DefaultProductFlavor> productFlavors
+ final NamedDomainObjectContainer<DefaultBuildType> buildTypes
+ final NamedDomainObjectContainer<SigningConfig> signingConfigs
+
+ String resourcePrefix
+
+ List<String> flavorDimensionList
+ String testBuildType = "debug"
+
+ private String defaultPublishConfig = "release"
+ private boolean publishNonDefault = false
+
+ private Closure<Void> variantFilter
private final DefaultDomainObjectSet<TestVariant> testVariantList =
new DefaultDomainObjectSet<TestVariant>(TestVariant.class)
@@ -68,28 +87,39 @@
private final List<DeviceProvider> deviceProviderList = Lists.newArrayList();
private final List<TestServer> testServerList = Lists.newArrayList();
- protected final BasePlugin plugin
-
+ private final BasePlugin plugin
/**
* The source sets container.
*/
final NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer
- BaseExtension(BasePlugin plugin, ProjectInternal project, Instantiator instantiator) {
+ BaseExtension(
+ @NonNull BasePlugin plugin,
+ @NonNull ProjectInternal project,
+ @NonNull Instantiator instantiator,
+ @NonNull NamedDomainObjectContainer<DefaultBuildType> buildTypes,
+ @NonNull NamedDomainObjectContainer<DefaultProductFlavor> productFlavors,
+ @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
+ final boolean isLibrary) {
this.plugin = plugin
+ this.buildTypes = buildTypes
+ this.productFlavors = productFlavors
+ this.signingConfigs = signingConfigs
defaultConfig = instantiator.newInstance(ProductFlavorDsl.class, BuilderConstants.MAIN,
- project.fileResolver, instantiator)
+ project, instantiator, project.getLogger())
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)
+ packagingOptions = instantiator.newInstance(PackagingOptionsImpl.class)
+ jacoco = instantiator.newInstance(JacocoExtension.class)
sourceSetsContainer = project.container(AndroidSourceSet,
- new AndroidSourceSetFactory(instantiator, project.fileResolver))
+ new AndroidSourceSetFactory(instantiator, project, isLibrary))
sourceSetsContainer.whenObjectAdded { AndroidSourceSet sourceSet ->
ConfigurationContainer configurations = project.getConfigurations()
@@ -109,46 +139,77 @@
packageConfiguration = configurations.create(sourceSet.getPackageConfigurationName())
}
packageConfiguration.setVisible(false)
- packageConfiguration.extendsFrom(compileConfiguration)
- packageConfiguration.setDescription(
- String.format("Classpath packaged with the compiled %s classes.",
- sourceSet.getName()));
+ if (isLibrary) {
+ packageConfiguration.setDescription(
+ String.format("Classpath only used for publishing.",
+ sourceSet.getName()));
+ } else {
+ packageConfiguration.setDescription(
+ String.format("Classpath packaged with the compiled %s classes.",
+ sourceSet.getName()));
+ }
+
+ Configuration providedConfiguration = configurations.findByName(
+ sourceSet.getProvidedConfigurationName())
+ if (providedConfiguration == null) {
+ providedConfiguration = configurations.create(sourceSet.getProvidedConfigurationName())
+ }
+ providedConfiguration.setVisible(false);
+ providedConfiguration.setDescription(
+ String.format("Classpath for only compiling the %s sources.", 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();
+ plugin.checkTasksAlreadyCreated()
this.target = target
}
+ void compileSdkVersion(int apiLevel) {
+ compileSdkVersion("android-" + apiLevel)
+ }
+
+ void setCompileSdkVersion(int apiLevel) {
+ compileSdkVersion(apiLevel)
+ }
+
void setCompileSdkVersion(String target) {
- plugin.checkTasksAlreadyCreated();
compileSdkVersion(target)
}
void buildToolsVersion(String version) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
buildToolsRevision = FullRevision.parseRevision(version)
}
void setBuildToolsVersion(String version) {
- plugin.checkTasksAlreadyCreated();
buildToolsVersion(version)
}
+ 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 flavorDimensions(String... dimensions) {
+ plugin.checkTasksAlreadyCreated()
+ flavorDimensionList = Arrays.asList(dimensions)
+ }
+
void sourceSets(Action<NamedDomainObjectContainer<AndroidSourceSet>> action) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
action.execute(sourceSetsContainer)
}
@@ -157,37 +218,47 @@
}
void defaultConfig(Action<DefaultProductFlavor> action) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
action.execute(defaultConfig)
}
void aaptOptions(Action<AaptOptionsImpl> action) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
action.execute(aaptOptions)
}
void dexOptions(Action<DexOptionsImpl> action) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
action.execute(dexOptions)
}
void lintOptions(Action<LintOptionsImpl> action) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
action.execute(lintOptions)
}
void testOptions(Action<TestOptions> action) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
action.execute(testOptions)
}
void compileOptions(Action<CompileOptions> action) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
action.execute(compileOptions)
}
+ void packagingOptions(Action<PackagingOptionsImpl> action) {
+ plugin.checkTasksAlreadyCreated()
+ action.execute(packagingOptions)
+ }
+
+ void jacoco(Action<JacocoExtension> action) {
+ plugin.checkTasksAlreadyCreated()
+ action.execute(jacoco)
+ }
+
void deviceProvider(DeviceProvider deviceProvider) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
deviceProviderList.add(deviceProvider)
}
@@ -197,7 +268,7 @@
}
void testServer(TestServer testServer) {
- plugin.checkTasksAlreadyCreated();
+ plugin.checkTasksAlreadyCreated()
testServerList.add(testServer)
}
@@ -206,11 +277,41 @@
return testServerList
}
+ public void defaultPublishConfig(String value) {
+ defaultPublishConfig = value
+ }
+
+ public void publishNonDefault(boolean value) {
+ publishNonDefault = value
+ }
+
+ public String getDefaultPublishConfig() {
+ return defaultPublishConfig
+ }
+
+ public boolean getPublishNonDefault() {
+ return publishNonDefault
+ }
+
+ void variantFilter(Closure<Void> filter) {
+ variantFilter = filter
+ }
+
+ public Closure<Void> getVariantFilter() {
+ return variantFilter;
+ }
+
+ void resourcePrefix(String prefix) {
+ resourcePrefix = prefix
+ }
+
@NonNull
public DefaultDomainObjectSet<TestVariant> getTestVariants() {
return testVariantList
}
+ abstract void addVariant(BaseVariant variant)
+
void addTestVariant(TestVariant testVariant) {
testVariantList.add(testVariant)
}
@@ -240,10 +341,11 @@
@NonNull BaseVariant variant,
@NonNull String assembleTaskName,
@NonNull String javaCompileTaskName,
+ @NonNull Configuration configuration,
@NonNull File classesFolder,
@Nullable SourceProvider sourceProvider) {
plugin.registerJavaArtifact(name, variant, assembleTaskName, javaCompileTaskName,
- classesFolder, sourceProvider)
+ configuration, classesFolder, sourceProvider)
}
public void registerMultiFlavorSourceProvider(
@@ -266,18 +368,66 @@
return buildToolsRevision
}
+ public File getSdkDirectory() {
+ return plugin.getSdkFolder()
+ }
+
+ public List<String> getBootClasspath() {
+ return plugin.getBootClasspath()
+ }
+
public File getAdbExe() {
- return plugin.sdkParser.adb
+ return plugin.getSdkInfo().adb
}
public ILogger getLogger() {
return plugin.logger
}
+ protected getPlugin() {
+ return plugin
+ }
+
public File getDefaultProguardFile(String name) {
- return new File(plugin.sdkDirectory,
+ return new File(sdkDirectory,
SdkConstants.FD_TOOLS + File.separatorChar
+ SdkConstants.FD_PROGUARD + File.separatorChar
+ name);
}
+
+ // ---------------
+ // TEMP for compatibility
+ // STOPSHIP Remove in 1.0
+
+ // by default, use the new manifest merger.
+ boolean useOldManifestMerger = false;
+
+ void useOldManifestMerger(boolean flag) {
+ if (flag) {
+ plugin.displayDeprecationWarning("Support for old manifest merger is deprecated and will be removed in 1.0")
+ }
+ this.useOldManifestMerger = flag;
+ }
+
+ private boolean enforceUniquePackageName = true
+
+ public void enforceUniquePackageName(boolean value) {
+ if (!value) {
+ plugin.displayDeprecationWarning("Support for libraries with same package name is deprecated and will be removed in 1.0")
+ }
+ enforceUniquePackageName = value
+ }
+
+ public void setEnforceUniquePackageName(boolean value) {
+ enforceUniquePackageName(value)
+ }
+
+ public getEnforceUniquePackageName() {
+ return enforceUniquePackageName
+ }
+
+ public void flavorGroups(String... groups) {
+ plugin.displayDeprecationWarning("'flavorGroups' has been renamed 'flavorDimensions'. It will be removed in 1.0")
+ flavorDimensions(groups);
+ }
}
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
index b06ed55..9087508 100644
--- 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
@@ -15,33 +15,48 @@
*/
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.ConfigurationDependencies
+import com.android.build.gradle.internal.LibraryCache
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.SdkHandler
+import com.android.build.gradle.internal.VariantManager
import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
+import com.android.build.gradle.internal.coverage.JacocoInstrumentTask
+import com.android.build.gradle.internal.coverage.JacocoPlugin
+import com.android.build.gradle.internal.coverage.JacocoReportTask
+import com.android.build.gradle.internal.dependency.ClassifiedJarDependency
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.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.model.ArtifactMetaDataImpl
import com.android.build.gradle.internal.model.JavaArtifactImpl
import com.android.build.gradle.internal.model.ModelBuilder
+import com.android.build.gradle.internal.publishing.ApkPublishArtifact
import com.android.build.gradle.internal.tasks.AndroidReportTask
+import com.android.build.gradle.internal.tasks.CheckManifest
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.GenerateApkDataTask
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.PrepareSdkTask
import com.android.build.gradle.internal.tasks.SigningReportTask
import com.android.build.gradle.internal.tasks.TestServerTask
import com.android.build.gradle.internal.tasks.UninstallTask
@@ -54,28 +69,36 @@
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.internal.variant.VariantFactory
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.GenerateResValues
import com.android.build.gradle.tasks.Lint
import com.android.build.gradle.tasks.MergeAssets
+import com.android.build.gradle.tasks.MergeManifests
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.ProcessManifest
import com.android.build.gradle.tasks.ProcessTestManifest
+import com.android.build.gradle.tasks.ProcessTestManifest2
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.core.AndroidBuilder
+import com.android.builder.core.DefaultBuildType
+import com.android.builder.core.DefaultProductFlavor
+import com.android.builder.core.VariantConfiguration
+import com.android.builder.dependency.DependencyContainer
import com.android.builder.dependency.JarDependency
import com.android.builder.dependency.LibraryDependency
+import com.android.builder.internal.compiler.PreDexCache
+import com.android.builder.internal.testing.SimpleTestCallable
import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
+import com.android.builder.model.ApiVersion
import com.android.builder.model.ArtifactMetaData
import com.android.builder.model.BuildType
import com.android.builder.model.JavaArtifact
@@ -83,9 +106,14 @@
import com.android.builder.model.SigningConfig
import com.android.builder.model.SourceProvider
import com.android.builder.model.SourceProviderContainer
+import com.android.builder.png.PngProcessor
+import com.android.builder.sdk.SdkInfo
+import com.android.builder.sdk.TargetInfo
import com.android.builder.testing.ConnectedDeviceProvider
import com.android.builder.testing.api.DeviceProvider
import com.android.builder.testing.api.TestServer
+import com.android.ide.common.internal.ExecutorSingleton
+import com.android.ide.common.sdk.SdkVersionInfo
import com.android.utils.ILogger
import com.google.common.collect.ArrayListMultimap
import com.google.common.collect.ListMultimap
@@ -103,10 +131,12 @@
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.ResolvedComponentResult
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.internal.project.ProjectInternal
import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.Logger
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.specs.Specs
@@ -122,34 +152,53 @@
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 com.android.SdkConstants.FN_ANDROID_MANIFEST_XML
+import static com.android.builder.core.BuilderConstants.ANDROID_TEST
+import static com.android.builder.core.BuilderConstants.CONNECTED
+import static com.android.builder.core.BuilderConstants.DEBUG
+import static com.android.builder.core.BuilderConstants.DEVICE
+import static com.android.builder.core.BuilderConstants.EXT_LIB_ARCHIVE
+import static com.android.builder.core.BuilderConstants.FD_ANDROID_RESULTS
+import static com.android.builder.core.BuilderConstants.FD_ANDROID_TESTS
+import static com.android.builder.core.BuilderConstants.FD_FLAVORS
+import static com.android.builder.core.BuilderConstants.FD_FLAVORS_ALL
+import static com.android.builder.core.BuilderConstants.FD_REPORTS
+import static com.android.builder.core.BuilderConstants.RELEASE
+import static com.android.builder.core.VariantConfiguration.Type.TEST
+import static com.android.builder.model.AndroidProject.FD_GENERATED
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS
+import static com.android.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY
+import static com.android.builder.model.AndroidProject.PROPERTY_APK_LOCATION
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_KEY_ALIAS
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_KEY_PASSWORD
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_STORE_FILE
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_STORE_PASSWORD
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_STORE_TYPE
+import static com.android.sdklib.BuildToolInfo.PathId.ZIP_ALIGN
import static java.io.File.separator
-
/**
* Base class for all Android plugins
*/
public abstract class BasePlugin {
- protected final static String DIR_BUNDLES = "bundles";
+ public 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 GRADLE_MIN_VERSION = "1.10"
+ public static final String[] GRADLE_SUPPORTED_VERSIONS = [ GRADLE_MIN_VERSION, "1.11", "1.12" ]
public static final String INSTALL_GROUP = "Install"
public static File TEST_SDK_DIR;
+ public static final String FILE_JACOCO_AGENT = 'jacocoagent.jar'
+
protected Instantiator instantiator
private ToolingModelBuilderRegistry registry
- private final Map<Object, AndroidBuilder> builders = Maps.newIdentityHashMap()
+ protected JacocoPlugin jacocoPlugin
+
+ private BaseExtension extension
+ private VariantManager variantManager
final List<BaseVariantData> variantDataList = []
final Map<LibraryDependencyImpl, PrepareLibraryTask> prepareTaskMap = [:]
@@ -157,7 +206,8 @@
protected Project project
private LoggerWrapper loggerWrapper
- private Sdk sdk
+ protected SdkHandler sdkHandler
+ private AndroidBuilder androidBuilder
private String creator
private boolean hasCreatedTasks = false
@@ -168,13 +218,16 @@
protected DefaultAndroidSourceSet mainSourceSet
protected DefaultAndroidSourceSet testSourceSet
- protected Task mainPreBuild
+ protected PrepareSdkTask mainPreBuild
protected Task uninstallAll
protected Task assembleTest
protected Task deviceCheck
protected Task connectedCheck
- protected Task lint
- protected Task lintCompile
+ protected Copy jacocoAgentTask
+
+ public Task lintCompile
+ protected Task lintAll
+ protected Task lintVital
protected BasePlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
this.instantiator = instantiator
@@ -187,22 +240,84 @@
}
}
- public abstract BaseExtension getExtension()
- protected abstract void doCreateAndroidTasks()
+ protected abstract Class<? extends BaseExtension> getExtensionClass()
+ protected abstract VariantFactory getVariantFactory()
+
+ public Instantiator getInstantiator() {
+ return instantiator
+ }
+
+ public VariantManager getVariantManager() {
+ return variantManager
+ }
+
+ BaseExtension getExtension() {
+ return extension
+ }
protected void apply(Project project) {
this.project = project
checkGradleVersion()
- sdk = new Sdk(project, logger)
+ sdkHandler = new SdkHandler(project, logger)
+ androidBuilder = new AndroidBuilder(
+ project == project.rootProject ? project.name : project.path,
+ creator, logger, verbose)
project.apply plugin: JavaBasePlugin
+ project.apply plugin: JacocoPlugin
+ jacocoPlugin = project.plugins.getPlugin(JacocoPlugin)
+
// Register a builder for the custom tooling model
registry.register(new ModelBuilder());
+ def buildTypeContainer = project.container(DefaultBuildType,
+ new BuildTypeFactory(instantiator, project, project.getLogger()))
+ def productFlavorContainer = project.container(GroupableProductFlavorDsl,
+ new GroupableProductFlavorFactory(instantiator, project, project.getLogger()))
+ def signingConfigContainer = project.container(SigningConfig,
+ new SigningConfigFactory(instantiator))
+
+ extension = project.extensions.create('android', getExtensionClass(),
+ this, (ProjectInternal) project, instantiator,
+ buildTypeContainer, productFlavorContainer, signingConfigContainer,
+ this instanceof LibraryPlugin)
+ setBaseExtension(extension)
+
+ variantManager = new VariantManager(project, this, extension, getVariantFactory())
+
+ // map the whenObjectAdded callbacks on the containers.
+ signingConfigContainer.whenObjectAdded { SigningConfig signingConfig ->
+ variantManager.addSigningConfig((SigningConfigDsl) signingConfig)
+ }
+
+ buildTypeContainer.whenObjectAdded { DefaultBuildType buildType ->
+ variantManager.addBuildType((BuildTypeDsl) buildType)
+ }
+
+ productFlavorContainer.whenObjectAdded { GroupableProductFlavorDsl productFlavor ->
+ variantManager.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.")
+ }
+
project.tasks.assemble.description =
- "Assembles all variants of all applications and secondary packages."
+ "Assembles all variants of all applications and secondary packages."
uninstallAll = project.tasks.create("uninstallAll")
uninstallAll.description = "Uninstall all applications."
@@ -216,30 +331,43 @@
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
+ mainPreBuild = project.tasks.create("preBuild", PrepareSdkTask)
+ mainPreBuild.plugin = this
project.afterEvaluate {
createAndroidTasks(false)
}
+
+ // call back on execution. This is called after the whole build is done (not
+ // after the current project is done).
+ // This is will be called for each (android) projects though, so this should support
+ // being called 2+ times.
+ project.gradle.buildFinished {
+ ExecutorSingleton.shutdown()
+ PngProcessor.clearCache()
+ sdkHandler.unload()
+ PreDexCache.getCache().clear(
+ project.rootProject.file(
+ "${project.rootProject.buildDir}/${FD_INTERMEDIATES}/dex-cache/cache.xml"),
+ logger)
+ LibraryCache.getCache().unload()
+ }
+
+ project.gradle.taskGraph.whenReady { taskGraph ->
+ for (Task task : taskGraph.allTasks) {
+ if (task instanceof PreDex) {
+ PreDexCache.getCache().load(
+ project.rootProject.file(
+ "${project.rootProject.buildDir}/${FD_INTERMEDIATES}/dex-cache/cache.xml"))
+ break;
+ }
+ }
+ }
}
- protected void setBaseExtension(@NonNull BaseExtension extension) {
- sdk.setExtension(extension)
+ private void setBaseExtension(@NonNull BaseExtension extension) {
mainSourceSet = (DefaultAndroidSourceSet) extension.sourceSets.create(extension.defaultConfig.name)
- testSourceSet = (DefaultAndroidSourceSet) extension.sourceSets.create(INSTRUMENT_TEST)
+ testSourceSet = (DefaultAndroidSourceSet) extension.sourceSets.create(ANDROID_TEST)
defaultConfigData = new ProductFlavorData<DefaultProductFlavor>(
extension.defaultConfig, mainSourceSet,
@@ -289,8 +417,46 @@
}
hasCreatedTasks = true
- doCreateAndroidTasks()
+ // setup SDK repositories.
+ for (File file : sdkHandler.sdkLoader.repositories) {
+ project.repositories.maven {
+ url = file.toURI()
+ }
+ }
+
+ variantManager.createAndroidTasks(getSigningOverride())
createReportTasks()
+
+ if (lintVital != null) {
+ project.gradle.taskGraph.whenReady { taskGraph ->
+ if (taskGraph.hasTask(lintAll)) {
+ lintVital.setEnabled(false)
+ }
+ }
+ }
+ }
+
+ private SigningConfig getSigningOverride() {
+ if (project.hasProperty(PROPERTY_SIGNING_STORE_FILE) &&
+ project.hasProperty(PROPERTY_SIGNING_STORE_PASSWORD) &&
+ project.hasProperty(PROPERTY_SIGNING_KEY_ALIAS) &&
+ project.hasProperty(PROPERTY_SIGNING_KEY_PASSWORD)) {
+
+ SigningConfigDsl signingConfigDsl = new SigningConfigDsl("externalOverride")
+ Map<String, ?> props = project.getProperties();
+
+ signingConfigDsl.setStoreFile(new File((String) props.get(PROPERTY_SIGNING_STORE_FILE)))
+ signingConfigDsl.setStorePassword((String) props.get(PROPERTY_SIGNING_STORE_PASSWORD));
+ signingConfigDsl.setKeyAlias((String) props.get(PROPERTY_SIGNING_KEY_ALIAS));
+ signingConfigDsl.setKeyPassword((String) props.get(PROPERTY_SIGNING_KEY_PASSWORD));
+
+ if (project.hasProperty(PROPERTY_SIGNING_STORE_TYPE)) {
+ signingConfigDsl.setStoreType((String) props.get(PROPERTY_SIGNING_STORE_TYPE))
+ }
+
+ return signingConfigDsl
+ }
+ return null
}
void checkTasksAlreadyCreated() {
@@ -304,7 +470,6 @@
}
}
-
ProductFlavorData getDefaultConfigData() {
return defaultConfigData
}
@@ -313,22 +478,6 @@
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)
@@ -341,44 +490,151 @@
return project.logger.isEnabled(LogLevel.DEBUG)
}
- AndroidBuilder getAndroidBuilder(BaseVariantData variantData) {
- AndroidBuilder androidBuilder = builders.get(variantData)
+ void setAssembleTest(Task assembleTest) {
+ this.assembleTest = assembleTest
+ }
- if (androidBuilder == null) {
- SdkParser parser = getLoadedSdkParser()
- androidBuilder = new AndroidBuilder(parser, creator, logger, verbose)
- if (this instanceof LibraryPlugin) {
- androidBuilder.setBuildingLibrary(true);
- }
-
- builders.put(variantData, androidBuilder)
- }
-
+ AndroidBuilder getAndroidBuilder() {
return androidBuilder
}
-
- protected String getRuntimeJars() {
- return runtimeJarList.join(File.pathSeparator)
+ public File getSdkFolder() {
+ return sdkHandler.getSdkFolder()
}
- public List<String> getRuntimeJarList() {
- SdkParser sdkParser = getLoadedSdkParser()
- return AndroidBuilder.getBootClasspath(sdkParser);
+ public File getNdkFolder() {
+ return sdkHandler.getNdkFolder()
}
- protected void createProcessManifestTask(BaseVariantData variantData,
- String manifestOurDir) {
+ public SdkInfo getSdkInfo() {
+ return sdkHandler.getSdkInfo()
+ }
+
+ public List<String> getBootClasspath() {
+ ensureTargetSetup()
+
+ return androidBuilder.getBootClasspath()
+ }
+
+ public void ensureTargetSetup() {
+ // check if the target has been set.
+ TargetInfo targetInfo = androidBuilder.getTargetInfo()
+ if (targetInfo == null) {
+ sdkHandler.initTarget(
+ extension.getCompileSdkVersion(),
+ extension.buildToolsRevision,
+ androidBuilder)
+ }
+ }
+
+ public void createMergeManifestsTask(BaseVariantData variantData,
+ String manifestOutDir) {
+ if (extension.getUseOldManifestMerger()) {
+ createOldProcessManifestTask(variantData, manifestOutDir);
+ return;
+ }
+ VariantConfiguration config = variantData.variantConfiguration
+
def processManifestTask = project.tasks.create(
"process${variantData.variantConfiguration.fullName.capitalize()}Manifest",
- ProcessAppManifest)
- variantData.processManifestTask = processManifestTask
+ MergeManifests)
+ variantData.manifestProcessorTask = processManifestTask
+ processManifestTask.plugin = this
+
processManifestTask.dependsOn variantData.prepareDependenciesTask
+ processManifestTask.variantConfiguration = config
+ processManifestTask.conventionMapping.libraries = {
+ List<ManifestDependencyImpl> manifests = getManifestDependencies(config.directLibraries)
+
+ if (variantData.generateApkDataTask != null) {
+ manifests.add(new ManifestDependencyImpl(
+ variantData.generateApkDataTask.getManifestFile(),
+ Collections.emptyList()))
+ }
+
+ return manifests
+ }
+
+ ProductFlavor mergedFlavor = config.mergedFlavor
+
+ processManifestTask.conventionMapping.minSdkVersion = {
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename()
+ }
+
+ mergedFlavor.minSdkVersion?.apiString
+ }
+
+ processManifestTask.conventionMapping.targetSdkVersion = {
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename()
+ }
+
+ return mergedFlavor.targetSdkVersion?.apiString
+ }
+
+ processManifestTask.conventionMapping.manifestOutputFile = {
+ project.file(
+ "$project.buildDir/${FD_INTERMEDIATES}/${manifestOutDir}/" +
+ "${variantData.variantConfiguration.dirName}/AndroidManifest.xml")
+ }
+ }
+
+ public void createProcessManifestTask(BaseVariantData variantData, String manifestOutDir) {
+ if (extension.getUseOldManifestMerger()) {
+ createOldProcessManifestTask(variantData, manifestOutDir);
+ return;
+ }
+ VariantConfiguration config = variantData.variantConfiguration
+
+ def processManifest = project.tasks.create(
+ "process${variantData.variantConfiguration.fullName.capitalize()}Manifest",
+ ProcessManifest)
+ variantData.manifestProcessorTask = processManifest
+ processManifest.plugin = this
+
+ processManifest.dependsOn variantData.prepareDependenciesTask
+ processManifest.variantConfiguration = config
+
+ ProductFlavor mergedFlavor = config.mergedFlavor
+
+ processManifest.conventionMapping.minSdkVersion = {
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename()
+ }
+ return mergedFlavor.minSdkVersion?.apiString
+ }
+
+ processManifest.conventionMapping.targetSdkVersion = {
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename()
+ }
+
+ return mergedFlavor.targetSdkVersion?.apiString
+ }
+
+ processManifest.conventionMapping.manifestOutputFile = {
+ project.file(
+ "$project.buildDir/${FD_INTERMEDIATES}/${manifestOutDir}/" +
+ "${variantData.variantConfiguration.dirName}/AndroidManifest.xml")
+ }
+ }
+
+ public void createOldProcessManifestTask(BaseVariantData variantData,
+ String manifestOurDir) {
+ VariantConfiguration config = variantData.variantConfiguration
+
+ def processManifestTask = project.tasks.create(
+ "merge${variantData.variantConfiguration.fullName.capitalize()}Manifests",
+ ProcessAppManifest)
+ variantData.manifestProcessorTask = processManifestTask
+ processManifestTask.dependsOn variantData.prepareDependenciesTask
+ if (config.type != TEST) {
+ processManifestTask.dependsOn variantData.checkManifestTask
+ }
processManifestTask.plugin = this
- processManifestTask.variant = variantData
- VariantConfiguration config = variantData.variantConfiguration
ProductFlavor mergedFlavor = config.mergedFlavor
processManifestTask.conventionMapping.mainManifest = {
@@ -388,7 +644,7 @@
config.manifestOverlays
}
processManifestTask.conventionMapping.packageNameOverride = {
- config.packageOverride
+ config.idOverride
}
processManifestTask.conventionMapping.versionName = {
config.versionName
@@ -400,41 +656,65 @@
config.versionCode
}
processManifestTask.conventionMapping.minSdkVersion = {
- mergedFlavor.minSdkVersion
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename()
+ }
+
+ mergedFlavor.minSdkVersion?.apiString
}
+
processManifestTask.conventionMapping.targetSdkVersion = {
- mergedFlavor.targetSdkVersion
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename()
+ }
+
+ return mergedFlavor.targetSdkVersion?.apiString
}
processManifestTask.conventionMapping.manifestOutputFile = {
project.file(
- "$project.buildDir/${manifestOurDir}/${variantData.variantConfiguration.dirName}/AndroidManifest.xml")
+ "$project.buildDir/${FD_INTERMEDIATES}/${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
+ def processTestManifestTask;
+ if (extension.getUseOldManifestMerger()) {
+ processTestManifestTask = project.tasks.create(
+ "process${variantData.variantConfiguration.fullName.capitalize()}Manifest",
+ ProcessTestManifest)
+ } else {
+ processTestManifestTask = project.tasks.create(
+ "process${variantData.variantConfiguration.fullName.capitalize()}Manifest",
+ ProcessTestManifest2)
+ }
+
+ variantData.manifestProcessorTask = processTestManifestTask
processTestManifestTask.dependsOn variantData.prepareDependenciesTask
processTestManifestTask.plugin = this
- processTestManifestTask.variant = variantData
VariantConfiguration config = variantData.variantConfiguration
- processTestManifestTask.conventionMapping.testPackageName = {
- config.packageName
+ processTestManifestTask.conventionMapping.testApplicationId = {
+ config.applicationId
}
processTestManifestTask.conventionMapping.minSdkVersion = {
- config.minSdkVersion
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename()
+ }
+
+ config.minSdkVersion?.apiString
}
processTestManifestTask.conventionMapping.targetSdkVersion = {
- config.targetSdkVersion
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename()
+ }
+
+ return config.targetSdkVersion?.apiString
}
- processTestManifestTask.conventionMapping.testedPackageName = {
- config.testedPackageName
+ processTestManifestTask.conventionMapping.testedApplicationId = {
+ config.testedApplicationId
}
processTestManifestTask.conventionMapping.instrumentationRunner = {
config.instrumentationRunner
@@ -450,21 +730,27 @@
}
processTestManifestTask.conventionMapping.manifestOutputFile = {
project.file(
- "$project.buildDir/${manifestOurDir}/${variantData.variantConfiguration.dirName}/AndroidManifest.xml")
+ "$project.buildDir/${FD_INTERMEDIATES}/${manifestOurDir}/${variantData.variantConfiguration.dirName}/AndroidManifest.xml")
}
}
- protected void createRenderscriptTask(BaseVariantData variantData) {
+ public void createRenderscriptTask(BaseVariantData variantData) {
VariantConfiguration config = variantData.variantConfiguration
def renderscriptTask = project.tasks.create(
"compile${variantData.variantConfiguration.fullName.capitalize()}Renderscript",
RenderscriptCompile)
variantData.renderscriptCompileTask = renderscriptTask
+ if (config.type == TEST) {
+ renderscriptTask.dependsOn variantData.manifestProcessorTask
+ } else {
+ renderscriptTask.dependsOn variantData.checkManifestTask
+ }
ProductFlavor mergedFlavor = config.mergedFlavor
boolean ndkMode = mergedFlavor.renderscriptNdkMode
+ variantData.resourceGenTask.dependsOn renderscriptTask
// only put this dependency if rs will generate Java code
if (!ndkMode) {
variantData.sourceGenTask.dependsOn renderscriptTask
@@ -472,9 +758,22 @@
renderscriptTask.dependsOn variantData.prepareDependenciesTask
renderscriptTask.plugin = this
- renderscriptTask.variant = variantData
- renderscriptTask.targetApi = mergedFlavor.renderscriptTargetApi
+ renderscriptTask.conventionMapping.targetApi = {
+ int targetApi = mergedFlavor.renderscriptTargetApi
+ ApiVersion apiVersion = config.getMinSdkVersion()
+ if (apiVersion != null) {
+ int minSdk = apiVersion.apiLevel
+ if (apiVersion.codename != null) {
+ minSdk = SdkVersionInfo.getApiByBuildCode(apiVersion.codename, true)
+ }
+
+ return targetApi > minSdk ? targetApi : minSdk
+ }
+
+ return targetApi
+ }
+
renderscriptTask.supportMode = mergedFlavor.renderscriptSupportMode
renderscriptTask.ndkMode = ndkMode
renderscriptTask.debugBuild = config.buildType.renderscriptDebugBuild
@@ -484,32 +783,32 @@
renderscriptTask.conventionMapping.importDirs = { config.renderscriptImports }
renderscriptTask.conventionMapping.sourceOutputDir = {
- project.file("$project.buildDir/source/rs/${variantData.variantConfiguration.dirName}")
+ project.file("$project.buildDir/${FD_GENERATED}/source/rs/${variantData.variantConfiguration.dirName}")
}
renderscriptTask.conventionMapping.resOutputDir = {
- project.file("$project.buildDir/res/rs/${variantData.variantConfiguration.dirName}")
+ project.file("$project.buildDir/${FD_GENERATED}/res/rs/${variantData.variantConfiguration.dirName}")
}
renderscriptTask.conventionMapping.objOutputDir = {
- project.file("$project.buildDir/rs/${variantData.variantConfiguration.dirName}/obj")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/rs/${variantData.variantConfiguration.dirName}/obj")
}
renderscriptTask.conventionMapping.libOutputDir = {
- project.file("$project.buildDir/rs/${variantData.variantConfiguration.dirName}/lib")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/rs/${variantData.variantConfiguration.dirName}/lib")
}
renderscriptTask.conventionMapping.ndkConfig = { config.ndkConfig }
}
- protected void createMergeResourcesTask(@NonNull BaseVariantData variantData,
+ public void createMergeResourcesTask(@NonNull BaseVariantData variantData,
final boolean process9Patch) {
MergeResources mergeResourcesTask = basicCreateMergeResourcesTask(
variantData,
"merge",
- "$project.buildDir/res/all/${variantData.variantConfiguration.dirName}",
+ "$project.buildDir/${FD_INTERMEDIATES}/res/${variantData.variantConfiguration.dirName}",
true /*includeDependencies*/,
process9Patch)
variantData.mergeResourcesTask = mergeResourcesTask
}
- protected MergeResources basicCreateMergeResourcesTask(
+ public MergeResources basicCreateMergeResourcesTask(
@NonNull BaseVariantData variantData,
@NonNull String taskNamePrefix,
@NonNull String outputLocation,
@@ -519,17 +818,22 @@
"$taskNamePrefix${variantData.variantConfiguration.fullName.capitalize()}Resources",
MergeResources)
- mergeResourcesTask.dependsOn variantData.prepareDependenciesTask, variantData.renderscriptCompileTask
+ mergeResourcesTask.dependsOn variantData.prepareDependenciesTask, variantData.resourceGenTask
mergeResourcesTask.plugin = this
- mergeResourcesTask.variant = variantData
mergeResourcesTask.incrementalFolder = project.file(
- "$project.buildDir/incremental/${taskNamePrefix}Resources/${variantData.variantConfiguration.dirName}")
+ "$project.buildDir/${FD_INTERMEDIATES}/incremental/${taskNamePrefix}Resources/${variantData.variantConfiguration.dirName}")
mergeResourcesTask.process9Patch = process9Patch
+ mergeResourcesTask.conventionMapping.useAaptCruncher = { extension.aaptOptions.useAaptPngCruncher }
+
mergeResourcesTask.conventionMapping.inputResourceSets = {
- variantData.variantConfiguration.getResourceSets(
- variantData.renderscriptCompileTask.getResOutputDir(),
+ def generatedResFolders = [ variantData.renderscriptCompileTask.getResOutputDir(),
+ variantData.generateResValuesTask.getResOutputDir() ]
+ if (variantData.generateApkDataTask != null) {
+ generatedResFolders.add(variantData.generateApkDataTask.getResOutputDir())
+ }
+ variantData.variantConfiguration.getResourceSets(generatedResFolders,
includeDependencies)
}
@@ -538,31 +842,36 @@
return mergeResourcesTask
}
- protected void createMergeAssetsTask(@NonNull BaseVariantData variantData,
- @Nullable String outputLocation,
- final boolean includeDependencies) {
+ public void createMergeAssetsTask(@NonNull BaseVariantData variantData,
+ @Nullable String outputLocation,
+ final boolean includeDependencies) {
if (outputLocation == null) {
- outputLocation = "$project.buildDir/assets/${variantData.variantConfiguration.dirName}"
+ outputLocation = "$project.buildDir/${FD_INTERMEDIATES}/assets/${variantData.variantConfiguration.dirName}"
}
+ VariantConfiguration variantConfig = variantData.variantConfiguration
+
def mergeAssetsTask = project.tasks.create(
- "merge${variantData.variantConfiguration.fullName.capitalize()}Assets",
+ "merge${variantConfig.fullName.capitalize()}Assets",
MergeAssets)
variantData.mergeAssetsTask = mergeAssetsTask
- mergeAssetsTask.dependsOn variantData.prepareDependenciesTask
+ mergeAssetsTask.dependsOn variantData.prepareDependenciesTask, variantData.assetGenTask
mergeAssetsTask.plugin = this
- mergeAssetsTask.variant = variantData
mergeAssetsTask.incrementalFolder =
- project.file("$project.buildDir/incremental/mergeAssets/${variantData.variantConfiguration.dirName}")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/incremental/mergeAssets/${variantConfig.dirName}")
mergeAssetsTask.conventionMapping.inputAssetSets = {
- variantData.variantConfiguration.getAssetSets(includeDependencies)
+ def generatedAssets = []
+ if (variantData.copyApkTask != null) {
+ generatedAssets.add(variantData.copyApkTask.destinationDir)
+ }
+ variantConfig.getAssetSets(generatedAssets, includeDependencies)
}
mergeAssetsTask.conventionMapping.outputDir = { project.file(outputLocation) }
}
- protected void createBuildConfigTask(BaseVariantData variantData) {
+ public void createBuildConfigTask(BaseVariantData variantData) {
def generateBuildConfigTask = project.tasks.create(
"generate${variantData.variantConfiguration.fullName.capitalize()}BuildConfig",
GenerateBuildConfig)
@@ -571,21 +880,22 @@
VariantConfiguration variantConfiguration = variantData.variantConfiguration
variantData.sourceGenTask.dependsOn generateBuildConfigTask
- if (variantConfiguration.type == VariantConfiguration.Type.TEST) {
+ if (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.dependsOn variantData.manifestProcessorTask
+ } else {
+ generateBuildConfigTask.dependsOn variantData.checkManifestTask
}
generateBuildConfigTask.plugin = this
- generateBuildConfigTask.variant = variantData
generateBuildConfigTask.conventionMapping.buildConfigPackageName = {
- variantConfiguration.originalPackageName
+ variantConfiguration.originalApplicationId
}
generateBuildConfigTask.conventionMapping.appPackageName = {
- variantConfiguration.packageName
+ variantConfiguration.applicationId
}
generateBuildConfigTask.conventionMapping.versionName = {
@@ -617,30 +927,57 @@
}
generateBuildConfigTask.conventionMapping.sourceOutputDir = {
- project.file("$project.buildDir/source/buildConfig/${variantData.variantConfiguration.dirName}")
+ project.file("$project.buildDir/${FD_GENERATED}/source/buildConfig/${variantData.variantConfiguration.dirName}")
}
}
- protected void createProcessResTask(BaseVariantData variantData) {
- createProcessResTask(variantData, "$project.buildDir/symbols/${variantData.variantConfiguration.dirName}")
+ public void createGenerateResValuesTask(BaseVariantData variantData) {
+ GenerateResValues generateResValuesTask = project.tasks.create(
+ "generate${variantData.variantConfiguration.fullName.capitalize()}ResValues",
+ GenerateResValues)
+ variantData.generateResValuesTask = generateResValuesTask
+ variantData.resourceGenTask.dependsOn generateResValuesTask
+
+ VariantConfiguration variantConfiguration = variantData.variantConfiguration
+
+ generateResValuesTask.plugin = this
+
+ generateResValuesTask.conventionMapping.items = {
+ variantConfiguration.resValues
+ }
+
+ generateResValuesTask.conventionMapping.resOutputDir = {
+ project.file("$project.buildDir/${FD_GENERATED}/res/generated/${variantData.variantConfiguration.dirName}")
+ }
}
- protected void createProcessResTask(BaseVariantData variantData, final String symbolLocation) {
- def processResources = project.tasks.create(
+ public void createProcessResTask(
+ @NonNull BaseVariantData variantData,
+ boolean generateResourcePackage) {
+ createProcessResTask(variantData,
+ "$project.buildDir/${FD_INTERMEDIATES}/symbols/${variantData.variantConfiguration.dirName}",
+ generateResourcePackage)
+ }
+
+ public void createProcessResTask(
+ @NonNull BaseVariantData variantData,
+ @NonNull final String symbolLocation,
+ boolean generateResourcePackage) {
+ ProcessAndroidResources 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.dependsOn variantData.manifestProcessorTask, variantData.mergeResourcesTask, variantData.mergeAssetsTask
processResources.plugin = this
- processResources.variant = variantData
+ processResources.enforceUniquePackageName = extension.getEnforceUniquePackageName()
VariantConfiguration variantConfiguration = variantData.variantConfiguration
processResources.conventionMapping.manifestFile = {
- variantData.processManifestTask.manifestOutputFile
+ variantData.manifestProcessorTask.manifestOutputFile
}
processResources.conventionMapping.resDir = {
@@ -655,23 +992,25 @@
getTextSymbolDependencies(variantConfiguration.allLibraries)
}
processResources.conventionMapping.packageForR = {
- variantConfiguration.originalPackageName
+ variantConfiguration.originalApplicationId
}
// TODO: unify with generateBuilderConfig, compileAidl, and library packaging somehow?
processResources.conventionMapping.sourceOutputDir = {
- project.file("$project.buildDir/source/r/${variantData.variantConfiguration.dirName}")
+ project.file("$project.buildDir/${FD_GENERATED}/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 (generateResourcePackage) {
+ processResources.conventionMapping.packageOutputFile = {
+ project.file(
+ "$project.buildDir/${FD_INTERMEDIATES}/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")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/proguard/${variantData.variantConfiguration.dirName}/aapt_rules.txt")
}
}
@@ -681,7 +1020,7 @@
processResources.conventionMapping.resourceConfigs = { variantConfiguration.mergedFlavor.resourceConfigurations }
}
- protected void createProcessJavaResTask(BaseVariantData variantData) {
+ public void createProcessJavaResTask(BaseVariantData variantData) {
VariantConfiguration variantConfiguration = variantData.variantConfiguration
Copy processResources = project.tasks.create(
@@ -690,24 +1029,24 @@
variantData.processJavaResourcesTask = processResources
// set the input
- processResources.from(((AndroidSourceSet) variantConfiguration.defaultSourceSet).resources)
+ processResources.from(((AndroidSourceSet) variantConfiguration.defaultSourceSet).resources.getSourceFiles())
- if (variantConfiguration.type != VariantConfiguration.Type.TEST) {
+ if (variantConfiguration.type != TEST) {
processResources.from(
- ((AndroidSourceSet) variantConfiguration.buildTypeSourceSet).resources)
+ ((AndroidSourceSet) variantConfiguration.buildTypeSourceSet).resources.getSourceFiles())
}
if (variantConfiguration.hasFlavors()) {
for (SourceProvider flavorSourceSet : variantConfiguration.flavorSourceProviders) {
- processResources.from(((AndroidSourceSet) flavorSourceSet).resources)
+ processResources.from(((AndroidSourceSet) flavorSourceSet).resources.getSourceFiles())
}
}
processResources.conventionMapping.destinationDir = {
- project.file("$project.buildDir/javaResources/${variantData.variantConfiguration.dirName}")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/javaResources/${variantData.variantConfiguration.dirName}")
}
}
- protected void createAidlTask(BaseVariantData variantData) {
+ public void createAidlTask(@NonNull BaseVariantData variantData, @Nullable File parcelableDir) {
VariantConfiguration variantConfiguration = variantData.variantConfiguration
def compileTask = project.tasks.create(
@@ -719,69 +1058,52 @@
variantData.aidlCompileTask.dependsOn variantData.prepareDependenciesTask
compileTask.plugin = this
- compileTask.variant = variantData
compileTask.incrementalFolder =
- project.file("$project.buildDir/incremental/aidl/${variantData.variantConfiguration.dirName}")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/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}")
+ project.file("$project.buildDir/${FD_GENERATED}/source/aidl/${variantData.variantConfiguration.dirName}")
}
+ compileTask.aidlParcelableDir = parcelableDir
}
- protected void createCompileTask(BaseVariantData variantData,
+ public 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
+ compileTask.dependsOn variantData.sourceGenTask
+
+ compileTask.source = variantData.getJavaSources()
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
+ project.files(androidBuilder.getCompileClasspath(config)) + testedVariantData.javaCompileTask.classpath + testedVariantData.javaCompileTask.outputs.files
}
} else {
compileTask.conventionMapping.classpath = {
- project.files(getAndroidBuilder(variantData).getCompileClasspath(config))
+ project.files(androidBuilder.getCompileClasspath(config))
}
}
// TODO - dependency information for the compile classpath is being lost.
// Add a temporary approximation
- compileTask.dependsOn project.configurations.compile.buildDependencies
+ compileTask.dependsOn variantData.variantDependency.compileConfiguration.buildDependencies
compileTask.conventionMapping.destinationDir = {
- project.file("$project.buildDir/classes/${variantData.variantConfiguration.dirName}")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/classes/${variantData.variantConfiguration.dirName}")
}
compileTask.conventionMapping.dependencyCacheDir = {
- project.file("$project.buildDir/dependency-cache/${variantData.variantConfiguration.dirName}")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/dependency-cache/${variantData.variantConfiguration.dirName}")
}
// set source/target compatibility
@@ -796,25 +1118,66 @@
// 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()
+ compileTask.options.bootClasspath = androidBuilder.getBootClasspath().join(File.pathSeparator)
}
}
- protected void createNdkTasks(@NonNull BaseVariantData variantData) {
- createNdkTasks(
- variantData,
- { project.file("$project.buildDir/ndk/${variantData.variantConfiguration.dirName}/lib") }
- )
+ public void createCopyMicroApkTask(@NonNull BaseVariantData variantData,
+ @NonNull Configuration config) {
+ Copy copyTask = project.tasks.create("copy${variantData.variantConfiguration.fullName.capitalize()}MicroApk",
+ Copy)
+ variantData.copyApkTask = copyTask
+
+ File outDir = project.file("$project.buildDir/${FD_INTERMEDIATES}/${FD_GENERATED}/assets/microapk/${variantData.variantConfiguration.dirName}")
+
+ copyTask.from { config.getFiles() }
+ copyTask.into { outDir }
+
+ // make sure the destination folder is empty first
+ copyTask.doFirst {
+ outDir.deleteDir()
+ outDir.mkdirs()
+ }
+
+ copyTask.dependsOn config
+ variantData.assetGenTask.dependsOn copyTask
}
- protected void createNdkTasks(@NonNull BaseVariantData variantData,
- @NonNull Closure<File> soFolderClosure) {
+ public void createGenerateMicroApkDataTask(@NonNull BaseVariantData variantData,
+ @NonNull Configuration config) {
+ GenerateApkDataTask task = project.tasks.create(
+ "generate${variantData.variantConfiguration.fullName.capitalize()}ApkData",
+ GenerateApkDataTask)
+
+ variantData.generateApkDataTask = task
+
+ task.plugin = this
+ task.conventionMapping.resOutputDir = {
+ project.file("$project.buildDir/${FD_GENERATED}/res/microapk/${variantData.variantConfiguration.dirName}")
+ }
+ task.conventionMapping.apkFile = {
+ // only care about the first one. There shouldn't be more anyway.
+ config.getFiles().iterator().next()
+ }
+ task.conventionMapping.manifestFile = {
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/${FD_GENERATED}/manifests/microapk/${variantData.variantConfiguration.dirName}/${FN_ANDROID_MANIFEST_XML}")
+ }
+ task.conventionMapping.mainPkgName = {
+ variantData.variantConfiguration.getApplicationId()
+ }
+
+ task.dependsOn config
+ variantData.resourceGenTask.dependsOn task
+ }
+
+ public void createNdkTasks(@NonNull BaseVariantData variantData) {
NdkCompile ndkCompile = project.tasks.create(
"compile${variantData.variantConfiguration.fullName.capitalize()}Ndk",
NdkCompile)
+ ndkCompile.dependsOn mainPreBuild
+
ndkCompile.plugin = this
- ndkCompile.variant = variantData
variantData.ndkCompileTask = ndkCompile
VariantConfiguration variantConfig = variantData.variantConfiguration
@@ -836,7 +1199,7 @@
}
ndkCompile.conventionMapping.generatedMakefile = {
- project.file("$project.buildDir/ndk/${variantData.variantConfiguration.dirName}/Android.mk")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/ndk/${variantData.variantConfiguration.dirName}/Android.mk")
}
ndkCompile.conventionMapping.ndkConfig = { variantConfig.ndkConfig }
@@ -846,9 +1209,11 @@
}
ndkCompile.conventionMapping.objFolder = {
- project.file("$project.buildDir/ndk/${variantData.variantConfiguration.dirName}/obj")
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/ndk/${variantData.variantConfiguration.dirName}/obj")
}
- ndkCompile.conventionMapping.soFolder = soFolderClosure
+ ndkCompile.conventionMapping.soFolder = {
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/ndk/${variantData.variantConfiguration.dirName}/lib")
+ }
}
/**
@@ -858,20 +1223,16 @@
* @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.")
- }
-
+ public void createTestApkTasks(@NonNull TestVariantData variantData,
+ @NonNull BaseVariantData testedVariantData) {
createAnchorTasks(variantData)
// Add a task to process the manifest
createProcessTestManifestTask(variantData, "manifests")
+ // Add a task to create the res values
+ createGenerateResValuesTask(variantData)
+
// Add a task to compile renderscript files.
createRenderscriptTask(variantData)
@@ -884,7 +1245,7 @@
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.manifestProcessorTask.dependsOn testedVariantData.assembleTask
variantData.mergeResourcesTask.dependsOn testedVariantData.assembleTask
}
}
@@ -893,12 +1254,12 @@
createBuildConfigTask(variantData)
// Add a task to generate resource source files
- createProcessResTask(variantData)
+ createProcessResTask(variantData, true /*generateResourcePackage*/)
// process java resources
createProcessJavaResTask(variantData)
- createAidlTask(variantData)
+ createAidlTask(variantData, null /*parcelableDir*/)
// Add a task to compile the test application
createCompileTask(variantData, testedVariantData)
@@ -906,7 +1267,7 @@
// Add NDK tasks
createNdkTasks(variantData)
- addPackageTasks(variantData, null)
+ addPackageTasks(variantData, null, false /*publishApk*/)
if (assembleTest != null) {
assembleTest.dependsOn variantData.assembleTask
@@ -914,9 +1275,9 @@
}
// TODO - should compile src/lint/java from src/lint/java and jar it into build/lint/lint.jar
- protected void createLintCompileTask() {
+ public void createLintCompileTask() {
lintCompile = project.tasks.create("compileLint", Task)
- File outputDir = new File("$project.buildDir/lint")
+ File outputDir = new File("$project.buildDir/${FD_INTERMEDIATES}/lint")
lintCompile.doFirst{
// create the directory for lint output if it does not exist.
@@ -933,12 +1294,19 @@
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;
+ return config.getType() != 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() {
+ public void createLintTasks() {
+ Lint lint = project.tasks.create("lint", Lint)
+ lint.description = "Runs lint on all variants."
+ lint.group = JavaBasePlugin.VERIFICATION_GROUP
+ lint.setPlugin(this)
+ project.tasks.check.dependsOn lint
+ lintAll = lint
+
int count = variantDataList.size()
for (int i = 0 ; i < count ; i++) {
final BaseVariantData baseVariantData = variantDataList.get(i)
@@ -946,29 +1314,51 @@
continue;
}
+ // wire the main lint task dependency.
+ lint.dependsOn baseVariantData.javaCompileTask, lintCompile
+
String variantName = baseVariantData.variantConfiguration.fullName
def capitalizedVariantName = variantName.capitalize()
- Task lintCheck = project.tasks.create("lint" + capitalizedVariantName, Lint)
- lintCheck.dependsOn baseVariantData.javaCompileTask, lintCompile
+ Lint variantLintCheck = project.tasks.create("lint" + capitalizedVariantName, Lint)
+ variantLintCheck.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
+ variantLintCheck.setPlugin(this)
+ variantLintCheck.setVariantName(variantName)
+ variantLintCheck.description = "Runs lint on the " + capitalizedVariantName + " build"
+ variantLintCheck.group = JavaBasePlugin.VERIFICATION_GROUP
}
}
- protected void createCheckTasks(boolean hasFlavors, boolean isLibraryTest) {
+ private void createLintVitalTask(@NonNull ApkVariantData variantData) {
+ assert extension.lintOptions.checkReleaseBuilds
+ if (!variantData.variantConfiguration.buildType.debuggable) {
+ String variantName = variantData.variantConfiguration.fullName
+ def capitalizedVariantName = variantName.capitalize()
+ def taskName = "lintVital" + capitalizedVariantName
+ Lint lintReleaseCheck = project.tasks.create(taskName, Lint)
+ // TODO: Make this task depend on lintCompile too (resolve initialization order first)
+ lintReleaseCheck.dependsOn variantData.javaCompileTask
+ lintReleaseCheck.setPlugin(this)
+ lintReleaseCheck.setVariantName(variantName)
+ lintReleaseCheck.setFatalOnly(true)
+ lintReleaseCheck.description = "Runs lint on just the fatal issues in the " +
+ capitalizedVariantName + " build"
+ variantData.assembleTask.dependsOn lintReleaseCheck
+ lintVital = lintReleaseCheck
+ }
+ }
+
+ public 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()}"
+ String connectedRootName = "${CONNECTED}${ANDROID_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) {
@@ -980,14 +1370,14 @@
mainConnectedTask.conventionMapping.resultsDir = {
String rootLocation = extension.testOptions.resultsDir != null ?
- extension.testOptions.resultsDir : "$project.buildDir/$FD_INSTRUMENT_RESULTS"
+ extension.testOptions.resultsDir : "$project.buildDir/${FD_OUTPUTS}/$FD_ANDROID_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.buildDir/${FD_OUTPUTS}/$FD_REPORTS/$FD_ANDROID_TESTS"
project.file("$rootLocation/connected/$FD_FLAVORS_ALL")
}
@@ -999,7 +1389,7 @@
// 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()}",
+ mainProviderTask = project.tasks.create("${DEVICE}${ANDROID_TEST.capitalize()}",
AndroidReportTask)
mainProviderTask.group = JavaBasePlugin.VERIFICATION_GROUP
mainProviderTask.description = "Installs and runs instrumentation tests using all Device Providers."
@@ -1008,14 +1398,14 @@
mainProviderTask.conventionMapping.resultsDir = {
String rootLocation = extension.testOptions.resultsDir != null ?
- extension.testOptions.resultsDir : "$project.buildDir/$FD_INSTRUMENT_RESULTS"
+ extension.testOptions.resultsDir : "$project.buildDir/${FD_OUTPUTS}/$FD_ANDROID_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.buildDir/${FD_OUTPUTS}/$FD_REPORTS/$FD_ANDROID_TESTS"
project.file("$rootLocation/devices/$FD_FLAVORS_ALL")
}
@@ -1047,19 +1437,48 @@
DeviceProviderInstrumentTestTask,
testVariantData,
baseVariantData,
- new ConnectedDeviceProvider(getSdkParser()),
+ new ConnectedDeviceProvider(getSdkInfo().adb),
CONNECTED
)
mainConnectedTask.dependsOn connectedTask
testVariantData.connectedTestTask = connectedTask
+ if (baseVariantData.variantConfiguration.buildType.isTestCoverageEnabled()) {
+ def reportTask = project.tasks.create(
+ "create${baseVariantData.variantConfiguration.fullName.capitalize()}CoverageReport",
+ JacocoReportTask)
+ reportTask.reportName = baseVariantData.variantConfiguration.fullName
+ reportTask.conventionMapping.jacocoClasspath = {
+ project.configurations[JacocoPlugin.ANT_CONFIGURATION_NAME]
+ }
+ reportTask.conventionMapping.coverageFile = {
+ new File(connectedTask.getCoverageDir(), SimpleTestCallable.FILE_COVERAGE_EC)
+ }
+ reportTask.conventionMapping.classDir = {
+ baseVariantData.javaCompileTask.destinationDir
+ }
+ reportTask.conventionMapping.sourceDir = { baseVariantData.getJavaSourceFoldersForCoverage() }
+
+ reportTask.conventionMapping.reportDir = {
+ String rootLocation = extension.testOptions.reportDir != null ?
+ extension.testOptions.reportDir :
+ "$project.buildDir/${FD_OUTPUTS}/$FD_REPORTS/$FD_ANDROID_TESTS"
+
+ project.file(
+ "$project.buildDir/${FD_OUTPUTS}/$FD_REPORTS/coverage/${baseVariantData.variantConfiguration.dirName}")
+ }
+
+ reportTask.dependsOn connectedTask
+ mainConnectedTask.dependsOn reportTask
+ }
+
// 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()}",
+ "${deviceProvider.name}${ANDROID_TEST.capitalize()}${baseVariantData.variantConfiguration.fullName.capitalize()}" :
+ "${deviceProvider.name}${ANDROID_TEST.capitalize()}",
"Installs and runs the tests for Build '${baseVariantData.variantConfiguration.fullName}' using Provider '${deviceProvider.name.capitalize()}'.",
isLibraryTest ?
DeviceProviderInstrumentTestLibraryTask :
@@ -1086,6 +1505,7 @@
"${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
@@ -1154,7 +1574,7 @@
testTask.conventionMapping.resultsDir = {
String rootLocation = extension.testOptions.resultsDir != null ?
extension.testOptions.resultsDir :
- "$project.buildDir/$FD_INSTRUMENT_RESULTS"
+ "$project.buildDir/${FD_OUTPUTS}/$FD_ANDROID_RESULTS"
String flavorFolder = variantData.variantConfiguration.flavorName
if (!flavorFolder.isEmpty()) {
@@ -1166,7 +1586,17 @@
testTask.conventionMapping.reportsDir = {
String rootLocation = extension.testOptions.reportDir != null ?
extension.testOptions.reportDir :
- "$project.buildDir/$FD_REPORTS/$FD_INSTRUMENT_TESTS"
+ "$project.buildDir/${FD_OUTPUTS}/$FD_REPORTS/$FD_ANDROID_TESTS"
+
+ String flavorFolder = variantData.variantConfiguration.flavorName
+ if (!flavorFolder.isEmpty()) {
+ flavorFolder = "$FD_FLAVORS/" + flavorFolder
+ }
+
+ project.file("$rootLocation/$subFolder/$flavorFolder")
+ }
+ testTask.conventionMapping.coverageDir = {
+ String rootLocation = "$project.buildDir/${FD_OUTPUTS}/code-coverage"
String flavorFolder = variantData.variantConfiguration.flavorName
if (!flavorFolder.isEmpty()) {
@@ -1184,42 +1614,60 @@
* @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.
+ * @param publishApk if true the generated APK gets published.
*/
- protected void addPackageTasks(@NonNull ApkVariantData variantData,
- @Nullable Task assembleTask) {
+ public void addPackageTasks(@NonNull ApkVariantData variantData,
+ @Nullable Task assembleTask,
+ boolean publishApk) {
VariantConfiguration variantConfig = variantData.variantConfiguration
boolean runProguard = variantConfig.buildType.runProguard &&
- (variantConfig.type != VariantConfiguration.Type.TEST ||
- (variantConfig.type == VariantConfiguration.Type.TEST &&
+ (variantConfig.type != TEST ||
+ (variantConfig.type == TEST &&
variantConfig.testedConfig.type != VariantConfiguration.Type.LIBRARY))
+ boolean runInstrumentation = variantConfig.buildType.isTestCoverageEnabled() &&
+ variantConfig.type != TEST
+
// common dex task configuration
- String dexTaskName = "dex${variantData.variantConfiguration.fullName.capitalize()}"
+ String dexTaskName = "dex${variantConfig.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.conventionMapping.outputFolder = {
+ project.file("${project.buildDir}/${FD_INTERMEDIATES}/dex/${variantConfig.dirName}")
}
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() }
+ dexTask.dependsOn variantData.obfuscationTask
+ dexTask.conventionMapping.inputFiles = { project.files(outFile).files }
+ dexTask.conventionMapping.libraries = { Collections.emptyList() }
} else {
+ JacocoInstrumentTask jacocoTask = null
+ Copy agentTask = null
+ if (runInstrumentation) {
+ jacocoTask = project.tasks.create(
+ "instrument${variantConfig.fullName.capitalize()}", JacocoInstrumentTask)
+ jacocoTask.dependsOn variantData.javaCompileTask
+ jacocoTask.conventionMapping.jacocoClasspath = { project.configurations[JacocoPlugin.ANT_CONFIGURATION_NAME] }
+ jacocoTask.conventionMapping.inputDir = { variantData.javaCompileTask.destinationDir }
+ jacocoTask.conventionMapping.outputDir = {
+ project.file("${project.buildDir}/${FD_INTERMEDIATES}/coverage-instrumented-classes/${variantConfig.dirName}")
+ }
+
+ dexTask.dependsOn jacocoTask
+
+ agentTask = getJacocoAgentTask()
+ }
// if required, pre-dexing task.
PreDex preDexTask = null;
@@ -1228,52 +1676,78 @@
def preDexTaskName = "preDex${variantData.variantConfiguration.fullName.capitalize()}"
preDexTask = project.tasks.create(preDexTaskName, PreDex)
- preDexTask.dependsOn variantData.javaCompileTask
+ preDexTask.dependsOn variantData.variantDependency.packageConfiguration.buildDependencies
preDexTask.plugin = this
preDexTask.dexOptions = extension.dexOptions
preDexTask.conventionMapping.inputFiles = {
- project.files(getAndroidBuilder(variantData).getPackagedJars(variantConfig))
+ Set<File> set = androidBuilder.getPackagedJars(variantConfig)
+ if (jacocoTask != null) {
+ set.add(new File(agentTask.destinationDir, FILE_JACOCO_AGENT))
+ }
+
+ return set
}
preDexTask.conventionMapping.outputFolder = {
project.file(
- "${project.buildDir}/pre-dexed/${variantData.variantConfiguration.dirName}")
+ "${project.buildDir}/${FD_INTERMEDIATES}/pre-dexed/${variantData.variantConfiguration.dirName}")
+ }
+
+ if (agentTask != null) {
+ preDexTask.dependsOn agentTask
}
}
// then dexing task
dexTask.dependsOn variantData.javaCompileTask
+
if (runPreDex) {
dexTask.dependsOn preDexTask
+ } else {
+ dexTask.dependsOn variantData.variantDependency.packageConfiguration.buildDependencies
}
- dexTask.conventionMapping.inputFiles = { variantData.javaCompileTask.outputs.files }
+ dexTask.conventionMapping.inputFiles = {
+ if (jacocoTask != null) {
+ return project.files(jacocoTask.getOutputDir()).files
+ }
+
+ variantData.javaCompileTask.outputs.files.files
+ }
if (runPreDex) {
- dexTask.conventionMapping.preDexedLibraries = {
- project.fileTree(preDexTask.outputFolder).files
+ dexTask.conventionMapping.libraries = {
+ return project.fileTree(preDexTask.outputFolder).files
}
} else {
- dexTask.conventionMapping.preDexedLibraries = {
- project.files(getAndroidBuilder(variantData).getPackagedJars(variantConfig))
+ dexTask.conventionMapping.libraries = {
+ Set<File> set = androidBuilder.getPackagedJars(variantConfig)
+ if (jacocoTask != null) {
+ set.add(project.file("$project.buildDir/${FD_INTERMEDIATES}/jacoco/jacocoagent.jar"))
+ }
+
+ return set
+ }
+
+ if (agentTask != null) {
+ dexTask.dependsOn agentTask
}
}
}
// Add a task to generate application package
- def packageApp = project.tasks.create(
+ PackageApplication 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.dexFolder = { dexTask.outputFolder }
+ packageApp.conventionMapping.packagedJars = { androidBuilder.getPackagedJars(variantConfig) }
packageApp.conventionMapping.javaResourceDir = {
getOptionalDir(variantData.processJavaResourcesTask.destinationDir)
}
@@ -1283,10 +1757,11 @@
set.addAll(variantData.ndkCompileTask.soFolder)
set.addAll(variantData.renderscriptCompileTask.libOutputDir)
set.addAll(variantConfig.libraryJniFolders)
+ set.addAll(variantConfig.jniLibsList)
if (variantConfig.mergedFlavor.renderscriptSupportMode) {
- File rsLibs = getAndroidBuilder(variantData).getSupportNativeLibFolder()
- if (rsLibs.isDirectory()) {
+ File rsLibs = androidBuilder.getSupportNativeLibFolder()
+ if (rsLibs != null && rsLibs.isDirectory()) {
set.add(rsLibs);
}
}
@@ -1297,6 +1772,7 @@
packageApp.conventionMapping.jniDebugBuild = { variantConfig.buildType.jniDebugBuild }
SigningConfigDsl sc = (SigningConfigDsl) variantConfig.signingConfig
+
packageApp.conventionMapping.signingConfig = { sc }
if (sc != null) {
ValidateSigningTask validateSigningTask = validateSigningTaskMap.get(sc)
@@ -1312,13 +1788,29 @@
packageApp.dependsOn validateSigningTask
}
- def signedApk = variantData.isSigned()
- def apkName = signedApk ?
- "${project.archivesBaseName}-${variantData.variantConfiguration.baseName}-unaligned.apk" :
- "${project.archivesBaseName}-${variantData.variantConfiguration.baseName}-unsigned.apk"
+ boolean signedApk = variantData.isSigned()
+ String projectBaseName = project.archivesBaseName
+ String apkName = signedApk ?
+ "$projectBaseName-${variantData.variantConfiguration.baseName}-unaligned.apk" :
+ "$projectBaseName-${variantData.variantConfiguration.baseName}-unsigned.apk"
+
+ packageApp.conventionMapping.packagingOptions = { extension.packagingOptions }
+
+ String defaultLocation = "$project.buildDir/${FD_OUTPUTS}/apk"
+ String apkLocation = defaultLocation
+ if (project.hasProperty(PROPERTY_APK_LOCATION)) {
+ apkLocation = project.getProperties().get(PROPERTY_APK_LOCATION)
+ }
packageApp.conventionMapping.outputFile = {
- project.file("$project.buildDir/apk/${apkName}")
+ // if this is the final task then the location is
+ // the potentially overriden one.
+ if (!signedApk || !variantData.zipAlign) {
+ project.file("$apkLocation/${apkName}")
+ } else {
+ // otherwise default one.
+ project.file("$defaultLocation/${apkName}")
+ }
}
Task appTask = packageApp
@@ -1336,14 +1828,20 @@
zipAlignTask.conventionMapping.inputFile = { packageApp.outputFile }
zipAlignTask.conventionMapping.outputFile = {
project.file(
- "$project.buildDir/apk/${project.archivesBaseName}-${variantData.variantConfiguration.baseName}.apk")
+ "$apkLocation/$projectBaseName-${variantData.variantConfiguration.baseName}.apk")
}
- zipAlignTask.conventionMapping.zipAlignExe = { getSdkParser().zipAlign }
+ zipAlignTask.conventionMapping.zipAlignExe = {
+ String path = androidBuilder.targetInfo?.buildTools?.getPath(ZIP_ALIGN)
+ if (path != null) {
+ return new File(path)
+ }
+
+ return null
+ }
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
@@ -1354,22 +1852,45 @@
installTask.group = INSTALL_GROUP
installTask.dependsOn appTask
installTask.conventionMapping.packageFile = { outputFileTask.outputFile }
- installTask.conventionMapping.adbExe = { getSdkParser().adb }
+ installTask.conventionMapping.adbExe = { androidBuilder.sdkInfo?.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 = createAssembleTask(variantData)
}
assembleTask.dependsOn appTask
variantData.assembleTask = assembleTask
+ if (extension.lintOptions.checkReleaseBuilds) {
+ createLintVitalTask(variantData)
+ }
variantData.outputFile = { outputFileTask.outputFile }
+ if (publishApk) {
+ String fullName = variantData.variantConfiguration.fullName
+
+ if (extension.defaultPublishConfig.equals(fullName)) {
+
+ // add the artifact that will be published
+ project.artifacts.add("default", new ApkPublishArtifact(
+ projectBaseName,
+ null,
+ outputFileTask))
+ }
+
+ // also publish the artifact with its full config name
+ if (extension.publishNonDefault) {
+ project.artifacts.add(variantData.variantDependency.publishConfiguration.name,
+ new ApkPublishArtifact(
+ projectBaseName,
+ variantData.variantDependency.publishConfiguration.name,
+ outputFileTask))
+ }
+ }
+
// add an uninstall task
def uninstallTask = project.tasks.create(
"uninstall${variantData.variantConfiguration.fullName.capitalize()}",
@@ -1377,12 +1898,63 @@
uninstallTask.description = "Uninstalls the " + variantData.description
uninstallTask.group = INSTALL_GROUP
uninstallTask.variant = variantData
- uninstallTask.conventionMapping.adbExe = { getSdkParser().adb }
+ uninstallTask.conventionMapping.adbExe = { sdkHandler.sdkInfo?.adb }
variantData.uninstallTask = uninstallTask
uninstallAll.dependsOn uninstallTask
}
+ public Task createAssembleTask(BaseVariantData variantData) {
+ Task assembleTask = project.tasks.
+ create("assemble${variantData.variantConfiguration.fullName.capitalize()}")
+ assembleTask.description = "Assembles the " + variantData.description
+ assembleTask.group = org.gradle.api.plugins.BasePlugin.BUILD_GROUP
+ return assembleTask
+ }
+
+ public Copy getJacocoAgentTask() {
+ if (jacocoAgentTask == null) {
+ jacocoAgentTask = project.tasks.create("unzipJacocoAgent", Copy)
+ jacocoAgentTask.from { project.configurations[JacocoPlugin.AGENT_CONFIGURATION_NAME].collect { project.zipTree(it) } }
+ jacocoAgentTask.include FILE_JACOCO_AGENT
+ jacocoAgentTask.into "$project.buildDir/${FD_INTERMEDIATES}/jacoco"
+ }
+
+ return jacocoAgentTask
+ }
+
+ /**
+ * creates a zip align. This does not use convention mapping,
+ * and is meant to let other plugin create zip align tasks.
+ *
+ * @param name the name of the task
+ * @param inputFile the input file
+ * @param outputFile the output file
+ *
+ * @return the task
+ */
+ @NonNull
+ ZipAlign createZipAlignTask(
+ @NonNull String name,
+ @NonNull File inputFile,
+ @NonNull File outputFile) {
+ // Add a task to zip align application package
+ def zipAlignTask = project.tasks.create(name, ZipAlign)
+
+ zipAlignTask.inputFile = inputFile
+ zipAlignTask.outputFile = outputFile
+ zipAlignTask.conventionMapping.zipAlignExe = {
+ String path = androidBuilder.targetInfo?.buildTools?.getPath(ZIP_ALIGN)
+ if (path != null) {
+ return new File(path)
+ }
+
+ return null
+ }
+
+ return zipAlignTask
+ }
+
/**
* Creates the proguarding task for the given Variant.
* @param variantData the variant data.
@@ -1391,29 +1963,30 @@
* @return outFile file outputted by proguard
*/
@NonNull
- protected File createProguardTasks(@NonNull BaseVariantData variantData,
- @Nullable BaseVariantData testedVariantData) {
+ public 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
+ proguardTask.dependsOn variantData.javaCompileTask, variantData.variantDependency.packageConfiguration.buildDependencies
+
if (testedVariantData != null) {
- proguardTask.dependsOn testedVariantData.proguardTask
+ proguardTask.dependsOn testedVariantData.obfuscationTask
}
- variantData.proguardTask = proguardTask
+ variantData.obfuscationTask = proguardTask
// --- Output File ---
File outFile;
if (variantData instanceof LibraryVariantData) {
outFile = project.file(
- "${project.buildDir}/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/classes.jar")
+ "${project.buildDir}/${FD_INTERMEDIATES}/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/classes.jar")
} else {
outFile = project.file(
- "${project.buildDir}/classes-proguard/${variantData.variantConfiguration.dirName}/classes.jar")
+ "${project.buildDir}/${FD_INTERMEDIATES}/classes-proguard/${variantData.variantConfiguration.dirName}/classes.jar")
}
// --- Proguard Config ---
@@ -1427,7 +2000,7 @@
"}")
// 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")
+ proguardTask.applymapping("${project.buildDir}/${FD_OUTPUTS}/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.
@@ -1445,8 +2018,6 @@
// --- InJars / LibraryJars ---
- List<File> packagedJars = getAndroidBuilder(variantData).getPackagedJars(variantConfig)
-
if (variantData instanceof LibraryVariantData) {
String packageName = variantConfig.getPackageFromManifest()
if (packageName == null) {
@@ -1458,67 +2029,92 @@
// 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")
+ if (!((LibraryExtension)extension).packageBuildConfig) {
+ 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')
+ // injar: the local dependencies
+ Closure inJars = {
+ Arrays.asList(getLocalJarFileList(variantData.variantDependency))
}
- // libjar: the library dependencies
- for (File libJar : packagedJars) {
- proguardTask.libraryjars(libJar, filter: '!META-INF/MANIFEST.MF')
+ proguardTask.injars(inJars, filter: '!META-INF/MANIFEST.MF')
+
+ // libjar: the library dependencies. In this case we take all the compile-scope
+ // dependencies
+ Closure libJars = {
+ Set<File> compiledJars = androidBuilder.getCompileClasspath(variantConfig)
+ Object[] localJars = getLocalJarFileList(variantData.variantDependency)
+
+ compiledJars.findAll({ !localJars.contains(it) })
}
+ proguardTask.libraryjars(libJars, 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')
+ // injar: the packaged dependencies
+ Closure inJars = {
+ androidBuilder.getPackagedJars(variantConfig)
}
+
+ proguardTask.injars(inJars, filter: '!META-INF/MANIFEST.MF')
+
+ // the provided-only jars as libraries.
+ Closure libJars = {
+ variantData.variantConfiguration.providedOnlyJars
+ }
+
+ proguardTask.libraryjars(libJars)
}
- // libraryJars: the runtime jars
- for (String runtimeJar : getRuntimeJarList()) {
- proguardTask.libraryjars(runtimeJar)
+ // libraryJars: the runtime jars. Do this in doFirst since the boot classpath isn't
+ // available until the SDK is loaded in the prebuild task
+ proguardTask.doFirst {
+ for (String runtimeJar : androidBuilder.getBootClasspath()) {
+ 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')
+ Closure testedPackagedJars = {
+ androidBuilder.getPackagedJars(testedVariantData.variantConfiguration)
}
+
+ proguardTask.libraryjars(testedPackagedJars, 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")
+ final File proguardOut = project.file(
+ "${project.buildDir}/${FD_OUTPUTS}/proguard/${variantData.variantConfiguration.dirName}")
+
+ proguardTask.dump(new File(proguardOut, "dump.txt"))
+ proguardTask.printseeds(new File(proguardOut, "seeds.txt"))
+ proguardTask.printusage(new File(proguardOut, "usage.txt"))
+ proguardTask.printmapping(new File(proguardOut, "mapping.txt"))
+
+ // proguard doesn't verify that the seed/mapping/usage folders exist and will fail
+ // if they don't so create them.
+ proguardTask.doFirst {
+ proguardOut.mkdirs()
+ }
return outFile
}
@@ -1535,7 +2131,7 @@
signingReportTask.setGroup("Android")
}
- protected void createAnchorTasks(@NonNull BaseVariantData variantData) {
+ public void createAnchorTasks(@NonNull BaseVariantData variantData) {
variantData.preBuildTask = project.tasks.create(
"pre${variantData.variantConfiguration.fullName.capitalize()}Build")
variantData.preBuildTask.dependsOn mainPreBuild
@@ -1562,8 +2158,27 @@
// also create sourceGenTask
variantData.sourceGenTask = project.tasks.create(
"generate${variantData.variantConfiguration.fullName.capitalize()}Sources")
+ // and resGenTask
+ variantData.resourceGenTask = project.tasks.create(
+ "generate${variantData.variantConfiguration.fullName.capitalize()}Resources")
+ variantData.assetGenTask = project.tasks.create(
+ "generate${variantData.variantConfiguration.fullName.capitalize()}Assets")
}
+ public void createCheckManifestTask(@NonNull BaseVariantData variantData) {
+ String name = variantData.variantConfiguration.fullName
+ variantData.checkManifestTask = project.tasks.create(
+ "check${name.capitalize()}Manifest",
+ CheckManifest)
+ variantData.checkManifestTask.dependsOn variantData.preBuildTask
+
+ variantData.prepareDependenciesTask.dependsOn variantData.checkManifestTask
+
+ variantData.checkManifestTask.variantName = name
+ variantData.checkManifestTask.conventionMapping.manifest = {
+ variantData.variantConfiguration.getDefaultSourceSet().manifestFile
+ }
+ }
private final Map<String, ArtifactMetaData> extraArtifactMap = Maps.newHashMap()
private final ListMultimap<String, AndroidArtifact> extraAndroidArtifacts = ArrayListMultimap.create()
@@ -1652,6 +2267,7 @@
@NonNull BaseVariant variant,
@NonNull String assembleTaskName,
@NonNull String javaCompileTaskName,
+ @NonNull Configuration configuration,
@NonNull File classesFolder,
@Nullable SourceProvider sourceProvider) {
ArtifactMetaData artifactMetaData = extraArtifactMap.get(name)
@@ -1666,18 +2282,30 @@
JavaArtifact artifact = new JavaArtifactImpl(
name, assembleTaskName, javaCompileTaskName, classesFolder,
- null, sourceProvider, null)
+ new ConfigurationDependencies(configuration),
+ sourceProvider, null)
extraJavaArtifacts.put(variant.name, artifact)
}
+ public static Object[] getLocalJarFileList(DependencyContainer dependencyContainer) {
+ Set<File> files = Sets.newHashSet()
+ for (JarDependency jarDependency : dependencyContainer.localDependencies) {
+ files.add(jarDependency.jarFile)
+ }
+
+ return files.toArray()
+ }
+
+
//----------------------------------------------------------------------------------------------
//------------------------------ START DEPENDENCY STUFF ----------------------------------------
//----------------------------------------------------------------------------------------------
- def addDependencyToPrepareTask(@NonNull BaseVariantData variantData,
- @NonNull PrepareDependenciesTask prepareDependenciesTask,
- @NonNull LibraryDependencyImpl lib) {
- def prepareLibTask = prepareTaskMap.get(lib)
+ private void addDependencyToPrepareTask(
+ @NonNull BaseVariantData variantData,
+ @NonNull PrepareDependenciesTask prepareDependenciesTask,
+ @NonNull LibraryDependencyImpl lib) {
+ PrepareLibraryTask prepareLibTask = prepareTaskMap.get(lib)
if (prepareLibTask != null) {
prepareDependenciesTask.dependsOn prepareLibTask
prepareLibTask.dependsOn variantData.preBuildTask
@@ -1688,65 +2316,108 @@
}
}
- def resolveDependencies(VariantDependencies variantDeps) {
+ public void resolveDependencies(VariantDependencies variantDeps) {
Map<ModuleVersionIdentifier, List<LibraryDependencyImpl>> modules = [:]
Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts = [:]
Multimap<LibraryDependency, VariantDependencies> reverseMap = ArrayListMultimap.create()
resolveDependencyForConfig(variantDeps, modules, artifacts, reverseMap)
+ Set<Project> projects = project.rootProject.allprojects;
+
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)
- }
+ Task task = handleLibrary(project, androidDependency)
// Use the reverse map to find all the configurations that included this android
// library so that we can make sure they are built.
+ // TODO fix, this is not optimum as we bring in more dependencies than we should.
List<VariantDependencies> configDepList = reverseMap.get(androidDependency)
if (configDepList != null && !configDepList.isEmpty()) {
for (VariantDependencies configDependencies: configDepList) {
- prepareLibraryTask.dependsOn configDependencies.compileConfiguration.buildDependencies
+ task.dependsOn configDependencies.compileConfiguration.buildDependencies
}
}
+
+ // check if this library is created by a parent (this is based on the
+ // output file.
+ // TODO Fix this as it's fragile
+ /*
+ This is a somewhat better way but it doesn't work in some project with
+ weird setups...
+ Project parentProject = DependenciesImpl.getProject(library.getBundle(), projects)
+ if (parentProject != null) {
+ String configName = library.getProjectVariant();
+ if (configName == null) {
+ configName = "default"
+ }
+
+ prepareLibraryTask.dependsOn parentProject.getPath() + ":assemble${configName.capitalize()}"
+ }
+ */
}
}
}
- def resolveDependencyForConfig(
+ /**
+ * Handles the library and returns a task to "prepare" the library (ie unarchive it). The task
+ * will be reused for all projects using the same library.
+ *
+ * @param project the project
+ * @param library the library.
+ * @return the prepare task.
+ */
+ protected PrepareLibraryTask handleLibrary(
+ @NonNull Project project,
+ @NonNull LibraryDependencyImpl library) {
+ String bundleName = GUtil
+ .toCamelCase(library.getName().replaceAll("\\:", " "))
+
+ PrepareLibraryTask prepareLibraryTask = prepareTaskMap.get(library)
+
+ if (prepareLibraryTask == null) {
+ prepareLibraryTask = project.tasks.create(
+ "prepare" + bundleName + "Library", PrepareLibraryTask.class)
+
+ prepareLibraryTask.setDescription("Prepare " + library.getName())
+ prepareLibraryTask.conventionMapping.bundle = { library.getBundle() }
+ prepareLibraryTask.conventionMapping.explodedDir = { library.getBundleFolder() }
+
+ prepareTaskMap.put(library, prepareLibraryTask)
+ }
+
+ return prepareLibraryTask;
+ }
+
+ private void resolveDependencyForConfig(
VariantDependencies variantDeps,
Map<ModuleVersionIdentifier, List<LibraryDependencyImpl>> modules,
Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts,
Multimap<LibraryDependency, VariantDependencies> reverseMap) {
Configuration compileClasspath = variantDeps.compileConfiguration
+ Configuration packageClasspath = variantDeps.packageConfiguration
// TODO - shouldn't need to do this - fix this in Gradle
ensureConfigured(compileClasspath)
+ ensureConfigured(packageClasspath)
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
+ collectArtifacts(packageClasspath, artifacts)
+
+ List<LibraryDependencyImpl> bundles = []
+ Map<File, JarDependency> jars = [:]
+ Map<File, JarDependency> localJars = [:]
+
+ Set<DependencyResult> dependencies = compileClasspath.incoming.resolutionResult.root.dependencies
dependencies.each { DependencyResult dep ->
if (dep instanceof ResolvedDependencyResult) {
addDependency(dep.selected, variantDeps, bundles, jars, modules, artifacts, reverseMap)
@@ -1765,32 +2436,38 @@
!(dep instanceof ProjectDependency)) {
Set<File> files = ((SelfResolvingDependency) dep).resolve()
for (File f : files) {
- // TODO: support compile only dependencies.
- localJars << new JarDependency(f)
+ localJars.put(f, new JarDependency(f, true /*compiled*/, false /*packaged*/))
}
}
}
- // 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()) {
+ // 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.
+
Set<File> compileFiles = compileClasspath.files
Set<File> packageFiles = packageClasspath.files
for (File f : packageFiles) {
if (compileFiles.contains(f)) {
+ // if also in compile
+ JarDependency jarDep = jars.get(f);
+ if (jarDep == null) {
+ jarDep = localJars.get(f);
+ }
+ if (jarDep != null) {
+ jarDep.setPackaged(true)
+ }
continue
}
if (f.getName().toLowerCase().endsWith(".jar")) {
- jars.add(new JarDependency(f, false /*compiled*/, true /*packaged*/))
+ jars.put(f, new JarDependency(f, false /*compiled*/, true /*packaged*/))
} else {
throw new RuntimeException("Package-only dependency '" +
f.absolutePath +
- "' is not supported")
+ "' is not supported in project " + project.name)
}
}
} else if (!currentUnresolvedDependencies.isEmpty()) {
@@ -1798,25 +2475,40 @@
}
variantDeps.addLibraries(bundles)
- variantDeps.addJars(jars)
- variantDeps.addLocalJars(localJars)
+ variantDeps.addJars(jars.values())
+ variantDeps.addLocalJars(localJars.values())
// TODO - filter bundles out of source set classpath
configureBuild(variantDeps)
}
- def ensureConfigured(Configuration config) {
+ protected void 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
+ private void collectArtifacts(
+ Configuration configuration,
+ Map<ModuleVersionIdentifier,
+ List<ResolvedArtifact>> artifacts) {
+
+ // To keep backwards-compatibility, we check first if we have the JVM arg. If not, we look for
+ // the project property.
+ boolean buildModelOnly = false;
+ String val = System.getProperty(PROPERTY_BUILD_MODEL_ONLY);
+ if ("true".equalsIgnoreCase(val)) {
+ buildModelOnly = true;
+ } else if (project.hasProperty(PROPERTY_BUILD_MODEL_ONLY)) {
+ Object value = project.getProperties().get(PROPERTY_BUILD_MODEL_ONLY);
+ if (value instanceof String) {
+ buildModelOnly = Boolean.parseBoolean(value);
+ }
+ }
+
+ Set<ResolvedArtifact> allArtifacts
if (buildModelOnly) {
allArtifacts = configuration.resolvedConfiguration.lenientConfiguration.getArtifacts(Specs.satisfyAll())
} else {
@@ -1824,35 +2516,44 @@
}
allArtifacts.each { ResolvedArtifact artifact ->
- def id = artifact.moduleVersion.id
- List<ResolvedArtifact> moduleArtifacts = artifacts[id]
+ ModuleVersionIdentifier id = artifact.moduleVersion.id
+ List<ResolvedArtifact> moduleArtifacts = artifacts.get(id)
+
if (moduleArtifacts == null) {
- moduleArtifacts = []
- artifacts[id] = moduleArtifacts
+ moduleArtifacts = Lists.newArrayList()
+ artifacts.put(id, moduleArtifacts)
}
- moduleArtifacts << artifact
+
+ if (!moduleArtifacts.contains(artifact)) {
+ moduleArtifacts.add(artifact)
+ }
}
}
- def addDependency(ResolvedModuleVersionResult moduleVersion,
+ def addDependency(ResolvedComponentResult moduleVersion,
VariantDependencies configDependencies,
- Collection<LibraryDependencyImpl> bundles,
- List<JarDependency> jars,
+ Collection<LibraryDependency> bundles,
+ Map<File, JarDependency> jars,
Map<ModuleVersionIdentifier, List<LibraryDependencyImpl>> modules,
Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts,
Multimap<LibraryDependency, VariantDependencies> reverseMap) {
- def id = moduleVersion.id
+ ModuleVersionIdentifier id = moduleVersion.moduleVersion
if (configDependencies.checker.excluded(id)) {
return
}
- List<LibraryDependencyImpl> bundlesForThisModule = modules[id]
- if (bundlesForThisModule == null) {
- bundlesForThisModule = []
- modules[id] = bundlesForThisModule
+ if (id.name.equals("support-annotations") && id.group.equals("com.android.support")) {
+ configDependencies.annotationsPresent = true
+ }
- def nestedBundles = []
- def dependencies = moduleVersion.dependencies
+ List<LibraryDependencyImpl> bundlesForThisModule = modules.get(id)
+ if (bundlesForThisModule == null) {
+ bundlesForThisModule = Lists.newArrayList()
+ modules.put(id, bundlesForThisModule)
+
+ List<LibraryDependency> nestedBundles = Lists.newArrayList()
+
+ Set<DependencyResult> dependencies = moduleVersion.dependencies
dependencies.each { DependencyResult dep ->
if (dep instanceof ResolvedDependencyResult) {
addDependency(dep.selected, configDependencies, nestedBundles,
@@ -1860,20 +2561,30 @@
}
}
- def moduleArtifacts = artifacts[id]
+ List<ResolvedArtifact> moduleArtifacts = artifacts.get(id)
+
moduleArtifacts?.each { artifact ->
if (artifact.type == EXT_LIB_ARCHIVE) {
- String bundleName = GUtil.toCamelCase(id.group + " " + id.name + " " + id.version)
+ String path = "$id.group/$id.name/$id.version"
+ String name = "$id.group:$id.name:$id.version"
+ if (artifact.classifier != null) {
+ path += "/$artifact.classifier"
+ name += ":$artifact.classifier"
+ }
def explodedDir = project.file(
- "$project.buildDir/exploded-bundles/${bundleName}.aar")
+ "$project.rootProject.buildDir/${FD_INTERMEDIATES}/exploded-aar/$path")
LibraryDependencyImpl adep = new LibraryDependencyImpl(
- artifact.file, explodedDir, nestedBundles,
- id.group + ":" + id.name + ":" + id.version)
+ artifact.file, explodedDir, nestedBundles, name, artifact.classifier)
bundlesForThisModule << adep
reverseMap.put(adep, configDependencies)
} else {
- // TODO: support compile only dependencies.
- jars << new JarDependency(artifact.file)
+ jars.put(artifact.file,
+ new ClassifiedJarDependency(
+ artifact.file,
+ true /*compiled*/,
+ false /*packaged*/,
+ true /*proguarded*/,
+ artifact.classifier))
}
}
@@ -1941,7 +2652,7 @@
for (LibraryDependency lib : libraries) {
// get the dependencies
List<ManifestDependencyImpl> children = getManifestDependencies(lib.dependencies)
- list.add(new ManifestDependencyImpl(lib.manifest, children))
+ list.add(new ManifestDependencyImpl(lib.getName(), lib.manifest, children))
}
return list
@@ -1982,4 +2693,20 @@
public Project getProject() {
return project
}
+
+ public void displayDeprecationWarning(String message) {
+ displayDeprecationWarning(logger, project, message)
+ }
+
+ public static void displayDeprecationWarning(ILogger logger, Project project, String message) {
+ logger.warning(createDeprecationWarning(project.path, message))
+ }
+
+ public static void displayDeprecationWarning(Logger logger, Project project, String message) {
+ logger.warn(createDeprecationWarning(project.path, message))
+ }
+
+ private static String createDeprecationWarning(String projectName, String message) {
+ return "WARNING [Project: $projectName] $message"
+ }
}
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
index e1cf6a3..d549b52 100644
--- 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
@@ -15,62 +15,59 @@
*/
package com.android.build.gradle
+import com.android.build.gradle.api.BaseVariant
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.core.DefaultBuildType
+import com.android.builder.core.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 '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)
+ LibraryExtension(BasePlugin plugin, ProjectInternal project, Instantiator instantiator,
+ NamedDomainObjectContainer<DefaultBuildType> buildTypes,
+ NamedDomainObjectContainer<DefaultProductFlavor> productFlavors,
+ NamedDomainObjectContainer<SigningConfig> signingConfigs,
+ boolean isLibrary) {
+ super(plugin, project, instantiator, buildTypes, productFlavors, signingConfigs, isLibrary)
}
public DefaultDomainObjectSet<LibraryVariant> getLibraryVariants() {
return libraryVariantList
}
- void addLibraryVariant(LibraryVariant libraryVariant) {
- libraryVariantList.add(libraryVariant)
+ @Override
+ void addVariant(BaseVariant variant) {
+ libraryVariantList.add((LibraryVariant) variant)
+ }
+
+ // ---------------
+ // TEMP for compatibility
+ // STOPSHIP Remove in 1.0
+
+ private boolean packageBuildConfig = true
+
+ public void packageBuildConfig(boolean value) {
+ if (!value) {
+ plugin.displayDeprecationWarning("Support for not packaging BuildConfig is deprecated and will be removed in 1.0")
+ }
+
+ packageBuildConfig = value
+ }
+
+ public void setPackageBuildConfig(boolean value) {
+ packageBuildConfig(value)
+ }
+
+ boolean getPackageBuildConfig() {
+ return packageBuildConfig
}
}
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
index 96f4654..c8d66e6 100644
--- 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
@@ -14,43 +14,13 @@
* 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 com.android.build.gradle.internal.variant.LibraryVariantFactory
+import com.android.build.gradle.internal.variant.VariantFactory
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.api.Task
import org.gradle.internal.reflect.Instantiator
-import org.gradle.tooling.BuildException
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import javax.inject.Inject
@@ -59,9 +29,11 @@
*/
public class LibraryPlugin extends BasePlugin implements Plugin<Project> {
- LibraryExtension extension
- BuildTypeData debugBuildTypeData
- BuildTypeData releaseBuildTypeData
+ /**
+ * Default assemble task for the default-published artifact. this is needed for
+ * the prepare task on the consuming project.
+ */
+ Task assembleDefault
@Inject
public LibraryPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
@@ -69,383 +41,19 @@
}
@Override
- public LibraryExtension getExtension() {
- return extension
+ public Class<? extends BaseExtension> getExtensionClass() {
+ return LibraryExtension.class
+ }
+
+ @Override
+ protected VariantFactory getVariantFactory() {
+ return new LibraryVariantFactory(this, (LibraryExtension) getExtension());
}
@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)
- }
+ assembleDefault = project.tasks.create("assembleDefault")
}
}
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
index e9ddf50..d0b9a50 100644
--- 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
@@ -23,9 +23,9 @@
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
+import static com.android.builder.core.BuilderConstants.REPORTS
+import static com.android.builder.core.BuilderConstants.FD_ANDROID_RESULTS
+import static com.android.builder.core.BuilderConstants.INSTRUMENTATION_TESTS
/**
* Gradle plugin class for 'reporting' projects.
*
@@ -52,7 +52,7 @@
mergeReportsTask.conventionMapping.resultsDir = {
String location = extension.resultsDir != null ?
- extension.resultsDir : "$project.buildDir/$FD_INSTRUMENT_RESULTS"
+ extension.resultsDir : "$project.buildDir/$FD_ANDROID_RESULTS"
project.file(location)
}
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
index 1c37913..2f7b979 100644
--- 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
@@ -18,13 +18,16 @@
import com.android.annotations.NonNull;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.tasks.util.PatternFilterable;
+
import java.io.File;
import java.util.Set;
/**
* An AndroidSourceDirectorySet represents a lit of directory input for an Android project.
*/
-public interface AndroidSourceDirectorySet {
+public interface AndroidSourceDirectorySet extends PatternFilterable {
/**
* A concise name for the source directory (typically used to identify it in a collection).
@@ -63,6 +66,23 @@
AndroidSourceDirectorySet setSrcDirs(Iterable<?> srcDirs);
/**
+ * Returns the list of source files as a {@link org.gradle.api.file.FileTree}
+ *
+ * @return a non null {@link FileTree} for all the source files in this set.
+ */
+ @NonNull
+ public FileTree getSourceFiles();
+
+ /**
+ * Returns the filter used to select the source from the source directories.
+ *
+ * @return a non null {@link org.gradle.api.tasks.util.PatternFilterable}
+ */
+ @NonNull
+ PatternFilterable getFilter();
+
+
+ /**
* Returns the resolved directories.
* @return a non null set of File objects.
*/
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
index 72f58d7..251d29d 100644
--- 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
@@ -39,13 +39,13 @@
* @return the java resources. Never returns null.
*/
@NonNull
- SourceDirectorySet getResources();
+ AndroidSourceDirectorySet getResources();
/**
* Configures the Java resources for this set.
*
- * <p>The given closure is used to configure the {@link SourceDirectorySet} which contains the
- * java resources.
+ * <p>The given closure is used to configure the {@link AndroidSourceDirectorySet} which
+ * contains the java resources.
*
* @param configureClosure The closure to use to configure the javaResources.
* @return this
@@ -60,13 +60,13 @@
* @return the Java source. Never returns null.
*/
@NonNull
- SourceDirectorySet getJava();
+ AndroidSourceDirectorySet getJava();
/**
* Configures the Java source for this set.
*
- * <p>The given closure is used to configure the {@link SourceDirectorySet} which contains the
- * Java source.
+ * <p>The given closure is used to configure the {@link AndroidSourceDirectorySet} which
+ * contains the Java source.
*
* @param configureClosure The closure to use to configure the Java source.
* @return this
@@ -75,23 +75,6 @@
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
*/
@@ -106,6 +89,13 @@
String getPackageConfigurationName();
/**
+ * Returns the name of the compiled-only configuration for this source set.
+ * @return The provided configuration name
+ */
+ @NonNull
+ String getProvidedConfigurationName();
+
+ /**
* The Android Manifest file for this source set.
*
* @return the manifest. Never returns null.
@@ -226,6 +216,26 @@
AndroidSourceSet jni(Closure configureClosure);
/**
+ * The Android JNI libs directory for this source set.
+ *
+ * @return the libs. Never returns null.
+ */
+ @NonNull
+ AndroidSourceDirectorySet getJniLibs();
+
+ /**
+ * Configures the location of the Android JNI libs for this set.
+ *
+ * <p>The given closure is used to configure the {@link AndroidSourceDirectorySet}
+ * which contains the JNI libs.
+ *
+ * @param configureClosure The closure to use to configure the JNI libs.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet jniLibs(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.
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
index 404625e..337e627 100644
--- 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
@@ -21,11 +21,12 @@
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;
+import java.io.File;
+import java.util.Collection;
/**
* A Build variant and all its public data.
@@ -34,19 +35,16 @@
/**
- * Returns the list of {@link com.android.builder.DefaultProductFlavor} for this build variant.
- *
- * This is always non-null but could be empty.
+ * Return the app versionCode. Even the value is not found, then 1 is returned as this
+ * is the implicit value that the platform would use.
*/
- @NonNull
- List<DefaultProductFlavor> getProductFlavors();
+ int getVersionCode();
/**
- * Returns a {@link com.android.builder.DefaultProductFlavor} that represents the merging
- * of the default config and the flavors of this build variant.
+ * Return the app versionName or null if none found.
*/
- @NonNull
- DefaultProductFlavor getMergedFlavor();
+ @Nullable
+ String getVersionName();
/**
* Returns the {@link SigningConfig} for this build variant,
@@ -78,6 +76,22 @@
@Nullable
ZipAlign getZipAlign();
+ @NonNull
+ ZipAlign createZipAlignTask(@NonNull String taskName, @NonNull File inputFile, @NonNull File outputFile);
+
+ /**
+ * Returns the list of jar files that are on the compile classpath. This does not include
+ * the runtime.
+ */
+ @NonNull
+ Collection<File> getCompileLibraries();
+
+ /**
+ * Returns the list of jar files that are packaged in the APK.
+ */
+ @NonNull
+ Collection<File> getApkLibraries();
+
/**
* Returns the installation task.
*
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
index 4357456..c9b76d2 100644
--- 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
@@ -22,17 +22,22 @@
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.ManifestProcessorTask;
import com.android.build.gradle.tasks.RenderscriptCompile;
-import com.android.builder.DefaultBuildType;
-import com.android.builder.DefaultProductFlavor;
+import com.android.builder.core.DefaultBuildType;
+import com.android.builder.core.DefaultProductFlavor;
+import com.android.builder.model.SourceProvider;
+
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;
+import java.util.List;
+
/**
* A Build variant and all its public data. This is the base class for items common to apps,
@@ -79,17 +84,34 @@
String getFlavorName();
/**
- * Returns the {@link com.android.builder.DefaultBuildType} for this build variant.
+ * Returns the {@link com.android.builder.core.DefaultBuildType} for this build variant.
*/
@NonNull
DefaultBuildType getBuildType();
/**
- * Returns a {@link com.android.builder.DefaultProductFlavor} that represents the merging
+ * Returns a {@link com.android.builder.core.DefaultProductFlavor} that represents the merging
* of the default config and the flavors of this build variant.
*/
@NonNull
- DefaultProductFlavor getConfig();
+ DefaultProductFlavor getMergedFlavor();
+
+ /**
+ * Returns the list of {@link com.android.builder.core.DefaultProductFlavor} for this build variant.
+ *
+ * This is always non-null but could be empty.
+ */
+ @NonNull
+ List<DefaultProductFlavor> getProductFlavors();
+
+ /**
+ * Returns a list of sorted SourceProvider in order of ascending order, meaning, the earlier
+ * items are meant to be overridden by later items.
+ *
+ * @return a list of source provider
+ */
+ @NonNull
+ List<SourceProvider> getSourceSets();
/**
* Returns the output file for this build variants. Depending on the configuration, this could
@@ -103,10 +125,28 @@
void setOutputFile(@NonNull File outputFile);
/**
+ * Returns the package name of the variant.
+ */
+ @NonNull
+ String getPackageName();
+
+ /**
+ * Returns the pre-build anchor task
+ */
+ @NonNull
+ Task getPreBuild();
+
+ /**
+ * Returns the check manifest task.
+ */
+ @NonNull
+ Task getCheckManifest();
+
+ /**
* Returns the Manifest processing task.
*/
@NonNull
- ProcessManifest getProcessManifest();
+ ManifestProcessorTask getProcessManifest();
/**
* Returns the AIDL compilation task.
@@ -151,6 +191,18 @@
JavaCompile getJavaCompile();
/**
+ * Returns the NDK Compilation task.
+ */
+ @NonNull
+ NdkCompile getNdkCompile();
+
+ /**
+ * Returns the obfuscation task. This can be null if obfuscation is not enabled.
+ */
+ @Nullable
+ Task getObfuscation();
+
+ /**
* Returns the Java resource processing task.
*/
@NonNull
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/api/VariantFilter.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/VariantFilter.java
new file mode 100644
index 0000000..a710f34
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/VariantFilter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.builder.model.BuildType;
+import com.android.builder.model.ProductFlavor;
+
+import java.util.List;
+
+/**
+ * Interface for variant control, allowing to query a variant for some base
+ * data and allowing to disable some variants.
+ */
+public interface VariantFilter {
+
+ /**
+ * Sets whether or not to ignore this particular variant. Default is false.
+ * @param ignore whether to ignore the variant
+ */
+ public void setIgnore(boolean ignore);
+
+ /**
+ * Returns the ProductFlavor that represents the default config.
+ */
+ @NonNull
+ public ProductFlavor getDefaultConfig();
+
+ /**
+ * Returns the Build Type.
+ */
+ @NonNull
+ public BuildType getBuildType();
+
+ /**
+ * Returns the list of flavors, or an empty list.
+ */
+ @NonNull
+ public List<ProductFlavor> getFlavors();
+}
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
index 4b11232..6a6b239 100644
--- 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
@@ -16,7 +16,7 @@
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 com.android.builder.core.DefaultBuildType
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
@@ -53,4 +53,9 @@
Configuration getPackageConfiguration() {
return project.configurations.getByName(sourceSet.packageConfigurationName)
}
+
+ @NonNull
+ Configuration getProvidedConfiguration() {
+ return project.configurations.getByName(sourceSet.providedConfigurationName)
+ }
}
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
index de70e31..995d2ba 100644
--- 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
@@ -26,4 +26,6 @@
JavaVersion sourceCompatibility = JavaVersion.VERSION_1_6
JavaVersion targetCompatibility = JavaVersion.VERSION_1_6
String encoding = "UTF-8"
+
+ boolean ndkCygwinMode = false
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationDependencies.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationDependencies.java
new file mode 100644
index 0000000..a58f5e3
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationDependencies.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.model.JavaLibraryImpl;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.artifacts.Configuration;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Implementation of {@link com.android.builder.model.Dependencies} over a Gradle
+ * Configuration object. This is used to lazily query the list of files from the config object.
+ */
+public class ConfigurationDependencies implements Dependencies {
+
+ @NonNull
+ private final Configuration configuration;
+
+ public ConfigurationDependencies(@NonNull Configuration configuration) {
+
+ this.configuration = configuration;
+ }
+
+ @NonNull
+ @Override
+ public List<AndroidLibrary> getLibraries() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public Collection<JavaLibrary> getJavaLibraries() {
+ Set<File> files = configuration.getFiles();
+ if (files.isEmpty()) {
+ return Collections.emptySet();
+ }
+ Set<JavaLibrary> javaLibraries = Sets.newHashSet();
+ for (File file : files) {
+ javaLibraries.add(new JavaLibraryImpl(file));
+ }
+ return javaLibraries;
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getProjects() {
+ return Collections.emptyList();
+ }
+}
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
index 2fc9c60..17f1491 100644
--- 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
@@ -17,10 +17,12 @@
package com.android.build.gradle.internal;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
import org.gradle.api.artifacts.Configuration;
/**
- * Provides the compile and package configuration.
+ * Provides the compile, provided and package configurations.
*/
public interface ConfigurationProvider {
@@ -29,4 +31,7 @@
@NonNull
Configuration getPackageConfiguration();
+
+ @Nullable
+ Configuration getProvidedConfiguration();
}
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
deleted file mode 100644
index 8b4e65a..0000000
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationProviderImpl.groovy
+++ /dev/null
@@ -1,47 +0,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.
- */
-
-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/LibraryCache.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LibraryCache.groovy
new file mode 100644
index 0000000..bb0fb11
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LibraryCache.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.concurrency.GuardedBy
+import com.google.common.collect.Maps
+import org.gradle.api.Project
+
+import java.util.concurrent.CountDownLatch
+
+/**
+ * Cache to library prepareTask.
+ *
+ * Each project creates its own version of LibraryDependencyImpl, but they all represent the
+ * same library. This creates a single task that will unarchive the aar so that this is done only
+ * once even for multi-module projects where 2+ modules depend on the same library.
+ *
+ * The prepareTask is created in the root project always.
+ */
+public class LibraryCache {
+
+ @NonNull
+ private static final LibraryCache sCache = new LibraryCache()
+
+ @NonNull
+ public static LibraryCache getCache() {
+ return sCache
+ }
+
+ public synchronized unload() {
+ bundleLatches.clear();
+ }
+
+ @GuardedBy("this")
+ private final Map<String, CountDownLatch> bundleLatches = Maps.newHashMap()
+
+ public void unzipLibrary(
+ @NonNull String taskName,
+ @NonNull Project project,
+ @NonNull File bundle,
+ @NonNull File folderOut) {
+
+ // only synchronize access to the latch so that unzipping 2+ different
+ // libraries in parallel will work.
+ boolean newItem = false;
+ CountDownLatch latch;
+ synchronized (this) {
+ String path = bundle.getCanonicalPath()
+ latch = bundleLatches.get(path)
+ if (latch == null) {
+ latch = new CountDownLatch(1)
+ bundleLatches.put(path, latch)
+ newItem = true
+ }
+ }
+
+ if (newItem) {
+ try {
+ project.logger.debug("$taskName: ERASE ${folderOut.getPath()}")
+ folderOut.deleteDir()
+ folderOut.mkdirs()
+
+ project.logger.debug("$taskName: UNZIP ${bundle.getPath()} -> ${folderOut.getPath()}")
+ project.copy {
+ from project.zipTree(bundle)
+ into folderOut
+ }
+ } finally {
+ latch.countDown()
+ }
+ } else {
+ latch.await()
+ }
+ }
+}
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
index 3bf7b8d..8af46ea 100644
--- 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
@@ -67,11 +67,13 @@
mCustomRules = customRules;
}
+ @NonNull
@Override
public List<File> findRuleJars(@NonNull Project project) {
return mCustomRules;
}
+ @NonNull
@Override
protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
// Should not be called by lint since we supply an explicit set of projects
@@ -81,7 +83,7 @@
@Override
public File getSdkHome() {
- File sdkHome = mPlugin.getSdkDirectory();
+ File sdkHome = mPlugin.getSdkFolder();
if (sdkHome != null) {
return sdkHome;
}
@@ -159,13 +161,13 @@
fileMap.put(fileName, canonical);
canonical.variants = Sets.newHashSet();
canonical.gradleProject = project;
+ merged.add(canonical);
}
- merged.add(canonical);
canonical.variants.add(variant);
}
}
- // Clear out variants on any nodes that don't define all
+ // Clear out variants on any nodes that 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
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
index 2ce9124..4cf646f 100644
--- 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
@@ -1,26 +1,32 @@
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 static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT;
+import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT;
+import static java.io.File.separatorChar;
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.ApiVersion;
import com.android.builder.model.BuildTypeContainer;
import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
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.LintUtils;
import com.android.tools.lint.detector.api.Project;
import com.android.utils.Pair;
import com.android.utils.XmlUtils;
+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 org.w3c.dom.Document;
@@ -32,13 +38,14 @@
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 {
+ protected AndroidVersion mMinSdkVersion;
+ protected AndroidVersion mTargetSdkVersion;
+
private LintGradleProject(
@NonNull LintGradleClient client,
@NonNull File dir,
@@ -69,7 +76,7 @@
@NonNull AndroidProject project,
@NonNull Variant variant,
@NonNull org.gradle.api.Project gradleProject) {
- File dir = gradleProject.getRootDir();
+ File dir = gradleProject.getProjectDir();
AppGradleProject lintProject = new AppGradleProject(client, dir,
dir, project, variant);
@@ -89,7 +96,6 @@
return Pair.<LintGradleProject,List<File>>of(lintProject, customRules);
}
-
@Override
protected void initialize() {
// Deliberately not calling super; that code is for ADT compatibility
@@ -114,6 +120,38 @@
return true;
}
+ protected static boolean dependsOn(@NonNull Dependencies dependencies,
+ @NonNull String artifact) {
+ for (AndroidLibrary library : dependencies.getLibraries()) {
+ if (dependsOn(library, artifact)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected static boolean dependsOn(@NonNull AndroidLibrary library, @NonNull String artifact) {
+ if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
+ if (library.getJarFile().getName().startsWith("support-v4-")) {
+ return true;
+ }
+
+ } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
+ File bundle = library.getBundle();
+ if (bundle.getName().startsWith("appcompat-v7-")) {
+ return true;
+ }
+ }
+
+ for (AndroidLibrary dependency : library.getLibraryDependencies()) {
+ if (dependsOn(dependency, artifact)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
void addDirectLibrary(@NonNull Project project) {
mDirectLibraries.add(project);
}
@@ -151,7 +189,9 @@
@NonNull File referenceDir,
@NonNull AndroidProject project,
@NonNull Variant variant) {
- super(client, dir, referenceDir, variant.getMainArtifact().getGeneratedManifest());
+ //TODO FIXME: handle multi-apk
+ super(client, dir, referenceDir,
+ variant.getMainArtifact().getOutputs().iterator().next().getGeneratedManifest());
mProject = project;
mVariant = variant;
@@ -269,6 +309,13 @@
}
}
}
+
+ for (File file : mVariant.getMainArtifact().getGeneratedResourceFolders()) {
+ if (file.exists()) {
+ mResourceFolders.add(file);
+ }
+ }
+
}
return mResourceFolders;
@@ -280,13 +327,19 @@
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);
+ Collection<File> srcDirs = provider.getJavaDirectories();
+ for (File srcDir : srcDirs) {
+ if (srcDir.exists()) { // model returns path whether or not it exists
+ mJavaSourceFolders.add(srcDir);
}
}
}
+
+ for (File file : mVariant.getMainArtifact().getGeneratedSourceFolders()) {
+ if (file.exists()) {
+ mJavaSourceFolders.add(file);
+ }
+ }
}
return mJavaSourceFolders;
@@ -310,9 +363,10 @@
@Override
public List<File> getJavaLibraries() {
if (mJavaLibraries == null) {
- Collection<File> jars = mVariant.getMainArtifact().getDependencies().getJars();
- mJavaLibraries = Lists.newArrayListWithExpectedSize(jars.size());
- for (File jar : jars) {
+ Collection<JavaLibrary> libs = mVariant.getMainArtifact().getDependencies().getJavaLibraries();
+ mJavaLibraries = Lists.newArrayListWithExpectedSize(libs.size());
+ for (JavaLibrary lib : libs) {
+ File jar = lib.getJarFile();
if (jar.exists()) {
mJavaLibraries.add(jar);
}
@@ -328,7 +382,7 @@
// 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();
+ String packageName = mProject.getDefaultConfig().getProductFlavor().getApplicationId();
if (packageName != null) {
return packageName;
}
@@ -338,23 +392,41 @@
}
@Override
- public int getMinSdk() {
- int minSdk = mProject.getDefaultConfig().getProductFlavor().getMinSdkVersion();
- if (minSdk != -1) {
- return minSdk;
+ @NonNull
+ public AndroidVersion getMinSdkVersion() {
+ if (mMinSdkVersion == null) {
+ ApiVersion minSdk = mVariant.getMergedFlavor().getMinSdkVersion();
+ if (minSdk == null) {
+ ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
+ minSdk = flavor.getMinSdkVersion();
+ }
+ if (minSdk != null) {
+ mMinSdkVersion = LintUtils.convertVersion(minSdk, mClient.getTargets());
+ } else {
+ mMinSdkVersion = super.getMinSdkVersion(); // from manifest
+ }
}
- return mMinSdk; // from manifest
+ return mMinSdkVersion;
}
@Override
- public int getTargetSdk() {
- int targetSdk = mProject.getDefaultConfig().getProductFlavor().getTargetSdkVersion();
- if (targetSdk != -1) {
- return targetSdk;
+ @NonNull
+ public AndroidVersion getTargetSdkVersion() {
+ if (mTargetSdkVersion == null) {
+ ApiVersion targetSdk = mVariant.getMergedFlavor().getTargetSdkVersion();
+ if (targetSdk == null) {
+ ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
+ targetSdk = flavor.getTargetSdkVersion();
+ }
+ if (targetSdk != null) {
+ mTargetSdkVersion = LintUtils.convertVersion(targetSdk, mClient.getTargets());
+ } else {
+ mTargetSdkVersion = super.getTargetSdkVersion(); // from manifest
+ }
}
- return targetSdk; // from manifest
+ return mTargetSdkVersion;
}
@Override
@@ -367,6 +439,26 @@
return super.getBuildSdk();
}
+
+ @Nullable
+ @Override
+ public Boolean dependsOn(@NonNull String artifact) {
+ if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
+ if (mSupportLib == null) {
+ Dependencies dependencies = mVariant.getMainArtifact().getDependencies();
+ mSupportLib = dependsOn(dependencies, artifact);
+ }
+ return mSupportLib;
+ } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
+ if (mAppCompat == null) {
+ Dependencies dependencies = mVariant.getMainArtifact().getDependencies();
+ mAppCompat = dependsOn(dependencies, artifact);
+ }
+ return mAppCompat;
+ } else {
+ return super.dependsOn(artifact);
+ }
+ }
}
private static class LibraryProject extends LintGradleProject {
@@ -460,15 +552,38 @@
@Override
public List<File> getJavaLibraries() {
if (mJavaLibraries == null) {
+ mJavaLibraries = Lists.newArrayList();
File jarFile = mLibrary.getJarFile();
if (jarFile.exists()) {
- mJavaLibraries = Collections.singletonList(jarFile);
- } else {
- mJavaLibraries = Collections.emptyList();
+ mJavaLibraries.add(jarFile);
+ }
+
+ for (File local : mLibrary.getLocalJars()) {
+ if (local.exists()) {
+ mJavaLibraries.add(local);
+ }
}
}
return mJavaLibraries;
}
+
+ @Nullable
+ @Override
+ public Boolean dependsOn(@NonNull String artifact) {
+ if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
+ if (mSupportLib == null) {
+ mSupportLib = dependsOn(mLibrary, artifact);
+ }
+ return mSupportLib;
+ } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
+ if (mAppCompat == null) {
+ mAppCompat = dependsOn(mLibrary, artifact);
+ }
+ return mAppCompat;
+ } else {
+ return super.dependsOn(artifact);
+ }
+ }
}
}
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
index b5674f9..c49f39e 100644
--- 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
@@ -17,7 +17,7 @@
class LintGradleRequest extends LintRequest {
@NonNull private final LintGradleClient mLintClient;
@NonNull private final BasePlugin mPlugin;
- @NonNull private final String mVariantName;
+ @Nullable private final String mVariantName;
@NonNull private final AndroidProject mModelProject;
public LintGradleRequest(
@@ -38,7 +38,10 @@
public Collection<Project> getProjects() {
if (mProjects == null) {
Variant variant = findVariant(mModelProject, mVariantName);
- assert variant != null : mVariantName;
+ if (variant == null) {
+ mProjects = Collections.emptyList();
+ return mProjects;
+ }
Pair<LintGradleProject,List<File>> result = LintGradleProject.create(
mLintClient, mModelProject, variant, mPlugin.getProject());
mProjects = Collections.<Project>singletonList(result.getFirst());
@@ -49,7 +52,7 @@
}
private static Variant findVariant(@NonNull AndroidProject project,
- @NonNull String variantName) {
+ @Nullable String variantName) {
if (variantName != null) {
for (Variant variant : project.getVariants()) {
if (variantName.equals(variant.getName())) {
@@ -58,6 +61,10 @@
}
}
+ if (!project.getVariants().isEmpty()) {
+ return project.getVariants().iterator().next();
+ }
+
return null;
}
}
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
index 2c7272a..cba5246 100644
--- 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
@@ -16,16 +16,48 @@
package com.android.build.gradle.internal
+import com.android.annotations.NonNull
import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
-import com.android.builder.DefaultProductFlavor
+import com.android.builder.core.BuilderConstants
+import com.android.builder.core.DefaultProductFlavor
import org.gradle.api.Project
import org.gradle.api.Task
+import org.gradle.api.artifacts.Configuration
/**
* Class containing a ProductFlavor and associated data (sourcesets)
*/
public class ProductFlavorData<T extends DefaultProductFlavor> {
+ private static class ConfigurationProviderImpl implements ConfigurationProvider {
+
+ private final Project project
+ private final DefaultAndroidSourceSet sourceSet
+
+ 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)
+ }
+
+ @Override
+ @NonNull
+ Configuration getProvidedConfiguration() {
+ return project.configurations.getByName(sourceSet.providedConfigurationName)
+ }
+ }
+
final T productFlavor
final DefaultAndroidSourceSet sourceSet
@@ -33,7 +65,7 @@
final ConfigurationProvider mainProvider
final ConfigurationProvider testProvider
- Task assembleTask
+ final Task assembleTask
ProductFlavorData(T productFlavor,
DefaultAndroidSourceSet sourceSet, DefaultAndroidSourceSet testSourceSet,
@@ -43,6 +75,14 @@
this.testSourceSet = testSourceSet
mainProvider = new ConfigurationProviderImpl(project, sourceSet)
testProvider = new ConfigurationProviderImpl(project, testSourceSet)
+
+ if (!BuilderConstants.MAIN.equals(sourceSet.name)) {
+ assembleTask = project.tasks.create("assemble${sourceSet.name.capitalize()}")
+ assembleTask.description = "Assembles all ${sourceSet.name.capitalize()} builds"
+ assembleTask.setGroup("Build")
+ } else {
+ assembleTask = null
+ }
}
public static String getFlavoredName(ProductFlavorData[] flavorDataArray, boolean capitalized) {
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
deleted file mode 100644
index 88c8726..0000000
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/Sdk.groovy
+++ /dev/null
@@ -1,211 +0,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.
- */
-
-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/SdkHandler.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/SdkHandler.java
new file mode 100644
index 0000000..9d3065f
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/SdkHandler.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
+import static com.android.build.gradle.BasePlugin.TEST_SDK_DIR;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.sdk.DefaultSdkLoader;
+import com.android.builder.sdk.PlatformLoader;
+import com.android.builder.sdk.SdkInfo;
+import com.android.builder.sdk.SdkLoader;
+import com.android.builder.sdk.TargetInfo;
+import com.android.sdklib.repository.FullRevision;
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.io.Closeables;
+import com.google.common.io.Closer;
+
+import org.gradle.api.Project;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Properties;
+
+/**
+ * Handles the all things SDK for the Gradle plugin. There is one instance per project, around
+ * a singleton {@link com.android.builder.sdk.SdkLoader}.
+ */
+public class SdkHandler {
+
+ @NonNull
+ private final ILogger logger;
+
+ private SdkLoader sdkLoader;
+ private File sdkFolder;
+ private File ndkFolder;
+ private boolean isRegularSdk = true;
+
+ public SdkHandler(@NonNull Project project,
+ @NonNull ILogger logger) {
+ this.logger = logger;
+ findLocation(project);
+ }
+
+ public SdkInfo getSdkInfo() {
+ SdkLoader sdkLoader = getSdkLoader();
+ return sdkLoader.getSdkInfo(logger);
+ }
+
+ public void initTarget(
+ String targetHash,
+ FullRevision buildToolRevision,
+ @NonNull AndroidBuilder androidBuilder) {
+ if (targetHash == null) {
+ throw new IllegalArgumentException("android.compileSdkVersion is missing!");
+ }
+
+ if (buildToolRevision == null) {
+ throw new IllegalArgumentException("android.buildToolsVersion is missing!");
+ }
+
+ SdkLoader sdkLoader = getSdkLoader();
+
+ SdkInfo sdkInfo = sdkLoader.getSdkInfo(logger);
+ TargetInfo targetInfo = sdkLoader.getTargetInfo(targetHash, buildToolRevision, logger);
+
+ androidBuilder.setTargetInfo(sdkInfo, targetInfo);
+ }
+
+ public synchronized SdkLoader getSdkLoader() {
+ if (sdkLoader == null) {
+ if (isRegularSdk) {
+ if (sdkFolder == 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.");
+ }
+
+ // check if the SDK folder actually exist.
+ // For internal test we provide a fake SDK location through
+ // TEST_SDK_DIR in order to have an SDK, even though we don't use it
+ // so in this case we ignore the check.
+ if (TEST_SDK_DIR == null && !sdkFolder.isDirectory()) {
+ throw new RuntimeException(String.format(
+ "The SDK directory '%s' does not exist.", sdkFolder));
+ }
+
+ sdkLoader = DefaultSdkLoader.getLoader(sdkFolder);
+ } else {
+ sdkLoader = PlatformLoader.getLoader(sdkFolder);
+ }
+ }
+
+ return sdkLoader;
+ }
+
+ public synchronized void unload() {
+ if (sdkLoader != null) {
+ if (isRegularSdk) {
+ DefaultSdkLoader.unload();
+ } else {
+ PlatformLoader.unload();
+ }
+
+ sdkLoader = null;
+ }
+ }
+
+ @Nullable
+ public File getNdkFolder() {
+ return ndkFolder;
+ }
+
+ private void findSdkLocation(@NonNull Properties properties, @NonNull File rootDir) {
+ String sdkDirProp = properties.getProperty("sdk.dir");
+ if (sdkDirProp != null) {
+ sdkFolder = new File(sdkDirProp);
+ return;
+ }
+
+ sdkDirProp = properties.getProperty("android.dir");
+ if (sdkDirProp != null) {
+ sdkFolder = new File(rootDir, sdkDirProp);
+ isRegularSdk = false;
+ return;
+ }
+
+ String envVar = System.getenv("ANDROID_HOME");
+ if (envVar != null) {
+ sdkFolder = new File(envVar);
+ return;
+ }
+
+ String property = System.getProperty("android.home");
+ if (property != null) {
+ sdkFolder = new File(property);
+ }
+ }
+
+ private void findNdkLocation(@NonNull Properties properties) {
+ String ndkDirProp = properties.getProperty("ndk.dir");
+ if (ndkDirProp != null) {
+ ndkFolder = new File(ndkDirProp);
+ return;
+ }
+
+ String envVar = System.getenv("ANDROID_NDK_HOME");
+ if (envVar != null) {
+ ndkFolder = new File(envVar);
+ }
+ }
+
+ private void findLocation(@NonNull Project project) {
+ if (TEST_SDK_DIR != null) {
+ sdkFolder = TEST_SDK_DIR;
+ return;
+ }
+
+ File rootDir = project.getRootDir();
+ File localProperties = new File(rootDir, FN_LOCAL_PROPERTIES);
+ Properties properties = new Properties();
+
+ if (localProperties.isFile()) {
+ InputStreamReader reader = null;
+ try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ FileInputStream fis = new FileInputStream(localProperties);
+ reader = new InputStreamReader(fis, Charsets.UTF_8);
+ properties.load(reader);
+ } catch (FileNotFoundException ignored) {
+ // ignore since we check up front and we don't want to fail on it anyway
+ // in case there's an env var.
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to read ${localProperties}", e);
+ } finally {
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+ }
+
+ findSdkLocation(properties, rootDir);
+ findNdkLocation(properties);
+ }
+
+ @Nullable
+ public File getSdkFolder() {
+ return sdkFolder;
+ }
+}
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
index 0cc3f7c..f3ac7bd 100644
--- 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
@@ -41,6 +41,12 @@
@NonNull
@Override
+ public String getName() {
+ return sourceSet.getName();
+ }
+
+ @NonNull
+ @Override
public File getManifestFile() {
throw new IllegalAccessError("Shouldn't access manifest from SourceSetSourceProviderWrapper");
}
@@ -48,7 +54,7 @@
@NonNull
@Override
public Collection<File> getJavaDirectories() {
- return sourceSet.getAllJava().getSrcDirs();
+ return sourceSet.getJava().getSrcDirs();
}
@NonNull
@@ -86,4 +92,10 @@
public Collection<File> getAssetsDirectories() {
return Collections.emptyList();
}
+
+ @NonNull
+ @Override
+ public Collection<File> getJniLibsDirectories() {
+ return Collections.emptyList();
+ }
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/VariantManager.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/VariantManager.java
new file mode 100644
index 0000000..f2a3b68
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/VariantManager.java
@@ -0,0 +1,635 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static com.android.builder.core.BuilderConstants.DEBUG;
+import static com.android.builder.core.BuilderConstants.ANDROID_TEST;
+import static com.android.builder.core.BuilderConstants.LINT;
+import static com.android.builder.core.BuilderConstants.UI_TEST;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.BaseExtension;
+import com.android.build.gradle.BasePlugin;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet;
+import com.android.build.gradle.internal.api.TestVariantImpl;
+import com.android.build.gradle.internal.api.TestedVariant;
+import com.android.build.gradle.internal.dependency.VariantDependencies;
+import com.android.build.gradle.internal.dsl.BuildTypeDsl;
+import com.android.build.gradle.internal.dsl.GroupableProductFlavorDsl;
+import com.android.build.gradle.internal.dsl.SigningConfigDsl;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.TestVariantData;
+import com.android.build.gradle.internal.variant.TestedVariantData;
+import com.android.build.gradle.internal.variant.VariantFactory;
+import com.android.builder.core.DefaultProductFlavor;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+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 org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+
+import java.util.List;
+import java.util.Map;
+
+import groovy.lang.Closure;
+
+/**
+ * Class to create, manage variants.
+ */
+public class VariantManager {
+
+ @NonNull
+ private final Project project;
+ @NonNull
+ private final BasePlugin basePlugin;
+ @NonNull
+ private final BaseExtension extension;
+ @NonNull
+ private final VariantFactory variantFactory;
+
+ private final Map<String, BuildTypeData> buildTypes = Maps.newHashMap();
+ private final Map<String, ProductFlavorData<GroupableProductFlavorDsl>> productFlavors = Maps.newHashMap();
+ private final Map<String, SigningConfig> signingConfigs = Maps.newHashMap();
+
+ private final VariantFilterImpl variantFilter = new VariantFilterImpl();
+
+ public VariantManager(
+ @NonNull Project project,
+ @NonNull BasePlugin basePlugin,
+ @NonNull BaseExtension extension,
+ @NonNull VariantFactory variantFactory) {
+ this.extension = extension;
+ this.basePlugin = basePlugin;
+ this.project = project;
+ this.variantFactory = variantFactory;
+ }
+
+ @NonNull
+ public Map<String, BuildTypeData> getBuildTypes() {
+ return buildTypes;
+ }
+
+ @NonNull
+ public Map<String, ProductFlavorData<GroupableProductFlavorDsl>> getProductFlavors() {
+ return productFlavors;
+ }
+
+ @NonNull
+ public Map<String, SigningConfig> getSigningConfigs() {
+ return signingConfigs;
+ }
+
+ public void addSigningConfig(@NonNull SigningConfigDsl signingConfigDsl) {
+ signingConfigs.put(signingConfigDsl.getName(), signingConfigDsl);
+ }
+
+ /**
+ * Adds new BuildType, creating a BuildTypeData, and the associated source set,
+ * and adding it to the map.
+ * @param buildType the build type.
+ */
+ public void addBuildType(@NonNull BuildTypeDsl buildType) {
+ buildType.init(signingConfigs.get(DEBUG));
+
+ String name = buildType.getName();
+ checkName(name, "BuildType");
+
+ if (productFlavors.containsKey(name)) {
+ throw new RuntimeException("BuildType names cannot collide with ProductFlavor names");
+ }
+
+ DefaultAndroidSourceSet sourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(name);
+
+ BuildTypeData buildTypeData = new BuildTypeData(buildType, sourceSet, project);
+ project.getTasks().getByName("assemble").dependsOn(buildTypeData.getAssembleTask());
+
+ buildTypes.put(name, buildTypeData);
+ }
+
+ /**
+ * Adds a new ProductFlavor, creating a ProductFlavorData and associated source sets,
+ * and adding it to the map.
+ *
+ * @param productFlavor the product flavor
+ */
+ public void addProductFlavor(@NonNull GroupableProductFlavorDsl productFlavor) {
+ String name = productFlavor.getName();
+ checkName(name, "ProductFlavor");
+
+ if (buildTypes.containsKey(name)) {
+ throw new RuntimeException("ProductFlavor names cannot collide with BuildType names");
+ }
+
+ DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(
+ productFlavor.getName());
+ String testName = ANDROID_TEST + StringHelper.capitalize(productFlavor.getName());
+ DefaultAndroidSourceSet testSourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(
+ testName);
+
+ ProductFlavorData<GroupableProductFlavorDsl> productFlavorData =
+ new ProductFlavorData<GroupableProductFlavorDsl>(
+ productFlavor, mainSourceSet, testSourceSet, project);
+
+ productFlavors.put(productFlavor.getName(), productFlavorData);
+ }
+
+ /**
+ * Task creation entry point.
+ *
+ * @param signingOverride a signing override. Generally driven through the IDE.
+ */
+ public void createAndroidTasks(@Nullable SigningConfig signingOverride) {
+ // Add a compile lint task
+ basePlugin.createLintCompileTask();
+
+ if (productFlavors.isEmpty()) {
+ createTasksForDefaultBuild(signingOverride);
+ } else {
+ // there'll be more than one test app, so we need a top level assembleTest
+ Task assembleTest = project.getTasks().create("assembleTest");
+ assembleTest.setGroup(org.gradle.api.plugins.BasePlugin.BUILD_GROUP);
+ assembleTest.setDescription("Assembles all the Test applications");
+ basePlugin.setAssembleTest(assembleTest);
+
+ // check whether we have multi flavor builds
+ List<String> flavorDimensionList = extension.getFlavorDimensionList();
+ if (flavorDimensionList == null || flavorDimensionList.size() < 2) {
+ for (ProductFlavorData productFlavorData : productFlavors.values()) {
+ createTasksForFlavoredBuild(signingOverride, productFlavorData);
+ }
+ } else {
+ // need to group the flavor per dimension.
+ // First a map of dimension -> list(ProductFlavor)
+ ArrayListMultimap<String, ProductFlavorData<GroupableProductFlavorDsl>> map = ArrayListMultimap.create();
+ for (ProductFlavorData<GroupableProductFlavorDsl> productFlavorData : productFlavors.values()) {
+
+ GroupableProductFlavorDsl flavor = productFlavorData.getProductFlavor();
+ String flavorDimension = flavor.getFlavorDimension();
+
+ if (flavorDimension == null) {
+ throw new RuntimeException(String.format(
+ "Flavor '%1$s' has no flavor dimension.", flavor.getName()));
+ }
+ if (!flavorDimensionList.contains(flavorDimension)) {
+ throw new RuntimeException(String.format(
+ "Flavor '%1$s' has unknown dimension '%2$s'.",
+ flavor.getName(), flavor.getFlavorDimension()));
+ }
+
+ map.put(flavorDimension, productFlavorData);
+ }
+
+ // now we use the flavor dimensions to generate an ordered array of flavor to use
+ ProductFlavorData[] array = new ProductFlavorData[flavorDimensionList.size()];
+ createTasksForMultiFlavoredBuilds(array, 0, map, signingOverride);
+ }
+ }
+
+ // create the lint tasks.
+ basePlugin.createLintTasks();
+
+ // create the test tasks.
+ basePlugin.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.flavorDimensionList), creating all possible combination.
+ *
+ * @param datas the arrays to fill
+ * @param index the current index to fill
+ * @param map the map of dimension -> list(ProductFlavor)
+ * @param signingOverride a signing override. Generally driven through the IDE.
+ */
+ private void createTasksForMultiFlavoredBuilds(
+ ProductFlavorData[] datas,
+ int index,
+ ListMultimap<String, ? extends ProductFlavorData> map,
+ @Nullable SigningConfig signingOverride) {
+ if (index == datas.length) {
+ createTasksForFlavoredBuild(signingOverride, datas);
+ return;
+ }
+
+ // fill the array at the current index.
+ // get the dimension name that matches the index we are filling.
+ String dimension = extension.getFlavorDimensionList().get(index);
+
+ // from our map, get all the possible flavors in that dimension.
+ List<? extends ProductFlavorData> flavorList = map.get(dimension);
+
+ // loop on all the flavors to add them to the current index and recursively fill the next
+ // indices.
+ for (ProductFlavorData flavor : flavorList) {
+ datas[index] = flavor;
+ createTasksForMultiFlavoredBuilds(datas, index + 1, map, signingOverride);
+ }
+ }
+
+ /**
+ * 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>.
+ *
+ * @param signingOverride a signing override. Generally driven through the IDE.
+ */
+ private void createTasksForDefaultBuild(@Nullable SigningConfig signingOverride) {
+ BuildTypeData testData = buildTypes.get(extension.getTestBuildType());
+ if (testData == null) {
+ throw new RuntimeException(String.format(
+ "Test Build Type '%1$s' does not exist.", extension.getTestBuildType()));
+ }
+
+ BaseVariantData testedVariantData = null;
+
+ ProductFlavorData defaultConfigData = basePlugin.getDefaultConfigData();
+
+ DefaultProductFlavor defaultConfig = defaultConfigData.getProductFlavor();
+ DefaultAndroidSourceSet defaultConfigSourceSet = defaultConfigData.getSourceSet();
+
+ Closure<Void> variantFilterClosure = basePlugin.getExtension().getVariantFilter();
+
+ for (BuildTypeData buildTypeData : buildTypes.values()) {
+ boolean ignore = false;
+ if (variantFilterClosure != null) {
+ variantFilter.reset(defaultConfig, buildTypeData.getBuildType(), null);
+ variantFilterClosure.call(variantFilter);
+ ignore = variantFilter.isIgnore();
+ }
+
+ if (!ignore) {
+ VariantConfiguration variantConfig = new VariantConfiguration(
+ defaultConfig,
+ defaultConfigSourceSet,
+ buildTypeData.getBuildType(),
+ buildTypeData.getSourceSet(),
+ variantFactory.getVariantConfigurationType(),
+ signingOverride);
+
+ // create the variant and get its internal storage object.
+ BaseVariantData variantData = variantFactory.createVariantData(variantConfig);
+ // create its dependencies. They'll be resolved below.
+ VariantDependencies variantDep = VariantDependencies.compute(
+ project, variantConfig.getFullName(),
+ isVariantPublished(),
+ variantFactory.isLibrary(),
+ buildTypeData, defaultConfigData.getMainProvider());
+ variantData.setVariantDependency(variantDep);
+
+ if (buildTypeData == testData) {
+ testedVariantData = variantData;
+ }
+
+ basePlugin.resolveDependencies(variantDep);
+ variantConfig.setDependencies(variantDep);
+
+ basePlugin.getVariantDataList().add(variantData);
+ variantFactory.createTasks(variantData,
+ buildTypes.get(variantConfig.getBuildType().getName()).getAssembleTask());
+ }
+ }
+
+ if (testedVariantData != null) {
+ VariantConfiguration testedConfig = testedVariantData.getVariantConfiguration();
+ // handle the test variant
+ VariantConfiguration testVariantConfig = new VariantConfiguration(
+ defaultConfig,
+ defaultConfigData.getTestSourceSet(),
+ testData.getBuildType(),
+ null,
+ VariantConfiguration.Type.TEST, testedConfig,
+ signingOverride);
+
+ // create the internal storage for this test variant.
+ TestVariantData testVariantData = new TestVariantData(testVariantConfig, (TestedVariantData) testedVariantData);
+ // link the testVariant to the tested variant in the other direction
+ ((TestedVariantData) testedVariantData).setTestVariantData(testVariantData);
+
+ // dependencies for the test variant, they'll be resolved below
+ VariantDependencies variantDep = VariantDependencies.compute(
+ project, testVariantConfig.getFullName(),
+ false /*publishVariant*/,
+ variantFactory.isLibrary(),
+ defaultConfigData.getTestProvider(),
+ testedConfig.getType() == VariantConfiguration.Type.LIBRARY ?
+ testedVariantData.getVariantDependency() : null);
+ testVariantData.setVariantDependency(variantDep);
+
+ basePlugin.resolveDependencies(variantDep);
+ testVariantConfig.setDependencies(variantDep);
+
+ basePlugin.getVariantDataList().add(testVariantData);
+ basePlugin.createTestApkTasks(testVariantData, testedVariantData);
+ }
+ }
+
+ /**
+ * Creates Task for a given flavor. This will create tasks for all build types for the given
+ * flavor.
+ *
+ * @param signingOverride a signing override. Generally driven through the IDE.
+ * @param flavorDataList the flavor(s) to build.
+ */
+ private void createTasksForFlavoredBuild(@Nullable SigningConfig signingOverride,
+ @NonNull ProductFlavorData... flavorDataList) {
+
+ BuildTypeData testData = buildTypes.get(extension.getTestBuildType());
+ if (testData == null) {
+ throw new RuntimeException(String.format(
+ "Test Build Type '%1$s' does not exist.", extension.getTestBuildType()));
+ }
+
+ // because this method is called multiple times, we need to keep track
+ // of the variantData only for this call.
+ final List<BaseVariantData> localVariantDataList = Lists.newArrayListWithCapacity(buildTypes.size());
+
+ BaseVariantData testedVariantData = null;
+
+ // assembleTask for this flavor(dimension), created on demand if needed.
+ Task assembleTask = null;
+
+ ProductFlavorData defaultConfigData = basePlugin.getDefaultConfigData();
+ DefaultProductFlavor defaultConfig = defaultConfigData.getProductFlavor();
+ DefaultAndroidSourceSet defaultConfigSourceSet = defaultConfigData.getSourceSet();
+
+ final List<ConfigurationProvider> variantProviders = Lists.newArrayListWithCapacity(flavorDataList.length + 2);
+
+ Closure<Void> variantFilterClosure = basePlugin.getExtension().getVariantFilter();
+ final List<ProductFlavor> productFlavorList = (variantFilterClosure != null) ? Lists.<ProductFlavor>newArrayListWithCapacity(flavorDataList.length) : null;
+
+ for (BuildTypeData buildTypeData : buildTypes.values()) {
+ boolean ignore = false;
+ if (variantFilterClosure != null) {
+ productFlavorList.clear();
+ for (ProductFlavorData data : flavorDataList) {
+ productFlavorList.add(data.getProductFlavor());
+ }
+ variantFilter.reset(defaultConfig, buildTypeData.getBuildType(), productFlavorList);
+ variantFilterClosure.call(variantFilter);
+ ignore = variantFilter.isIgnore();
+ }
+
+ if (!ignore) {
+ if (assembleTask == null && flavorDataList.length > 1) {
+ assembleTask = createAssembleTask(flavorDataList);
+ project.getTasks().getByName("assemble").dependsOn(assembleTask);
+ }
+
+ /// add the container of dependencies
+ // the order of the libraries is important. In descending order:
+ // build types, flavors, defaultConfig.
+ variantProviders.clear();
+ variantProviders.add(buildTypeData);
+
+ VariantConfiguration variantConfig = new VariantConfiguration(
+ defaultConfig,
+ defaultConfigSourceSet,
+ buildTypeData.getBuildType(),
+ buildTypeData.getSourceSet(),
+ variantFactory.getVariantConfigurationType(),
+ signingOverride);
+
+ for (ProductFlavorData data : flavorDataList) {
+ String dimensionName = "";
+ DefaultProductFlavor productFlavor = data.getProductFlavor();
+
+ if (productFlavor instanceof GroupableProductFlavorDsl) {
+ dimensionName = ((GroupableProductFlavorDsl) productFlavor).getFlavorDimension();
+ }
+ variantConfig.addProductFlavor(
+ productFlavor,
+ data.getSourceSet(),
+ dimensionName
+ );
+ variantProviders.add(data.getMainProvider());
+ }
+
+ // now add the defaultConfig
+ variantProviders.add(basePlugin.getDefaultConfigData().getMainProvider());
+
+ // create the variant and get its internal storage object.
+ BaseVariantData variantData = variantFactory.createVariantData(variantConfig);
+
+ NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer = extension
+ .getSourceSetsContainer();
+
+ DefaultAndroidSourceSet variantSourceSet = (DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(
+ variantConfig.getFullName());
+ variantConfig.setVariantSourceProvider(variantSourceSet);
+ // TODO: hmm this won't work
+ //variantProviders.add(new ConfigurationProviderImpl(project, variantSourceSet))
+
+ if (flavorDataList.length > 1) {
+ DefaultAndroidSourceSet multiFlavorSourceSet = (DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(variantConfig.getFlavorName());
+ variantConfig.setMultiFlavorSourceProvider(multiFlavorSourceSet);
+ // TODO: hmm this won't work
+ //variantProviders.add(new ConfigurationProviderImpl(project, multiFlavorSourceSet))
+ }
+
+ VariantDependencies variantDep = VariantDependencies.compute(
+ project, variantConfig.getFullName(),
+ isVariantPublished(),
+ variantFactory.isLibrary(),
+ variantProviders.toArray(new ConfigurationProvider[variantProviders.size()]));
+ variantData.setVariantDependency(variantDep);
+
+ localVariantDataList.add(variantData);
+
+ if (buildTypeData == testData) {
+ testedVariantData = variantData;
+ }
+
+ basePlugin.resolveDependencies(variantDep);
+ variantConfig.setDependencies(variantDep);
+
+ basePlugin.getVariantDataList().add(variantData);
+ variantFactory.createTasks(variantData, null);
+
+ // setup the task dependencies
+ // build type
+ buildTypeData.getAssembleTask().dependsOn(variantData.assembleTask);
+ // each flavor
+ for (ProductFlavorData data : flavorDataList) {
+ data.getAssembleTask().dependsOn(variantData.assembleTask);
+ }
+
+ // flavor combo
+ if (assembleTask != null) {
+ assembleTask.dependsOn(variantData.assembleTask);
+ }
+ }
+ }
+
+ if (testedVariantData != null) {
+ VariantConfiguration testedConfig = testedVariantData.getVariantConfiguration();
+
+ // handle test variant
+ VariantConfiguration testVariantConfig = new VariantConfiguration(
+ defaultConfig,
+ defaultConfigData.getTestSourceSet(),
+ testData.getBuildType(),
+ null,
+ VariantConfiguration.Type.TEST,
+ testedVariantData.getVariantConfiguration(),
+ signingOverride);
+
+ /// 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 = Lists.newArrayListWithExpectedSize(1 + flavorDataList.length);
+
+ for (ProductFlavorData data : flavorDataList) {
+ String dimensionName = "";
+ DefaultProductFlavor productFlavor = data.getProductFlavor();
+
+ if (productFlavor instanceof GroupableProductFlavorDsl) {
+ dimensionName = ((GroupableProductFlavorDsl) productFlavor).getFlavorDimension();
+ }
+ testVariantConfig.addProductFlavor(
+ productFlavor,
+ data.getTestSourceSet(),
+ dimensionName);
+ testVariantProviders.add(data.getTestProvider());
+ }
+
+ // now add the default config
+ testVariantProviders.add(basePlugin.getDefaultConfigData().getTestProvider());
+
+ // create the internal storage for this variant.
+ TestVariantData testVariantData = new TestVariantData(testVariantConfig, (TestedVariantData) testedVariantData);
+ localVariantDataList.add(testVariantData);
+ // link the testVariant to the tested variant in the other direction
+ ((TestedVariantData) testedVariantData).setTestVariantData(testVariantData);
+
+ if (testedConfig.getType() == VariantConfiguration.Type.LIBRARY) {
+ testVariantProviders.add(testedVariantData.getVariantDependency());
+ }
+
+ // dependencies for the test variant
+ VariantDependencies variantDep = VariantDependencies.compute(
+ project, testVariantData.getVariantConfiguration().getFullName(),
+ false /*publishVariant*/,
+ variantFactory.isLibrary(),
+ testVariantProviders.toArray(new ConfigurationProvider[testVariantProviders.size()]));
+ testVariantData.setVariantDependency(variantDep);
+
+ basePlugin.resolveDependencies(variantDep);
+ testVariantConfig.setDependencies(variantDep);
+
+ basePlugin.getVariantDataList().add(testVariantData);
+ basePlugin.createTestApkTasks(testVariantData,
+ (BaseVariantData) testVariantData.getTestedVariantData());
+ }
+ }
+
+ @NonNull
+ private Task createAssembleTask(ProductFlavorData[] flavorDataList) {
+ String name = ProductFlavorData.getFlavoredName(flavorDataList, true);
+
+ Task assembleTask = project.getTasks().create("assemble" + name);
+ assembleTask.setDescription("Assembles all builds for flavor combination: " + name);
+ assembleTask.setGroup("Build");
+
+ return assembleTask;
+ }
+
+ private 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 : basePlugin.getVariantDataList()) {
+ if (map.get(variantData) != null) {
+ continue;
+ }
+
+ if (variantData instanceof TestVariantData) {
+ TestVariantData testVariantData = (TestVariantData) variantData;
+ createVariantApiObjects(
+ map,
+ (BaseVariantData) testVariantData.getTestedVariantData(),
+ testVariantData);
+ } else {
+ createVariantApiObjects(
+ map,
+ variantData,
+ ((TestedVariantData) variantData).getTestVariantData());
+ }
+ }
+ }
+
+ private boolean isVariantPublished() {
+ return extension.getPublishNonDefault();
+ }
+
+ private void createVariantApiObjects(
+ @NonNull Map<BaseVariantData, BaseVariant> map,
+ @NonNull BaseVariantData variantData,
+ @Nullable TestVariantData testVariantData) {
+ BaseVariant variantApi = variantFactory.createVariantApi(variantData);
+
+ TestVariantImpl testVariant = null;
+ if (testVariantData != null) {
+ testVariant = basePlugin.getInstantiator().newInstance(TestVariantImpl.class, testVariantData, basePlugin);
+ }
+
+ if (testVariant != null) {
+ ((TestedVariant) variantApi).setTestVariant(testVariant);
+ testVariant.setTestedVariant(variantApi);
+ }
+
+ extension.addVariant(variantApi);
+ map.put(variantData, variantApi);
+
+ if (testVariant != null) {
+ extension.addTestVariant(testVariant);
+ map.put(testVariantData, testVariant);
+ }
+ }
+
+ private static void checkName(@NonNull String name, @NonNull String displayName) {
+ if (name.startsWith(ANDROID_TEST)) {
+ throw new RuntimeException(String.format(
+ "%1$s names cannot start with '%2$s'", displayName, ANDROID_TEST));
+ }
+
+ if (name.startsWith(UI_TEST)) {
+ throw new RuntimeException(String.format(
+ "%1$s names cannot start with %2$s", displayName, UI_TEST));
+ }
+
+ if (LINT.equals(name)) {
+ throw new RuntimeException(String.format(
+ "%1$s names cannot be %2$s", displayName, LINT));
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantImpl.java
new file mode 100644
index 0000000..d25d512
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantImpl.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.BasePlugin;
+import com.android.build.gradle.api.ApkVariant;
+import com.android.build.gradle.internal.variant.ApkVariantData;
+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.model.SigningConfig;
+
+import org.gradle.api.DefaultTask;
+
+import java.io.File;
+import java.util.Collection;
+
+public abstract class ApkVariantImpl extends BaseVariantImpl implements ApkVariant {
+
+ @NonNull
+ private BasePlugin plugin;
+
+ protected ApkVariantImpl(@NonNull BasePlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @NonNull
+ protected abstract ApkVariantData getApkVariantData();
+
+ @Override
+ @Nullable
+ public String getVersionName() {
+ return getApkVariantData().getVariantConfiguration().getVersionName();
+ }
+
+ @Override
+ public int getVersionCode() {
+ return getApkVariantData().getVariantConfiguration().getVersionCode();
+ }
+
+ @Override
+ public void setOutputFile(@NonNull File outputFile) {
+ ApkVariantData variantData = getApkVariantData();
+ if (variantData.zipAlignTask != null) {
+ variantData.zipAlignTask.setOutputFile(outputFile);
+ } else {
+ variantData.packageApplicationTask.setOutputFile(outputFile);
+ }
+
+ // also set it on the variant Data so that the values are in sync
+ variantData.setOutputFile(outputFile);
+ }
+
+ @Override
+ public Dex getDex() {
+ return getApkVariantData().dexTask;
+ }
+
+ @Override
+ public PackageApplication getPackageApplication() {
+ return getApkVariantData().packageApplicationTask;
+ }
+
+ @Override
+ public ZipAlign getZipAlign() {
+ return getApkVariantData().zipAlignTask;
+ }
+
+ @Override
+ public DefaultTask getInstall() {
+ return getApkVariantData().installTask;
+ }
+
+ @Override
+ public DefaultTask getUninstall() {
+ return getApkVariantData().uninstallTask;
+ }
+
+ @Override
+ public SigningConfig getSigningConfig() {
+ return getApkVariantData().getVariantConfiguration().getSigningConfig();
+ }
+
+ @Override
+ public boolean isSigningReady() {
+ return getApkVariantData().isSigned();
+ }
+
+ @Override
+ @NonNull
+ public ZipAlign createZipAlignTask(
+ @NonNull String taskName,
+ @NonNull File inputFile,
+ @NonNull File outputFile) {
+ ApkVariantData variantData = getApkVariantData();
+
+ //noinspection VariableNotUsedInsideIf
+ if (variantData.zipAlignTask != null) {
+ throw new RuntimeException(String.format(
+ "ZipAlign task for variant '%s' already exists.", getName()));
+ }
+
+ ZipAlign task = plugin.createZipAlignTask(taskName, inputFile, outputFile);
+
+ // update variant data
+ variantData.setOutputFile(outputFile);
+ variantData.zipAlignTask = task;
+
+ // setup dependencies
+ variantData.assembleTask.dependsOn(task);
+
+ return task;
+ }
+
+ @Override
+ @NonNull
+ public Collection<File> getCompileLibraries() {
+ return plugin.getAndroidBuilder().getCompileClasspath(
+ getVariantData().getVariantConfiguration());
+ }
+
+ @Override
+ @NonNull
+ public Collection<File> getApkLibraries() {
+ return plugin.getAndroidBuilder().getPackagedJars(getVariantData().getVariantConfiguration());
+ }
+}
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
index fcc3d0b..0c7b034 100644
--- 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
@@ -18,103 +18,51 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.build.gradle.BasePlugin;
import com.android.build.gradle.api.ApplicationVariant;
import com.android.build.gradle.api.TestVariant;
+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.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 {
+public class ApplicationVariantImpl extends ApkVariantImpl implements ApplicationVariant, TestedVariant {
@NonNull
private final ApplicationVariantData variantData;
+
@Nullable
private TestVariant testVariant = null;
- public ApplicationVariantImpl(@NonNull ApplicationVariantData variantData) {
+ public ApplicationVariantImpl(@NonNull ApplicationVariantData variantData,
+ @NonNull BasePlugin plugin) {
+ super(plugin);
this.variantData = variantData;
}
@Override
+ @NonNull
protected BaseVariantData getVariantData() {
return variantData;
}
+ @Override
+ @NonNull
+ protected ApkVariantData getApkVariantData() {
+ return variantData;
+ }
+
+ @Override
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
index 894fe8a..5b4e1ad 100644
--- 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
@@ -24,20 +24,26 @@
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.ManifestProcessorTask;
import com.android.build.gradle.tasks.RenderscriptCompile;
-import com.android.builder.DefaultBuildType;
-import com.android.builder.DefaultProductFlavor;
+import com.android.builder.core.DefaultBuildType;
+import com.android.builder.core.DefaultProductFlavor;
+import com.android.builder.model.SourceProvider;
+
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;
+import java.util.List;
+
abstract class BaseVariantImpl implements BaseVariant {
+ @NonNull
protected abstract BaseVariantData getVariantData();
@Override
@@ -76,10 +82,22 @@
return getVariantData().getVariantConfiguration().getBuildType();
}
+ @Override
+ @NonNull
+ public List<DefaultProductFlavor> getProductFlavors() {
+ return getVariantData().getVariantConfiguration().getFlavorConfigs();
+ }
+
+ @Override
+ @NonNull
+ public DefaultProductFlavor getMergedFlavor() {
+ return getVariantData().getVariantConfiguration().getMergedFlavor();
+ }
+
@NonNull
@Override
- public DefaultProductFlavor getConfig() {
- return getVariantData().getVariantConfiguration().getDefaultConfig();
+ public List<SourceProvider> getSourceSets() {
+ return getVariantData().getVariantConfiguration().getSortedSourceProviders();
}
@Override
@@ -90,8 +108,26 @@
@Override
@NonNull
- public ProcessManifest getProcessManifest() {
- return getVariantData().processManifestTask;
+ public String getPackageName() {
+ return getVariantData().getPackageName();
+ }
+
+ @Override
+ @NonNull
+ public Task getPreBuild() {
+ return getVariantData().preBuildTask;
+ }
+
+ @Override
+ @NonNull
+ public Task getCheckManifest() {
+ return getVariantData().checkManifestTask;
+ }
+
+ @Override
+ @NonNull
+ public ManifestProcessorTask getProcessManifest() {
+ return getVariantData().manifestProcessorTask;
}
@Override
@@ -133,6 +169,18 @@
return getVariantData().javaCompileTask;
}
+ @NonNull
+ @Override
+ public NdkCompile getNdkCompile() {
+ return getVariantData().ndkCompileTask;
+ }
+
+ @Nullable
+ @Override
+ public Task getObfuscation() {
+ return getVariantData().obfuscationTask;
+ }
+
@Override
@NonNull
public Copy getProcessJavaResources() {
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
index a9647e6..050c4a1 100644
--- 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
@@ -19,25 +19,36 @@
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 org.gradle.api.Project;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
import java.io.File;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import groovy.lang.Closure;
+
/**
* Default implementation of the AndroidSourceDirectorySet.
*/
public class DefaultAndroidSourceDirectorySet implements AndroidSourceDirectorySet {
private final String name;
- private final FileResolver fileResolver;
+ private final Project project;
private List<Object> source = Lists.newArrayList();
+ private final PatternSet filter = new PatternSet();
- DefaultAndroidSourceDirectorySet(@NonNull String name, @NonNull FileResolver fileResolver) {
+ DefaultAndroidSourceDirectorySet(@NonNull String name,
+ @NonNull Project project) {
this.name = name;
- this.fileResolver = fileResolver;
+ this.project = project;
}
@Override
@@ -72,13 +83,101 @@
@Override
@NonNull
- public Set<File> getSrcDirs() {
- return fileResolver.resolveFiles(source.toArray()).getFiles();
+ public FileTree getSourceFiles() {
+ FileTree src = null;
+ Set<File> sources = getSrcDirs();
+ if (!sources.isEmpty()) {
+ src = project.files(new ArrayList<Object>(sources)).getAsFileTree().matching(filter);
+ }
+ return src == null ? project.files().getAsFileTree() : src;
}
@Override
@NonNull
+ public Set<File> getSrcDirs() {
+ return project.files(source.toArray()).getFiles();
+ }
+
+ @Override
+ @NonNull
+ public PatternFilterable getFilter() {
+ return filter;
+ }
+
+
+ @Override
+ @NonNull
public String toString() {
return source.toString();
}
+
+ @Override
+ public Set<String> getIncludes() {
+ return filter.getIncludes();
+ }
+
+ @Override
+ public Set<String> getExcludes() {
+ return filter.getExcludes();
+ }
+
+ @Override
+ public PatternFilterable setIncludes(Iterable<String> includes) {
+ filter.setIncludes(includes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable setExcludes(Iterable<String> excludes) {
+ filter.setExcludes(excludes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable include(String... includes) {
+ filter.include(includes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable include(Iterable<String> includes) {
+ filter.include(includes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable include(Spec<FileTreeElement> includeSpec) {
+ filter.include(includeSpec);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable include(Closure includeSpec) {
+ filter.include(includeSpec);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable exclude(Iterable<String> excludes) {
+ filter.exclude(excludes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable exclude(String... excludes) {
+ filter.exclude(excludes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable exclude(Spec<FileTreeElement> excludeSpec) {
+ filter.exclude(excludeSpec);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable exclude(Closure excludeSpec) {
+ filter.exclude(excludeSpec);
+ return this;
+ }
}
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
index 67778bc..bf6e94a 100644
--- 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
@@ -17,7 +17,8 @@
package com.android.build.gradle.internal.api;
import com.android.build.gradle.api.AndroidSourceFile;
-import org.gradle.api.internal.file.FileResolver;
+
+import org.gradle.api.Project;
import java.io.File;
@@ -26,12 +27,12 @@
public class DefaultAndroidSourceFile implements AndroidSourceFile {
private final String name;
- private final FileResolver fileResolver;
+ private final Project project;
private Object source;
- DefaultAndroidSourceFile(String name, FileResolver fileResolver) {
+ DefaultAndroidSourceFile(String name, Project project) {
this.name = name;
- this.fileResolver = fileResolver;
+ this.project = project;
}
@Override
@@ -47,7 +48,7 @@
@Override
public File getSrcFile() {
- return fileResolver.resolve(source);
+ return project.file(source);
}
@Override
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
index 0520fa5..6c164bc 100644
--- 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
@@ -22,81 +22,72 @@
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.Project;
import org.gradle.api.tasks.SourceSet;
import org.gradle.util.ConfigureUtil;
import org.gradle.util.GUtil;
import java.io.File;
+import java.util.Collection;
import java.util.Collections;
import java.util.Set;
+import groovy.lang.Closure;
+
/**
*/
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 boolean isLibrary;
+
+ private final AndroidSourceDirectorySet javaSource;
+ private final AndroidSourceDirectorySet 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 AndroidSourceDirectorySet jniLibs;
private final String displayName;
- private final SourceDirectorySet allSource;
- public DefaultAndroidSourceSet(@NonNull String name, @NonNull FileResolver fileResolver) {
+ public DefaultAndroidSourceSet(@NonNull String name,
+ Project project, boolean isLibrary) {
this.name = name;
+ this.isLibrary = isLibrary;
displayName = GUtil.toWords(this.name);
String javaSrcDisplayName = String.format("%s Java source", displayName);
- javaSource = new DefaultSourceDirectorySet(javaSrcDisplayName, fileResolver);
+ javaSource = new DefaultAndroidSourceDirectorySet(javaSrcDisplayName, project);
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);
+ javaResources = new DefaultAndroidSourceDirectorySet(javaResourcesDisplayName, project);
+ javaResources.getFilter().exclude("**/*.java");
String manifestDisplayName = String.format("%s manifest", displayName);
- manifest = new DefaultAndroidSourceFile(manifestDisplayName, fileResolver);
+ manifest = new DefaultAndroidSourceFile(manifestDisplayName, project);
String assetsDisplayName = String.format("%s assets", displayName);
- assets = new DefaultAndroidSourceDirectorySet(assetsDisplayName, fileResolver);
+ assets = new DefaultAndroidSourceDirectorySet(assetsDisplayName, project);
String resourcesDisplayName = String.format("%s resources", displayName);
- res = new DefaultAndroidSourceDirectorySet(resourcesDisplayName, fileResolver);
+ res = new DefaultAndroidSourceDirectorySet(resourcesDisplayName, project);
String aidlDisplayName = String.format("%s aidl", displayName);
- aidl = new DefaultAndroidSourceDirectorySet(aidlDisplayName, fileResolver);
+ aidl = new DefaultAndroidSourceDirectorySet(aidlDisplayName, project);
String renderscriptDisplayName = String.format("%s renderscript", displayName);
- renderscript = new DefaultAndroidSourceDirectorySet(renderscriptDisplayName, fileResolver);
+ renderscript = new DefaultAndroidSourceDirectorySet(renderscriptDisplayName, project);
String jniDisplayName = String.format("%s jni", displayName);
- jni = new DefaultAndroidSourceDirectorySet(jniDisplayName, fileResolver);
+ jni = new DefaultAndroidSourceDirectorySet(jniDisplayName, project);
+
+ String libsDisplayName = String.format("%s jniLibs", displayName);
+ jniLibs = new DefaultAndroidSourceDirectorySet(libsDisplayName, project);
}
@Override
@@ -128,6 +119,14 @@
@Override
@NonNull
public String getPackageConfigurationName() {
+ if (isLibrary) {
+ if (name.equals(SourceSet.MAIN_SOURCE_SET_NAME)) {
+ return "publish";
+ } else {
+ return String.format("%sPublish", name);
+ }
+ }
+
if (name.equals(SourceSet.MAIN_SOURCE_SET_NAME)) {
return "apk";
} else {
@@ -137,6 +136,16 @@
@Override
@NonNull
+ public String getProvidedConfigurationName() {
+ if (name.equals(SourceSet.MAIN_SOURCE_SET_NAME)) {
+ return "provided";
+ } else {
+ return String.format("%sProvided", name);
+ }
+ }
+
+ @Override
+ @NonNull
public AndroidSourceFile getManifest() {
return manifest;
}
@@ -215,7 +224,20 @@
@Override
@NonNull
- public SourceDirectorySet getJava() {
+ public AndroidSourceDirectorySet getJniLibs() {
+ return jniLibs;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet jniLibs(Closure configureClosure) {
+ ConfigureUtil.configure(configureClosure, getJniLibs());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet getJava() {
return javaSource;
}
@@ -226,15 +248,10 @@
return this;
}
- @Override
- @NonNull
- public SourceDirectorySet getAllJava() {
- return allJavaSource;
- }
@Override
@NonNull
- public SourceDirectorySet getResources() {
+ public AndroidSourceDirectorySet getResources() {
return javaResources;
}
@@ -247,12 +264,6 @@
@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"));
@@ -262,6 +273,7 @@
aidl.setSrcDirs(Collections.singletonList(path + "/aidl"));
renderscript.setSrcDirs(Collections.singletonList(path + "/rs"));
jni.setSrcDirs(Collections.singletonList(path + "/jni"));
+ jniLibs.setSrcDirs(Collections.singletonList(path + "/jniLibs"));
return this;
}
@@ -314,4 +326,10 @@
public Set<File> getAssetsDirectories() {
return getAssets().getSrcDirs();
}
+
+ @NonNull
+ @Override
+ public Collection<File> getJniLibsDirectories() {
+ return getJniLibs().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
index f48a529..9b3346a 100644
--- 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
@@ -30,7 +30,7 @@
* implementation of the {@link LibraryVariant} interface around a
* {@link LibraryVariantData} object.
*/
-public class LibraryVariantImpl extends BaseVariantImpl implements LibraryVariant {
+public class LibraryVariantImpl extends BaseVariantImpl implements LibraryVariant, TestedVariant {
@NonNull
private final LibraryVariantData variantData;
@@ -42,10 +42,12 @@
}
@Override
+ @NonNull
protected BaseVariantData getVariantData() {
return variantData;
}
+ @Override
public void setTestVariant(@Nullable TestVariant testVariant) {
this.testVariant = testVariant;
}
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
index 073c49d..5d7d9e3 100644
--- 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
@@ -17,58 +17,42 @@
package com.android.build.gradle.internal.api;
import com.android.annotations.NonNull;
+import com.android.build.gradle.BasePlugin;
import com.android.build.gradle.api.BaseVariant;
import com.android.build.gradle.api.TestVariant;
+import com.android.build.gradle.internal.variant.ApkVariantData;
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 {
+public class TestVariantImpl extends ApkVariantImpl implements TestVariant {
@NonNull
private final TestVariantData variantData;
@NonNull
private BaseVariant testedVariant;
- public TestVariantImpl(@NonNull TestVariantData variantData) {
+ public TestVariantImpl(@NonNull TestVariantData variantData, @NonNull BasePlugin plugin) {
+ super(plugin);
this.variantData = variantData;
}
@Override
+ @NonNull
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);
- }
+ protected ApkVariantData getApkVariantData() {
+ return variantData;
}
@Override
@@ -82,31 +66,6 @@
}
@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;
}
@@ -116,14 +75,4 @@
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/api/TestedVariant.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/TestedVariant.java
new file mode 100644
index 0000000..b6f9686
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/TestedVariant.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Nullable;
+import com.android.build.gradle.api.TestVariant;
+
+/**
+ * API for tested variant api object.
+ */
+public interface TestedVariant {
+
+ void setTestVariant(@Nullable TestVariant testVariant);
+
+ @Nullable
+ TestVariant getTestVariant();
+}
+
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoExtension.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoExtension.groovy
new file mode 100644
index 0000000..88a058a
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoExtension.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.coverage
+
+/**
+ * Extension of the Jacoco support.
+ *
+ * This is accessed with
+ *
+ * <pre>
+ * android {
+ * jacoco {
+ * ...
+ * }
+ * }
+ * </pre>
+ */
+class JacocoExtension {
+ /**
+ * The version of jacoco to use.
+ */
+ String version = '0.6.2.201302030002'
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoInstrumentTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoInstrumentTask.groovy
new file mode 100644
index 0000000..82e55b3
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoInstrumentTask.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.coverage
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Simple Jacoco instrument task that calls the Ant version.
+ */
+public class JacocoInstrumentTask extends DefaultTask {
+
+ @InputDirectory
+ File inputDir
+
+ @OutputDirectory
+ File outputDir
+
+ /**
+ * Classpath containing Jacoco classes for use by the task.
+ */
+ @InputFiles
+ FileCollection jacocoClasspath
+
+ @TaskAction
+ void instrument() {
+ File outDir = getOutputDir()
+ outDir.deleteDir()
+ outDir.mkdirs()
+
+ getAnt().taskdef(name: 'instrumentWithJacoco',
+ classname: 'org.jacoco.ant.InstrumentTask',
+ classpath: getJacocoClasspath().asPath)
+ getAnt().instrumentWithJacoco(destdir: outDir) {
+ fileset(dir: getInputDir())
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoPlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoPlugin.groovy
new file mode 100644
index 0000000..1852665
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoPlugin.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.coverage
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/**
+ * Jacoco plugin. This is very similar to the built-in support for Jacoco but we dup it in order
+ * to control it as we need our own offline instrumentation.
+ *
+ * This may disappear if we can ever reuse the built-in support.
+ *
+ */
+class JacocoPlugin implements Plugin<Project> {
+ public static final String ANT_CONFIGURATION_NAME = 'androidJacocoAnt'
+ public static final String AGENT_CONFIGURATION_NAME = 'androidJacocoAgent'
+
+ private Project project;
+
+ @Override
+ void apply(Project project) {
+ this.project = project
+
+ addJacocoConfigurations()
+ configureAgentDependencies()
+ configureTaskClasspathDefaults()
+ }
+
+ /**
+ * Creates the configurations used by plugin.
+ * @param project the project to add the configurations to
+ */
+ private void addJacocoConfigurations() {
+ this.project.configurations.create(AGENT_CONFIGURATION_NAME).with {
+ visible = false
+ transitive = true
+ description = 'The Jacoco agent to use to get coverage data.'
+ }
+ this.project.configurations.create(ANT_CONFIGURATION_NAME).with {
+ visible = false
+ transitive = true
+ description = 'The Jacoco ant tasks to use to get execute Gradle tasks.'
+ }
+ }
+
+ /**
+ * Configures the agent dependencies using the 'jacocoAnt' configuration.
+ * Uses the version declared in 'toolVersion' of the Jacoco extension if no dependencies are explicitly declared.
+ */
+ private void configureAgentDependencies() {
+ def config = project.configurations[AGENT_CONFIGURATION_NAME]
+ config.incoming.beforeResolve {
+ if (config.dependencies.empty) {
+ config.dependencies.add(project.dependencies.create("org.jacoco:org.jacoco.agent:${project.android.jacoco.version}"))
+ }
+ }
+ }
+
+ /**
+ * Configures the classpath for Jacoco tasks using the 'jacocoAnt' configuration.
+ * Uses the version information declared in 'toolVersion' of the Jacoco extension if no dependencies are explicitly declared.
+ */
+ private void configureTaskClasspathDefaults() {
+ def config = project.configurations[ANT_CONFIGURATION_NAME]
+ config.incoming.beforeResolve {
+ if (config.dependencies.empty) {
+ config.dependencies.add(project.dependencies.create("org.jacoco:org.jacoco.ant:${project.android.jacoco.version}"))
+ }
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoReportTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoReportTask.groovy
new file mode 100644
index 0000000..0b66817
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoReportTask.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.coverage
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.FileCollection
+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.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+/**
+ * Simple Jacoco report task that calls the Ant version.
+ */
+public class JacocoReportTask extends DefaultTask {
+ @InputFile
+ File coverageFile
+
+ @OutputDirectory
+ File reportDir
+
+ @InputDirectory
+ File classDir
+
+ @InputFiles
+ List<File> sourceDir
+
+ @Input
+ String reportName
+
+ @InputFiles
+ FileCollection jacocoClasspath
+
+ @TaskAction
+ void report() {
+ File reportOutDir = getReportDir()
+ reportOutDir.deleteDir()
+ reportOutDir.mkdirs()
+
+ getAnt().taskdef(name: 'reportWithJacoco',
+ classname: 'org.jacoco.ant.ReportTask',
+ classpath: getJacocoClasspath().asPath)
+ getAnt().reportWithJacoco {
+ executiondata {
+ file(file: getCoverageFile())
+ }
+ structure(name: getReportName()) {
+ sourcefiles {
+ for (File source : getSourceDir()) {
+ fileset(dir: source)
+ }
+ }
+ classfiles {
+ fileset(
+ dir: getClassDir(),
+ excludes: "**/R.class,**/R*.class,**/Manifest.class,**/Manifest*.class,**/BuildConfig.class")
+ }
+ }
+
+ html(destdir: reportOutDir)
+ xml(destfile: new File(reportOutDir, "report.xml"))
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/ClassifiedJarDependency.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/ClassifiedJarDependency.java
new file mode 100644
index 0000000..12b676d
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/ClassifiedJarDependency.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.JarDependency;
+
+import java.io.File;
+
+/**
+ * Custom version of JarDependency which adds a classifier fields. This will indicate
+ * whether a jar dependency was from a project's default artifact or a custom one.
+ * (custom one are represented differently in the model).
+ */
+public class ClassifiedJarDependency extends JarDependency {
+
+ private final String classifier;
+
+ public ClassifiedJarDependency(@NonNull File jarFile,
+ boolean compiled,
+ boolean packaged,
+ boolean proguarded,
+ @Nullable String classifier) {
+ super(jarFile, compiled, packaged, proguarded);
+ this.classifier = classifier;
+ }
+
+ public String getClassifier() {
+ return classifier;
+ }
+}
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
index 75d4112..908e85c 100644
--- 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
@@ -17,25 +17,33 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
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.android.builder.model.MavenCoordinates;
import java.io.File;
import java.util.List;
+@Immutable
public class LibraryDependencyImpl extends LibraryBundle {
@NonNull
private final List<LibraryDependency> dependencies;
+ @Nullable
+ private final String variantName;
+
public LibraryDependencyImpl(@NonNull File bundle,
@NonNull File explodedBundle,
@NonNull List<LibraryDependency> dependencies,
- @Nullable String name) {
+ @Nullable String name,
+ @Nullable String variantName) {
super(bundle, explodedBundle, name);
this.dependencies = dependencies;
+ this.variantName = variantName;
}
@NonNull
@@ -55,4 +63,22 @@
public List<? extends ManifestDependency> getManifestDependencies() {
return dependencies;
}
+
+ @Nullable
+ @Override
+ public String getProjectVariant() {
+ return variantName;
+ }
+
+ @Nullable
+ @Override
+ public MavenCoordinates getRequestedCoordinates() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public MavenCoordinates getResolvedCoordinates() {
+ return null;
+ }
}
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
index 3af2ff9..6632e42 100644
--- 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
@@ -17,9 +17,9 @@
package com.android.build.gradle.internal.dependency;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.builder.dependency.ManifestDependency;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Nested;
+import com.google.common.collect.Lists;
import java.io.File;
import java.util.List;
@@ -29,16 +29,34 @@
*/
public class ManifestDependencyImpl implements ManifestDependency{
+ @NonNull
private final File manifest;
+ @NonNull
private final List<ManifestDependencyImpl> dependencies;
+ @NonNull
+ private final String name;
public ManifestDependencyImpl(@NonNull File manifest,
@NonNull List<ManifestDependencyImpl> dependencies) {
this.manifest = manifest;
this.dependencies = dependencies;
+ this.name = manifest.getName();
}
- @InputFile
+ public ManifestDependencyImpl(@NonNull String name,
+ @NonNull File manifest,
+ @NonNull List<ManifestDependencyImpl> dependencies) {
+ this.manifest = manifest;
+ this.dependencies = dependencies;
+ this.name = name;
+ }
+
+ @Override
+ @Nullable
+ public String getName() {
+ return name;
+ }
+
@Override
@NonNull
public File getManifest() {
@@ -51,9 +69,18 @@
return dependencies;
}
- @Nested
@NonNull
public List<ManifestDependencyImpl> getManifestDependenciesForInput() {
return dependencies;
}
+
+ public List<File> getAllManifests() {
+ List<File> files = Lists.newArrayListWithExpectedSize(1 + dependencies.size() * 2);
+ files.add(manifest);
+ for (ManifestDependencyImpl manifestDep : dependencies) {
+ files.addAll(manifestDep.getAllManifests());
+ }
+
+ return files;
+ }
}
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
index 71320f3..0a71270 100644
--- 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
@@ -16,6 +16,7 @@
package com.android.build.gradle.internal.dependency
import com.android.annotations.NonNull
+import com.android.annotations.Nullable
import com.android.build.gradle.internal.ConfigurationProvider
import com.android.builder.dependency.DependencyContainer
import com.android.builder.dependency.JarDependency
@@ -37,9 +38,11 @@
final String name
@NonNull
- final Configuration compileConfiguration
+ private final Configuration compileConfiguration
@NonNull
- final Configuration packageConfiguration
+ private final Configuration packageConfiguration
+ @NonNull
+ private final Configuration publishConfiguration
@NonNull
private final List<LibraryDependencyImpl> libraries = []
@@ -48,49 +51,105 @@
@NonNull
private final List<JarDependency> localJars = []
+ /**
+ * Whether we have a direct dependency on com.android.support:support-annotations; this
+ * is used to drive whether we extract annotations when building libraries for example
+ */
+ boolean annotationsPresent
+
DependencyChecker checker
static VariantDependencies compute(@NonNull Project project,
@NonNull String name,
+ boolean publishVariant,
+ boolean isLibrary,
@NonNull ConfigurationProvider... providers) {
- Set<Configuration> compileConfigs = Sets.newHashSet()
- Set<Configuration> apkConfigs = Sets.newHashSet()
+ Set<Configuration> compileConfigs = Sets.newHashSetWithExpectedSize(providers.length * 2)
+ Set<Configuration> apkConfigs = Sets.newHashSetWithExpectedSize(providers.length)
for (ConfigurationProvider provider : providers) {
- compileConfigs.add(provider.compileConfiguration)
- apkConfigs.add(provider.packageConfiguration)
+ if (provider != null) {
+ compileConfigs.add(provider.compileConfiguration)
+ if (provider.providedConfiguration != null) {
+ compileConfigs.add(provider.providedConfiguration)
+ }
+
+ apkConfigs.add(provider.compileConfiguration)
+ apkConfigs.add(provider.packageConfiguration)
+ }
}
Configuration compile = project.configurations.create("_${name}Compile")
+ compile.visible = false
+ compile.description = "## Internal use, do not manually configure ##"
compile.setExtendsFrom(compileConfigs)
- Configuration apk = project.configurations.create("_${name}Apk")
+ Configuration apk = project.configurations.create(isLibrary? "_${name}Publish" : "_${name}Apk")
+ apk.visible = false
+ apk.description = "## Internal use, do not manually configure ##"
apk.setExtendsFrom(apkConfigs)
- return new VariantDependencies(name, compile, apk);
+ Configuration publish = null;
+ if (publishVariant) {
+ publish = project.configurations.create(name)
+ publish.description = "Published Configuration for Variant ${name}"
+ // if the variant is not a library, then the publishing configuration should
+ // not extend from the apkConfigs. It's mostly there to access the artifact from
+ // another project but it shouldn't bring any dependencies with it.
+ if (isLibrary) {
+ publish.setExtendsFrom(apkConfigs)
+ }
+ }
+
+ return new VariantDependencies(name, compile, apk, publish);
}
- private VariantDependencies(@NonNull String name,
- @NonNull Configuration compileConfiguration,
- @NonNull Configuration packageConfiguration) {
+ private VariantDependencies(@NonNull String name,
+ @NonNull Configuration compileConfiguration,
+ @NonNull Configuration packageConfiguration,
+ @Nullable Configuration publishConfiguration) {
this.name = name
this.compileConfiguration = compileConfiguration
this.packageConfiguration = packageConfiguration
+ this.publishConfiguration = publishConfiguration
}
public String getName() {
return name
}
- void addLibraries(List<LibraryDependencyImpl> list) {
+ @Override
+ @NonNull
+ Configuration getCompileConfiguration() {
+ return compileConfiguration
+ }
+
+ @Override
+ @NonNull
+ Configuration getPackageConfiguration() {
+ return packageConfiguration
+ }
+
+ @Override
+ @Nullable
+ Configuration getProvidedConfiguration() {
+ return null
+ }
+
+ @Nullable
+ Configuration getPublishConfiguration() {
+ return publishConfiguration
+ }
+
+ void addLibraries(@NonNull List<LibraryDependencyImpl> list) {
libraries.addAll(list)
}
- void addJars(List<JarDependency> list) {
+ void addJars(@NonNull Collection<JarDependency> list) {
jars.addAll(list)
}
- void addLocalJars(List<JarDependency> list) {
+ void addLocalJars(@NonNull Collection<JarDependency> list) {
localJars.addAll(list)
}
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
index d50371e..7285a5a 100644
--- 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
@@ -27,6 +27,9 @@
@Input
private List<String> noCompressList
+ @Input
+ private boolean useAaptPngCruncher = true;
+
public void setIgnoreAssetsPattern(String ignoreAssetsPattern) {
this.ignoreAssetsPattern = ignoreAssetsPattern
}
@@ -49,6 +52,19 @@
return noCompressList
}
+ public void useAaptPngCruncher(boolean value) {
+ useAaptPngCruncher = value;
+ }
+
+ public void setUseAaptPngCruncher(boolean value) {
+ useAaptPngCruncher = value;
+ }
+
+ @Override
+ public boolean getUseAaptPngCruncher() {
+ return useAaptPngCruncher;
+ }
+
// -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
public void noCompress(String 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
index d1a9af4..3976bc5 100644
--- 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
@@ -20,7 +20,7 @@
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.api.Project;
import org.gradle.internal.reflect.Instantiator;
/**
@@ -32,16 +32,21 @@
@NonNull
private final Instantiator instantiator;
@NonNull
- private final FileResolver fileResolver;
+ private final Project project;
+
+ private final boolean isLibrary;
public AndroidSourceSetFactory(@NonNull Instantiator instantiator,
- @NonNull FileResolver fileResolver) {
+ @NonNull Project project,
+ boolean isLibrary) {
this.instantiator = instantiator;
- this.fileResolver = fileResolver;
+ this.isLibrary = isLibrary;
+ this.project = project;
}
@Override
public AndroidSourceSet create(String name) {
- return instantiator.newInstance(DefaultAndroidSourceSet.class, name, fileResolver);
+ return instantiator.newInstance(DefaultAndroidSourceSet.class,
+ name, project, isLibrary);
}
}
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
index 6b51bc4..270513c 100644
--- 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
@@ -18,13 +18,16 @@
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.core.AndroidBuilder
+import com.android.builder.core.BuilderConstants
+import com.android.builder.core.DefaultBuildType
+import com.android.builder.model.BuildType
+import com.android.builder.model.ClassField
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.api.Project
+import org.gradle.api.logging.Logger
import org.gradle.internal.reflect.Instantiator
/**
* DSL overlay to make methods that accept String... work.
@@ -33,23 +36,30 @@
private static final long serialVersionUID = 1L
@NonNull
- private final FileResolver fileResolver
+ private final Project project
+ @NonNull
+ private final Logger logger
private final NdkConfigDsl ndkConfig
+
public BuildTypeDsl(@NonNull String name,
- @NonNull FileResolver fileResolver,
- @NonNull Instantiator instantiator) {
+ @NonNull Project project,
+ @NonNull Instantiator instantiator,
+ @NonNull Logger logger) {
super(name)
- this.fileResolver = fileResolver
+ this.project = project
+ this.logger = logger
ndkConfig = instantiator.newInstance(NdkConfigDsl.class)
}
@VisibleForTesting
BuildTypeDsl(@NonNull String name,
- @NonNull FileResolver fileResolver) {
+ @NonNull Project project,
+ @NonNull Logger logger) {
super(name)
- this.fileResolver = fileResolver
+ this.project = project
+ this.logger = logger
ndkConfig = null
}
@@ -62,7 +72,7 @@
public void init(SigningConfig debugSigningConfig) {
if (BuilderConstants.DEBUG.equals(getName())) {
setDebuggable(true)
- setZipAlign(false)
+ setEmbedMicroApp(false)
assert debugSigningConfig != null
setSigningConfig(debugSigningConfig)
@@ -86,18 +96,35 @@
@NonNull String type,
@NonNull String name,
@NonNull String value) {
+ ClassField alreadyPresent = getBuildConfigFields().get(name);
+ if (alreadyPresent != null) {
+ logger.info(
+ "BuildType(${getName()}): buildConfigField '$name' value is being replaced: ${alreadyPresent.value} -> $value");
+ }
addBuildConfigField(AndroidBuilder.createClassField(type, name, value));
}
+ public void resValue(
+ @NonNull String type,
+ @NonNull String name,
+ @NonNull String value) {
+ ClassField alreadyPresent = getResValues().get(name);
+ if (alreadyPresent != null) {
+ logger.info(
+ "BuildType(${getName()}): resValue '$name' value is being replaced: ${alreadyPresent.value} -> $value");
+ }
+ addResValue(AndroidBuilder.createClassField(type, name, value));
+ }
+
@NonNull
public BuildTypeDsl proguardFile(Object proguardFile) {
- proguardFiles.add(fileResolver.resolve(proguardFile));
+ proguardFiles.add(project.file(proguardFile));
return this;
}
@NonNull
public BuildTypeDsl proguardFiles(Object... proguardFileArray) {
- proguardFiles.addAll(fileResolver.resolveFiles(proguardFileArray).files);
+ proguardFiles.addAll(project.files(proguardFileArray).files);
return this;
}
@@ -105,14 +132,14 @@
public BuildTypeDsl setProguardFiles(Iterable<?> proguardFileIterable) {
proguardFiles.clear();
for (Object proguardFile : proguardFileIterable) {
- proguardFiles.add(fileResolver.resolve(proguardFile));
+ proguardFiles.add(project.file(proguardFile));
}
return this;
}
@NonNull
public BuildTypeDsl consumerProguardFiles(Object... proguardFileArray) {
- consumerProguardFiles.addAll(fileResolver.resolveFiles(proguardFileArray).files);
+ consumerProguardFiles.addAll(project.files(proguardFileArray).files);
return this;
}
@@ -120,7 +147,7 @@
public BuildTypeDsl setConsumerProguardFiles(Iterable<?> proguardFileIterable) {
consumerProguardFiles.clear();
for (Object proguardFile : proguardFileIterable) {
- consumerProguardFiles.add(fileResolver.resolve(proguardFile));
+ consumerProguardFiles.add(project.file(proguardFile));
}
return this;
}
@@ -128,4 +155,33 @@
void ndk(Action<NdkConfigDsl> action) {
action.execute(ndkConfig)
}
+
+ // ---------------
+ // TEMP for compatibility
+ // STOPSHIP Remove in 1.0
+
+ /**
+ * Sets the package name.
+ *
+ * @param packageName the package name
+ * @return the flavor object
+ */
+
+ /** Package name suffix applied to this build type. */
+ @NonNull
+ public BuildType setPackageNameSuffix(@Nullable String packageNameSuffix) {
+ logger.warn("WARNING: packageNameSuffix is deprecated (and will soon stop working); change to \"applicationIdSuffix\" instead");
+ return setApplicationIdSuffix(packageNameSuffix);
+ }
+
+ @NonNull
+ public BuildType packageNameSuffix(@Nullable String packageNameSuffix) {
+ return setPackageNameSuffix(packageNameSuffix);
+ }
+
+ @Nullable
+ public String getPackageNameSuffix() {
+ logger.warn("WARNING: packageNameSuffix is deprecated (and will soon stop working); change to \"applicationIdSuffix\" instead");
+ return getApplicationIdSuffix();
+ }
}
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
index 486727e..39f01be 100644
--- 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
@@ -17,9 +17,11 @@
package com.android.build.gradle.internal.dsl;
import com.android.annotations.NonNull;
-import com.android.builder.DefaultBuildType;
+import com.android.builder.core.DefaultBuildType;
+
import org.gradle.api.NamedDomainObjectFactory;
-import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.Project;
+import org.gradle.api.logging.Logger;
import org.gradle.internal.reflect.Instantiator;
/**
@@ -30,16 +32,21 @@
@NonNull
private final Instantiator instantiator;
@NonNull
- private final FileResolver fileResolver;
+ private final Project project;
+
+ @NonNull
+ private final Logger logger;
public BuildTypeFactory(@NonNull Instantiator instantiator,
- @NonNull FileResolver fileResolver) {
+ @NonNull Project project,
+ @NonNull Logger logger) {
this.instantiator = instantiator;
- this.fileResolver = fileResolver;
+ this.project = project;
+ this.logger = logger;
}
@Override
public DefaultBuildType create(String name) {
- return instantiator.newInstance(BuildTypeDsl.class, name, fileResolver, instantiator);
+ return instantiator.newInstance(BuildTypeDsl.class, name, project, instantiator, logger);
}
}
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
index 8895ca0..ed8ebed 100644
--- 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
@@ -16,16 +16,13 @@
package com.android.build.gradle.internal.dsl
-import com.android.builder.DexOptions
+import com.android.builder.core.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
@@ -34,27 +31,19 @@
@Input
private boolean isJumboModeFlag = false
+ private int threadCount = 4
+
@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
+ return isIncrementalFlag
}
@Override
@@ -88,4 +77,14 @@
public String getJavaMaxHeapSize() {
return javaMaxHeapSize
}
+
+ public void setThreadCount(int threadCount) {
+ this.threadCount = threadCount
+ }
+
+ @Override
+ int getThreadCount() {
+ return threadCount
+ }
+
}
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
index 621ac85..eb4737b 100644
--- 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
@@ -15,23 +15,36 @@
*/
package com.android.build.gradle.internal.dsl
-
import com.android.annotations.NonNull
-import org.gradle.api.internal.file.FileResolver
+import com.android.build.gradle.BasePlugin
+import org.gradle.api.Project
+import org.gradle.api.logging.Logger
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
+ String flavorDimension
public GroupableProductFlavorDsl(
@NonNull String name,
- @NonNull FileResolver fileResolver,
- @NonNull Instantiator instantiator) {
- super(name, fileResolver, instantiator)
+ @NonNull Project project,
+ @NonNull Instantiator instantiator,
+ @NonNull Logger logger) {
+ super(name, project, instantiator, logger)
+ }
+
+ // ---------------
+ // TEMP for compatibility
+ // STOPSHIP Remove in 1.0
+
+ public void flavorGroup(String value) {
+ BasePlugin.displayDeprecationWarning(logger, project, "'flavorGroup' has been renamed 'flavorDimension'. It will be removed in 1.0")
+ flavorDimension = value
+ }
+
+ public void setFlavorGroup(String value) {
+ flavorGroup(value)
}
}
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
index ed422d9..b3bf93c 100644
--- 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
@@ -17,30 +17,36 @@
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.api.Project;
+import org.gradle.api.logging.Logger;
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> {
+public class GroupableProductFlavorFactory implements NamedDomainObjectFactory<GroupableProductFlavorDsl> {
@NonNull
private final Instantiator instantiator;
@NonNull
- private final FileResolver fileResolver;
+ private final Project project;
+ @NonNull
+ private final Logger logger;
public GroupableProductFlavorFactory(@NonNull Instantiator instantiator,
- @NonNull FileResolver fileResolver) {
- this.fileResolver = fileResolver;
+ @NonNull Project project,
+ @NonNull Logger logger) {
this.instantiator = instantiator;
+ this.project = project;
+ this.logger = logger;
}
@Override
public GroupableProductFlavorDsl create(String name) {
return instantiator.newInstance(GroupableProductFlavorDsl.class,
- name, fileResolver, instantiator);
+ name, project, instantiator, logger);
}
}
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
index 253cdd3..ce6380c 100644
--- 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
@@ -16,15 +16,20 @@
package com.android.build.gradle.internal.dsl
+import com.android.tools.lint.checks.BuiltinIssueRegistry
+
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS
+
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.android.tools.lint.detector.api.Severity
+import com.google.common.collect.Maps
import com.google.common.collect.Sets
import org.gradle.api.GradleException
import org.gradle.api.Project
@@ -60,6 +65,10 @@
private boolean warningsAsErrors
@Input
private boolean showAll
+ @Input
+ private boolean checkReleaseBuilds = true;
+ @Input
+ private boolean explainIssues = true;
@InputFile
private File lintConfig
@Input
@@ -75,6 +84,8 @@
@OutputFile
private File xmlOutput
+ private Map<String,Severity> severities = Maps.newHashMap();
+
public LintOptionsImpl() {
}
@@ -96,7 +107,10 @@
boolean checkAllWarnings,
boolean ignoreWarnings,
boolean warningsAsErrors,
- boolean showAll) {
+ boolean showAll,
+ boolean explainIssues,
+ boolean checkReleaseBuilds,
+ @Nullable Map<String,Integer> severityOverrides) {
this.disable = disable
this.enable = enable
this.check = check
@@ -115,6 +129,14 @@
this.ignoreWarnings = ignoreWarnings
this.warningsAsErrors = warningsAsErrors
this.showAll = showAll
+ this.explainIssues = explainIssues
+ this.checkReleaseBuilds = checkReleaseBuilds
+
+ if (severityOverrides != null) {
+ for (Map.Entry<String,Integer> entry : severityOverrides.entrySet()) {
+ severities.put(entry.key, convert(entry.value))
+ }
+ }
}
@NonNull
@@ -137,7 +159,10 @@
source.isCheckAllWarnings(),
source.isIgnoreWarnings(),
source.isWarningsAsErrors(),
- source.isShowAll()
+ source.isShowAll(),
+ source.isExplainIssues(),
+ source.isCheckReleaseBuilds(),
+ source.getSeverityOverrides()
)
}
@@ -280,6 +305,14 @@
this.warningsAsErrors = allErrors
}
+ public boolean isExplainIssues() {
+ return explainIssues
+ }
+
+ public void setExplainIssues(boolean explainIssues) {
+ this.explainIssues = explainIssues
+ }
+
/**
* Returns whether lint should include all output (e.g. include all alternate
* locations, not truncating long messages, etc.)
@@ -296,6 +329,15 @@
this.showAll = showAll
}
+ @Override
+ public boolean isCheckReleaseBuilds() {
+ return checkReleaseBuilds;
+ }
+
+ public void setCheckReleaseBuilds(boolean checkReleaseBuilds) {
+ this.checkReleaseBuilds = checkReleaseBuilds
+ }
+
/**
* Returns the default configuration file to use as a fallback
*/
@@ -389,9 +431,11 @@
flags.setWarningsAsErrors(warningsAsErrors)
flags.setShowEverything(showAll)
flags.setDefaultConfiguration(lintConfig)
+ flags.setSeverityOverrides(severities)
+ flags.setExplainIssues(explainIssues)
- if (report) {
- if (textReport) {
+ if (report || flags.isFatalOnly() && this.abortOnError) {
+ if (textReport || flags.isFatalOnly()) {
File output = textOutput
if (output == null) {
output = new File(STDOUT)
@@ -418,24 +462,10 @@
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")
+ if (output == null || flags.isFatalOnly()) {
+ output = createOutputPath(project, variantName, ".html", flags.isFatalOnly())
} else if (!output.isAbsolute()) {
output = project.file(output.getPath())
}
@@ -446,12 +476,19 @@
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)
+ if (xmlReport) {
+ File output = xmlOutput
+ if (output == null || flags.isFatalOnly()) {
+ output = createOutputPath(project, variantName, DOT_XML, flags.isFatalOnly())
+ } 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)
+ }
}
}
}
@@ -488,41 +525,72 @@
private static File createOutputPath(
@NonNull Project project,
@NonNull String variantName,
- @NonNull String extension) {
+ @NonNull String extension,
+ boolean fatalOnly) {
StringBuilder base = new StringBuilder()
+ base.append(FD_OUTPUTS)
+ base.append(File.separator)
base.append("lint-results")
if (variantName != null) {
base.append("-")
base.append(variantName)
}
+ if (fatalOnly) {
+ base.append("-fatal")
+ }
base.append(extension)
return new File(project.buildDir, base.toString())
}
- // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
+ @Override
+ @Nullable
+ public Map<String, Integer> getSeverityOverrides() {
+ if (severities == null || severities.isEmpty()) {
+ return null
+ }
+
+ Map<String, Integer> map =
+ Maps.newHashMapWithExpectedSize(severities.size())
+ for (Map.Entry<String,Severity> entry : severities.entrySet()) {
+ map.put(entry.key, convert(entry.value))
+ }
+
+ return map
+ }
+
+ // -- DSL Methods.
public void check(String id) {
check.add(id)
}
public void check(String... ids) {
- check.addAll(ids)
+ for (String id : ids) {
+ check(id);
+ }
}
public void enable(String id) {
enable.add(id)
+ def issue = new BuiltinIssueRegistry().getIssue(id)
+ severities.put(id, issue != null ? issue.defaultSeverity : Severity.WARNING)
}
public void enable(String... ids) {
- enable.addAll(ids)
+ for (String id : ids) {
+ enable(id);
+ }
}
public void disable(String id) {
disable.add(id)
+ severities.put(id, Severity.IGNORE)
}
public void disable(String... ids) {
- disable.addAll(ids)
+ for (String id : ids) {
+ disable(id);
+ }
}
// For textOutput 'stdout' (normally a file)
@@ -534,4 +602,81 @@
void textOutput(File textOutput) {
this.textOutput = textOutput;
}
+
+ public void fatal(String id) {
+ severities.put(id, Severity.FATAL)
+ }
+
+ public void fatal(String... ids) {
+ for (String id : ids) {
+ fatal(id);
+ }
+ }
+
+ public void error(String id) {
+ severities.put(id, Severity.ERROR)
+ }
+
+ public void error(String... ids) {
+ for (String id : ids) {
+ error(id);
+ }
+ }
+
+ public void warning(String id) {
+ severities.put(id, Severity.WARNING)
+ }
+
+ public void warning(String... ids) {
+ for (String id : ids) {
+ warning(id);
+ }
+ }
+
+ public void ignore(String id) {
+ severities.put(id, Severity.IGNORE)
+ }
+
+ public void ignore(String... ids) {
+ for (String id : ids) {
+ ignore(id);
+ }
+ }
+
+ // Without these qualifiers, Groovy compilation will fail with "Apparent variable
+ // 'SEVERITY_FATAL' was found in a static scope but doesn't refer to a local variable,
+ // static field or class"
+ @SuppressWarnings("UnnecessaryQualifiedReference")
+ private static int convert(Severity s) {
+ switch (s) {
+ case Severity.FATAL:
+ return LintOptions.SEVERITY_FATAL
+ case Severity.ERROR:
+ return LintOptions.SEVERITY_ERROR
+ case Severity.WARNING:
+ return LintOptions.SEVERITY_WARNING
+ case Severity.INFORMATIONAL:
+ return LintOptions.SEVERITY_INFORMATIONAL
+ case Severity.IGNORE:
+ default:
+ return LintOptions.SEVERITY_IGNORE
+ }
+ }
+
+ @SuppressWarnings("UnnecessaryQualifiedReference")
+ private static Severity convert(int s) {
+ switch (s) {
+ case LintOptions.SEVERITY_FATAL:
+ return Severity.FATAL
+ case LintOptions.SEVERITY_ERROR:
+ return Severity.ERROR
+ case LintOptions.SEVERITY_WARNING:
+ return Severity.WARNING
+ case LintOptions.SEVERITY_INFORMATIONAL:
+ return Severity.INFORMATIONAL
+ case LintOptions.SEVERITY_IGNORE:
+ default:
+ return Severity.IGNORE
+ }
+ }
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/PackagingOptionsImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/PackagingOptionsImpl.java
new file mode 100644
index 0000000..1693044
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/PackagingOptionsImpl.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.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.PackagingOptions;
+import com.google.common.collect.Sets;
+import org.gradle.api.tasks.Input;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class PackagingOptionsImpl implements PackagingOptions {
+
+ private Set<String> excludes;
+ private Set<String> pickFirsts;
+
+ @Override
+ @NonNull
+ @Input
+ public Set<String> getExcludes() {
+ if (excludes == null) {
+ return Collections.emptySet();
+ }
+ return excludes;
+ }
+
+ public void exclude(String path) {
+ if (excludes == null) {
+ excludes = Sets.newHashSet();
+ }
+
+ excludes.add(path);
+ }
+
+ @Override
+ @NonNull
+ @Input
+ public Set<String> getPickFirsts() {
+ if (pickFirsts == null) {
+ return Collections.emptySet();
+ }
+ return pickFirsts;
+ }
+
+ public void pickFirst(String path) {
+ if (pickFirsts == null) {
+ pickFirsts = Sets.newHashSet();
+ }
+
+ pickFirsts.add(path);
+ }
+}
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
index 669fc8f..f10ad25 100644
--- 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
@@ -15,32 +15,40 @@
*/
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.build.gradle.BasePlugin
+import com.android.builder.core.AndroidBuilder
+import com.android.builder.core.BuilderConstants
+import com.android.builder.core.DefaultApiVersion
+import com.android.builder.core.DefaultProductFlavor
+import com.android.builder.model.ApiVersion
+import com.android.builder.model.ClassField
import com.android.builder.model.NdkConfig
+import com.android.builder.model.ProductFlavor
import org.gradle.api.Action
-import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.Project
+import org.gradle.api.logging.Logger
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
+ protected final Project project
+ @NonNull
+ protected final Logger logger
private final NdkConfigDsl ndkConfig
ProductFlavorDsl(@NonNull String name,
- @NonNull FileResolver fileResolver,
- @NonNull Instantiator instantiator) {
+ @NonNull Project project,
+ @NonNull Instantiator instantiator,
+ @NonNull Logger logger) {
super(name)
- this.fileResolver = fileResolver
-
+ this.project = project
+ this.logger = logger
ndkConfig = instantiator.newInstance(NdkConfigDsl.class)
}
@@ -50,24 +58,119 @@
return ndkConfig;
}
+ @NonNull
+ public ProductFlavor setMinSdkVersion(int minSdkVersion) {
+ setMinSdkVersion(new DefaultApiVersion(minSdkVersion));
+ return this;
+ }
+
+ @NonNull
+ public ProductFlavor minSdkVersion(int minSdkVersion) {
+ setMinSdkVersion(minSdkVersion);
+ return this;
+ }
+
+ @NonNull
+ public ProductFlavor setMinSdkVersion(String minSdkVersion) {
+ setMinSdkVersion(getApiVersion(minSdkVersion))
+ return this;
+ }
+
+ @NonNull
+ public ProductFlavor minSdkVersion(String minSdkVersion) {
+ setMinSdkVersion(minSdkVersion);
+ return this;
+ }
+
+ @NonNull
+ public ProductFlavor setTargetSdkVersion(int targetSdkVersion) {
+ setTargetSdkVersion(new DefaultApiVersion(targetSdkVersion));
+ return this;
+ }
+
+ @NonNull
+ public ProductFlavor targetSdkVersion(int targetSdkVersion) {
+ setTargetSdkVersion(targetSdkVersion);
+ return this;
+ }
+
+ @NonNull
+ public ProductFlavor setTargetSdkVersion(String targetSdkVersion) {
+ setTargetSdkVersion(getApiVersion(targetSdkVersion))
+ return this;
+ }
+
+ @NonNull
+ public ProductFlavor targetSdkVersion(String targetSdkVersion) {
+ setTargetSdkVersion(targetSdkVersion);
+ return this;
+ }
+
+ @Nullable
+ private static ApiVersion getApiVersion(@Nullable String value) {
+ if (value != null && !value.isEmpty()) {
+ if (Character.isDigit(value.charAt(0))) {
+ try {
+ int apiLevel = Integer.valueOf(value)
+ return new DefaultApiVersion(apiLevel)
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("'${value}' is not a valid API level. ", e)
+ }
+ }
+
+ return new DefaultApiVersion(value)
+ }
+
+ return null
+ }
+
// -- 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) {
+ ClassField alreadyPresent = getBuildConfigFields().get(name);
+ if (alreadyPresent != null) {
+ String flavorName = getName();
+ if (BuilderConstants.MAIN.equals(flavorName)) {
+ logger.info(
+ "DefaultConfig: buildConfigField '$name' value is being replaced: ${alreadyPresent.value} -> $value");
+ } else {
+ logger.info(
+ "ProductFlavor($flavorName): buildConfigField '$name' value is being replaced: ${alreadyPresent.value} -> $value");
+ }
+ }
addBuildConfigField(AndroidBuilder.createClassField(type, name, value));
}
+ public void resValue(
+ @NonNull String type,
+ @NonNull String name,
+ @NonNull String value) {
+ ClassField alreadyPresent = getResValues().get(name);
+ if (alreadyPresent != null) {
+ String flavorName = getName();
+ if (BuilderConstants.MAIN.equals(flavorName)) {
+ logger.info(
+ "DefaultConfig: resValue '$name' value is being replaced: ${alreadyPresent.value} -> $value");
+ } else {
+ logger.info(
+ "ProductFlavor($flavorName): resValue '$name' value is being replaced: ${alreadyPresent.value} -> $value");
+ }
+ }
+ addResValue(AndroidBuilder.createClassField(type, name, value));
+ }
+
@NonNull
public ProductFlavorDsl proguardFile(Object proguardFile) {
- proguardFiles.add(fileResolver.resolve(proguardFile))
+ proguardFiles.add(project.file(proguardFile))
return this
}
@NonNull
public ProductFlavorDsl proguardFiles(Object... proguardFileArray) {
- proguardFiles.addAll(fileResolver.resolveFiles(proguardFileArray).files)
+ proguardFiles.addAll(project.files(proguardFileArray).files)
return this
}
@@ -75,22 +178,22 @@
public ProductFlavorDsl setProguardFiles(Iterable<?> proguardFileIterable) {
proguardFiles.clear()
for (Object proguardFile : proguardFileIterable) {
- proguardFiles.add(fileResolver.resolve(proguardFile))
+ proguardFiles.add(project.file(proguardFile))
}
return this
}
@NonNull
public ProductFlavorDsl consumerProguardFiles(Object... proguardFileArray) {
- consumerProguardFiles.addAll(fileResolver.resolveFiles(proguardFileArray).files)
+ consumerProguardFiles.addAll(project.files(proguardFileArray).files)
return this
}
@NonNull
- public ProductFlavorDsl setconsumerProguardFiles(Iterable<?> proguardFileIterable) {
+ public ProductFlavorDsl setConsumerProguardFiles(Iterable<?> proguardFileIterable) {
consumerProguardFiles.clear()
for (Object proguardFile : proguardFileIterable) {
- consumerProguardFiles.add(fileResolver.resolve(proguardFile))
+ consumerProguardFiles.add(project.file(proguardFile))
}
return this
}
@@ -109,4 +212,43 @@
void resConfigs(@NonNull Collection<String> config) {
addResourceConfigurations(config);
}
+
+ // ---------------
+ // TEMP for compatibility
+ // STOPSHIP Remove in 1.0
+
+ /**
+ * Sets the package name.
+ *
+ * @param packageName the package name
+ * @return the flavor object
+ */
+ @NonNull
+ public ProductFlavor setPackageName(String packageName) {
+ BasePlugin.displayDeprecationWarning(logger, project, "\"packageName\" is deprecated (and will soon stop working); change to \"applicationId\" instead");
+ return setApplicationId(packageName);
+ }
+
+ @NonNull
+ public ProductFlavor packageName(String packageName) {
+ return setPackageName(packageName); // not setApplicationId: we want the warning message
+ }
+
+ @Nullable
+ public String getPackageName() {
+ BasePlugin.displayDeprecationWarning(logger, project, "\"packageName\" is deprecated (and will soon stop working); change to \"applicationId\" instead");
+ return getApplicationId();
+ }
+
+ @Nullable
+ public String getTestPackageName() {
+ BasePlugin.displayDeprecationWarning(logger, project, "\"testPackageName\" is deprecated (and will soon stop working); change to \"testApplicationId\" instead");
+ return getTestApplicationId();
+ }
+
+ @Nullable
+ public ProductFlavor setTestPackageName(String packageName) {
+ BasePlugin.displayDeprecationWarning(logger, project, "\"testPackageName\" is deprecated (and will soon stop working); change to \"testApplicationId\" instead");
+ return setTestApplicationId(packageName);
+ }
}
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
index 2960e18..b43a065 100644
--- 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
@@ -17,7 +17,7 @@
package com.android.build.gradle.internal.dsl;
import com.android.annotations.NonNull;
-import com.android.builder.BuilderConstants;
+import com.android.builder.core.BuilderConstants;
import com.android.builder.model.SigningConfig;
import com.android.builder.signing.DefaultSigningConfig;
import com.android.prefs.AndroidLocation;
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
index f27a055..0cf5dde 100644
--- 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
@@ -19,11 +19,13 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidArtifactOutput;
import com.android.builder.model.Dependencies;
import com.android.builder.model.SourceProvider;
import java.io.File;
import java.io.Serializable;
+import java.util.Collection;
import java.util.List;
/**
@@ -33,30 +35,27 @@
private static final long serialVersionUID = 1L;
@NonNull
- private final File outputFile;
+ private final Collection<AndroidArtifactOutput> outputs;
private final boolean isSigned;
@Nullable
private final String signingConfigName;
@NonNull
- private final String packageName;
+ private final String applicationId;
@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 Collection<AndroidArtifactOutput> outputs,
@NonNull String assembleTaskName,
- @NonNull File outputFile,
boolean isSigned,
@Nullable String signingConfigName,
- @NonNull String packageName,
+ @NonNull String applicationId,
@NonNull String sourceGenTaskName,
@NonNull String javaCompileTaskName,
- @NonNull File generatedManifest,
@NonNull List<File> generatedSourceFolders,
@NonNull List<File> generatedResourceFolders,
@NonNull File classesFolder,
@@ -66,20 +65,19 @@
super(name, assembleTaskName, javaCompileTaskName, classesFolder, dependencies,
variantSourceProvider, multiFlavorSourceProviders);
- this.outputFile = outputFile;
+ this.outputs = outputs;
this.isSigned = isSigned;
this.signingConfigName = signingConfigName;
- this.packageName = packageName;
+ this.applicationId = applicationId;
this.sourceGenTaskName = sourceGenTaskName;
- this.generatedManifest = generatedManifest;
this.generatedSourceFolders = generatedSourceFolders;
this.generatedResourceFolders = generatedResourceFolders;
}
@NonNull
@Override
- public File getOutputFile() {
- return outputFile;
+ public Collection<AndroidArtifactOutput> getOutputs() {
+ return outputs;
}
@Override
@@ -95,8 +93,8 @@
@NonNull
@Override
- public String getPackageName() {
- return packageName;
+ public String getApplicationId() {
+ return applicationId;
}
@NonNull
@@ -107,12 +105,6 @@
@NonNull
@Override
- public File getGeneratedManifest() {
- return generatedManifest;
- }
-
- @NonNull
- @Override
public List<File> getGeneratedSourceFolders() {
return generatedSourceFolders;
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactOutputImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactOutputImpl.java
new file mode 100644
index 0000000..1995f0d
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactOutputImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.AndroidArtifactOutput;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Implementation of AndroidArtifactOutput that is serializable
+ */
+public class AndroidArtifactOutputImpl implements AndroidArtifactOutput, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final File outputFile;
+ @NonNull
+ private final File generatedManifest;
+ @NonNull
+ private final String assembleTaskName;
+ private final int versionCode;
+ @Nullable
+ private final String densityFilter;
+ @Nullable
+ private final String abiFilter;
+
+ AndroidArtifactOutputImpl(
+ @NonNull File outputFile,
+ @NonNull String assembleTaskName,
+ @NonNull File generatedManifest,
+ int versionCode,
+ @Nullable String densityFilter,
+ @Nullable String abiFilter) {
+ this.outputFile = outputFile;
+ this.generatedManifest = generatedManifest;
+ this.assembleTaskName = assembleTaskName;
+ this.versionCode = versionCode;
+ this.densityFilter = densityFilter;
+ this.abiFilter = abiFilter;
+ }
+
+ @NonNull
+ @Override
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ @NonNull
+ @Override
+ public String getAssembleTaskName() {
+ return assembleTaskName;
+ }
+
+ @NonNull
+ @Override
+ public File getGeneratedManifest() {
+ return generatedManifest;
+ }
+
+ @Override
+ public int versionCode() {
+ return versionCode;
+ }
+
+ @Nullable
+ @Override
+ public String densityFilter() {
+ return densityFilter;
+ }
+
+ @Nullable
+ @Override
+ public String abiFilter() {
+ return abiFilter;
+ }
+}
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
index 8635ce8..eaa1914 100644
--- 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
@@ -20,6 +20,7 @@
import com.android.annotations.Nullable;
import com.android.builder.dependency.LibraryDependency;
import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.MavenCoordinates;
import java.io.File;
import java.io.Serializable;
@@ -31,6 +32,8 @@
@Nullable
private final String project;
+ @Nullable
+ private final String variant;
@NonNull
private final File bundle;
@NonNull
@@ -60,7 +63,8 @@
AndroidLibraryImpl(@NonNull LibraryDependency libraryDependency,
@NonNull List<AndroidLibrary> dependencies,
- @Nullable String project) {
+ @Nullable String project,
+ @Nullable String variant) {
this.dependencies = dependencies;
bundle = libraryDependency.getBundle();
folder = libraryDependency.getFolder();
@@ -76,6 +80,7 @@
lintJar = libraryDependency.getLintJar();
this.project = project;
+ this.variant = variant;
}
@Nullable
@@ -84,6 +89,12 @@
return project;
}
+ @Nullable
+ @Override
+ public String getProjectVariant() {
+ return variant;
+ }
+
@NonNull
@Override
public File getBundle() {
@@ -161,4 +172,17 @@
public File getLintJar() {
return lintJar;
}
+
+
+ @Nullable
+ @Override
+ public MavenCoordinates getRequestedCoordinates() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public MavenCoordinates getResolvedCoordinates() {
+ return null;
+ }
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ApiVersionImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ApiVersionImpl.java
new file mode 100644
index 0000000..7420b4a
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ApiVersionImpl.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ApiVersion;
+import com.android.sdklib.AndroidVersion;
+
+import java.io.Serializable;
+
+/**
+ * Implementation of ApiVersion that is serializable so that it can be used in the
+ * model returned by the Gradle plugin.
+ **/
+public class ApiVersionImpl implements ApiVersion, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final int mApiLevel;
+ @Nullable
+ private final String mCodename;
+
+ @Nullable
+ public static ApiVersion clone(@Nullable ApiVersion apiVersion) {
+ if (apiVersion == null) {
+ return null;
+ }
+
+ return new ApiVersionImpl(apiVersion);
+ }
+
+ public static ApiVersion clone(@NonNull AndroidVersion androidVersion) {
+ return new ApiVersionImpl(androidVersion.getApiLevel(), androidVersion.getCodename());
+ }
+
+ private ApiVersionImpl(@NonNull ApiVersion apiVersion) {
+ this(apiVersion.getApiLevel(), apiVersion.getCodename());
+ }
+
+ private ApiVersionImpl(int apiLevel, @Nullable String codename) {
+ mApiLevel = apiLevel;
+ mCodename = codename;
+ }
+
+ @Override
+ public int getApiLevel() {
+ return mApiLevel;
+ }
+
+ @Nullable
+ @Override
+ public String getCodename() {
+ return mCodename;
+ }
+
+ @NonNull
+ @Override
+ public String getApiString() {
+ return mCodename != null ? mCodename : Integer.toString(mApiLevel);
+ }
+}
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
index e3f5cdc..9a79983 100644
--- 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
@@ -26,6 +26,7 @@
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
/**
* Implementation of BuildType that is serializable. Objects used in the DSL cannot be
@@ -36,26 +37,30 @@
private String name;
private boolean debuggable;
+ private boolean testCoverageEnabled;
private boolean jniDebugBuild;
private boolean renderscriptDebugBuild;
private int renderscriptOptimLevel;
- private String packageNameSuffix;
+ private String applicationIdSuffix;
private String versionNameSuffix;
private boolean runProguard;
private boolean zipAlign;
+ private boolean embedMicroApp;
@NonNull
static BuildTypeImpl cloneBuildType(BuildType buildType) {
BuildTypeImpl clonedBuildType = new BuildTypeImpl();
clonedBuildType.name = buildType.getName();
clonedBuildType.debuggable = buildType.isDebuggable();
+ clonedBuildType.testCoverageEnabled = buildType.isTestCoverageEnabled();
clonedBuildType.jniDebugBuild = buildType.isJniDebugBuild();
clonedBuildType.renderscriptDebugBuild = buildType.isRenderscriptDebugBuild();
clonedBuildType.renderscriptOptimLevel = buildType.getRenderscriptOptimLevel();
- clonedBuildType.packageNameSuffix = buildType.getPackageNameSuffix();
+ clonedBuildType.applicationIdSuffix = buildType.getApplicationIdSuffix();
clonedBuildType.versionNameSuffix = buildType.getVersionNameSuffix();
clonedBuildType.runProguard = buildType.isRunProguard();
clonedBuildType.zipAlign = buildType.isZipAlign();
+ clonedBuildType.embedMicroApp = buildType.isEmbedMicroApp();
return clonedBuildType;
}
@@ -75,6 +80,11 @@
}
@Override
+ public boolean isTestCoverageEnabled() {
+ return testCoverageEnabled;
+ }
+
+ @Override
public boolean isJniDebugBuild() {
return jniDebugBuild;
}
@@ -91,8 +101,8 @@
@Nullable
@Override
- public String getPackageNameSuffix() {
- return packageNameSuffix;
+ public String getApplicationIdSuffix() {
+ return applicationIdSuffix;
}
@Nullable
@@ -113,8 +123,14 @@
@NonNull
@Override
- public List<ClassField> getBuildConfigFields() {
- return Collections.emptyList();
+ public Map<String, ClassField> getBuildConfigFields() {
+ return Collections.emptyMap();
+ }
+
+ @NonNull
+ @Override
+ public Map<String, ClassField> getResValues() {
+ return Collections.emptyMap();
}
@NonNull
@@ -134,4 +150,9 @@
public NdkConfig getNdkConfig() {
return null;
}
+
+ @Override
+ public boolean isEmbedMicroApp() {
+ return embedMicroApp;
+ }
}
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
index b8f7ec2..066d906 100644
--- 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
@@ -17,8 +17,8 @@
package com.android.build.gradle.internal.model;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
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;
@@ -60,6 +60,10 @@
private final JavaCompileOptions javaCompileOptions;
@NonNull
private final LintOptions lintOptions;
+ @NonNull
+ private final File buildFolder;
+ @Nullable
+ private final String resourcePrefix;
private final boolean isLibrary;
private final Collection<BuildTypeContainer> buildTypes = Lists.newArrayList();
@@ -78,6 +82,8 @@
@NonNull Collection<String> unresolvedDependencies,
@NonNull CompileOptions compileOptions,
@NonNull LintOptions lintOptions,
+ @NonNull File buildFolder,
+ @Nullable String resourcePrefix,
boolean isLibrary) {
this.modelVersion = modelVersion;
this.name = name;
@@ -89,6 +95,8 @@
this.unresolvedDependencies = unresolvedDependencies;
javaCompileOptions = new DefaultJavaCompileOptions(compileOptions);
this.lintOptions = lintOptions;
+ this.buildFolder = buildFolder;
+ this.resourcePrefix = resourcePrefix;
this.isLibrary = isLibrary;
}
@@ -211,4 +219,17 @@
public JavaCompileOptions getJavaCompileOptions() {
return javaCompileOptions;
}
+
+ @Override
+ @NonNull
+ public File getBuildFolder() {
+ return buildFolder;
+ }
+
+ @Override
+ @Nullable
+ public String getResourcePrefix() {
+ return resourcePrefix;
+ }
+
}
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
index fd8f5bf..6443aa9 100644
--- 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
@@ -18,13 +18,18 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.build.gradle.BasePlugin;
+import com.android.build.gradle.internal.dependency.ClassifiedJarDependency;
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.build.gradle.internal.variant.BaseVariantData;
import com.android.builder.dependency.JarDependency;
import com.android.builder.dependency.LibraryDependency;
import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
import com.google.common.collect.Lists;
+
import org.gradle.api.Project;
import java.io.File;
@@ -42,59 +47,82 @@
@NonNull
private final List<AndroidLibrary> libraries;
@NonNull
- private final List<File> jars;
+ private final List<JavaLibrary> javaLibraries;
@NonNull
private final List<String> projects;
@NonNull
+ static DependenciesImpl cloneDependenciesForJavaArtifacts(@NonNull Dependencies dependencies) {
+ List<AndroidLibrary> libraries = Collections.emptyList();
+ List<JavaLibrary> javaLibraries = Lists.newArrayList(dependencies.getJavaLibraries());
+ List<String> projects = Collections.emptyList();
+
+ return new DependenciesImpl(libraries, javaLibraries, projects);
+ }
+
+ @NonNull
static DependenciesImpl cloneDependencies(
- @Nullable VariantDependencies variantDependencies,
+ @NonNull BaseVariantData variantData,
+ @NonNull BasePlugin basePlugin,
@NonNull Set<Project> gradleProjects) {
+ VariantDependencies variantDependencies = variantData.getVariantDependency();
+
List<AndroidLibrary> libraries;
- List<File> jars;
+ List<JavaLibrary> javaLibraries;
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();
+ List<LibraryDependencyImpl> libs = variantDependencies.getLibraries();
+ libraries = Lists.newArrayListWithCapacity(libs.size());
+ for (LibraryDependencyImpl libImpl : libs) {
+ AndroidLibrary clonedLib = getAndroidLibrary(libImpl, gradleProjects);
+ libraries.add(clonedLib);
}
- return new DependenciesImpl(libraries, jars, projects);
+ List<JarDependency> jarDeps = variantDependencies.getJarDependencies();
+ List<JarDependency> localDeps = variantDependencies.getLocalDependencies();
+
+ javaLibraries = Lists.newArrayListWithExpectedSize(jarDeps.size() + localDeps.size());
+ projects = Lists.newArrayList();
+
+ for (JarDependency jarDep : jarDeps) {
+ boolean customArtifact = jarDep instanceof ClassifiedJarDependency &&
+ ((ClassifiedJarDependency)jarDep).getClassifier() != null;
+
+ File jarFile = jarDep.getJarFile();
+ Project projectMatch;
+ if (!customArtifact && (projectMatch = getProject(jarFile, gradleProjects)) != null) {
+ projects.add(projectMatch.getPath());
+ } else {
+ javaLibraries.add(new JavaLibraryImpl(jarFile));
+ }
+ }
+
+ for (JarDependency jarDep : localDeps) {
+ javaLibraries.add(new JavaLibraryImpl(jarDep.getJarFile()));
+ }
+
+ if (variantData.getVariantConfiguration().getMergedFlavor().getRenderscriptSupportMode()) {
+ File supportJar = basePlugin.getAndroidBuilder().getRenderScriptSupportJar();
+ if (supportJar != null) {
+ javaLibraries.add(new JavaLibraryImpl(supportJar));
+ }
+ }
+
+ return new DependenciesImpl(libraries, javaLibraries, projects);
+ }
+
+ public DependenciesImpl(@NonNull Set<JavaLibrary> javaLibraries) {
+ this.javaLibraries = Lists.newArrayList(javaLibraries);
+ this.libraries = Collections.emptyList();
+ this.projects = Collections.emptyList();
}
private DependenciesImpl(@NonNull List<AndroidLibrary> libraries,
- @NonNull List<File> jars,
+ @NonNull List<JavaLibrary> javaLibraries,
@NonNull List<String> projects) {
this.libraries = libraries;
- this.jars = jars;
+ this.javaLibraries = javaLibraries;
this.projects = projects;
}
@@ -106,8 +134,8 @@
@NonNull
@Override
- public List<File> getJars() {
- return jars;
+ public List<JavaLibrary> getJavaLibraries() {
+ return javaLibraries;
}
@NonNull
@@ -130,11 +158,12 @@
}
return new AndroidLibraryImpl(libImpl, clonedDeps,
- projectMatch != null ? projectMatch.getPath() : null);
+ projectMatch != null ? projectMatch.getPath() : null,
+ libImpl.getProjectVariant());
}
@Nullable
- private static Project getProject(File outputFile, Set<Project> gradleProjects) {
+ public 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) {
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
index 4c7584c..7c0d7fc 100644
--- 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
@@ -32,14 +32,17 @@
private static final long serialVersionUID = 1L;
public static JavaArtifactImpl clone(@NonNull JavaArtifact javaArtifact) {
+ SourceProvider variantSP = javaArtifact.getVariantSourceProvider();
+ SourceProvider flavorsSP = javaArtifact.getMultiFlavorSourceProvider();
+
return new JavaArtifactImpl(
javaArtifact.getName(),
javaArtifact.getAssembleTaskName(),
javaArtifact.getJavaCompileTaskName(),
javaArtifact.getClassesFolder(),
- javaArtifact.getDependencies(), // TODO:FixME
- SourceProviderImpl.cloneProvider(javaArtifact.getVariantSourceProvider()),
- SourceProviderImpl.cloneProvider(javaArtifact.getMultiFlavorSourceProvider()));
+ DependenciesImpl.cloneDependenciesForJavaArtifacts(javaArtifact.getDependencies()),
+ variantSP != null ? SourceProviderImpl.cloneProvider(variantSP) : null,
+ flavorsSP != null ? SourceProviderImpl.cloneProvider(flavorsSP) : null);
}
public JavaArtifactImpl(@NonNull String name,
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/JavaLibraryImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/JavaLibraryImpl.java
new file mode 100644
index 0000000..5314bb0
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/JavaLibraryImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.JavaLibrary;
+import com.android.builder.model.MavenCoordinates;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public class JavaLibraryImpl implements JavaLibrary, Serializable {
+ private final File jarFile;
+
+ public JavaLibraryImpl(@NonNull File jarFile) {
+ this.jarFile = jarFile;
+ }
+
+ @NonNull
+ @Override
+ public File getJarFile() {
+ return jarFile;
+ }
+
+ @NonNull
+ @Override
+ public List<? extends JavaLibrary> getDependencies() {
+ return Collections.emptyList();
+ }
+
+ @Nullable
+ @Override
+ public MavenCoordinates getRequestedCoordinates() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public MavenCoordinates getResolvedCoordinates() {
+ return null;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/MavenCoordinatesImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/MavenCoordinatesImpl.java
new file mode 100644
index 0000000..69d7a9e
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/MavenCoordinatesImpl.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.MavenCoordinates;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class MavenCoordinatesImpl implements MavenCoordinates, Serializable {
+ private final String groupId;
+ private final String artifactId;
+ private final String version;
+ private final String packaging;
+ private final String classifier;
+
+ public MavenCoordinatesImpl(String groupId, String artifactId, String version) {
+ this(groupId, artifactId, version, null, null);
+ }
+
+ public MavenCoordinatesImpl(String groupId, String artifactId, String version, String packaging,
+ String classifier) {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ this.version = version;
+ this.packaging = packaging != null ? packaging : SdkConstants.EXT_JAR;
+ this.classifier = classifier;
+ }
+
+ @NonNull
+ @Override
+ public String getGroupId() {
+ return groupId;
+ }
+
+ @NonNull
+ @Override
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ @NonNull
+ @Override
+ public String getVersion() {
+ return version;
+ }
+
+ @NonNull
+ @Override
+ public String getPackaging() {
+ return packaging;
+ }
+
+ @Nullable
+ @Override
+ public String getClassifier() {
+ return classifier;
+ }
+
+ @Override
+ public String toString() {
+ List<String> segments = Lists.newArrayList(groupId, artifactId, packaging);
+ if (!Strings.isNullOrEmpty(classifier)) {
+ segments.add(classifier);
+ }
+ segments.add(version);
+ return Joiner.on(':').join(segments);
+ }
+}
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
index e04678a..1ec3847 100644
--- 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
@@ -27,17 +27,20 @@
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.core.DefaultProductFlavor
+import com.android.builder.core.VariantConfiguration
import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidArtifactOutput
import com.android.builder.model.AndroidProject
+import com.android.builder.model.ApiVersion
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.android.sdklib.AndroidVersion
+import com.android.sdklib.IAndroidTarget
import com.google.common.collect.Lists
import org.gradle.api.Project
import org.gradle.api.plugins.UnknownPluginException
@@ -46,9 +49,8 @@
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_ANDROID_TEST
import static com.android.builder.model.AndroidProject.ARTIFACT_MAIN
-
/**
* Builder for the custom Android model.
*/
@@ -69,8 +71,6 @@
if (appPlugin == null) {
basePlugin = libPlugin = getPlugin(project, LibraryPlugin.class)
- } else {
- signingConfigs = appPlugin.extension.signingConfigs
}
if (basePlugin == null) {
@@ -78,21 +78,19 @@
return null
}
- if (libPlugin != null) {
- signingConfigs = Collections.singletonList(libPlugin.extension.debugSigningConfig)
- }
+ signingConfigs = basePlugin.extension.signingConfigs
- SdkParser sdkParser = basePlugin.getLoadedSdkParser()
- List<String> bootClasspath = basePlugin.runtimeJarList
+ // get the boot classpath. This will ensure the target is configured.
+ List<String> bootClasspath = basePlugin.bootClasspath
+
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,
+ ARTIFACT_ANDROID_TEST,
true /*isTest*/,
ArtifactMetaData.TYPE_ANDROID));
@@ -102,7 +100,7 @@
DefaultAndroidProject androidProject = new DefaultAndroidProject(
getModelVersion(),
project.name,
- compileTarget,
+ basePlugin.getAndroidBuilder().getTarget().hashString(),
bootClasspath,
frameworkSource,
cloneSigningConfigs(signingConfigs),
@@ -110,30 +108,22 @@
basePlugin.unresolvedDependencies,
basePlugin.extension.compileOptions,
lintOptions,
+ project.getBuildDir(),
+ basePlugin.extension.resourcePrefix,
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) {
+ for (BuildTypeData btData : basePlugin.variantManager.buildTypes.values()) {
androidProject.addBuildType(BuildTypeContainerImpl.createBTC(
- libPlugin.debugBuildTypeData,
- basePlugin.getExtraBuildTypeSourceProviders(libPlugin.debugBuildTypeData.buildType.name)))
- .addBuildType(BuildTypeContainerImpl.createBTC(
- libPlugin.releaseBuildTypeData,
- basePlugin.getExtraBuildTypeSourceProviders(libPlugin.releaseBuildTypeData.buildType.name)))
+ btData,
+ basePlugin.getExtraBuildTypeSourceProviders(btData.buildType.name)))
+ }
+ for (ProductFlavorData pfData : basePlugin.variantManager.productFlavors.values()) {
+ androidProject.addProductFlavors(ProductFlavorContainerImpl.createPFC(
+ pfData,
+ basePlugin.getExtraFlavorSourceProviders(pfData.productFlavor.name)))
}
Set<Project> gradleProjects = project.getRootProject().getAllprojects();
@@ -185,7 +175,7 @@
// extra Android Artifacts
AndroidArtifact testArtifact = testVariantData != null ?
- createArtifactInfo(ARTIFACT_INSTRUMENT_TEST, testVariantData, basePlugin, gradleProjects) : null
+ createArtifactInfo(ARTIFACT_ANDROID_TEST, testVariantData, basePlugin, gradleProjects) : null
List<AndroidArtifact> extraAndroidArtifacts = Lists.newArrayList(
basePlugin.getExtraAndroidArtifacts(variantName))
@@ -193,19 +183,33 @@
extraAndroidArtifacts.add(testArtifact)
}
- // extra Java Artifacts
- List<JavaArtifact> extraJavaArtifacts = Lists.newArrayList(
- basePlugin.getExtraJavaArtifacts(variantName))
+ // clone the Java Artifacts
+ Collection<JavaArtifact> javaArtifacts = basePlugin.getExtraJavaArtifacts(variantName)
+ List<JavaArtifact> clonedJavaArtifacts = Lists.newArrayListWithCapacity(javaArtifacts.size())
+ for (JavaArtifact javaArtifact : javaArtifacts) {
+ clonedJavaArtifacts.add(JavaArtifactImpl.clone(javaArtifact))
+ }
+
+ // if the target is a codename, override the model value.
+ ApiVersion sdkVersionOverride = null
+ IAndroidTarget androidTarget = basePlugin.androidBuilder.getTargetInfo().target
+ AndroidVersion version = androidTarget.getVersion()
+ if (version.codename != null) {
+ sdkVersionOverride = ApiVersionImpl.clone(version)
+ }
VariantImpl variant = new VariantImpl(
variantName,
variantData.variantConfiguration.baseName,
variantData.variantConfiguration.buildType.name,
getProductFlavorNames(variantData),
- ProductFlavorImpl.cloneFlavor(variantData.variantConfiguration.mergedFlavor),
+ ProductFlavorImpl.cloneFlavor(
+ variantData.variantConfiguration.mergedFlavor,
+ sdkVersionOverride,
+ sdkVersionOverride),
mainArtifact,
extraAndroidArtifacts,
- extraJavaArtifacts)
+ clonedJavaArtifacts)
return variant
}
@@ -241,20 +245,29 @@
variantSourceProvider = variantSourceProvider != null ? SourceProviderImpl.cloneProvider(variantSourceProvider) : null
multiFlavorSourceProvider = multiFlavorSourceProvider != null ? SourceProviderImpl.cloneProvider(multiFlavorSourceProvider) : null
+ // create a single output for now
+ AndroidArtifactOutput output = new AndroidArtifactOutputImpl(
+ variantData.outputFile,
+ variantData.assembleTask.name,
+ variantData.manifestProcessorTask.manifestOutputFile,
+ vC.mergedFlavor.versionCode,
+ null, /*densityFilter*/
+ null /*abiFilter*/
+ );
+
return new AndroidArtifactImpl(
name,
+ Collections.singletonList(output),
variantData.assembleTask.name,
- variantData.outputFile,
vC.isSigningReady(),
signingConfigName,
- vC.packageName,
+ vC.applicationId,
variantData.sourceGenTask.name,
variantData.javaCompileTask.name,
- variantData.processManifestTask.manifestOutputFile,
getGeneratedSourceFolders(variantData),
getGeneratedResourceFolders(variantData),
variantData.javaCompileTask.destinationDir,
- DependenciesImpl.cloneDependencies(variantData.variantDependency, gradleProjects),
+ DependenciesImpl.cloneDependencies(variantData, basePlugin, gradleProjects),
variantSourceProvider,
multiFlavorSourceProvider)
}
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
index 92fa794..344dd4f 100644
--- 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
@@ -16,9 +16,10 @@
package com.android.build.gradle.internal.model;
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST;
+
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;
@@ -57,12 +58,12 @@
// instrument test Source Provider
SourceProviderContainer testASP = SourceProviderContainerImpl.create(
- AndroidProject.ARTIFACT_INSTRUMENT_TEST, productFlavorData.getTestSourceSet());
+ ARTIFACT_ANDROID_TEST, productFlavorData.getTestSourceSet());
clonedContainer.add(testASP);
return new ProductFlavorContainerImpl(
- ProductFlavorImpl.cloneFlavor(productFlavorData.getProductFlavor()),
+ ProductFlavorImpl.cloneFlavor(productFlavorData.getProductFlavor(), null, null),
SourceProviderImpl.cloneProvider(productFlavorData.getSourceSet()),
clonedContainer);
}
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
index 9f25e30..65b6234 100644
--- 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
@@ -18,9 +18,11 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.builder.model.ApiVersion;
import com.android.builder.model.ClassField;
import com.android.builder.model.NdkConfig;
import com.android.builder.model.ProductFlavor;
+import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.io.File;
@@ -28,6 +30,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -38,27 +41,35 @@
private static final long serialVersionUID = 1L;
private String name = null;
- private int mMinSdkVersion = -1;
- private int mTargetSdkVersion = -1;
+ private ApiVersion mMinSdkVersion = null;
+ private ApiVersion mTargetSdkVersion = null;
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 mApplicationId = null;
+ private String mTestApplicationId = null;
private String mTestInstrumentationRunner = null;
private Boolean mTestHandleProfiling = null;
private Boolean mTestFunctionalTest = null;
private Set<String> mResourceConfigurations = null;
+ private Map<String, String> mManifestPlaceholders = null;
@NonNull
- static ProductFlavorImpl cloneFlavor(ProductFlavor productFlavor) {
+ static ProductFlavorImpl cloneFlavor(
+ @NonNull ProductFlavor productFlavor,
+ @Nullable ApiVersion minSdkVersionOverride,
+ @Nullable ApiVersion targetSdkVersionOverride) {
ProductFlavorImpl clonedFlavor = new ProductFlavorImpl();
clonedFlavor.name = productFlavor.getName();
- clonedFlavor.mMinSdkVersion = productFlavor.getMinSdkVersion();
- clonedFlavor.mTargetSdkVersion = productFlavor.getTargetSdkVersion();
+ clonedFlavor.mMinSdkVersion = minSdkVersionOverride != null ?
+ minSdkVersionOverride :
+ ApiVersionImpl.clone(productFlavor.getMinSdkVersion());
+ clonedFlavor.mTargetSdkVersion = targetSdkVersionOverride != null ?
+ targetSdkVersionOverride :
+ ApiVersionImpl.clone(productFlavor.getTargetSdkVersion());
clonedFlavor.mRenderscriptTargetApi = productFlavor.getRenderscriptTargetApi();
clonedFlavor.mRenderscriptSupportMode = productFlavor.getRenderscriptSupportMode();
clonedFlavor.mRenderscriptNdkMode = productFlavor.getRenderscriptNdkMode();
@@ -66,9 +77,9 @@
clonedFlavor.mVersionCode = productFlavor.getVersionCode();
clonedFlavor.mVersionName = productFlavor.getVersionName();
- clonedFlavor.mPackageName = productFlavor.getPackageName();
+ clonedFlavor.mApplicationId = productFlavor.getApplicationId();
- clonedFlavor.mTestPackageName = productFlavor.getTestPackageName();
+ clonedFlavor.mTestApplicationId = productFlavor.getTestApplicationId();
clonedFlavor.mTestInstrumentationRunner = productFlavor.getTestInstrumentationRunner();
clonedFlavor.mTestHandleProfiling = productFlavor.getTestHandleProfiling();
clonedFlavor.mTestFunctionalTest = productFlavor.getTestFunctionalTest();
@@ -76,22 +87,25 @@
clonedFlavor.mResourceConfigurations = Sets.newHashSet(
productFlavor.getResourceConfigurations());
+ clonedFlavor.mManifestPlaceholders = Maps.newHashMap(
+ productFlavor.getManifestPlaceholders());
+
return clonedFlavor;
}
private ProductFlavorImpl() {
}
- @NonNull
@Override
+ @NonNull
public String getName() {
return name;
}
- @Nullable
@Override
- public String getPackageName() {
- return mPackageName;
+ @Nullable
+ public String getApplicationId() {
+ return mApplicationId;
}
@Override
@@ -99,19 +113,21 @@
return mVersionCode;
}
- @Nullable
@Override
+ @Nullable
public String getVersionName() {
return mVersionName;
}
@Override
- public int getMinSdkVersion() {
+ @Nullable
+ public ApiVersion getMinSdkVersion() {
return mMinSdkVersion;
}
@Override
- public int getTargetSdkVersion() {
+ @Nullable
+ public ApiVersion getTargetSdkVersion() {
return mTargetSdkVersion;
}
@@ -132,8 +148,8 @@
@Nullable
@Override
- public String getTestPackageName() {
- return mTestPackageName;
+ public String getTestApplicationId() {
+ return mTestApplicationId;
}
@Nullable
@@ -154,11 +170,16 @@
return mTestFunctionalTest;
}
+ @NonNull
+ @Override
+ public Map<String, ClassField> getBuildConfigFields() {
+ return Collections.emptyMap();
+ }
@NonNull
@Override
- public List<ClassField> getBuildConfigFields() {
- return Collections.emptyList();
+ public Map<String, ClassField> getResValues() {
+ return Collections.emptyMap();
}
@NonNull
@@ -185,6 +206,12 @@
return mResourceConfigurations;
}
+ @NonNull
+ @Override
+ public Map<String, String> getManifestPlaceholders() {
+ return mManifestPlaceholders;
+ }
+
@Override
public String toString() {
return "ProductFlavorImpl{" +
@@ -196,12 +223,13 @@
", mRenderscriptNdkMode=" + mRenderscriptNdkMode +
", mVersionCode=" + mVersionCode +
", mVersionName='" + mVersionName + '\'' +
- ", mPackageName='" + mPackageName + '\'' +
- ", mTestPackageName='" + mTestPackageName + '\'' +
+ ", mApplicationId='" + mApplicationId + '\'' +
+ ", mTestApplicationId='" + mTestApplicationId + '\'' +
", mTestInstrumentationRunner='" + mTestInstrumentationRunner + '\'' +
", mTestHandleProfiling='" + mTestHandleProfiling + '\'' +
", mTestFunctionalTest='" + mTestFunctionalTest + '\'' +
", mResourceConfigurations='" + mResourceConfigurations + '\'' +
+ ", mManifestPlaceholders='" + mManifestPlaceholders + '\'' +
'}';
}
}
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
index de02737..42d2623 100644
--- 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
@@ -31,6 +31,7 @@
class SourceProviderImpl implements SourceProvider, Serializable {
private static final long serialVersionUID = 1L;
+ private String name;
private File manifestFile;
private Collection<File> javaDirs;
private Collection<File> resourcesDirs;
@@ -39,11 +40,13 @@
private Collection<File> jniDirs;
private Collection<File> resDirs;
private Collection<File> assetsDirs;
+ private Collection<File> libsDirs;
@NonNull
- static SourceProviderImpl cloneProvider(SourceProvider sourceProvider) {
+ static SourceProviderImpl cloneProvider(@NonNull SourceProvider sourceProvider) {
SourceProviderImpl sourceProviderClone = new SourceProviderImpl();
+ sourceProviderClone.name = sourceProvider.getName();
sourceProviderClone.manifestFile = sourceProvider.getManifestFile();
sourceProviderClone.javaDirs = sourceProvider.getJavaDirectories();
sourceProviderClone.resourcesDirs = sourceProvider.getResourcesDirectories();
@@ -52,6 +55,7 @@
sourceProviderClone.jniDirs = sourceProvider.getJniDirectories();
sourceProviderClone.resDirs = sourceProvider.getResDirectories();
sourceProviderClone.assetsDirs = sourceProvider.getAssetsDirectories();
+ sourceProviderClone.libsDirs = sourceProvider.getJniLibsDirectories();
return sourceProviderClone;
}
@@ -72,6 +76,12 @@
@NonNull
@Override
+ public String getName() {
+ return name;
+ }
+
+ @NonNull
+ @Override
public File getManifestFile() {
return manifestFile;
}
@@ -118,6 +128,12 @@
return assetsDirs;
}
+ @NonNull
+ @Override
+ public Collection<File> getJniLibsDirectories() {
+ return libsDirs;
+ }
+
@Override
public String toString() {
return "SourceProviderImpl{" +
@@ -129,6 +145,7 @@
", jniDirs=" + jniDirs +
", resDirs=" + resDirs +
", assetsDirs=" + assetsDirs +
+ ", libsDirs=" + libsDirs +
'}';
}
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/publishing/ApkPublishArtifact.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/publishing/ApkPublishArtifact.java
new file mode 100644
index 0000000..13bdf33
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/publishing/ApkPublishArtifact.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.publishing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.tasks.OutputFileTask;
+
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Set;
+
+/**
+ * custom implementation of PublishArtifact for published APKs.
+ */
+public class ApkPublishArtifact implements PublishArtifact {
+
+ @NonNull
+ private final String name;
+
+ @Nullable
+ private final String classifier;
+
+ @NonNull
+ private final OutputFileTask task;
+ @NonNull
+ private final TaskDependency taskDependency;
+
+ private static final class DefaultTaskDependency implements TaskDependency {
+
+ @NonNull
+ private final Set<Task> tasks;
+
+ DefaultTaskDependency(@NonNull Task task) {
+ this.tasks = Collections.singleton(task);
+ }
+
+ @Override
+ public Set<? extends Task> getDependencies(Task task) {
+ return tasks;
+ }
+ }
+
+ public ApkPublishArtifact(
+ @NonNull String name,
+ @Nullable String classifier,
+ @NonNull OutputFileTask task) {
+ this.name = name;
+ this.classifier = classifier;
+ this.task = task;
+ this.taskDependency = new DefaultTaskDependency((Task) task);
+ }
+
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getExtension() {
+ return "apk";
+ }
+
+ @Override
+ public String getType() {
+ return "apk";
+ }
+
+ @Override
+ public String getClassifier() {
+ return classifier;
+ }
+
+ @Override
+ public File getFile() {
+ return task.getOutputFile();
+ }
+
+ @Override
+ public Date getDate() {
+ return null;
+ }
+
+ @Override
+ public TaskDependency getBuildDependencies() {
+ return taskDependency;
+ }
+}
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
index 41c3794..69ebb96 100644
--- 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
@@ -14,38 +14,20 @@
* 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 com.android.builder.core.AndroidBuilder
import org.gradle.api.DefaultTask
public abstract class BaseTask extends DefaultTask {
BasePlugin plugin
- BaseVariantData variant
protected AndroidBuilder getBuilder() {
- return plugin.getAndroidBuilder(variant)
+ return plugin.getAndroidBuilder()
}
protected static void emptyFolder(File folder) {
- deleteFolder(folder)
+ folder.deleteDir()
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/CheckManifest.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/CheckManifest.groovy
new file mode 100644
index 0000000..ba070ca
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/CheckManifest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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
+
+/**
+ * Class that checks the presence of the manifest.
+ */
+public class CheckManifest extends DefaultTask {
+
+ @InputFile
+ File manifest
+
+ String variantName
+
+ @TaskAction
+ void check() {
+ // use getter to resolve convention mapping
+ File f = getManifest()
+ if (!f.isFile()) {
+ throw new IllegalArgumentException(
+ "Main Manifest missing for variant ${getVariantName()}. Expected path: ${f.getAbsolutePath()}");
+ }
+ }
+}
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
deleted file mode 100644
index f264bc9..0000000
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyBasedCompileTask.groovy
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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/DeviceProviderInstrumentTestTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.groovy
index fa22b5e..4ebc2b1 100644
--- 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
@@ -16,6 +16,7 @@
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.BaseVariantData
import com.android.build.gradle.internal.variant.TestVariantData
import com.android.builder.testing.SimpleTestRunner
import com.android.builder.testing.TestRunner
@@ -33,10 +34,12 @@
File reportsDir
File resultsDir
+ File coverageDir
String flavorName
DeviceProvider deviceProvider
+ BaseVariantData variant
boolean ignoreFailures
boolean testFailed
@@ -46,10 +49,11 @@
assert variant instanceof TestVariantData
File resultsOutDir = getResultsDir()
-
- // empty the folder.
emptyFolder(resultsOutDir)
+ File coverageOutDir = getCoverageDir()
+ emptyFolder(coverageOutDir)
+
File testApk = getTestApp()
File testedApk = getTestedApp()
@@ -65,7 +69,7 @@
deviceProvider.devices,
deviceProvider.getMaxThreads(),
deviceProvider.getTimeout(),
- resultsOutDir, plugin.logger);
+ resultsOutDir, coverageOutDir, plugin.logger);
} finally {
deviceProvider.terminate();
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/GenerateApkDataTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/GenerateApkDataTask.groovy
new file mode 100644
index 0000000..59a7bec
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/GenerateApkDataTask.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.core.AndroidBuilder
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Task to generate micro app data res file.
+ */
+public class GenerateApkDataTask extends BaseTask {
+
+ @InputFile
+ File apkFile
+
+ @OutputDirectory
+ File resOutputDir
+
+ @OutputFile
+ File manifestFile
+
+ @Input
+ String mainPkgName
+
+ @TaskAction
+ void generate() {
+ // always empty output dir.
+ File outDir = getResOutputDir()
+ emptyFolder(outDir)
+
+ AndroidBuilder builder = getBuilder();
+
+ builder.generateApkData(getApkFile(), outDir, getMainPkgName())
+ builder.generateApkDataEntryInManifest(getManifestFile())
+ }
+}
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
index 1fa520b7..c3b1413 100644
--- 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
@@ -16,10 +16,14 @@
package com.android.build.gradle.internal.tasks
import com.android.build.gradle.internal.dependency.DependencyChecker
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.builder.model.ApiVersion
+import com.android.ide.common.sdk.SdkVersionInfo
import com.android.utils.Pair
import org.gradle.api.tasks.TaskAction
public class PrepareDependenciesTask extends BaseTask {
+ BaseVariantData variant
final List<DependencyChecker> checkers = []
final Set<Pair<Integer, String>> androidDependencies = []
@@ -29,14 +33,22 @@
@TaskAction
protected void prepare() {
- def minSdkVersion = variant.variantConfiguration.minSdkVersion
+ ApiVersion minSdkVersion = variant.variantConfiguration.minSdkVersion
+ int minSdk = 1
+ if (minSdkVersion != null) {
+ if (minSdkVersion.getCodename() != null) {
+ minSdk = SdkVersionInfo.getApiByBuildCode(minSdkVersion.getCodename(), true)
+ } else {
+ minSdk = minSdkVersion.getApiLevel()
+ }
+ }
for (DependencyChecker checker : checkers) {
for (Integer api : checker.foundAndroidApis) {
- if (api > minSdkVersion) {
+ if (api > minSdk) {
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))
+ checker.configurationDependencies.name.capitalize(), api, variant.name, minSdk))
}
}
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
index e490a93..c849361 100644
--- 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
@@ -15,6 +15,7 @@
*/
package com.android.build.gradle.internal.tasks
+import com.android.build.gradle.internal.LibraryCache
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputDirectory
@@ -29,9 +30,6 @@
@TaskAction
def prepare() {
- project.copy {
- from project.zipTree(bundle)
- into explodedDir
- }
+ LibraryCache.getCache().unzipLibrary(this.name, project, getBundle(), getExplodedDir())
}
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareSdkTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareSdkTask.groovy
new file mode 100644
index 0000000..dad379b
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareSdkTask.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+/**
+ * Basic task loading the SDK data.
+ */
+class PrepareSdkTask extends DefaultTask {
+
+ BasePlugin plugin
+
+ @TaskAction
+ void prepareSdk() {
+ plugin.ensureTargetSetup()
+ }
+}
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
index 9d7c170..0b1eed89 100644
--- 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
@@ -19,9 +19,9 @@
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.android.ide.common.signing.CertificateInfo
+import com.android.ide.common.signing.KeystoreHelper
+import com.android.ide.common.signing.KeytoolException
import com.google.common.collect.Maps
import org.gradle.api.tasks.TaskAction
import org.gradle.logging.StyledTextOutput
@@ -126,7 +126,9 @@
if (signingConfig.isSigningReady()) {
try {
CertificateInfo certificateInfo = KeystoreHelper.getCertificateInfo(
- signingConfig)
+ signingConfig.getStoreType(), signingConfig.getStoreFile(),
+ signingConfig.getStorePassword(), signingConfig.getKeyPassword(),
+ signingConfig.getKeyAlias())
if (certificateInfo != null) {
signingInfo.md5 = getFingerprint(certificateInfo.certificate, "MD5")
signingInfo.sha1 = getFingerprint(certificateInfo.certificate, "SHA1")
@@ -194,4 +196,4 @@
}
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/UninstallTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.groovy
index 96892a0..524ae78 100644
--- 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
@@ -15,6 +15,7 @@
*/
package com.android.build.gradle.internal.tasks
+import com.android.build.gradle.internal.variant.BaseVariantData
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
@@ -22,6 +23,8 @@
@InputFile
File adbExe
+ BaseVariantData variant
+
@TaskAction
public void uninstall() {
String packageName = variant.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
index cfbd2f3..1b4bdd7 100644
--- 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
@@ -17,7 +17,7 @@
package com.android.build.gradle.internal.tasks
import com.android.builder.model.SigningConfig
-import com.android.builder.signing.KeystoreHelper
+import com.android.ide.common.signing.KeystoreHelper
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
@@ -58,7 +58,9 @@
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())) {
+ if (!KeystoreHelper.createDebugStore(signingConfig.getStoreType(),
+ signingConfig.getStoreFile(), signingConfig.getStorePassword(),
+ signingConfig.getKeyPassword(), signingConfig.getKeyAlias(), 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/variant/ApkVariantData.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java
index 9c1f580..f857ad6 100644
--- 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
@@ -19,7 +19,7 @@
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 com.android.builder.core.VariantConfiguration;
import org.gradle.api.DefaultTask;
/**
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
index 91c4d08..d2df580 100644
--- 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
@@ -17,7 +17,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.builder.VariantConfiguration;
+import com.android.builder.core.VariantConfiguration;
/**
* Data about a variant that produce an application APK
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantFactory.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantFactory.java
new file mode 100644
index 0000000..b5bdfc6
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantFactory.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.build.gradle.BasePlugin;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.internal.api.ApplicationVariantImpl;
+import com.android.builder.core.VariantConfiguration;
+
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ */
+public class ApplicationVariantFactory implements VariantFactory {
+
+ public static final String CONFIG_WEAR_APP = "wearApp";
+
+
+ @NonNull
+ private final BasePlugin basePlugin;
+
+ public ApplicationVariantFactory(@NonNull BasePlugin basePlugin) {
+ this.basePlugin = basePlugin;
+ }
+
+ @Override
+ @NonNull
+ public BaseVariantData createVariantData(@NonNull VariantConfiguration variantConfiguration) {
+ return new ApplicationVariantData(variantConfiguration);
+ }
+
+ @Override
+ @NonNull
+ public BaseVariant createVariantApi(@NonNull BaseVariantData variantData) {
+ return basePlugin.getInstantiator().newInstance(ApplicationVariantImpl.class, variantData, basePlugin);
+ }
+
+ @NonNull
+ @Override
+ public VariantConfiguration.Type getVariantConfigurationType() {
+ return VariantConfiguration.Type.DEFAULT;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return false;
+ }
+
+ /**
+ * Creates the tasks for a given ApplicationVariantData.
+ * @param variantData the non-null ApplicationVariantData.
+ * @param assembleTask an optional assembleTask to be used. If null, a new one is created.
+ */
+ @Override
+ public void createTasks(
+ @NonNull BaseVariantData variantData,
+ @Nullable Task assembleTask) {
+
+ assert variantData instanceof ApplicationVariantData;
+ ApplicationVariantData appVariantData = (ApplicationVariantData) variantData;
+
+ basePlugin.createAnchorTasks(variantData);
+ basePlugin.createCheckManifestTask(variantData);
+
+ handleMicroApp(variantData);
+
+ // Add a task to process the manifest(s)
+ basePlugin.createMergeManifestsTask(variantData, "manifests");
+
+ // Add a task to create the res values
+ basePlugin.createGenerateResValuesTask(variantData);
+
+ // Add a task to compile renderscript files.
+ basePlugin.createRenderscriptTask(variantData);
+
+ // Add a task to merge the resource folders
+ basePlugin.createMergeResourcesTask(variantData, true /*process9Patch*/);
+
+ // Add a task to merge the asset folders
+ basePlugin.createMergeAssetsTask(variantData, null /*default location*/, true /*includeDependencies*/);
+
+ // Add a task to create the BuildConfig class
+ basePlugin.createBuildConfigTask(variantData);
+
+ // Add a task to generate resource source files
+ basePlugin.createProcessResTask(variantData, true /*generateResourcePackage*/);
+
+ // Add a task to process the java resources
+ basePlugin.createProcessJavaResTask(variantData);
+
+ basePlugin.createAidlTask(variantData, null /*parcelableDir*/);
+
+ // Add a compile task
+ basePlugin.createCompileTask(variantData, null/*testedVariant*/);
+
+ // Add NDK tasks
+ basePlugin.createNdkTasks(variantData);
+
+ basePlugin.addPackageTasks(appVariantData, assembleTask, true /*publishApk*/);
+ }
+
+ private void handleMicroApp(@NonNull BaseVariantData variantData) {
+
+ Configuration config = basePlugin.getProject().getConfigurations().findByName(
+ CONFIG_WEAR_APP);
+ Set<File> file = config.getFiles();
+
+ int count = file.size();
+ if (count == 1) {
+ if (variantData.getVariantConfiguration().getBuildType().isEmbedMicroApp()) {
+ basePlugin.createCopyMicroApkTask(variantData, config);
+ basePlugin.createGenerateMicroApkDataTask(variantData, config);
+ }
+ } else if (count > 1) {
+ throw new RuntimeException(
+ CONFIG_WEAR_APP + " configuration resolves to more than one apk.");
+ }
+ }
+}
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
index 83794e8..883b361 100644
--- 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
@@ -18,30 +18,36 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
+import com.android.build.gradle.api.AndroidSourceSet;
import com.android.build.gradle.internal.StringHelper;
import com.android.build.gradle.internal.dependency.VariantDependencies;
+import com.android.build.gradle.internal.tasks.CheckManifest;
+import com.android.build.gradle.internal.tasks.GenerateApkDataTask;
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.GenerateResValues;
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.ManifestProcessorTask;
import com.android.build.gradle.tasks.RenderscriptCompile;
-import com.android.builder.VariantConfiguration;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.model.SourceProvider;
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;
+import groovy.lang.Closure;
+
/**
* Base data about a variant.
*/
@@ -53,21 +59,28 @@
public Task preBuildTask;
public PrepareDependenciesTask prepareDependenciesTask;
public Task sourceGenTask;
+ public Task resourceGenTask;
+ public Task assetGenTask;
+ public CheckManifest checkManifestTask;
- public ProcessManifest processManifestTask;
+ public ManifestProcessorTask manifestProcessorTask;
public RenderscriptCompile renderscriptCompileTask;
public AidlCompile aidlCompileTask;
public MergeResources mergeResourcesTask;
public MergeAssets mergeAssetsTask;
public ProcessAndroidResources processResourcesTask;
public GenerateBuildConfig generateBuildConfigTask;
+ public GenerateResValues generateResValuesTask;
+ public Copy copyApkTask;
+ public GenerateApkDataTask generateApkDataTask;
public JavaCompile javaCompileTask;
- public ProGuardTask proguardTask;
+ public Task obfuscationTask;
public Copy processJavaResourcesTask;
public NdkCompile ndkCompileTask;
private Object outputFile;
+ private Object[] javaSources;
public Task assembleTask;
@@ -75,6 +88,7 @@
public BaseVariantData(@NonNull VariantConfiguration variantConfiguration) {
this.variantConfiguration = variantConfiguration;
+ variantConfiguration.checkSourceProviders();
}
@NonNull
@@ -94,9 +108,9 @@
@NonNull
public abstract String getDescription();
- @Nullable
+ @NonNull
public String getPackageName() {
- return variantConfiguration.getPackageName();
+ return variantConfiguration.getApplicationId();
}
@NonNull
@@ -137,10 +151,18 @@
}
public void addJavaSourceFoldersToModel(@NonNull File... generatedSourceFolders) {
+ if (extraGeneratedSourceFolders == null) {
+ extraGeneratedSourceFolders = Lists.newArrayList();
+ }
+
Collections.addAll(extraGeneratedSourceFolders, generatedSourceFolders);
}
public void addJavaSourceFoldersToModel(@NonNull Collection<File> generatedSourceFolders) {
+ if (extraGeneratedSourceFolders == null) {
+ extraGeneratedSourceFolders = Lists.newArrayList();
+ }
+
extraGeneratedSourceFolders.addAll(generatedSourceFolders);
}
@@ -171,4 +193,73 @@
addJavaSourceFoldersToModel(generatedSourceFolders);
}
+
+ /**
+ * Computes the Java sources to use for compilation. This Object[] contains
+ * {@link org.gradle.api.file.FileCollection} and {@link File} instances
+ */
+ @NonNull
+ public Object[] getJavaSources() {
+ if (javaSources == null) {
+ // Build the list of source folders.
+ List<Object> sourceList = Lists.newArrayList();
+
+ // First the actual source folders.
+ List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
+ for (SourceProvider provider : providers) {
+ sourceList.add(((AndroidSourceSet) provider).getJava().getSourceFiles());
+ }
+
+ // then all the generated src folders.
+ sourceList.add(processResourcesTask.getSourceOutputDir());
+ sourceList.add(generateBuildConfigTask.getSourceOutputDir());
+ sourceList.add(aidlCompileTask.getSourceOutputDir());
+ if (!variantConfiguration.getMergedFlavor().getRenderscriptNdkMode()) {
+ sourceList.add(renderscriptCompileTask.getSourceOutputDir());
+ }
+
+ javaSources = sourceList.toArray();
+ }
+
+ return javaSources;
+ }
+
+ /**
+ * Returns the Java folders needed for code coverage report.
+ *
+ * This includes all the source folders except for the ones containing R and buildConfig.
+ */
+ @NonNull
+ public List<File> getJavaSourceFoldersForCoverage() {
+ // Build the list of source folders.
+ List<File> sourceFolders = Lists.newArrayList();
+
+ // First the actual source folders.
+ List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
+ for (SourceProvider provider : providers) {
+ for (File sourceFolder : provider.getJavaDirectories()) {
+ if (sourceFolder.isDirectory()) {
+ sourceFolders.add(sourceFolder);
+ }
+ }
+ }
+
+ File sourceFolder;
+ // then all the generated src folders, except the ones for the R/Manifest and
+ // BuildConfig classes.
+ sourceFolder = aidlCompileTask.getSourceOutputDir();
+ if (sourceFolder.isDirectory()) {
+ sourceFolders.add(sourceFolder);
+ }
+
+ if (!variantConfiguration.getMergedFlavor().getRenderscriptNdkMode()) {
+ sourceFolder = renderscriptCompileTask.getSourceOutputDir();
+ if (sourceFolder.isDirectory()) {
+ sourceFolders.add(sourceFolder);
+ }
+ }
+
+ return sourceFolders;
+ }
+
}
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
index 8a86851..d8c30ab 100644
--- 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
@@ -17,7 +17,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.builder.VariantConfiguration;
+import com.android.builder.core.VariantConfiguration;
import org.gradle.api.tasks.bundling.Zip;
/**
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantFactory.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantFactory.groovy
new file mode 100644
index 0000000..5a4e7fb
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantFactory.groovy
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.SdkConstants
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.BasePlugin
+import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.internal.api.LibraryVariantImpl
+import com.android.build.gradle.internal.coverage.JacocoInstrumentTask
+import com.android.build.gradle.internal.coverage.JacocoPlugin
+import com.android.build.gradle.internal.tasks.MergeFileTask
+import com.android.build.gradle.tasks.ExtractAnnotations
+import com.android.build.gradle.tasks.MergeResources
+import com.android.builder.core.BuilderConstants
+import com.android.builder.core.DefaultBuildType
+import com.android.builder.core.VariantConfiguration
+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.android.builder.model.MavenCoordinates
+import org.gradle.api.Project
+import org.gradle.api.Task
+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.tooling.BuildException
+
+import static com.android.SdkConstants.FN_ANNOTATIONS_ZIP
+import static com.android.SdkConstants.LIBS_FOLDER
+import static com.android.build.gradle.BasePlugin.DIR_BUNDLES
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS
+
+/**
+ */
+public class LibraryVariantFactory implements VariantFactory {
+
+ @NonNull
+ private final BasePlugin basePlugin
+ @NonNull
+ private final LibraryExtension extension
+
+ public LibraryVariantFactory(@NonNull BasePlugin basePlugin,
+ @NonNull LibraryExtension extension) {
+ this.extension = extension
+ this.basePlugin = basePlugin
+ }
+
+ @Override
+ @NonNull
+ public BaseVariantData createVariantData(@NonNull VariantConfiguration variantConfiguration) {
+ return new LibraryVariantData(variantConfiguration)
+ }
+
+ @Override
+ @NonNull
+ public BaseVariant createVariantApi(@NonNull BaseVariantData variantData) {
+ return basePlugin.getInstantiator().newInstance(LibraryVariantImpl.class, variantData)
+ }
+
+ @NonNull
+ @Override
+ public VariantConfiguration.Type getVariantConfigurationType() {
+ return VariantConfiguration.Type.LIBRARY
+ }
+
+ @Override
+ boolean isLibrary() {
+ return true
+ }
+
+ @Override
+ public void createTasks(@NonNull BaseVariantData variantData, @Nullable Task assembleTask) {
+ LibraryVariantData libVariantData = variantData as LibraryVariantData
+ VariantConfiguration variantConfig = variantData.variantConfiguration
+ DefaultBuildType buildType = variantConfig.buildType
+
+ String fullName = variantConfig.fullName
+ String dirName = variantConfig.dirName
+ Project project = basePlugin.project
+
+ basePlugin.createAnchorTasks(variantData)
+
+ basePlugin.createCheckManifestTask(variantData)
+
+ // Add a task to create the res values
+ basePlugin.createGenerateResValuesTask(variantData)
+
+ // Add a task to process the manifest(s)
+ basePlugin.createProcessManifestTask(variantData, DIR_BUNDLES)
+
+ // Add a task to compile renderscript files.
+ basePlugin.createRenderscriptTask(variantData)
+
+ // Create a merge task to only merge the resources from this library and not
+ // the dependencies. This is what gets packaged in the aar.
+ MergeResources packageRes = basePlugin.basicCreateMergeResourcesTask(variantData,
+ "package",
+ "$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}/res",
+ false /*includeDependencies*/,
+ false /*process9Patch*/)
+
+ if (variantData.variantDependency.androidDependencies.isEmpty()) {
+ // if there is no android dependencies, then we should use the packageRes task above
+ // as the only res merging task.
+ variantData.mergeResourcesTask = packageRes
+ } else {
+ // 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.
+ basePlugin.createMergeResourcesTask(variantData, false /*process9Patch*/)
+ }
+
+ // Add a task to merge the assets folders
+ basePlugin.createMergeAssetsTask(variantData,
+ "$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}/assets",
+ false /*includeDependencies*/)
+
+ // Add a task to create the BuildConfig class
+ basePlugin.createBuildConfigTask(variantData)
+
+ // Add a task to generate resource source files, directing the location
+ // of the r.txt file to be directly in the bundle.
+ basePlugin.createProcessResTask(variantData,
+ "$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}",
+ false /*generateResourcePackage*/,
+ )
+
+ // process java resources
+ basePlugin.createProcessJavaResTask(variantData)
+
+ basePlugin.createAidlTask(variantData, basePlugin.project.file(
+ "$basePlugin.project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}/$SdkConstants.FD_AIDL"))
+
+ // Add a compile task
+ basePlugin.createCompileTask(variantData, null/*testedVariant*/)
+
+ // Add NDK tasks
+ basePlugin.createNdkTasks(variantData);
+
+ // package the prebuilt native libs into the bundle folder
+ Sync packageJniLibs = project.tasks.create(
+ "package${fullName.capitalize()}JniLibs",
+ Sync)
+ packageJniLibs.dependsOn variantData.ndkCompileTask
+ // package from 3 sources.
+ packageJniLibs.from(variantConfig.jniLibsList).include("**/*.so")
+ packageJniLibs.from(variantData.ndkCompileTask.soFolder).include("**/*.so")
+ packageJniLibs.into(project.file(
+ "$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}/jni"))
+
+ // package the renderscript header files files into the bundle folder
+ Sync packageRenderscript = project.tasks.create(
+ "package${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/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}/$SdkConstants.FD_RENDERSCRIPT"))
+
+ // merge consumer proguard files from different build types and flavors
+ MergeFileTask mergeProGuardFileTask = project.tasks.create(
+ "merge${fullName.capitalize()}ProguardFiles",
+ MergeFileTask)
+ mergeProGuardFileTask.conventionMapping.inputFiles = {
+ project.files(variantConfig.getConsumerProguardFiles()).files }
+ mergeProGuardFileTask.conventionMapping.outputFile = {
+ project.file(
+ "$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}/$LibraryBundle.FN_PROGUARD_TXT")
+ }
+
+ // copy lint.jar into the bundle folder
+ Copy lintCopy = project.tasks.create(
+ "copy${fullName.capitalize()}Lint",
+ Copy)
+ lintCopy.dependsOn basePlugin.lintCompile
+ lintCopy.from("$project.buildDir/${FD_INTERMEDIATES}/lint/lint.jar")
+ lintCopy.into("$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/$dirName")
+
+ Zip bundle = project.tasks.create(
+ "bundle${fullName.capitalize()}",
+ Zip)
+
+ def extract = variantData.variantDependency.annotationsPresent ? createExtractAnnotations(
+ fullName, project, variantData) : null
+
+ if (buildType.runProguard) {
+ // run proguard on output of compile task
+ basePlugin.createProguardTasks(variantData, null)
+
+ // hack since bundle can't depend on variantData.obfuscationTask
+ mergeProGuardFileTask.dependsOn variantData.obfuscationTask
+
+ bundle.dependsOn packageRes, packageRenderscript, mergeProGuardFileTask,
+ lintCopy, packageJniLibs
+ if (extract != null) {
+ bundle.dependsOn(extract)
+ }
+ } else {
+ final boolean instrumented = variantConfig.buildType.isTestCoverageEnabled()
+
+ // if needed, instrument the code
+ JacocoInstrumentTask jacocoTask = null
+ Copy agentTask = null
+ if (instrumented) {
+ jacocoTask = project.tasks.create(
+ "instrument${variantConfig.fullName.capitalize()}", JacocoInstrumentTask)
+ jacocoTask.dependsOn variantData.javaCompileTask
+ jacocoTask.conventionMapping.jacocoClasspath = { project.configurations[JacocoPlugin.ANT_CONFIGURATION_NAME] }
+ jacocoTask.conventionMapping.inputDir = { variantData.javaCompileTask.destinationDir }
+ jacocoTask.conventionMapping.outputDir = { project.file("${project.buildDir}/${FD_INTERMEDIATES}/coverage-instrumented-classes/${variantConfig.dirName}") }
+
+ agentTask = basePlugin.getJacocoAgentTask()
+ }
+
+ // package the local jar in libs/
+ Sync packageLocalJar = project.tasks.create(
+ "package${fullName.capitalize()}LocalJar",
+ Sync)
+ packageLocalJar.from(BasePlugin.getLocalJarFileList(variantData.variantDependency))
+ if (instrumented) {
+ packageLocalJar.dependsOn agentTask
+ packageLocalJar.from(new File(agentTask.destinationDir, BasePlugin.FILE_JACOCO_AGENT))
+ }
+ packageLocalJar.into(project.file(
+ "$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}/$LIBS_FOLDER"))
+
+ // jar the classes.
+ Jar jar = project.tasks.create("package${fullName.capitalize()}Jar", Jar);
+ jar.dependsOn variantData.javaCompileTask, variantData.processJavaResourcesTask
+ if (instrumented) {
+ jar.dependsOn jacocoTask
+ jar.from({ jacocoTask.getOutputDir() });
+ } else {
+ jar.from(variantData.javaCompileTask.outputs);
+ }
+ jar.from(variantData.processJavaResourcesTask.destinationDir)
+
+ jar.destinationDir = project.file(
+ "$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${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")
+ if (!extension.packageBuildConfig) {
+ jar.exclude(packageName + "/Manifest.class")
+ jar.exclude(packageName + "/Manifest\$*.class")
+ jar.exclude(packageName + "/BuildConfig.class")
+ }
+
+ bundle.dependsOn packageRes, jar, packageRenderscript, packageLocalJar,
+ mergeProGuardFileTask, lintCopy, packageJniLibs
+
+ if (extract != null) {
+ // In case extract annotations strips out private typedef annotation classes
+ jar.dependsOn extract
+ bundle.dependsOn extract
+ }
+ }
+
+ bundle.setDescription("Assembles a bundle containing the library in ${fullName.capitalize()}.");
+ bundle.destinationDir = project.file("$project.buildDir/${FD_OUTPUTS}/aar")
+ bundle.extension = BuilderConstants.EXT_LIB_ARCHIVE
+ bundle.from(project.file("$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}"))
+
+ libVariantData.packageLibTask = bundle
+ variantData.outputFile = bundle.archivePath
+
+ if (assembleTask == null) {
+ assembleTask = basePlugin.createAssembleTask(variantData)
+ }
+ assembleTask.dependsOn bundle
+ variantData.assembleTask = assembleTask
+
+ if (extension.defaultPublishConfig.equals(fullName)) {
+ VariantHelper.setupDefaultConfig(project,
+ variantData.variantDependency.packageConfiguration)
+
+ // add the artifact that will be published
+ project.artifacts.add("default", bundle)
+
+ basePlugin.assembleDefault.dependsOn variantData.assembleTask
+ }
+
+ // also publish the artifact with its full config name
+ if (extension.publishNonDefault) {
+ project.artifacts.add(variantData.variantDependency.publishConfiguration.name, bundle)
+ bundle.classifier = variantData.variantDependency.publishConfiguration.name
+ }
+
+
+ // configure the variant to be testable.
+ variantConfig.output = new LibraryBundle(
+ bundle.archivePath,
+ project.file("$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}"),
+ variantData.getName()) {
+
+ @Override
+ @Nullable
+ String getProject() {
+ return project.path
+ }
+
+ @Override
+ @Nullable
+ String getProjectVariant() {
+ return variantData.getName()
+ }
+
+ @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
+ }
+
+ @Override
+ @Nullable
+ MavenCoordinates getRequestedCoordinates() {
+ return null
+ }
+
+ @Override
+ @Nullable
+ MavenCoordinates getResolvedCoordinates() {
+ return null
+ }
+ };
+ }
+
+ public Task createExtractAnnotations(
+ String fullName, Project project, BaseVariantData variantData) {
+ VariantConfiguration config = variantData.variantConfiguration
+ String dirName = config.dirName
+
+ ExtractAnnotations task = project.tasks.create(
+ "extract${fullName.capitalize()}Annotations",
+ ExtractAnnotations)
+ task.description =
+ "Extracts Android annotations for the ${fullName} variant into the archive file"
+ task.group = org.gradle.api.plugins.BasePlugin.BUILD_GROUP
+ task.plugin = basePlugin
+ task.variant = variantData
+ task.destinationDir = project.file("$project.buildDir/${FD_INTERMEDIATES}/$DIR_BUNDLES/${dirName}")
+ task.output = new File(task.destinationDir, FN_ANNOTATIONS_ZIP)
+ task.classDir = project.file("$project.buildDir/${FD_INTERMEDIATES}/classes/${variantData.variantConfiguration.dirName}")
+ task.source = variantData.getJavaSources()
+ task.encoding = extension.compileOptions.encoding
+ task.sourceCompatibility = extension.compileOptions.sourceCompatibility
+ task.classpath = project.files(basePlugin.getAndroidBuilder().getCompileClasspath(config))
+ task.dependsOn variantData.javaCompileTask
+
+ // Setup the boot classpath just before the task actually runs since this will
+ // force the sdk to be parsed. (Same as in compileTask)
+ task.doFirst {
+ task.bootClasspath = basePlugin.getAndroidBuilder().getBootClasspath()
+ }
+
+ return task
+ }
+}
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
index b8d45ed..347aacb 100644
--- 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
@@ -17,7 +17,7 @@
import com.android.annotations.NonNull;
import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
-import com.android.builder.VariantConfiguration;
+import com.android.builder.core.VariantConfiguration;
import com.google.common.collect.Lists;
import java.util.List;
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/VariantFactory.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/VariantFactory.java
new file mode 100644
index 0000000..a9d8c10
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/VariantFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.build.gradle.api.BaseVariant;
+import com.android.builder.core.VariantConfiguration;
+
+import org.gradle.api.Task;
+
+/**
+ * Interface for Variant Factory.
+ *
+ * While VariantManager is the general variant management, implementation of this interface
+ * provides variant type (app, lib) specific implementation.
+ */
+public interface VariantFactory {
+
+ @NonNull
+ BaseVariantData createVariantData(@NonNull VariantConfiguration variantConfiguration);
+
+ @NonNull
+ BaseVariant createVariantApi(@NonNull BaseVariantData variantData);
+
+ @NonNull
+ VariantConfiguration.Type getVariantConfigurationType();
+
+ boolean isLibrary();
+
+ /**
+ * Creates the tasks for a given BaseVariantData.
+ * @param variantData the non-null BaseVariantData.
+ * @param assembleTask an optional assembleTask to be used. If null, a new one is created.
+ */
+ void createTasks(
+ @NonNull BaseVariantData variantData,
+ @Nullable Task assembleTask);
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/VariantHelper.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/VariantHelper.groovy
new file mode 100644
index 0000000..7d91e40
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/VariantHelper.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.google.common.collect.Sets
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.maven.MavenDeployer
+import org.gradle.api.plugins.MavenPlugin
+import org.gradle.api.tasks.Upload
+
+/**
+ */
+public class VariantHelper {
+
+ public static void setupDefaultConfig(@NonNull Project project, @NonNull Configuration configuration) {
+ // The library artifact is published (inter-project( for the "default" configuration so
+ // we make sure "default" extends from the actual configuration used for building.
+ Configuration defaultConfig = project.configurations["default"]
+ defaultConfig.setExtendsFrom(Collections.singleton(configuration))
+
+ // for the maven publication (for now), we need to manually include all the configuration
+ // object in a special mapping.
+ // It's not possible to put the top level config object as extended from config won't
+ // be included.
+ Set<Configuration> flattenedConfigs = flattenConfigurations(configuration)
+
+ project.plugins.withType(MavenPlugin) {
+ project.tasks.withType(Upload) { task ->
+ task.repositories.withType(MavenDeployer) { repo ->
+ for (Configuration config : flattenedConfigs) {
+ repo.pom.scopeMappings.addMapping(300,
+ project.configurations[config.name],
+ "compile")
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Build a set of configuration containing all the Configuration object that a given
+ * configuration extends from, directly or transitively.
+ *
+ * @param configuration the configuration
+ * @return a set of config.
+ */
+ private static Set<Configuration> flattenConfigurations(@NonNull Configuration configuration) {
+ Set<Configuration> configs = Sets.newHashSet()
+ configs.add(configuration)
+
+ for (Configuration extend : configuration.getExtendsFrom()) {
+ configs.addAll(flattenConfigurations(extend))
+ }
+
+ return configs
+ }
+
+}
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
index 6ad9277..b00d15c 100644
--- 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
@@ -18,45 +18,107 @@
import com.android.annotations.NonNull
import com.android.annotations.Nullable
-import com.android.build.gradle.internal.tasks.DependencyBasedCompileTask
+import com.android.annotations.concurrency.GuardedBy
+import com.android.build.gradle.internal.tasks.IncrementalTask
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.file.FileTree
import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.util.PatternSet
+
+import java.util.concurrent.Callable
/**
* Task to compile aidl files. Supports incremental update.
*/
-public class AidlCompile extends DependencyBasedCompileTask {
+public class AidlCompile extends IncrementalTask {
+
+ private static final String DEPENDENCY_STORE = "dependency.store"
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File sourceOutputDir
+
+ @OutputDirectory @Optional
+ File aidlParcelableDir
// ----- PRIVATE TASK API -----
- @InputFiles
List<File> sourceDirs
@InputFiles
List<File> importDirs
- @Override
+ final PatternSet patternSet = new PatternSet().include("**/*.aidl")
+
+ @InputFiles
+ FileTree getSourceFiles() {
+ FileTree src = null
+ Set<File> sources = getSourceDirs()
+ if (!sources.isEmpty()) {
+ src = getProject().files(new ArrayList<Object>(sources)).getAsFileTree().matching(patternSet)
+ }
+ return src == null ? getProject().files().getAsFileTree() : src
+ }
+
+ private static class DepFileProcessor implements DependencyFileProcessor {
+
+ @GuardedBy("this")
+ List<DependencyData> dependencyDataList = Lists.newArrayList()
+
+ List<DependencyData> getDependencyDataList() {
+ return dependencyDataList
+ }
+
+ @Override
+ DependencyData processFile(@NonNull File dependencyFile) {
+ DependencyData data = DependencyData.parseDependencyFile(dependencyFile)
+ if (data != null) {
+ synchronized (this) {
+ dependencyDataList.add(data)
+ }
+ }
+
+ return data;
+ }
+ }
+
protected boolean isIncremental() {
- return true
+ // TODO fix once dep file parsing is resolved.
+ return false
}
- @Override
- protected boolean supportsParallelization() {
- return true
- }
-
- @Override
- protected void compileAllFiles(DependencyFileProcessor dependencyFileProcessor) {
+ /**
+ * 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
+ */
+ private void compileAllFiles(DependencyFileProcessor dependencyFileProcessor) {
getBuilder().compileAllAidlFiles(
getSourceDirs(),
getSourceOutputDir(),
+ getAidlParcelableDir(),
getImportDirs(),
dependencyFileProcessor)
}
- @Override
- protected Object incrementalSetup() {
+ /**
+ * Returns the import folders.
+ */
+ @NonNull
+ private List<File> getImportFolders() {
List<File> fullImportDir = Lists.newArrayList()
fullImportDir.addAll(getImportDirs())
fullImportDir.addAll(getSourceDirs())
@@ -64,14 +126,151 @@
return fullImportDir
}
- @Override
- protected void compileSingleFile(@NonNull File file,
- @Nullable Object data,
- @NonNull DependencyFileProcessor dependencyFileProcessor) {
+ /**
+ * Compiles a single file.
+ * @param sourceFolder the file to compile.
+ * @param file the file to compile.
+ * @param importFolders the import folders.
+ * @param dependencyFileProcessor a DependencyFileProcessor
+ */
+ private void compileSingleFile(
+ @NonNull File sourceFolder,
+ @NonNull File file,
+ @Nullable List<File> importFolders,
+ @NonNull DependencyFileProcessor dependencyFileProcessor) {
getBuilder().compileAidlFile(
+ sourceFolder,
file,
getSourceOutputDir(),
- (List<File>)data,
+ getAidlParcelableDir(),
+ importFolders,
dependencyFileProcessor)
}
+
+ @Override
+ protected void doFullTaskAction() {
+ // this is full run, clean the previous output
+ File destinationDir = getSourceOutputDir()
+ emptyFolder(destinationDir)
+
+ File parcelableDir = getAidlParcelableDir()
+ if (parcelableDir != null) {
+ emptyFolder(parcelableDir)
+ }
+
+ 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 ignored) {
+ incrementalData.delete()
+ project.logger.info(
+ "Failed to read dependency store: full task run!")
+ doFullTaskAction()
+ return
+ }
+
+ final List<File> importFolders = getImportFolders()
+ final DepFileProcessor processor = new DepFileProcessor()
+
+ // use an executor to parallelize the compilation of multiple files.
+ WaitableExecutor<Void> executor = new WaitableExecutor<Void>()
+
+ 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 {
+ File file = entry.getKey()
+ compileSingleFile(getSourceFolder(file), file, importFolders, processor)
+ }
+ })
+ break
+ case FileStatus.CHANGED:
+ List<DependencyData> impactedData = inputMap.get(entry.getKey().absolutePath)
+ if (impactedData != null) {
+ final int count = impactedData.size();
+ for (int i = 0; i < count; i++) {
+ final DependencyData data = impactedData.get(i);
+
+ executor.execute(new Callable<Void>() {
+ @Override
+ Void call() throws Exception {
+ File file = new File(data.getMainFile());
+ compileSingleFile(getSourceFolder(file), file,
+ importFolders, processor)
+ }
+ })
+ }
+ }
+ break
+ case FileStatus.REMOVED:
+ final DependencyData data2 = mainFileMap.get(entry.getKey().absolutePath)
+ if (data2 != null) {
+ executor.execute(new Callable<Void>() {
+ @Override
+ Void call() throws Exception {
+ cleanUpOutputFrom(data2)
+ }
+ })
+ store.remove(data2)
+ }
+ break
+ }
+ }
+
+ try {
+ executor.waitForTasksWithQuickFail(true /*cancelRemaining*/)
+ } catch (Throwable t) {
+ incrementalData.delete()
+ throw t
+ }
+
+ // get all the update data for the recompiled objects
+ store.updateAll(processor.getDependencyDataList())
+
+ store.saveTo(incrementalData)
+ }
+
+ private File getSourceFolder(@NonNull File file) {
+ File parentDir = file
+ while ((parentDir = parentDir.getParentFile()) != null) {
+ for (File folder : getSourceDirs()) {
+ if (parentDir.equals(folder)) {
+ return folder;
+ }
+ }
+ }
+
+ assert false
+ }
+
+ private static void cleanUpOutputFrom(@NonNull DependencyData dependencyData) {
+ for (String output : dependencyData.getOutputFiles()) {
+ new File(output).delete()
+ }
+ for (String output : dependencyData.getSecondaryOutputFiles()) {
+ new File(output).delete()
+ }
+ }
}
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
index c74cdb3..297b38c 100644
--- 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
@@ -14,53 +14,96 @@
* limitations under the License.
*/
package com.android.build.gradle.tasks
+import com.android.SdkConstants
import com.android.build.gradle.internal.dsl.DexOptionsImpl
-import com.android.build.gradle.internal.tasks.IncrementalTask
-import com.android.ide.common.res2.FileStatus
+import com.android.build.gradle.internal.tasks.BaseTask
+import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Nested
-import org.gradle.api.tasks.OutputFile
+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 class Dex extends IncrementalTask {
+import java.util.concurrent.atomic.AtomicBoolean
+
+public class Dex extends BaseTask {
// ----- PUBLIC TASK API -----
- @OutputFile
- File outputFile
+ @OutputDirectory
+ File outputFolder
+
+ @Input @Optional
+ List<String> additionalParameters
+
+ boolean enableIncremental = true
// ----- PRIVATE TASK API -----
@InputFiles
- Iterable<File> inputFiles
+ Collection<File> inputFiles
@InputFiles
- Iterable<File> preDexedLibraries
+ Collection<File> libraries
@Nested
DexOptionsImpl dexOptions
- @Override
- protected void doFullTaskAction() {
- getBuilder().convertByteCode(
- getInputFiles(),
- getPreDexedLibraries(),
- getOutputFile(),
- getDexOptions(),
- false)
+ /**
+ * Actual entry point for the action.
+ * Calls out to the doTaskAction as needed.
+ */
+ @TaskAction
+ void taskAction(IncrementalTaskInputs inputs) {
+ if (!dexOptions.incremental || !enableIncremental) {
+ doTaskAction(false /*incremental*/)
+ return
+ }
+
+ if (!inputs.isIncremental()) {
+ project.logger.info("Unable to do incremental execution: full task run.")
+ doTaskAction(false /*incremental*/)
+ return
+ }
+
+ AtomicBoolean forceFullRun = new AtomicBoolean()
+
+ //noinspection GroovyAssignabilityCheck
+ inputs.outOfDate { change ->
+ // force full dx run if existing jar file is modified
+ // New jar files are fine.
+ if (change.isModified() && change.file.path.endsWith(SdkConstants.DOT_JAR)) {
+ project.logger.info("Force full dx run: Found updated ${change.file}")
+ forceFullRun.set(true)
+ }
+ }
+
+ //noinspection GroovyAssignabilityCheck
+ inputs.removed { change ->
+ // force full dx run if existing jar file is removed
+ if (change.file.path.endsWith(SdkConstants.DOT_JAR)) {
+ project.logger.info("Force full dx run: Found removed ${change.file}")
+ forceFullRun.set(true)
+ }
+ }
+
+ doTaskAction(!forceFullRun.get())
}
- @Override
- protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
+ private void doTaskAction(boolean incremental) {
+ File outFolder = getOutputFolder()
+
+ if (!incremental) {
+ emptyFolder(outFolder)
+ }
+
getBuilder().convertByteCode(
getInputFiles(),
- getPreDexedLibraries(),
- getOutputFile(),
+ getLibraries(),
+ outFolder,
getDexOptions(),
- true)
- }
-
- @Override
- protected boolean isIncremental() {
- return dexOptions.incremental
+ getAdditionalParameters(),
+ incremental)
}
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ExtractAnnotations.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ExtractAnnotations.groovy
new file mode 100644
index 0000000..d69a20a
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ExtractAnnotations.groovy
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.BasePlugin
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.tasks.annotations.ApiDatabase
+import com.android.build.gradle.tasks.annotations.Extractor
+import com.android.tools.lint.EcjParser
+import com.google.common.collect.Lists
+import com.google.common.collect.Maps
+import org.eclipse.jdt.core.compiler.IProblem
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions
+import org.eclipse.jdt.internal.compiler.util.Util
+import org.gradle.api.file.EmptyFileVisitor
+import org.gradle.api.file.FileVisitDetails
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.compile.AbstractCompile
+import org.gradle.tooling.BuildException
+
+import static com.android.SdkConstants.DOT_JAVA
+import static com.android.SdkConstants.UTF_8
+
+class ExtractAnnotations extends AbstractCompile {
+ public BasePlugin plugin
+ public BaseVariantData variant
+
+ /** Boot classpath: typically android.jar */
+ @Input
+ public List<String> bootClasspath
+
+ /** The output .zip file to write the annotations database to, if any */
+ @OutputFile
+ public File output
+
+ /**
+ * An optional pointer to an API file to filter the annotations by (any annotations
+ * not found in the API file are considered hidden/not exposed.) This is in the same
+ * format as the api-versions.xml file found in the SDK.
+ */
+ @Optional
+ @InputFile
+ public File apiFilter
+
+ /**
+ * A list of existing annotation zip files (or dirs) to merge in. This can be used to merge in
+ * a hardcoded set of annotations that are not present in the source code, such as
+ * {@code @Contract} annotations we'd like to record without actually having a dependency
+ * on the IDEA annotations library.
+ */
+ @Optional
+ @InputFile
+ public List<File> mergeJars
+
+ /**
+ * The encoding to use when reading source files. The output file will ignore this and
+ * will always be a UTF-8 encoded .xml file inside the annotations zip file.
+ */
+ @Optional
+ @Input
+ public String encoding
+
+ /**
+ * Location of class files. If set, any non-public typedef source retention annotations
+ * will be removed prior to .jar packaging.
+ */
+ @Optional
+ @InputFile
+ public File classDir
+
+ @Override
+ @TaskAction
+ protected void compile() {
+ if (!hasAndroidAnnotations()) {
+ return
+ }
+
+ if (encoding == null) {
+ encoding = UTF_8
+ }
+
+ Collection<CompilationUnitDeclaration> parsedUnits = parseSources()
+
+ for (CompilationUnitDeclaration unit : parsedUnits) {
+ // so maybe I don't need my map!!
+ def problems = unit.compilationResult().allProblems
+ for (IProblem problem : problems) {
+ if (problem.error) {
+ println "Not extracting annotations (compilation problems encountered)";
+ println "Error: " + problem.getOriginatingFileName() + ":" +
+ problem.getSourceLineNumber() + ": " + problem.getMessage()
+ // TODO: Consider whether we abort the build at this point!
+ return
+ }
+ }
+ }
+
+ // API definition file
+ ApiDatabase database = null;
+ if (apiFilter != null && apiFilter.exists()) {
+ try {
+ database = new ApiDatabase(apiFilter);
+ } catch (IOException e) {
+ throw new BuildException("Could not open API database " + apiFilter, e)
+ }
+ }
+
+ Extractor extractor = new Extractor(database, classDir);
+ extractor.extractFromProjectSource(parsedUnits)
+ if (mergeJars != null) {
+ for (File jar : mergeJars) {
+ extractor.mergeExisting(jar);
+ }
+ }
+ extractor.export(output)
+ extractor.removeTypedefClasses();
+ }
+
+ @Input
+ public boolean hasAndroidAnnotations() {
+ return variant.variantDependency.annotationsPresent
+ }
+
+ @NonNull
+ private Collection<CompilationUnitDeclaration> parseSources() {
+ List<ICompilationUnit> sourceUnits = Lists.newArrayListWithExpectedSize(100);
+
+ source.visit(new EmptyFileVisitor() {
+ @Override
+ void visitFile(FileVisitDetails fileVisitDetails) {
+ def file = fileVisitDetails.file;
+ def path = file.getPath()
+ if (path.endsWith(DOT_JAVA) && file.isFile()) {
+ char[] contents = Util.getFileCharContent(file, encoding);
+ ICompilationUnit unit = new CompilationUnit(contents, path, encoding);
+ sourceUnits.add(unit);
+ }
+ }
+ })
+
+ Map<ICompilationUnit, CompilationUnitDeclaration> outputMap = Maps.
+ newHashMapWithExpectedSize(sourceUnits.size())
+ List<String> jars = Lists.newArrayList();
+ if (bootClasspath != null) {
+ jars.addAll(bootClasspath)
+ }
+ if (classpath != null) {
+ for (File jar : classpath) {
+ jars.add(jar.getPath());
+ }
+ }
+
+ CompilerOptions options = EcjParser.createCompilerOptions();
+ options.docCommentSupport = true; // So I can find @hide
+
+ // Note: We can *not* set options.ignoreMethodBodies=true because it disables
+ // type attribution!
+
+ def level = getLanguageLevel(sourceCompatibility)
+ options.sourceLevel = level
+ options.complianceLevel = options.sourceLevel
+ // We don't generate code, but just in case the parser consults this flag
+ // and makes sure that it's not greater than the source level:
+ options.targetJDK = options.sourceLevel
+ options.originalComplianceLevel = options.sourceLevel;
+ options.originalSourceLevel = options.sourceLevel;
+ options.inlineJsrBytecode = true; // >= 1.5
+
+ EcjParser.parse(options, sourceUnits, jars, outputMap, null);
+
+ Collection<CompilationUnitDeclaration> parsedUnits = outputMap.values()
+ parsedUnits
+ }
+
+ private static long getLanguageLevel(String version) {
+ if ("1.6".equals(version)) {
+ return EcjParser.getLanguageLevel(1, 6);
+ } else if ("1.7".equals(version)) {
+ return EcjParser.getLanguageLevel(1, 7);
+ } else if ("1.5") {
+ return EcjParser.getLanguageLevel(1, 5);
+ } else {
+ return EcjParser.getLanguageLevel(1, 7);
+ }
+ }
+
+ private def addSources(List<ICompilationUnit> sourceUnits, File file) {
+ if (file.isDirectory()) {
+ def files = file.listFiles();
+ if (files != null) {
+ for (File sub : files) {
+ addSources(sourceUnits, sub);
+ }
+ }
+ } else if (file.getPath().endsWith(DOT_JAVA) && file.isFile()) {
+ char[] contents = Util.getFileCharContent(file, encoding);
+ ICompilationUnit unit = new CompilationUnit(contents, file.getPath(), encoding);
+ sourceUnits.add(unit);
+ }
+ }
+}
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
index 1f4d7af..ec15bfc 100644
--- 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
@@ -14,14 +14,16 @@
* limitations under the License.
*/
package com.android.build.gradle.tasks
-
-import com.android.build.gradle.internal.tasks.IncrementalTask
+import com.android.build.gradle.internal.tasks.BaseTask
import com.android.builder.compiling.BuildConfigGenerator
+import com.android.builder.model.ClassField
+import com.google.common.collect.Lists
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
-public class GenerateBuildConfig extends IncrementalTask {
+public class GenerateBuildConfig extends BaseTask {
// ----- PUBLIC TASK API -----
@@ -54,20 +56,43 @@
@Input
int versionCode
- @Input
List<Object> items;
- @Override
- protected void doFullTaskAction() {
+ @Input
+ List<String> getItemValues() {
+ List<Object> resolvedItems = getItems()
+ List<String> list = Lists.newArrayListWithCapacity(resolvedItems.size() * 3)
+
+ for (Object object : resolvedItems) {
+ if (object instanceof String) {
+ list.add((String) object)
+ } else if (object instanceof ClassField) {
+ ClassField field = (ClassField) object
+ list.add(field.type)
+ list.add(field.name)
+ list.add(field.value)
+ }
+ }
+
+ return list
+ }
+
+ @TaskAction
+ void generate() {
// 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,
+ getSourceOutputDir(),
getBuildConfigPackageName());
+ String vn = getVersionName()
+ if (vn == null) {
+ vn = ""
+ }
+
// 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
@@ -80,20 +105,17 @@
.addField("String", "BUILD_TYPE", "\"${getBuildTypeName()}\"")
.addField("String", "FLAVOR", "\"${getFlavorName()}\"")
.addField("int", "VERSION_CODE", Integer.toString(getVersionCode()))
- .addItems(getItems());
+ .addField("String", "VERSION_NAME", "\"${vn}\"")
+ .addItems(getItems())
- if (getVersionName() != null) {
- generator.addField("String", "VERSION_NAME", "\"${getVersionName()}\"")
- }
-
- List<String> flavors = getFlavorNamesWithDimensionNames();
- int count = flavors.size();
+ 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();
+ generator.generate()
}
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/GenerateResValues.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/GenerateResValues.groovy
new file mode 100644
index 0000000..4bba364
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/GenerateResValues.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.BaseTask
+import com.android.builder.compiling.ResValueGenerator
+import com.android.builder.model.ClassField
+import com.google.common.collect.Lists
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+
+public class GenerateResValues extends BaseTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File resOutputDir
+
+ // ----- PRIVATE TASK API -----
+
+ List<Object> items
+
+ @Input
+ List<String> getItemValues() {
+ List<Object> resolvedItems = getItems()
+ List<String> list = Lists.newArrayListWithCapacity(resolvedItems.size() * 3)
+
+ for (Object object : resolvedItems) {
+ if (object instanceof String) {
+ list.add((String) object)
+ } else if (object instanceof ClassField) {
+ ClassField field = (ClassField) object
+ list.add(field.type)
+ list.add(field.name)
+ list.add(field.value)
+ }
+ }
+
+ return list
+ }
+
+ @TaskAction
+ void generate() {
+ File folder = getResOutputDir()
+ List<Object> resolvedItems = getItems()
+
+ if (resolvedItems.isEmpty()) {
+ folder.deleteDir()
+ } else {
+ ResValueGenerator generator = new ResValueGenerator(folder)
+ generator.addItems(getItems())
+
+ generator.generate()
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/GroovyGradleDetector.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/GroovyGradleDetector.java
new file mode 100644
index 0000000..a7bd256
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/GroovyGradleDetector.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.tools.lint.checks.GradleDetector;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.DefaultPosition;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+
+import com.google.common.collect.Maps;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.GroovyCodeVisitor;
+import org.codehaus.groovy.ast.builder.AstBuilder;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of the {@link GradleDetector} using a real Groovy AST,
+ * which the Gradle plugin has access to
+ */
+public class GroovyGradleDetector extends GradleDetector {
+ static final Implementation IMPLEMENTATION = new Implementation(
+ GroovyGradleDetector.class,
+ Scope.GRADLE_SCOPE);
+
+ @Override
+ public void visitBuildScript(@NonNull final Context context, Map<String, Object> sharedData) {
+ try {
+ visitQuietly(context, sharedData);
+ } catch (Throwable t) {
+ // ignore
+ // Parsing the build script can involve class loading that we sometimes can't
+ // handle. This happens for example when running lint in build-system/tests/api/.
+ // This is a lint limitation rather than a user error, so don't complain
+ // about these. Consider reporting a Issue#LINT_ERROR.
+ }
+ }
+
+ private void visitQuietly(@NonNull final Context context, Map<String, Object> sharedData) {
+ String source = context.getContents();
+ if (source == null) {
+ return;
+ }
+
+ List<ASTNode> astNodes = new AstBuilder().buildFromString(source);
+ GroovyCodeVisitor visitor = new CodeVisitorSupport() {
+ private List<MethodCallExpression> mMethodCallStack = Lists.newArrayList();
+ @Override
+ public void visitMethodCallExpression(MethodCallExpression expression) {
+ mMethodCallStack.add(expression);
+ super.visitMethodCallExpression(expression);
+ Expression arguments = expression.getArguments();
+ String parent = expression.getMethodAsString();
+ String parentParent = getParentParent();
+ if (arguments instanceof ArgumentListExpression) {
+ ArgumentListExpression ale = (ArgumentListExpression)arguments;
+ List<Expression> expressions = ale.getExpressions();
+ if (expressions.size() == 1 &&
+ expressions.get(0) instanceof ClosureExpression) {
+ if (isInterestingBlock(parent, parentParent)) {
+ checkBlock(context, parent, parentParent, expression);
+ ClosureExpression closureExpression =
+ (ClosureExpression)expressions.get(0);
+ Statement block = closureExpression.getCode();
+ if (block instanceof BlockStatement) {
+ BlockStatement bs = (BlockStatement)block;
+ for (Statement statement : bs.getStatements()) {
+ if (statement instanceof ExpressionStatement) {
+ ExpressionStatement e = (ExpressionStatement)statement;
+ if (e.getExpression() instanceof MethodCallExpression) {
+ checkDslProperty(parent,
+ (MethodCallExpression)e.getExpression(),
+ parentParent);
+ }
+ } else if (statement instanceof ReturnStatement) {
+ // Single item in block
+ ReturnStatement e = (ReturnStatement)statement;
+ if (e.getExpression() instanceof MethodCallExpression) {
+ checkDslProperty(parent,
+ (MethodCallExpression)e.getExpression(),
+ parentParent);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (arguments instanceof TupleExpression) {
+ if (isInterestingStatement(parent, parentParent)) {
+ TupleExpression te = (TupleExpression) arguments;
+ Map<String, String> namedArguments = Maps.newHashMap();
+ List<String> unnamedArguments = Lists.newArrayList();
+ for (Expression subExpr : te.getExpressions()) {
+ if (subExpr instanceof NamedArgumentListExpression) {
+ NamedArgumentListExpression nale = (NamedArgumentListExpression) subExpr;
+ for (MapEntryExpression mae : nale.getMapEntryExpressions()) {
+ namedArguments.put(mae.getKeyExpression().getText(),
+ mae.getValueExpression().getText());
+ }
+ }
+ }
+ checkMethodCall(context, parent, parentParent, namedArguments, unnamedArguments, expression);
+ }
+ }
+ assert !mMethodCallStack.isEmpty();
+ assert mMethodCallStack.get(mMethodCallStack.size() - 1) == expression;
+ mMethodCallStack.remove(mMethodCallStack.size() - 1);
+ }
+
+ private String getParentParent() {
+ for (int i = mMethodCallStack.size() - 2; i >= 0; i--) {
+ MethodCallExpression expression = mMethodCallStack.get(i);
+ Expression arguments = expression.getArguments();
+ if (arguments instanceof ArgumentListExpression) {
+ ArgumentListExpression ale = (ArgumentListExpression)arguments;
+ List<Expression> expressions = ale.getExpressions();
+ if (expressions.size() == 1 &&
+ expressions.get(0) instanceof ClosureExpression) {
+ return expression.getMethodAsString();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void checkDslProperty(String parent, MethodCallExpression c,
+ String parentParent) {
+ String property = c.getMethodAsString();
+ if (isInterestingProperty(property, parent, getParentParent())) {
+ String value = getText(c.getArguments());
+ checkDslPropertyAssignment(context, property, value, parent, parentParent, c, c);
+ }
+ }
+
+ private String getText(ASTNode node) {
+ String source = context.getContents();
+ Pair<Integer, Integer> offsets = getOffsets(node, context);
+ return source.substring(offsets.getFirst(), offsets.getSecond());
+ }
+ };
+
+ for (ASTNode node : astNodes) {
+ node.visit(visitor);
+ }
+ }
+
+ @NonNull
+ private static Pair<Integer, Integer> getOffsets(ASTNode node, Context context) {
+ String source = context.getContents();
+ assert source != null; // because we successfully parsed
+ int start = 0;
+ int end = source.length();
+ int line = 1;
+ int startLine = node.getLineNumber();
+ int startColumn = node.getColumnNumber();
+ int endLine = node.getLastLineNumber();
+ int endColumn = node.getLastColumnNumber();
+ int column = 1;
+ for (int index = 0, len = end; index < len; index++) {
+ if (line == startLine && column == startColumn) {
+ start = index;
+ }
+ if (line == endLine && column == endColumn) {
+ end = index;
+ break;
+ }
+
+ char c = source.charAt(index);
+ if (c == '\n') {
+ line++;
+ column = 1;
+ } else {
+ column++;
+ }
+ }
+
+ return Pair.of(start, end);
+ }
+
+ @Override
+ protected int getStartOffset(@NonNull Context context, @NonNull Object cookie) {
+ ASTNode node = (ASTNode) cookie;
+ Pair<Integer, Integer> offsets = getOffsets(node, context);
+ return offsets.getFirst();
+ }
+
+ @Override
+ protected Location createLocation(@NonNull Context context, @NonNull Object cookie) {
+ ASTNode node = (ASTNode) cookie;
+ Pair<Integer, Integer> offsets = getOffsets(node, context);
+ int fromLine = node.getLineNumber() - 1;
+ int fromColumn = node.getColumnNumber() - 1;
+ int toLine = node.getLastLineNumber() - 1;
+ int toColumn = node.getLastColumnNumber() - 1;
+ return Location.create(context.file,
+ new DefaultPosition(fromLine, fromColumn, offsets.getFirst()),
+ new DefaultPosition(toLine, toColumn, offsets.getSecond()));
+ }
+}
\ No newline at end of file
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
index 60af0a4..44b9c94 100644
--- 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
@@ -20,16 +20,17 @@
import com.android.annotations.Nullable
import com.android.build.gradle.BasePlugin
import com.android.build.gradle.internal.LintGradleClient
+import com.android.build.gradle.internal.dsl.LintOptionsImpl
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.checks.GradleDetector
import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.Severity
import com.google.common.collect.Maps
import org.gradle.api.DefaultTask
@@ -37,11 +38,10 @@
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
+ private boolean mFatalOnly
public void setPlugin(@NonNull BasePlugin plugin) {
mPlugin = plugin
@@ -51,6 +51,10 @@
mVariantName = variantName
}
+ public void setFatalOnly(boolean fatalOnly) {
+ mFatalOnly = fatalOnly
+ }
+
@SuppressWarnings("GroovyUnusedDeclaration")
@TaskAction
public void lint() {
@@ -83,8 +87,10 @@
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"
+ if (!mFatalOnly) {
+ println "Ran lint on variant " + variant.getName() + ": " + warnings.size() +
+ " issues found"
+ }
}
List<Warning> mergedWarnings = LintGradleClient.merge(warningMap, modelProject)
@@ -102,16 +108,52 @@
LintCliFlags flags = new LintCliFlags()
LintGradleClient client = new LintGradleClient(registry, flags, mPlugin, modelProject,
null)
- mPlugin.getExtension().lintOptions.syncTo(client, flags, null, project, true)
+ syncOptions(mPlugin.getExtension().lintOptions, client, flags, null, project, true,
+ mFatalOnly)
+
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.")
+ abort()
}
}
+ private void abort() {
+ def message;
+ if (mFatalOnly) {
+ message = "" +
+ "Lint found fatal errors while assembling a release target.\n" +
+ "\n" +
+ "To proceed, either fix the issues identified by lint, or modify your build script as follows:\n" +
+ "...\n" +
+ "android {\n" +
+ " lintOptions {\n" +
+ " checkReleaseBuilds false\n" +
+ " // Or, if you prefer, you can continue to check for errors in release builds,\n" +
+ " // but continue the build even when errors are found:\n" +
+ " abortOnError false\n" +
+ " }\n" +
+ "}\n" +
+ "..."
+ ""
+ } else {
+ message = "" +
+ "Lint found errors in the project; aborting build.\n" +
+ "\n" +
+ "Fix the issues identified by lint, or add the following to your build script to proceed with errors:\n" +
+ "...\n" +
+ "android {\n" +
+ " lintOptions {\n" +
+ " abortOnError false\n" +
+ " }\n" +
+ "}\n" +
+ "..."
+ }
+ throw new GradleException(message);
+ }
+
/**
* Runs lint on a single specified variant
*/
@@ -124,11 +166,21 @@
@NonNull AndroidProject modelProject,
@NonNull String variantName,
boolean report) {
- IssueRegistry registry = new BuiltinIssueRegistry()
+ IssueRegistry registry = createIssueRegistry()
LintCliFlags flags = new LintCliFlags()
LintGradleClient client = new LintGradleClient(registry, flags, mPlugin, modelProject,
variantName)
- mPlugin.getExtension().lintOptions.syncTo(client, flags, variantName, project, report)
+ def options = mPlugin.getExtension().lintOptions
+ if (mFatalOnly) {
+ if (!options.isCheckReleaseBuilds()) {
+ return
+ }
+ flags.setFatalOnly(true)
+ }
+ syncOptions(options, client, flags, variantName, project, report, mFatalOnly)
+ if (!report || mFatalOnly) {
+ flags.setQuiet(true)
+ }
List<Warning> warnings;
try {
@@ -138,15 +190,65 @@
}
if (report && client.haveErrors() && flags.isSetExitCode()) {
- throw new GradleException("Lint found errors with abortOnError=true; aborting build.")
+ abort()
}
return warnings;
}
+ private static syncOptions(
+ @NonNull LintOptionsImpl options,
+ @NonNull LintGradleClient client,
+ @NonNull LintCliFlags flags,
+ @NonNull String variantName,
+ @NonNull Project project,
+ boolean report,
+ boolean fatalOnly) {
+ options.syncTo(client, flags, variantName, project, report)
+
+ if (fatalOnly) {
+ for (Reporter reporter : flags.getReporters()) {
+ reporter.setDisplayEmpty(false)
+ }
+ }
+ }
+
private static AndroidProject createAndroidProject(@NonNull Project gradleProject) {
String modelName = AndroidProject.class.getName()
ModelBuilder builder = new ModelBuilder()
return (AndroidProject) builder.buildAll(modelName, gradleProject)
}
+
+ private static BuiltinIssueRegistry createIssueRegistry() {
+ return new LintGradleIssueRegistry()
+ }
+
+ // Issue registry when Lint is run inside Gradle: we replace the Gradle
+ // detector with a local implementation which directly references Groovy
+ // for parsing. In Studio on the other hand, the implementation is replaced
+ // by a PSI-based check. (This is necessary for now since we don't have a
+ // tool-agnostic API for the Groovy AST and we don't want to add a 6.3MB dependency
+ // on Groovy itself quite yet.
+ public static class LintGradleIssueRegistry extends BuiltinIssueRegistry {
+ private boolean mInitialized;
+
+ public LintGradleIssueRegistry() {
+ }
+
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ List<Issue> issues = super.getIssues();
+ if (!mInitialized) {
+ mInitialized = true;
+ for (Issue issue : issues) {
+ if (issue.getImplementation().getDetectorClass() == GradleDetector.class) {
+ issue.setImplementation(GroovyGradleDetector.IMPLEMENTATION);
+ }
+ }
+ }
+
+ return issues;
+ }
+ }
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ManifestProcessorTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ManifestProcessorTask.groovy
new file mode 100644
index 0000000..7c5000c
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ManifestProcessorTask.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.google.common.base.Function
+import com.google.common.base.Joiner
+import com.google.common.collect.Iterables
+import com.google.common.collect.Lists
+import org.gradle.api.tasks.OutputFile
+/**
+ * A task that processes the manifest
+ */
+public abstract class ManifestProcessorTask extends IncrementalTask {
+
+ // ----- PUBLIC TASK API -----
+
+ /**
+ * The processed Manifest.
+ */
+ @OutputFile
+ File manifestOutputFile
+
+ /**
+ * Serialize a map key+value pairs into a comma separated list. Map elements are sorted to
+ * ensure stability between instances.
+ * @param mapToSerialize the map to serialize.
+ */
+ protected String serializeMap(Map<String, String> mapToSerialize) {
+ Joiner keyValueJoiner = Joiner.on(":");
+ // transform the map on a list of key:value items, sort it and concatenate it.
+ return Joiner.on(",").join(
+ Lists.newArrayList(Iterables.transform(
+ mapToSerialize.entrySet(),
+ new Function<Map.Entry<String, String>, String>() {
+
+ @Override
+ public String apply(final Map.Entry<String, String> input) {
+ return keyValueJoiner.join(input.getKey(), input.getValue());
+ }
+ })).sort())
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/MergeManifests.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/MergeManifests.groovy
new file mode 100644
index 0000000..d010954
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/MergeManifests.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 com.android.builder.core.VariantConfiguration
+import com.android.manifmerger.ManifestMerger2
+import com.google.common.collect.Lists
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Optional
+
+/**
+ * A task that processes the manifest
+ */
+public class MergeManifests extends ManifestProcessorTask {
+
+ // ----- PRIVATE TASK API -----
+ @InputFile
+ File getMainManifest() {
+ return variantConfiguration.getMainManifest();
+ }
+
+ @InputFiles
+ List<File> getManifestOverlays() {
+ return variantConfiguration.getManifestOverlays();
+ }
+
+ @Input @Optional
+ String getPackageOverride() {
+ return variantConfiguration.getIdOverride();
+ }
+
+ @Input
+ int getVersionCode() {
+ variantConfiguration.getVersionCode();
+ }
+
+ @Input @Optional
+ String getVersionName() {
+ variantConfiguration.getVersionName();
+ }
+
+ @Input @Optional
+ String minSdkVersion
+
+ @Input @Optional
+ String targetSdkVersion
+
+ /**
+ * Return a serializable version of our map of key value pairs for placeholder substitution.
+ * This serialized form is only used by gradle to compare past and present tasks to determine
+ * whether a task need to be re-run or not.
+ */
+ @Input @Optional
+ String getManifestPlaceholders() {
+
+ return serializeMap(variantConfiguration.getMergedFlavor().getManifestPlaceholders());
+ }
+
+ VariantConfiguration variantConfiguration
+ List<ManifestDependencyImpl> libraries
+
+ /**
+ * since libraries above can't return it's input files (@Nested doesn't
+ * work on lists), so do a method that will gather them and return them.
+ */
+ @InputFiles
+ List<File> getLibraryManifests() {
+ List<ManifestDependencyImpl> libs = getLibraries()
+ if (libs == null || libs.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<File> files = Lists.newArrayListWithCapacity(libs.size() * 2)
+ for (ManifestDependencyImpl mdi : libs) {
+ files.addAll(mdi.getAllManifests())
+ }
+
+ return files;
+ }
+
+ @Override
+ protected void doFullTaskAction() {
+
+ getBuilder().mergeManifests(
+ getMainManifest(),
+ getManifestOverlays(),
+ getLibraries(),
+ getPackageOverride(),
+ getVersionCode(),
+ getVersionName(),
+ getMinSdkVersion(),
+ getTargetSdkVersion(),
+ getManifestOutputFile().absolutePath,
+ ManifestMerger2.MergeType.APPLICATION,
+ variantConfiguration.getMergedFlavor().getManifestPlaceholders())
+ }
+}
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
index b6487df..0e0407d 100644
--- 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
@@ -15,8 +15,9 @@
*/
package com.android.build.gradle.tasks
-import com.android.build.gradle.LibraryPlugin
import com.android.build.gradle.internal.tasks.IncrementalTask
+import com.android.builder.internal.JavaPngCruncher
+import com.android.ide.common.internal.PngCruncher
import com.android.ide.common.res2.FileStatus
import com.android.ide.common.res2.FileValidity
import com.android.ide.common.res2.MergedResourceWriter
@@ -45,6 +46,12 @@
@Input
boolean process9Patch
+ @Input
+ boolean useAaptCruncher
+
+ @Input
+ boolean insertSourceMarkers = true
+
// actual inputs
List<ResourceSet> inputResourceSets
@@ -55,6 +62,10 @@
return true
}
+ private PngCruncher getCruncher() {
+ return getUseAaptCruncher() ? builder.aaptCruncher : new JavaPngCruncher()
+ }
+
@Override
protected void doFullTaskAction() {
// this is full run, clean the previous output
@@ -75,8 +86,8 @@
// get the merged set and write it down.
MergedResourceWriter writer = new MergedResourceWriter(
- destinationDir, getProcess9Patch() ? builder.aaptRunner : null)
- writer.setInsertSourceMarkers(builder.isInsertSourceMarkers())
+ destinationDir, getProcess9Patch() ? getCruncher() : null)
+ writer.setInsertSourceMarkers(getInsertSourceMarkers())
merger.mergeData(writer, false /*doCleanUp*/)
@@ -134,8 +145,8 @@
}
MergedResourceWriter writer = new MergedResourceWriter(
- getOutputDir(), getProcess9Patch() ? builder.aaptRunner : null)
- writer.setInsertSourceMarkers(builder.isInsertSourceMarkers())
+ getOutputDir(), getProcess9Patch() ? getCruncher() : null)
+ writer.setInsertSourceMarkers(getInsertSourceMarkers())
merger.mergeData(writer, false /*doCleanUp*/)
// No exception? Write the known state.
merger.writeBlobTo(getIncrementalFolder(), writer)
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
index eba8d72..f69b3ca 100644
--- 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
@@ -15,6 +15,7 @@
*/
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
@@ -32,8 +33,10 @@
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.gradle.api.tasks.util.PatternSet
-/**
- */
+
+import static com.android.SdkConstants.CURRENT_PLATFORM
+import static com.android.SdkConstants.PLATFORM_WINDOWS
+
class NdkCompile extends NdkTask {
List<File> sourceFolders
@@ -53,6 +56,9 @@
@Input
boolean ndkRenderScriptMode
+ @Input
+ boolean ndkCygwinMode
+
@InputFiles
FileTree getSource() {
FileTree src = null
@@ -77,9 +83,13 @@
return
}
- File ndkDirectory = getPlugin().ndkDirectory
+ File ndkDirectory = getPlugin().ndkFolder
if (ndkDirectory == null || !ndkDirectory.isDirectory()) {
- throw new GradleException("NDK not configured")
+ throw new GradleException(
+ "NDK not configured.\n" +
+ "Download the NDK from http://developer.android.com/tools/sdk/ndk/." +
+ "Then add ndk.dir=path/to/ndk in local.properties.\n" +
+ "(On Windows, make sure you escape backslashes, e.g. C:\\\\ndk rather than C:\\ndk)");
}
boolean generateMakefile = false
@@ -177,14 +187,18 @@
List<String> commands = Lists.newArrayList()
- commands.add(ndkLocation.absolutePath + File.separator + "ndk-build")
+ String exe = ndkLocation.absolutePath + File.separator + "ndk-build"
+ if (CURRENT_PLATFORM == PLATFORM_WINDOWS && !ndkCygwinMode) {
+ exe += ".cmd"
+ }
+ commands.add(exe)
commands.add("NDK_PROJECT_PATH=null")
commands.add("APP_BUILD_SCRIPT=" + makefile.absolutePath)
// target
- IAndroidTarget target = getPlugin().loadedSdkParser.target
+ IAndroidTarget target = getBuilder().getTarget()
if (!target.isPlatform()) {
target = target.parent
}
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
index de6d159..866b4e0 100644
--- 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
@@ -15,6 +15,7 @@
*/
package com.android.build.gradle.tasks
+import com.android.build.gradle.internal.dsl.PackagingOptionsImpl
import com.android.build.gradle.internal.dsl.SigningConfigDsl
import com.android.build.gradle.internal.tasks.IncrementalTask
import com.android.build.gradle.internal.tasks.OutputFileTask
@@ -36,8 +37,8 @@
@InputFile
File resourceFile
- @InputFile
- File dexFile
+ @InputDirectory
+ File dexFolder
@InputDirectory @Optional
File javaResourceDir
@@ -53,7 +54,7 @@
// ----- PRIVATE TASK API -----
@InputFiles
- List<File> packagedJars
+ Set<File> packagedJars
@Input
boolean jniDebugBuild
@@ -61,6 +62,9 @@
@Nested @Optional
SigningConfigDsl signingConfig
+ @Nested
+ PackagingOptionsImpl packagingOptions
+
@InputFiles
public FileTree getNativeLibraries() {
FileTree src = null
@@ -76,13 +80,14 @@
try {
getBuilder().packageApk(
getResourceFile().absolutePath,
- getDexFile().absolutePath,
+ getDexFolder(),
getPackagedJars(),
getJavaResourceDir()?.absolutePath,
getJniFolders(),
getAbiFilters(),
getJniDebugBuild(),
getSigningConfig(),
+ getPackagingOptions(),
getOutputFile().absolutePath)
} catch (DuplicateFileException e) {
def logger = getLogger()
@@ -90,6 +95,12 @@
logger.error("\tPath in archive: " + e.archivePath)
logger.error("\tOrigin 1: " + e.file1)
logger.error("\tOrigin 2: " + e.file2)
+ logger.error("You can ignore those files in your build.gradle:")
+ logger.error("\tandroid {")
+ logger.error("\t packagingOptions {")
+ logger.error("\t exclude '$e.archivePath'")
+ logger.error("\t }")
+ logger.error("\t}")
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
index 6a464fe..73a792f 100644
--- 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
@@ -18,16 +18,25 @@
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.android.builder.core.AndroidBuilder
+import com.android.builder.core.DexOptions
+import com.android.ide.common.internal.WaitableExecutor
+import com.google.common.base.Charsets
+import com.google.common.collect.ImmutableList
+import com.google.common.collect.ImmutableSet
+import com.google.common.collect.Sets
import com.google.common.hash.HashCode
import com.google.common.hash.HashFunction
import com.google.common.hash.Hashing
+import com.google.common.io.Files
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
+import org.gradle.api.tasks.incremental.InputFileDetails
+
+import java.util.concurrent.Callable
public class PreDex extends BaseTask {
@@ -39,7 +48,7 @@
// in the class uses it.
@SuppressWarnings("GroovyUnusedDeclaration")
@InputFiles
- Iterable<File> inputFiles
+ Collection<File> inputFiles
@OutputDirectory
File outputFolder
@@ -58,14 +67,42 @@
emptyFolder(outFolder)
}
- AndroidBuilder builder = getBuilder()
+ final AndroidBuilder builder = getBuilder()
+ final Set<String> hashs = Sets.newHashSet()
+ final WaitableExecutor<Void> executor = new WaitableExecutor<Void>()
+ final ImmutableSet.Builder<File> inputFileDetailses = ImmutableSet.builder()
- taskInputs.outOfDate { change ->
+ taskInputs.outOfDate { final change ->
+ inputFileDetailses.add(change.file)
+ }
- //noinspection GroovyAssignabilityCheck
- File preDexedFile = getDexFileName(outFolder, change.file)
- //noinspection GroovyAssignabilityCheck
- builder.preDexLibrary(change.file, preDexedFile, options)
+ for (final File f : inputFileDetailses.build()) {
+ executor.execute(new Callable<Void>() {
+ // this assignement should not be necessary, however groovy 2.3.2 seems
+ // quite lost with multithreading and access to contextual final variables.
+ final File fileToProcess = f;
+
+ @Override
+ public Void call() throws Exception {
+ // TODO remove once we can properly add a library as a dependency of its test.
+ String hash = getFileHash(fileToProcess)
+
+ synchronized (hashs) {
+ if (hashs.contains(hash)) {
+ return null
+ }
+
+ hashs.add(hash)
+ }
+
+ //noinspection GroovyAssignabilityCheck
+ File preDexedFile = getDexFileName(outFolder, fileToProcess)
+ //noinspection GroovyAssignabilityCheck
+ builder.preDexLibrary(fileToProcess, preDexedFile, options)
+
+ return null
+ }
+ });
}
taskInputs.removed { change ->
@@ -73,6 +110,18 @@
File preDexedFile = getDexFileName(outFolder, change.file)
preDexedFile.delete()
}
+
+ executor.waitForTasksWithQuickFail(false)
+ }
+
+ /**
+ * Returns the hash of a file.
+ * @param file the file to hash
+ * @return
+ */
+ private static String getFileHash(@NonNull File file) {
+ HashCode hashCode = Files.hash(file, Hashing.sha1())
+ return hashCode.toString()
}
/**
@@ -80,24 +129,25 @@
* if there are 2 libraries with the same file names (but different
* paths)
*
- * @param outFolder
+ * @param outFolder the output folder.
* @param inputFile the library
* @return
*/
@NonNull
private static File getDexFileName(@NonNull File outFolder, @NonNull File inputFile) {
// get the filename
- String name = inputFile.getName();
+ String name = inputFile.getName()
// remove the extension
- int pos = name.lastIndexOf('.');
+ int pos = name.lastIndexOf('.')
if (pos != -1) {
- name = name.substring(0, pos);
+ name = name.substring(0, pos)
}
- // add a hash of the original file path
- HashFunction hashFunction = Hashing.md5();
- HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath());
+ // add a hash of the original file path.
+ String input = inputFile.getAbsolutePath();
+ HashFunction hashFunction = Hashing.sha1()
+ HashCode hashCode = hashFunction.hashString(input, Charsets.UTF_16LE)
- return new File(outFolder, name + "-" + hashCode.toString() + SdkConstants.DOT_JAR);
+ 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
index 3c981ac..acfdf97 100644
--- 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
@@ -17,7 +17,7 @@
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 com.android.builder.core.VariantConfiguration
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
@@ -62,6 +62,9 @@
@Input @Optional
String packageForR
+ @Input
+ boolean enforceUniquePackageName
+
// this doesn't change from one build to another, so no need to annotate
VariantConfiguration.Type type
@@ -92,6 +95,8 @@
getType(),
getDebuggable(),
getAaptOptions(),
- getResourceConfigs())
+ getResourceConfigs(),
+ getEnforceUniquePackageName()
+ )
}
}
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
index 72ddcd8..e68d7bc 100644
--- 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
@@ -15,15 +15,15 @@
*/
package com.android.build.gradle.tasks
import com.android.build.gradle.internal.dependency.ManifestDependencyImpl
+import com.google.common.collect.Lists
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 {
+public class ProcessAppManifest extends ManifestProcessorTask {
// ----- PRIVATE TASK API -----
@@ -33,7 +33,6 @@
@InputFiles
List<File> manifestOverlays
- @Nested
List<ManifestDependencyImpl> libraries
@Input @Optional
@@ -45,11 +44,30 @@
@Input @Optional
String versionName
- @Input
- int minSdkVersion
+ @Input @Optional
+ String minSdkVersion
- @Input
- int targetSdkVersion
+ @Input @Optional
+ String targetSdkVersion
+
+ /*
+ * since libraries above can't return it's input files (@Nested doesn't
+ * work on lists), so do a method that will gather them and return them.
+ */
+ @InputFiles
+ List<File> getLibraryManifests() {
+ List<ManifestDependencyImpl> libs = getLibraries()
+ if (libs == null || libs.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<File> files = Lists.newArrayListWithCapacity(libs.size() * 2)
+ for (ManifestDependencyImpl mdi : libs) {
+ files.addAll(mdi.getAllManifests())
+ }
+
+ return files;
+ }
@Override
protected void doFullTaskAction() {
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
index 03d0d20..6f0a5d4 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,20 +13,79 @@
* 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
+
+import com.android.builder.core.VariantConfiguration
+import com.android.manifmerger.ManifestMerger2
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Optional
+
/**
- * A task that processes the manifest
+ * a Task that only merge a single manifest with its overlays.
*/
-public abstract class ProcessManifest extends IncrementalTask {
+class ProcessManifest extends ManifestProcessorTask {
// ----- PUBLIC TASK API -----
+ @Input @Optional
+ String minSdkVersion
+
+ @Input @Optional
+ String targetSdkVersion
+
+ VariantConfiguration variantConfiguration
+
+ @InputFile
+ File getMainManifest() {
+ return variantConfiguration.getMainManifest();
+ }
+
+ @Input @Optional
+ String getPackageOverride() {
+ return variantConfiguration.getApplicationId();
+ }
+
+ @Input
+ int getVersionCode() {
+ variantConfiguration.getVersionCode();
+ }
+
+ @Input @Optional
+ String getVersionName() {
+ variantConfiguration.getVersionName();
+ }
+
+ @InputFiles
+ List<File> getManifestOverlays() {
+ return variantConfiguration.getManifestOverlays();
+ }
/**
- * The processed Manifest.
+ * Return a serializable version of our map of key value pairs for placeholder substitution.
+ * This serialized form is only used by gradle to compare past and present tasks to determine
+ * whether a task need to be re-run or not.
*/
- @OutputFile
- File manifestOutputFile
+ @Input @Optional
+ String getManifestPlaceholders() {
+ return serializeMap(variantConfiguration.getMergedFlavor().getManifestPlaceholders());
+ }
+ @Override
+ protected void doFullTaskAction() {
+
+ getBuilder().mergeManifests(
+ getMainManifest(),
+ getManifestOverlays(),
+ Collections.emptyList(),
+ getPackageOverride(),
+ getVersionCode(),
+ getVersionName(),
+ getMinSdkVersion(),
+ getTargetSdkVersion(),
+ getManifestOutputFile().absolutePath,
+ ManifestMerger2.MergeType.LIBRARY,
+ variantConfiguration.getMergedFlavor().getManifestPlaceholders())
+ }
}
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
index 6a24155..8e565b1 100644
--- 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
@@ -14,29 +14,30 @@
* limitations under the License.
*/
package com.android.build.gradle.tasks
-
import com.android.build.gradle.internal.dependency.ManifestDependencyImpl
+import com.google.common.collect.Lists
import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.Nested
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Optional
/**
* A task that processes the manifest
*/
-public class ProcessTestManifest extends ProcessManifest {
+public class ProcessTestManifest extends ManifestProcessorTask {
// ----- PRIVATE TASK API -----
@Input
- String testPackageName
+ String testApplicationId
+
+ @Input @Optional
+ String minSdkVersion
+
+ @Input @Optional
+ String targetSdkVersion
@Input
- int minSdkVersion
-
- @Input
- int targetSdkVersion
-
- @Input
- String testedPackageName
+ String testedApplicationId
@Input
String instrumentationRunner
@@ -47,21 +48,66 @@
@Input
Boolean functionalTest;
- @Nested
List<ManifestDependencyImpl> libraries
+ // ---------------
+ // TEMP for compatibility
+ // STOPSHIP Remove in 1.0
+
+ // Deprecated; will be removed; use testApplicationId instead!
+ @Input @Optional
+ String testPackageName
+
+ // Deprecated; will be removed; use testedApplicationId instead!
+ @Input @Optional
+ String testedPackageName
+
+ /*
+ * since libraries above can't return it's input files (@Nested doesn't
+ * work on lists), so do a method that will gather them and return them.
+ */
+ @InputFiles
+ List<File> getLibraryManifests() {
+ List<ManifestDependencyImpl> libs = getLibraries()
+ if (libs == null || libs.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<File> files = Lists.newArrayListWithCapacity(libs.size() * 2)
+ for (ManifestDependencyImpl mdi : libs) {
+ files.addAll(mdi.getAllManifests())
+ }
+
+ return files;
+ }
+
@Override
protected void doFullTaskAction() {
+ migrateProperties()
+
getBuilder().processTestManifest(
- getTestPackageName(),
+ getTestApplicationId(),
getMinSdkVersion(),
getTargetSdkVersion(),
- getTestedPackageName(),
+ getTestedApplicationId(),
getInstrumentationRunner(),
getHandleProfiling(),
getFunctionalTest(),
getLibraries(),
- getManifestOutputFile().absolutePath)
+ getManifestOutputFile())
}
+ protected void migrateProperties() {
+ if (getTestApplicationId() == null && getTestPackageName() != null) {
+ logger.warn(
+ "WARNING: testPackageName is deprecated; change to \"testApplicationId\" instead");
+ testApplicationId = getTestPackageName();
+ }
+
+ if (getTestedApplicationId() == null && getTestedPackageName() != null) {
+ logger.warn(
+ "WARNING: testedPackageName is deprecated; change to \"testedApplicationId\" instead");
+ testedApplicationId = getTestedPackageName();
+ }
+ }
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest2.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest2.groovy
new file mode 100644
index 0000000..e5ad292
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest2.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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
+/**
+ * A task that processes the manifest
+ */
+public class ProcessTestManifest2 extends ProcessTestManifest {
+
+ @Override
+ protected void doFullTaskAction() {
+ migrateProperties()
+
+ getBuilder().processTestManifest2(
+ getTestApplicationId(),
+ getMinSdkVersion(),
+ getTargetSdkVersion(),
+ getTestedApplicationId(),
+ getInstrumentationRunner(),
+ getHandleProfiling(),
+ getFunctionalTest(),
+ getLibraries(),
+ getManifestOutputFile())
+ }
+}
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
index a5f7986..0be28bb 100644
--- 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
@@ -50,7 +50,7 @@
List<File> importDirs
@Input
- int targetApi
+ Integer targetApi
@Input
boolean supportMode
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/annotations/ApiDatabase.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/annotations/ApiDatabase.java
new file mode 100644
index 0000000..bdd5991
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/annotations/ApiDatabase.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.annotations;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Reads a signature file in the format of the new API files in frameworks/base/api */
+public class ApiDatabase {
+ @NonNull
+ private final List<String> lines;
+ /** Map from class name to set of field names */
+ @NonNull private final Map<String,Set<String>> fieldMap =
+ Maps.newHashMapWithExpectedSize(1000);
+ /** Map from class name to map of method names whose values are overloaded signatures */
+ @NonNull private final Map<String,Map<String,List<String>>> methodMap =
+ Maps.newHashMapWithExpectedSize(1000);
+ @NonNull private final Map<String, List<String>> inheritsFrom =
+ Maps.newHashMapWithExpectedSize(1000);
+
+ public ApiDatabase(@NonNull List<String> lines) {
+ this.lines = lines;
+ readApi();
+ }
+
+ public ApiDatabase(@NonNull File api) throws IOException {
+ this(Files.readLines(api, Charsets.UTF_8));
+ }
+
+ public boolean hasMethod(String className, String methodName, String arguments) {
+ Map<String, List<String>> methods = methodMap.get(className);
+ if (methods != null) {
+ List<String> strings = methods.get(methodName);
+ if (strings != null && strings.contains(arguments)) {
+ return true;
+ }
+ }
+
+ List<String> inheritsFrom = this.inheritsFrom.get(className);
+ if (inheritsFrom != null) {
+ for (String clz : inheritsFrom) {
+ if (hasMethod(clz, methodName, arguments)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public boolean hasField(String className, String fieldName) {
+ Set<String> fields = fieldMap.get(className);
+ if (fields != null && fields.contains(fieldName)) {
+ return true;
+ }
+
+ List<String> inheritsFrom = this.inheritsFrom.get(className);
+ if (inheritsFrom != null) {
+ for (String clz : inheritsFrom) {
+ if (hasField(clz, fieldName)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void readApi() {
+ String MODIFIERS =
+ "((deprecated|public|static|private|protected|final|abstract|\\s*)\\s+)*";
+ Pattern PACKAGE = Pattern.compile("package (\\S+) \\{");
+ Pattern CLASS =
+ Pattern.compile(MODIFIERS + "(class|interface|enum)\\s+(\\S+)\\s+(extends (.+))?(implements (.+))?(.*)\\{");
+ Pattern METHOD = Pattern.compile("(method|ctor)\\s+" +
+ MODIFIERS + "(.+)??\\s+(\\S+)\\s*\\((.*)\\)(.*);");
+ Pattern CTOR = Pattern.compile("(method|ctor)\\s+.*\\((.*)\\)(.*);");
+ Pattern FIELD = Pattern.compile("(enum_constant|field)\\s+" +
+ MODIFIERS + "(.+)\\s+(\\S+)\\s*;");
+
+ String currentPackage = null;
+ String currentClass = null;
+
+ for (String line : lines) {
+ line = line.trim();
+ if (line.isEmpty() || line.equals("}")) {
+ continue;
+ }
+ if (line.startsWith("method ")) {
+ Matcher matcher = METHOD.matcher(line);
+ if (!matcher.matches()) {
+ Extractor.warning("Warning: Did not match as a member: " + line);
+ } else {
+ assert currentClass != null;
+ Map<String,List<String>> memberMap = methodMap.get(currentClass);
+ if (memberMap == null) {
+ memberMap = Maps.newHashMap();
+ methodMap.put(currentClass, memberMap);
+ }
+ String methodName = matcher.group(5);
+ List<String> signatures = memberMap.get(methodName);
+ if (signatures == null) {
+ signatures = Lists.newArrayList();
+ memberMap.put(methodName, signatures);
+ }
+ String signature = matcher.group(6);
+ signature = signature.trim().replace(" ", "").replace(" ", "");
+ signatures.add(signature);
+ }
+ } else if (line.startsWith("ctor ")) {
+ Matcher matcher = CTOR.matcher(line);
+ if (!matcher.matches()) {
+ Extractor.warning("Warning: Did not match as a member: " + line);
+ } else {
+ assert currentClass != null;
+ Map<String,List<String>> memberMap = methodMap.get(currentClass);
+ if (memberMap == null) {
+ memberMap = Maps.newHashMap();
+ methodMap.put(currentClass, memberMap);
+ }
+ @SuppressWarnings("UnnecessaryLocalVariable")
+ String methodName = currentClass;
+ List<String> signatures = memberMap.get(methodName);
+ if (signatures == null) {
+ signatures = Lists.newArrayList();
+ memberMap.put(methodName, signatures);
+ memberMap.put(methodName.substring(methodName.lastIndexOf('.') + 1),
+ signatures);
+ }
+ String signature = matcher.group(2);
+ signature = signature.trim().replace(" ", "").replace(" ", "");
+ signatures.add(signature);
+ }
+ } else if (line.startsWith("enum_constant ") || line.startsWith("field ")) {
+ int equals = line.indexOf('=');
+ if (equals != -1) {
+ line = line.substring(0, equals).trim();
+ int semi = line.indexOf(';');
+ if (semi == -1) {
+ line = line + ';';
+ }
+ } else if (!line.endsWith(";")) {
+ int semi = line.indexOf(';');
+ if (semi != -1) {
+ line = line.substring(0, semi + 1);
+ }
+ }
+ Matcher matcher = FIELD.matcher(line);
+ if (!matcher.matches()) {
+ Extractor.warning("Warning: Did not match as a member: " + line);
+ } else {
+ assert currentClass != null;
+ String fieldName = matcher.group(5);
+ Set<String> fieldSet = fieldMap.get(currentClass);
+ if (fieldSet == null) {
+ fieldSet = Sets.newHashSet();
+ fieldMap.put(currentClass, fieldSet);
+ }
+ fieldSet.add(fieldName);
+ }
+ } else if (line.startsWith("package ")) {
+ Matcher matcher = PACKAGE.matcher(line);
+ if (!matcher.matches()) {
+ Extractor.warning("Warning: Did not match as a package: " + line);
+ } else {
+ currentPackage = matcher.group(1);
+ }
+ } else {
+ Matcher matcher = CLASS.matcher(line);
+ if (!matcher.matches()) {
+ Extractor.warning("Warning: Did not match as a class/interface: " + line);
+ } else {
+ currentClass = currentPackage + '.' + matcher.group(4);
+
+ String superClass = matcher.group(6);
+ if (superClass != null) {
+ Splitter splitter = Splitter.on(' ').trimResults().omitEmptyStrings();
+ for (String from : splitter.split(superClass)) {
+ if (from.equals("implements")) { // workaround for broken regexp
+ continue;
+ }
+ addInheritsFrom(currentClass, from);
+ }
+ addInheritsFrom(currentClass, superClass.trim());
+ }
+ String implementsList = matcher.group(8);
+ if (implementsList != null) {
+ Splitter splitter = Splitter.on(' ').trimResults().omitEmptyStrings();
+ for (String from : splitter.split(implementsList)) {
+ addInheritsFrom(currentClass, from);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void addInheritsFrom(String cls, String inheritsFrom) {
+ List<String> list = this.inheritsFrom.get(cls);
+ if (list == null) {
+ list = Lists.newArrayList();
+ this.inheritsFrom.put(cls, list);
+ }
+ list.add(inheritsFrom);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java
new file mode 100644
index 0000000..dafb471
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java
@@ -0,0 +1,1796 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.annotations;
+
+import static com.android.SdkConstants.AMP_ENTITY;
+import static com.android.SdkConstants.APOS_ENTITY;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.GT_ENTITY;
+import static com.android.SdkConstants.INT_DEF_ANNOTATION;
+import static com.android.SdkConstants.LT_ENTITY;
+import static com.android.SdkConstants.QUOT_ENTITY;
+import static com.android.SdkConstants.STRING_DEF_ANNOTATION;
+import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX;
+import static com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE;
+import static com.android.SdkConstants.TYPE_DEF_VALUE_ATTRIBUTE;
+import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+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 com.google.common.xml.XmlEscapers;
+
+import org.eclipse.jdt.internal.compiler.ASTVisitor;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.NameReference;
+import org.eclipse.jdt.internal.compiler.ast.NumberLiteral;
+import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
+import org.eclipse.jdt.internal.compiler.lookup.Binding;
+import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
+import org.eclipse.jdt.internal.compiler.lookup.Scope;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+
+/**
+ * Annotation extractor which looks for annotations in parsed compilation units and writes
+ * the annotations into a format suitable for use by IntelliJ and Android Studio etc;
+ * it's basically an XML file, organized by package, which lists the signatures for
+ * fields and methods in classes in the given package, and identifiers method parameters
+ * by index, and lists the annotations annotated on that element.
+ * <p>
+ * This is primarily intended for use in Android libraries such as the support library,
+ * where you want to use the resource int ({@code StringRes}, {@code DrawableRes}, and so on)
+ * annotations to indicate what types of id's are expected, or the {@code IntDef} or
+ * {@code StringDef} annotations to record which specific constants are allowed in int and
+ * String parameters.
+ * <p>
+ * However, the code is also used to extract SDK annotations from the platform, where
+ * the package names of the annotations differ slightly (and where the nullness annotations
+ * do not have class retention for example). Therefore, this code contains some extra
+ * support not needed when extracting annotations in an Android library, such as code
+ * to skip annotations for any method/field not mentioned in the API database, and code
+ * to rewrite the android.jar file to insert annotations in the generated bytecode.
+ * <p>
+ * TODO:
+ * - Warn if the {@code @IntDef} annotation is used on a non-int, and similarly if
+ * {@code @StringDef} is used on a non-string
+ * - Ignore annotations defined on @hide elements
+ */
+public class Extractor {
+ /**
+ * Whether we should include class-retention annotations into the extracted file;
+ * we don't need {@code android.support.annotation.Nullable} to be in the extracted XML
+ * file since it has class retention and will appear in the compiled .jar version of
+ * the library
+ */
+ private static final boolean INCLUDE_CLASS_RETENTION_ANNOTATIONS = false;
+
+ /**
+ * Whether we should skip nullable annotations in merged in annotations zip files
+ * (these are typically from infer nullity, which sometimes is a bit aggressive
+ * in assuming something should be marked as nullable; see for example issue #66999
+ * or all the manual removals of findViewById @Nullable return value annotations
+ */
+ private static final boolean INCLUDE_INFERRED_NULLABLE = false;
+
+ public static final String ANDROID_ANNOTATIONS_PREFIX = "android.annotation.";
+ public static final String ANDROID_NULLABLE = "android.annotation.Nullable";
+ public static final String SUPPORT_NULLABLE = "android.support.annotation.Nullable";
+ public static final String RESOURCE_TYPE_ANNOTATIONS_SUFFIX = "Res";
+ public static final String ANDROID_NOTNULL = "android.annotation.NonNull";
+ public static final String SUPPORT_NOTNULL = "android.support.annotation.NonNull";
+ public static final String ANDROID_INT_DEF = "android.annotation.IntDef";
+ public static final String ANDROID_STRING_DEF = "android.annotation.StringDef";
+ public static final String IDEA_NULLABLE = "org.jetbrains.annotations.Nullable";
+ public static final String IDEA_NOTNULL = "org.jetbrains.annotations.NotNull";
+ public static final String IDEA_MAGIC = "org.intellij.lang.annotations.MagicConstant";
+ public static final String IDEA_CONTRACT = "org.jetbrains.annotations.Contract";
+ public static final String IDEA_NON_NLS = "org.jetbrains.annotations.NonNls";
+
+ @NonNull
+ private final Map<String, AnnotationData> types = Maps.newHashMap();
+
+ @NonNull
+ private final Set<String> irrelevantAnnotations = Sets.newHashSet();
+
+ private final File classDir;
+
+ @NonNull
+ private Map<String, Map<String, List<Item>>> itemMap = Maps.newHashMap();
+
+ @Nullable
+ private ApiDatabase apiFilter;
+
+ private Map<String,Integer> stats = Maps.newHashMap();
+ private int filteredCount;
+ private int mergedCount;
+ private Set<CompilationUnitDeclaration> processedFiles = Sets.newHashSetWithExpectedSize(100);
+ private Set<String> ignoredAnnotations = Sets.newHashSet();
+ private boolean listIgnored;
+ private Map<String,Annotation> typedefs;
+ private List<File> classFiles;
+
+ public Extractor(@Nullable ApiDatabase apiFilter, @Nullable File classDir) {
+ this.apiFilter = apiFilter;
+ this.listIgnored = apiFilter != null;
+ this.classDir = classDir;
+ }
+
+ public void extractFromProjectSource(Collection<CompilationUnitDeclaration> units) {
+ TypedefCollector collector = new TypedefCollector(units, false /*requireHide*/,
+ true /*requireSourceRetention*/);
+ typedefs = collector.getTypedefs();
+ classFiles = collector.getNonPublicTypedefClassFiles();
+
+ for (CompilationUnitDeclaration unit : units) {
+ analyze(unit);
+ }
+ }
+
+ public void removeTypedefClasses() {
+ if (classDir != null && classFiles != null && !classFiles.isEmpty()) {
+ int count = 0;
+ for (File file : classFiles) {
+ if (!file.isAbsolute()) {
+ file = new File(classDir, file.getPath());
+ }
+ if (file.exists()) {
+ boolean deleted = file.delete();
+ if (deleted) {
+ count++;
+ } else {
+ warning("Could not delete typedef class " + file.getPath());
+ }
+ }
+ }
+ display("Deleted " + count + " typedef annotation classes");
+ }
+ }
+
+ public void export(@NonNull File output) {
+ if (itemMap.isEmpty()) {
+ if (output.exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ output.delete();
+ }
+ } else if (writeOutputFile(output)) {
+ writeStats();
+ display("Annotations written to " + output);
+ }
+ }
+
+ public void writeStats() {
+ if (!stats.isEmpty()) {
+ List<String> annotations = Lists.newArrayList(stats.keySet());
+ Collections.sort(annotations, new Comparator<String>() {
+ @Override
+ public int compare(String s1, String s2) {
+ int frequency1 = stats.get(s1);
+ int frequency2 = stats.get(s2);
+ int delta = frequency2 - frequency1;
+ if (delta != 0) {
+ return delta;
+ }
+ return s1.compareTo(s2);
+ }
+ });
+ Map<String,String> fqnToName = Maps.newHashMap();
+ int max = 0;
+ int count = 0;
+ for (String fqn : annotations) {
+ String name = fqn.substring(fqn.lastIndexOf('.') + 1);
+ fqnToName.put(fqn, name);
+ max = Math.max(max, name.length());
+ count += stats.get(fqn);
+ }
+
+ StringBuilder sb = new StringBuilder(200);
+ sb.append("Extracted ").append(count).append(" Annotations:");
+ for (String fqn : annotations) {
+ sb.append('\n');
+ String name = fqnToName.get(fqn);
+ for (int i = 0, n = max - name.length() + 1; i < n; i++) {
+ sb.append(' ');
+ }
+ sb.append('@');
+ sb.append(name);
+ sb.append(':').append(' ');
+ sb.append(Integer.toString(stats.get(fqn)));
+ }
+ if (sb.length() > 0) {
+ display(sb.toString());
+ }
+ }
+
+ if (filteredCount > 0) {
+ display(filteredCount + " of these were filtered out (not in API database file)");
+ }
+ if (mergedCount > 0) {
+ display(mergedCount + " additional annotations were merged in");
+ }
+ }
+
+ @SuppressWarnings("UseOfSystemOutOrSystemErr")
+ static void display(final String message) {
+ System.out.println(message);
+ }
+
+ @SuppressWarnings("UseOfSystemOutOrSystemErr")
+ static void error(String message) {
+ System.err.println("Error: " + message);
+ }
+
+ static void warning(String message) {
+ display("Warning: " + message);
+ }
+
+ private void analyze(CompilationUnitDeclaration unit) {
+ if (processedFiles.contains(unit)) {
+ // The code to process all roots seems to hit some of the same classes
+ // repeatedly... so filter these out manually
+ return;
+ }
+ processedFiles.add(unit);
+
+ AnnotationVisitor visitor = new AnnotationVisitor();
+ unit.traverse(visitor, unit.scope);
+ }
+
+ @Nullable
+ private static ClassScope findClassScope(Scope scope) {
+ while (scope != null) {
+ if (scope instanceof ClassScope) {
+ return (ClassScope)scope;
+ }
+ scope = scope.parent;
+ }
+
+ return null;
+ }
+
+ @Nullable
+ static String getFqn(@NonNull Annotation annotation) {
+ if (annotation.resolvedType != null) {
+ return new String(annotation.resolvedType.readableName());
+ }
+ return null;
+ }
+
+ @Nullable
+ private static String getFqn(@NonNull ClassScope scope) {
+ TypeDeclaration typeDeclaration = scope.referenceType();
+ if (typeDeclaration != null && typeDeclaration.binding != null) {
+ return new String(typeDeclaration.binding.readableName());
+ }
+ return null;
+ }
+
+ @Nullable
+ private static String getFqn(@NonNull MethodScope scope) {
+ ClassScope classScope = findClassScope(scope);
+ if (classScope != null) {
+ return getFqn(classScope);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static String getFqn(@NonNull BlockScope scope) {
+ ClassScope classScope = findClassScope(scope);
+ if (classScope != null) {
+ return getFqn(classScope);
+ }
+
+ return null;
+ }
+
+ static boolean hasSourceRetention(@NonNull Annotation[] annotations) {
+ for (Annotation annotation : annotations) {
+ String typeName = Extractor.getFqn(annotation);
+ if ("java.lang.annotation.Retention".equals(typeName)) {
+ MemberValuePair[] pairs = annotation.memberValuePairs();
+ if (pairs == null || pairs.length != 1) {
+ warning("Expected exactly one parameter passed to @Retention");
+ return false;
+ }
+ MemberValuePair pair = pairs[0];
+ Expression value = pair.value;
+ if (value instanceof NameReference) {
+ NameReference reference = (NameReference) value;
+ Binding binding = reference.binding;
+ if (binding != null) {
+ if (binding instanceof FieldBinding) {
+ FieldBinding fb = (FieldBinding) binding;
+ if ("SOURCE".equals(new String(fb.name)) &&
+ "java.lang.annotation.RetentionPolicy".equals(
+ new String(fb.declaringClass.readableName()))) {
+ return true;
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void addAnnotations(@Nullable Annotation[] annotations, @NonNull Item item) {
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ AnnotationData annotationData = createAnnotation(annotation);
+ if (annotationData != null) {
+ item.annotations.add(annotationData);
+ }
+ }
+ }
+ }
+
+ @Nullable
+ private AnnotationData createAnnotation(@NonNull Annotation annotation) {
+ String fqn = getFqn(annotation);
+ if (fqn == null) {
+ return null;
+ }
+
+ if (fqn.equals(ANDROID_NULLABLE) || fqn.equals(SUPPORT_NULLABLE)) {
+ recordStats(fqn);
+ return new AnnotationData(SUPPORT_NULLABLE);
+ }
+
+ if (fqn.equals(ANDROID_NOTNULL) || fqn.equals(SUPPORT_NOTNULL)) {
+ recordStats(fqn);
+ return new AnnotationData(SUPPORT_NOTNULL);
+ }
+
+ if (fqn.startsWith(SUPPORT_ANNOTATIONS_PREFIX)
+ && fqn.endsWith(RESOURCE_TYPE_ANNOTATIONS_SUFFIX)) {
+ recordStats(fqn);
+ return new AnnotationData(fqn);
+ }
+
+ AnnotationData typedef = types.get(fqn);
+ if (typedef != null) {
+ return typedef;
+ }
+
+ boolean intDef = fqn.equals(ANDROID_INT_DEF) || fqn.equals(INT_DEF_ANNOTATION);
+ boolean stringDef = fqn.equals(ANDROID_STRING_DEF) || fqn.equals(STRING_DEF_ANNOTATION);
+ if (intDef || stringDef) {
+ AtomicBoolean isFlag = new AtomicBoolean(false);
+ String constants = getAnnotationConstants(annotation, isFlag);
+ if (constants == null) {
+ return null;
+ }
+ boolean flag = intDef && isFlag.get();
+ recordStats(fqn);
+ return new AnnotationData(
+ intDef ? INT_DEF_ANNOTATION : STRING_DEF_ANNOTATION,
+ TYPE_DEF_VALUE_ATTRIBUTE, constants,
+ flag ? TYPE_DEF_FLAG_ATTRIBUTE : null, flag ? VALUE_TRUE : null);
+ }
+
+ return null;
+ }
+
+ private void recordStats(String fqn) {
+ Integer count = stats.get(fqn);
+ if (count == null) {
+ count = 0;
+ }
+ stats.put(fqn, count + 1);
+ }
+
+ @Nullable
+ private String getAnnotationConstants(
+ @NonNull Annotation annotation,
+ @NonNull AtomicBoolean flag /*out*/) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+
+ MemberValuePair[] annotationParameters = annotation.memberValuePairs();
+ if (annotationParameters != null) {
+ for (MemberValuePair pair : annotationParameters) {
+ String name = pair.name != null ? new String(pair.name) : TYPE_DEF_VALUE_ATTRIBUTE;
+ if (name.equals(TYPE_DEF_FLAG_ATTRIBUTE)) {
+ if (pair.value instanceof TrueLiteral) {
+ flag.set(true);
+ } else if (pair.value instanceof FalseLiteral) {
+ flag.set(false);
+ } else {
+ warning("Unexpected type of literal for annotation "
+ + "flag value: " + pair.value);
+ }
+
+ continue;
+ }
+ if (pair.value instanceof ArrayInitializer) {
+ ArrayInitializer arrayInitializer = (ArrayInitializer) pair.value;
+ appendConstants(sb, arrayInitializer);
+ } else {
+ warning("Unexpected type for annotation initializer " + pair.value);
+ }
+
+ }
+ }
+
+ sb.append('}');
+ return sb.toString();
+ }
+
+ private void appendConstants(StringBuilder sb, ArrayInitializer mv) {
+ boolean first = true;
+ for (Expression v : mv.expressions) {
+ if (v instanceof NameReference) {
+ NameReference reference = (NameReference) v;
+ if (reference.binding != null) {
+ if (reference.binding instanceof FieldBinding) {
+ FieldBinding fb = (FieldBinding)reference.binding;
+ if (fb.declaringClass != null) {
+ if (apiFilter != null &&
+ !apiFilter.hasField(
+ new String(fb.declaringClass.readableName()),
+ new String(fb.name))) {
+ if (isListIgnored()) {
+ display("Filtering out typedef constant "
+ + new String(fb.declaringClass.readableName()) + "."
+ + new String(fb.name) + "");
+ }
+ continue;
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append(fb.declaringClass.readableName());
+ sb.append('.');
+ sb.append(fb.name);
+ } else {
+ sb.append(reference.binding.readableName());
+ }
+ } else {
+ sb.append(reference.binding.readableName());
+ }
+ } else {
+ warning("No binding for reference " + reference);
+ }
+ } else if (v instanceof StringLiteral) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ StringLiteral s = (StringLiteral) v;
+ sb.append('"');
+ sb.append(s.source());
+ sb.append('"');
+ } else if (v instanceof NumberLiteral) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ NumberLiteral number = (NumberLiteral) v;
+ sb.append(number.source());
+ } else {
+ // BinaryExpression etc can happen if you put "3 + 4" in as an integer!
+ if (v.constant != null) {
+ if (v.constant.typeID() == TypeIds.T_int) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append(v.constant.intValue());
+ } else if (v.constant.typeID() == TypeIds.T_JavaLangString) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append('"');
+ sb.append(v.constant.stringValue());
+ sb.append('"');
+ } else {
+ warning("Unexpected type for constant " + v.constant.toString());
+ }
+ } else {
+ warning("Unexpected annotation expression of type " + v.getClass() + " and is "
+ + v);
+ }
+ }
+ }
+ }
+
+ private boolean hasRelevantAnnotations(@Nullable Annotation[] annotations) {
+ if (annotations == null) {
+ return false;
+ }
+
+ for (Annotation annotation : annotations) {
+ String fqn = getFqn(annotation);
+ if (fqn == null) {
+ continue;
+ }
+ if (fqn.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
+ //noinspection PointlessBooleanExpression,ConstantConditions,RedundantIfStatement
+ if (!INCLUDE_CLASS_RETENTION_ANNOTATIONS &&
+ (SUPPORT_NULLABLE.equals(fqn) ||
+ SUPPORT_NOTNULL.equals(fqn))) {
+ // @Nullable and @NonNull in the support package have class
+ // retention; don't include them in the side-door file!
+ return false;
+ }
+
+ return true;
+ }
+ if (fqn.equals(ANDROID_NULLABLE) || fqn.equals(ANDROID_NOTNULL)
+ || isMagicConstant(fqn)) {
+ return true;
+ } else if (fqn.equals(IDEA_CONTRACT)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ boolean isMagicConstant(String typeName) {
+ if (irrelevantAnnotations.contains(typeName)
+ || typeName.startsWith("java.lang.")) { // @Override, @SuppressWarnings, etc.
+ return false;
+ }
+ if (types.containsKey(typeName) ||
+ typeName.equals(INT_DEF_ANNOTATION) ||
+ typeName.equals(STRING_DEF_ANNOTATION) ||
+ typeName.equals(ANDROID_INT_DEF) ||
+ typeName.equals(ANDROID_STRING_DEF)) {
+ return true;
+ }
+
+ Annotation typeDef = typedefs.get(typeName);
+ // We only support a single level of IntDef type annotations, not arbitrary nesting
+ if (typeDef != null) {
+ String fqn = getFqn(typeDef);
+ if (fqn != null &&
+ (fqn.equals(INT_DEF_ANNOTATION) ||
+ fqn.equals(STRING_DEF_ANNOTATION) ||
+ fqn.equals(ANDROID_INT_DEF) ||
+ fqn.equals(ANDROID_STRING_DEF))) {
+ AnnotationData a = createAnnotation(typeDef);
+ if (a != null) {
+ types.put(typeName, a);
+ return true;
+ }
+ }
+ }
+
+
+ irrelevantAnnotations.add(typeName);
+
+ return false;
+ }
+
+ private boolean writeOutputFile(File dest) {
+ try {
+ FileOutputStream fileOutputStream = new FileOutputStream(dest);
+ JarOutputStream zos = new JarOutputStream(fileOutputStream);
+ try {
+ List<String> sortedPackages = new ArrayList<String>(itemMap.keySet());
+ Collections.sort(sortedPackages);
+ for (String pkg : sortedPackages) {
+ // Note: Using / rather than File.separator: jar lib requires it
+ String name = pkg.replace('.', '/') + "/annotations.xml";
+
+ JarEntry outEntry = new JarEntry(name);
+ zos.putNextEntry(outEntry);
+
+ StringWriter stringWriter = new StringWriter(1000);
+ PrintWriter writer = new PrintWriter(stringWriter);
+ try {
+ writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<root>");
+
+ Map<String, List<Item>> classMap = itemMap.get(pkg);
+ List<String> classes = new ArrayList<String>(classMap.keySet());
+ Collections.sort(classes);
+ for (String cls : classes) {
+ List<Item> items = classMap.get(cls);
+ Collections.sort(items);
+ for (Item item : items) {
+ item.write(writer);
+ }
+ }
+
+ writer.println("</root>\n");
+ writer.close();
+ String xml = stringWriter.toString();
+
+ // Validate
+ if (assertionsEnabled()) {
+ Document document = checkDocument(xml, false);
+ if (document == null) {
+ error("Could not parse XML document back in for entry " + name
+ + ": invalid XML?\n\"\"\"\n" + xml + "\n\"\"\"\n");
+ return false;
+ }
+ }
+
+ byte[] bytes = xml.getBytes(Charsets.UTF_8);
+ zos.write(bytes);
+ zos.closeEntry();
+ } finally {
+ writer.close();
+ }
+ }
+ } finally {
+ zos.flush();
+ zos.close();
+ }
+ } catch (IOException ioe) {
+ error(ioe.toString());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the annotation name to use, if any. The index is the parameter index, or -1
+ * for the method return value.
+ */
+ String getMethodAnnotation(@NonNull String fqn, @NonNull String name,
+ @Nullable String returnType, @NonNull String parameterList, boolean isConstructor,
+ int index) {
+ if (index == -1) {
+ MethodItem item = new MethodItem(fqn, returnType, name, parameterList,
+ isConstructor);
+ return getJarMarkerAnnotationName(findItem(fqn, item));
+ } else {
+ ParameterItem item = new ParameterItem(fqn, returnType, name, parameterList,
+ isConstructor, Integer.toString(index));
+ return getJarMarkerAnnotationName(findItem(fqn, item));
+ }
+ }
+
+ String getFieldAnnotation(@NonNull String fqn, @NonNull String name) {
+ FieldItem item = new FieldItem(fqn, name);
+ return getJarMarkerAnnotationName(findItem(fqn, item));
+ }
+
+ /** Returns the marker annotation name to write into android.jar, if any */
+ @Nullable
+ private static String getJarMarkerAnnotationName(Item item) {
+ if (item != null) {
+ for (AnnotationData annotation : item.annotations) {
+ if (annotation.name.equals(IDEA_NOTNULL)
+ || annotation.name.equals(ANDROID_NOTNULL)) {
+ return SUPPORT_NOTNULL;
+ } else if (annotation.name.equals(IDEA_NULLABLE)
+ || annotation.name.equals(ANDROID_NULLABLE)) {
+ return SUPPORT_NULLABLE;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void addItem(@NonNull String fqn, @NonNull Item item) {
+ // Not part of the API?
+ if (apiFilter != null && item.isFiltered(apiFilter)) {
+ if (isListIgnored()) {
+ display("Skipping API because it is not part of the API file: " + item);
+ }
+
+ filteredCount++;
+ return;
+ }
+
+ String pkg = getPackage(fqn);
+ Map<String, List<Item>> classMap = itemMap.get(pkg);
+ if (classMap == null) {
+ classMap = Maps.newHashMapWithExpectedSize(100);
+ itemMap.put(pkg, classMap);
+ }
+ List<Item> items = classMap.get(fqn);
+ if (items == null) {
+ items = Lists.newArrayList();
+ classMap.put(fqn, items);
+ }
+
+ items.add(item);
+ }
+
+ @Nullable
+ private Item findItem(@NonNull String fqn, @NonNull Item item) {
+ String pkg = getPackage(fqn);
+ Map<String, List<Item>> classMap = itemMap.get(pkg);
+ if (classMap == null) {
+ return null;
+ }
+ List<Item> items = classMap.get(fqn);
+ if (items == null) {
+ return null;
+ }
+ for (Item existing : items) {
+ if (existing.equals(item)) {
+ return existing;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static Document checkDocument(@NonNull String xml, boolean namespaceAware) {
+ try {
+ return XmlUtils.parseDocument(xml, namespaceAware);
+ } catch (SAXException sax) {
+ warning(sax.toString());
+ } catch (Exception e) {
+ // pass
+ // This method is deliberately silent; will return null
+ }
+
+ return null;
+ }
+
+ public void mergeExisting(@NonNull File file) {
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ mergeExisting(child);
+ }
+ }
+ } else if (file.isFile()) {
+ if (file.getPath().endsWith(DOT_JAR)) {
+ mergeFromJar(file);
+ } else if (file.getPath().endsWith(DOT_XML)) {
+ try {
+ String xml = Files.toString(file, Charsets.UTF_8);
+ mergeAnnotationsXml(xml);
+ } catch (IOException e) {
+ error("Aborting: I/O problem during transform: " + e.toString());
+ }
+ }
+ }
+ }
+
+ private void mergeFromJar(@NonNull File jar) {
+ // Reads in an existing annotations jar and merges in entries found there
+ // with the annotations analyzed from source.
+ JarInputStream zis = null;
+ try {
+ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+ FileInputStream fis = new FileInputStream(jar);
+ zis = new JarInputStream(fis);
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ if (entry.getName().endsWith(".xml")) {
+ byte[] bytes = ByteStreams.toByteArray(zis);
+ String xml = new String(bytes, Charsets.UTF_8);
+ mergeAnnotationsXml(xml);
+ }
+ entry = zis.getNextEntry();
+ }
+ } catch (IOException e) {
+ error("Aborting: I/O problem during transform: " + e.toString());
+ } finally {
+ //noinspection deprecation
+ try {
+ Closeables.close(zis, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+ }
+
+ private void mergeAnnotationsXml(@NonNull String xml) {
+ try {
+ Document document = XmlUtils.parseDocument(xml, false);
+ mergeDocument(document);
+ } catch (Exception e) {
+ warning(e.toString());
+ }
+ }
+
+ private void mergeDocument(@NonNull Document document) {
+ final Pattern XML_SIGNATURE = Pattern.compile(
+ // Class (FieldName | Type? Name(ArgList) Argnum?)
+ //"(\\S+) (\\S+|(.*)\\s+(\\S+)\\((.*)\\)( \\d+)?)");
+ "(\\S+) (\\S+|((.*)\\s+)?(\\S+)\\((.*)\\)( \\d+)?)");
+
+ Element root = document.getDocumentElement();
+ String rootTag = root.getTagName();
+ assert rootTag.equals("root") : rootTag;
+
+ for (Element item : getChildren(root)) {
+ String signature = item.getAttribute("name");
+ if (signature == null || signature.equals("null")) {
+ continue; // malformed item
+ }
+
+ if (!hasRelevantAnnotations(item)) {
+ continue;
+ }
+
+ signature = unescapeXml(signature);
+ Matcher matcher = XML_SIGNATURE.matcher(signature);
+ if (matcher.matches()) {
+ String containingClass = matcher.group(1);
+ if (containingClass == null) {
+ warning("Could not find class for " + signature);
+ }
+ String methodName = matcher.group(5);
+ if (methodName != null) {
+ String type = matcher.group(4);
+ boolean isConstructor = type == null;
+ String parameters = matcher.group(6);
+ mergeMethodOrParameter(item, matcher, containingClass, methodName, type,
+ isConstructor, parameters);
+ } else {
+ String fieldName = matcher.group(2);
+ mergeField(item, containingClass, fieldName);
+ }
+ } else {
+ if (signature.indexOf(' ') != -1 || signature.indexOf('.') == -1) {
+ warning("No merge match for signature " + signature);
+ } // else: probably just a class signature, e.g. for @NonNls
+ }
+ }
+ }
+
+ @NonNull
+ private static String unescapeXml(@NonNull String escaped) {
+ String workingString = escaped.replace(QUOT_ENTITY, "\"");
+ workingString = workingString.replace(LT_ENTITY, "<");
+ workingString = workingString.replace(GT_ENTITY, ">");
+ workingString = workingString.replace(APOS_ENTITY, "'");
+ workingString = workingString.replace(AMP_ENTITY, "&");
+
+ return workingString;
+ }
+
+ @NonNull
+ private static String escapeXml(@NonNull String unescaped) {
+ String escaped = XmlEscapers.xmlAttributeEscaper().escape(unescaped);
+ assert unescaped.equals(unescapeXml(escaped)) : unescaped + " to " + escaped;
+ return escaped;
+ }
+
+ private void mergeField(Element item, String containingClass, String fieldName) {
+ if (apiFilter != null &&
+ !apiFilter.hasField(containingClass, fieldName)) {
+ if (isListIgnored()) {
+ display("Skipping imported element because it is not part of the API file: "
+ + containingClass + "#" + fieldName);
+ }
+ filteredCount++;
+ } else {
+ FieldItem fieldItem = new FieldItem(containingClass, fieldName);
+ Item existing = findItem(containingClass, fieldItem);
+ if (existing != null) {
+ mergedCount += mergeAnnotations(item, existing);
+ } else {
+ addItem(containingClass, fieldItem);
+ mergedCount += addAnnotations(item, fieldItem);
+ }
+ }
+ }
+
+ private void mergeMethodOrParameter(Element item, Matcher matcher, String containingClass,
+ String methodName, String type, boolean constructor, String parameters) {
+ parameters = fixParameterString(parameters);
+
+ if (apiFilter != null &&
+ !apiFilter.hasMethod(containingClass, methodName, parameters)) {
+ if (isListIgnored()) {
+ display("Skipping imported element because it is not part of the API file: "
+ + containingClass + "#" + methodName + "(" + parameters + ")");
+ }
+ filteredCount++;
+ return;
+ }
+
+ String argNum = matcher.group(7);
+ if (argNum != null) {
+ argNum = argNum.trim();
+ ParameterItem parameterItem = new ParameterItem(containingClass, type,
+ methodName, parameters, constructor, argNum);
+ Item existing = findItem(containingClass, parameterItem);
+ if (existing != null) {
+ mergedCount += mergeAnnotations(item, existing);
+ } else {
+ addItem(containingClass, parameterItem);
+ mergedCount += addAnnotations(item, parameterItem);
+ }
+ } else {
+ MethodItem methodItem = new MethodItem(containingClass, type, methodName,
+ parameters, constructor);
+ Item existing = findItem(containingClass, methodItem);
+ if (existing != null) {
+ mergedCount += mergeAnnotations(item, existing);
+ } else {
+ addItem(containingClass, methodItem);
+ mergedCount += addAnnotations(item, methodItem);
+ }
+ }
+ }
+
+ // The parameter declaration used in XML files should not have duplicated spaces,
+ // and there should be no space after commas (we can't however strip out all spaces,
+ // since for example the spaces around the "extends" keyword needs to be there in
+ // types like Map<String,? extends Number>
+ private static String fixParameterString(String parameters) {
+ return parameters.replaceAll(" ", " ").replace(", ", ",");
+ }
+
+ private boolean hasRelevantAnnotations(Element item) {
+ for (Element annotationElement : getChildren(item)) {
+ if (isRelevantAnnotation(annotationElement)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ private boolean isRelevantAnnotation(Element annotationElement) {
+ AnnotationData annotation = createAnnotation(annotationElement);
+ if (isNullable(annotation.name) || isNonNull(annotation.name)
+ || annotation.name.startsWith(ANDROID_ANNOTATIONS_PREFIX)
+ || annotation.name.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
+ return true;
+ } else if (annotation.name.equals(IDEA_CONTRACT)) {
+ return true;
+ } else if (annotation.name.equals(IDEA_NON_NLS)) {
+ return false;
+ } else {
+ if (!ignoredAnnotations.contains(annotation.name)) {
+ ignoredAnnotations.add(annotation.name);
+ if (isListIgnored()) {
+ display("(Ignoring merge annotation " + annotation.name + ")");
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @NonNull
+ private static List<Element> getChildren(@NonNull Element element) {
+ NodeList itemList = element.getChildNodes();
+ int length = itemList.getLength();
+ List<Element> result = new ArrayList<Element>(Math.max(5, length / 2 + 1));
+ for (int i = 0; i < length; i++) {
+ Node node = itemList.item(i);
+ if (node.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+
+ result.add((Element) node);
+ }
+
+ return result;
+ }
+
+ private int addAnnotations(Element itemElement, Item item) {
+ int count = 0;
+ for (Element annotationElement : getChildren(itemElement)) {
+ if (!isRelevantAnnotation(annotationElement)) {
+ continue;
+ }
+ AnnotationData annotation = createAnnotation(annotationElement);
+ item.annotations.add(annotation);
+ count++;
+ }
+ return count;
+ }
+
+ private int mergeAnnotations(Element itemElement, Item item) {
+ int count = 0;
+ loop:
+ for (Element annotationElement : getChildren(itemElement)) {
+ if (!isRelevantAnnotation(annotationElement)) {
+ continue;
+ }
+ AnnotationData annotation = createAnnotation(annotationElement);
+ boolean haveNullable = false;
+ boolean haveNotNull = false;
+ for (AnnotationData existing : item.annotations) {
+ if (isNonNull(existing.name)) {
+ haveNotNull = true;
+ }
+ if (isNullable(existing.name)) {
+ haveNullable = true;
+ }
+ if (existing.equals(annotation)) {
+ continue loop;
+ }
+ }
+
+ // Make sure we don't have a conflict between nullable and not nullable
+ if (isNonNull(annotation.name) && haveNullable ||
+ isNullable(annotation.name) && haveNotNull) {
+ warning("Found both @Nullable and @NonNull after import for " + item);
+ continue;
+ }
+
+ item.annotations.add(annotation);
+ count++;
+ }
+
+ return count;
+ }
+
+ private static boolean isNonNull(String name) {
+ return name.equals(IDEA_NOTNULL)
+ || name.equals(ANDROID_NOTNULL)
+ || name.equals(SUPPORT_NOTNULL);
+ }
+
+ private static boolean isNullable(String name) {
+ return name.equals(IDEA_NULLABLE)
+ || name.equals(ANDROID_NULLABLE)
+ || name.equals(SUPPORT_NULLABLE);
+ }
+
+ private AnnotationData createAnnotation(Element annotationElement) {
+ String tagName = annotationElement.getTagName();
+ assert tagName.equals("annotation") : tagName;
+ String name = annotationElement.getAttribute("name");
+ assert name != null && !name.isEmpty();
+ AnnotationData annotation;
+ if (IDEA_MAGIC.equals(name)) {
+ List<Element> children = getChildren(annotationElement);
+ assert children.size() == 1 : children.size();
+ Element valueElement = children.get(0);
+ String valName = valueElement.getAttribute("name");
+ String value = valueElement.getAttribute("val");
+ boolean flag = valName.equals("flags");
+ if (valName.equals("valuesFromClass") || valName.equals("flagsFromClass")) {
+ // Not supported
+ return null;
+ }
+
+ //noinspection VariableNotUsedInsideIf
+ if (apiFilter != null) {
+ value = removeFiltered(value);
+ }
+
+ annotation = new AnnotationData(
+ valName.equals("stringValues") ? STRING_DEF_ANNOTATION : INT_DEF_ANNOTATION,
+ TYPE_DEF_VALUE_ATTRIBUTE, value,
+ flag ? TYPE_DEF_FLAG_ATTRIBUTE : null, flag ? VALUE_TRUE : null);
+ } else if (STRING_DEF_ANNOTATION.equals(name) || ANDROID_STRING_DEF.equals(name) ||
+ INT_DEF_ANNOTATION.equals(name) || ANDROID_INT_DEF.equals(name)) {
+ List<Element> children = getChildren(annotationElement);
+ Element valueElement = children.get(0);
+ String valName = valueElement.getAttribute("name");
+ assert TYPE_DEF_VALUE_ATTRIBUTE.equals(valName);
+ String value = valueElement.getAttribute("val");
+ boolean flag = false;
+ if (children.size() == 2) {
+ valueElement = children.get(1);
+ assert TYPE_DEF_FLAG_ATTRIBUTE.equals(valueElement.getAttribute("name"));
+ flag = VALUE_TRUE.equals(valueElement.getAttribute("val"));
+ }
+ boolean intDef = INT_DEF_ANNOTATION.equals(name) || ANDROID_INT_DEF.equals(name);
+ annotation = new AnnotationData(
+ intDef ? INT_DEF_ANNOTATION : STRING_DEF_ANNOTATION,
+ TYPE_DEF_VALUE_ATTRIBUTE, value,
+ flag ? TYPE_DEF_FLAG_ATTRIBUTE : null, flag ? VALUE_TRUE : null);
+ } else if (IDEA_CONTRACT.equals(name)) {
+ List<Element> children = getChildren(annotationElement);
+ assert children.size() == 1 : children.size();
+ Element valueElement = children.get(0);
+ String value = valueElement.getAttribute("val");
+ annotation = new AnnotationData(name, TYPE_DEF_VALUE_ATTRIBUTE, value, null, null);
+ } else if (isNonNull(name)) {
+ annotation = new AnnotationData(SUPPORT_NOTNULL);
+ } else if (isNullable(name)) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (!INCLUDE_INFERRED_NULLABLE && IDEA_NULLABLE.equals(name)) {
+ return null;
+ }
+ annotation = new AnnotationData(SUPPORT_NULLABLE);
+ } else {
+ annotation = new AnnotationData(name, null, null);
+ }
+ return annotation;
+ }
+
+ private String removeFiltered(String value) {
+ assert apiFilter != null;
+ if (value.startsWith("{")) {
+ value = value.substring(1);
+ }
+ if (value.endsWith("}")) {
+ value = value.substring(0, value.length() - 1);
+ }
+ value = value.trim();
+ StringBuilder sb = new StringBuilder(value.length());
+ sb.append('{');
+ for (String fqn : Splitter.on(',').omitEmptyStrings().trimResults().split(value)) {
+ fqn = unescapeXml(fqn);
+ if (fqn.startsWith("\"")) {
+ continue;
+ }
+ int index = fqn.lastIndexOf('.');
+ String cls = fqn.substring(0, index);
+ String field = fqn.substring(index + 1);
+ if (apiFilter.hasField(cls, field)) {
+ if (sb.length() > 1) { // 0: '{'
+ sb.append(", ");
+ }
+ sb.append(fqn);
+ } else if (isListIgnored()) {
+ display("Skipping constant from typedef because it is not part of the SDK: " + fqn);
+ }
+ }
+ sb.append('}');
+ return escapeXml(sb.toString());
+ }
+
+
+ private static String getPackage(String fqn) {
+ // Extract package from the given fqn. Attempts to handle inner classes;
+ // e.g. "foo.bar.Foo.Bar will return "foo.bar".
+ int index = 0;
+ int last = 0;
+ while (true) {
+ index = fqn.indexOf('.', index);
+ if (index == -1) {
+ break;
+ }
+ last = index;
+ if (index < fqn.length() - 1) {
+ char next = fqn.charAt(index + 1);
+ if (Character.isUpperCase(next)) {
+ break;
+ }
+ }
+ index++;
+ }
+
+ return fqn.substring(0, last);
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public void setListIgnored(boolean listIgnored) {
+ this.listIgnored = listIgnored;
+ }
+
+ public boolean isListIgnored() {
+ return listIgnored;
+ }
+
+ private static class AnnotationData {
+ @NonNull
+ public final String name;
+
+ @Nullable
+ public final String attributeName1;
+
+ @Nullable
+ public final String attributeValue1;
+
+ @Nullable
+ public final String attributeName2;
+
+ @Nullable
+ public final String attributeValue2;
+
+ private AnnotationData(@NonNull String name) {
+ this(name, null, null, null, null);
+ }
+
+ private AnnotationData(@NonNull String name, @Nullable String attributeName,
+ @Nullable String attributeValue) {
+ this(name, attributeName, attributeValue, null, null);
+ }
+
+ private AnnotationData(@NonNull String name,
+ @Nullable String attributeName1, @Nullable String attributeValue1,
+ @Nullable String attributeName2, @Nullable String attributeValue2) {
+ assert name.indexOf('.') != -1 : "Should use fully qualified name for " + name;
+ this.name = name;
+ this.attributeName1 = attributeName1;
+ this.attributeValue1 = attributeValue1;
+ this.attributeName2 = attributeName2;
+ this.attributeValue2 = attributeValue2;
+ }
+
+ void write(PrintWriter writer) {
+ writer.print(" <annotation name=\"");
+ writer.print(name);
+
+ if (attributeValue1 != null) {
+ writer.print("\">");
+ writer.println();
+ writer.print(" <val name=\"");
+ writer.print(attributeName1);
+ writer.print("\" val=\"");
+ writer.print(escapeXml(attributeValue1));
+ writer.println("\" />");
+ if (attributeValue2 != null) {
+ writer.print(" <val name=\"");
+ writer.print(attributeName2);
+ writer.print("\" val=\"");
+ writer.print(escapeXml(attributeValue2));
+ writer.println("\" />");
+ }
+
+ writer.println(" </annotation>");
+ } else {
+ writer.println("\" />");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ AnnotationData that = (AnnotationData) o;
+
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+ }
+
+ /**
+ * An item in the XML file: this corresponds to a method, a field, or a method parameter, and
+ * has an associated set of annotations
+ */
+ private abstract static class Item implements Comparable<Item> {
+
+ public final List<AnnotationData> annotations = Lists.newArrayList();
+
+ void write(PrintWriter writer) {
+ if (!isValid()) {
+ return;
+ }
+ writer.print(" <item name=\"");
+ writer.print(getSignature());
+ writer.println("\">");
+
+ // TODO: Show annotations WITH their items, if applicable. Such as in parameter lists!
+ for (AnnotationData annotation : annotations) {
+ annotation.write(writer);
+ }
+ writer.print(" </item>");
+ writer.println();
+ }
+
+ abstract boolean isValid();
+
+ abstract boolean isFiltered(@NonNull ApiDatabase database);
+
+ abstract String getSignature();
+
+ @Override
+ public int compareTo(@NonNull Item item) {
+ String signature1 = getSignature();
+ String signature2 = item.getSignature();
+
+ // IntelliJ's sorting order is not on the escaped HTML but the original
+ // signatures, which means android.os.AsyncTask<Params,Progress,Result>
+ // should appear *after* android.os.AsyncTask.Status, which when the <'s are
+ // escaped it does not
+ signature1 = signature1.replace('&', '.');
+ signature2 = signature2.replace('&', '.');
+
+ return signature1.compareTo(signature2);
+ }
+ }
+
+ private static class FieldItem extends Item {
+
+ @NonNull
+ public final String fieldName;
+
+ @NonNull
+ public final String containingClass;
+
+ private FieldItem(@NonNull String containingClass, @NonNull String fieldName) {
+ this.containingClass = containingClass;
+ this.fieldName = fieldName;
+ }
+
+ @Nullable
+ static FieldItem create(String classFqn, FieldBinding field) {
+ String name = new String(field.name);
+ return classFqn != null ? new FieldItem(classFqn, name) : null;
+ }
+
+ @Override
+ boolean isValid() {
+ return true;
+ }
+
+ @Override
+ boolean isFiltered(@NonNull ApiDatabase database) {
+ return !database.hasField(containingClass, fieldName);
+ }
+
+ @Override
+ String getSignature() {
+ return escapeXml(containingClass) + ' ' + fieldName;
+ }
+
+ @Override
+ public String toString() {
+ return "Field " + containingClass + "#" + fieldName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ FieldItem that = (FieldItem) o;
+
+ return containingClass.equals(that.containingClass) &&
+ fieldName.equals(that.fieldName);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = fieldName.hashCode();
+ result = 31 * result + containingClass.hashCode();
+ return result;
+ }
+ }
+
+ private static class MethodItem extends Item {
+
+ @NonNull
+ public final String methodName;
+
+ @NonNull
+ public final String containingClass;
+
+ @NonNull
+ public final String parameterList;
+
+ @Nullable
+ public final String returnType;
+
+ public final boolean isConstructor;
+
+ private MethodItem(@NonNull String containingClass, @Nullable String returnType,
+ @NonNull String methodName, @NonNull String parameterList, boolean isConstructor) {
+ this.containingClass = containingClass;
+ this.returnType = returnType;
+ this.methodName = methodName;
+ this.parameterList = parameterList;
+ this.isConstructor = isConstructor;
+ }
+
+ @Nullable
+ static MethodItem create(String classFqn, MethodBinding binding) {
+ if (classFqn == null || binding == null) {
+ return null;
+ }
+ String returnType = getReturnType(binding);
+ String methodName = getMethodName(binding);
+ String parameterList = getParameterList(binding);
+ if (returnType == null || methodName == null || parameterList == null) {
+ return null;
+ }
+ return new MethodItem(classFqn, returnType,
+ methodName, parameterList,
+ binding.isConstructor());
+ }
+
+ @Override
+ boolean isValid() {
+ return true;
+ }
+
+ @Override
+ String getSignature() {
+ StringBuilder sb = new StringBuilder(100);
+ sb.append(escapeXml(containingClass));
+ sb.append(' ');
+
+ if (isConstructor) {
+ sb.append(escapeXml(methodName));
+ } else {
+ assert returnType != null;
+ sb.append(escapeXml(returnType));
+ sb.append(' ');
+ sb.append(escapeXml(methodName));
+ }
+
+ sb.append('(');
+ // The signature must match *exactly* the formatting used by IDEA,
+ // since it looks up external annotations in a map by this key.
+ // Therefore, it is vital that the parameter list uses exactly one
+ // space after each comma between parameters, and *no* spaces between
+ // generics variables, e.g. foo(Map<A,B>, int)
+ assert parameterList.indexOf(' ') == -1 : parameterList + " in " +
+ containingClass + "#" + methodName;
+
+ // Insert spaces between commas, but not in generics signatures
+ int balance = 0;
+ for (int i = 0, n = parameterList.length(); i < n; i++) {
+ char c = parameterList.charAt(i);
+ if (c == '<') {
+ balance++;
+ sb.append("<");
+ } else if (c == '>') {
+ balance--;
+ sb.append(">");
+ } else if (c == ',') {
+ sb.append(',');
+ if (balance == 0) {
+ sb.append(' ');
+ }
+ } else {
+ sb.append(c);
+ }
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+
+ @Override
+ boolean isFiltered(@NonNull ApiDatabase database) {
+ return !database.hasMethod(containingClass, methodName, parameterList);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ MethodItem that = (MethodItem) o;
+
+ return isConstructor == that.isConstructor && containingClass
+ .equals(that.containingClass) && methodName.equals(that.methodName)
+ && parameterList.equals(that.parameterList) && !(returnType != null
+ ? !returnType.equals(that.returnType) : that.returnType != null);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = methodName.hashCode();
+ result = 31 * result + containingClass.hashCode();
+ result = 31 * result + parameterList.hashCode();
+ result = 31 * result + (returnType != null ? returnType.hashCode() : 0);
+ result = 31 * result + (isConstructor ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Method " + containingClass + "#" + methodName;
+ }
+ }
+
+ @Nullable
+ private static String getReturnType(MethodBinding binding) {
+ if (binding.returnType != null) {
+ return new String(binding.returnType.readableName());
+ } else if (binding.declaringClass != null) {
+ assert binding.isConstructor();
+ return new String(binding.declaringClass.readableName());
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static String getMethodName(@NonNull MethodBinding binding) {
+ if (binding.isConstructor()) {
+ if (binding.declaringClass != null) {
+ String classFqn = new String(binding.declaringClass.readableName());
+ return classFqn.substring(classFqn.lastIndexOf('.') + 1);
+ }
+
+ }
+ if (binding.selector != null) {
+ return new String(binding.selector);
+ }
+
+ assert binding.isConstructor();
+
+ return null;
+ }
+
+ @Nullable
+ private static String getParameterList(@NonNull MethodBinding binding) {
+ // Create compact type signature (no spaces around commas or generics arguments)
+ StringBuilder sb = new StringBuilder();
+ boolean isFirst = true;
+ TypeBinding[] typeParameters = binding.parameters;
+ if (typeParameters != null) {
+ for (TypeBinding parameter : typeParameters) {
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ sb.append(',');
+ }
+ sb.append(fixParameterString(new String(parameter.readableName())));
+ }
+ }
+ return sb.toString();
+ }
+
+ private static class ParameterItem extends MethodItem {
+ @NonNull
+ public String argIndex;
+
+ private ParameterItem(@NonNull String containingClass, @Nullable String returnType,
+ @NonNull String methodName, @NonNull String parameterList, boolean isConstructor,
+ @NonNull String argIndex) {
+ super(containingClass, returnType, methodName, parameterList, isConstructor);
+ this.argIndex = argIndex;
+ }
+
+ @Nullable
+ static ParameterItem create(AbstractMethodDeclaration methodDeclaration, Argument argument,
+ String classFqn, MethodBinding methodBinding,
+ LocalVariableBinding parameterBinding) {
+ if (classFqn == null || methodBinding == null || parameterBinding == null) {
+ return null;
+ }
+
+ String methodName = getMethodName(methodBinding);
+ String parameterList = getParameterList(methodBinding);
+ String returnType = getReturnType(methodBinding);
+ if (methodName == null || parameterList == null || returnType == null) {
+ return null;
+ }
+
+ int index = 0;
+ boolean found = false;
+ if (methodDeclaration.arguments != null) {
+ for (Argument a : methodDeclaration.arguments) {
+ if (a == argument) {
+ found = true;
+ break;
+ }
+ index++;
+ }
+ }
+ if (!found) {
+ return null;
+ }
+ String argNum = Integer.toString(index);
+ return new ParameterItem(classFqn, returnType, methodName, parameterList,
+ methodBinding.isConstructor(), argNum);
+ }
+
+
+ @Override
+ String getSignature() {
+ return super.getSignature() + ' ' + argIndex;
+ }
+
+ @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;
+ }
+
+ ParameterItem that = (ParameterItem) o;
+
+ return argIndex.equals(that.argIndex);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + argIndex.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Parameter #" + argIndex + " in " + super.toString();
+ }
+ }
+
+ class AnnotationVisitor extends ASTVisitor {
+ @Override
+ public boolean visit(Argument argument, BlockScope scope) {
+ Annotation[] annotations = argument.annotations;
+ if (hasRelevantAnnotations(annotations)) {
+ ReferenceContext referenceContext = scope.referenceContext();
+ if (referenceContext instanceof AbstractMethodDeclaration) {
+ MethodBinding binding = ((AbstractMethodDeclaration) referenceContext).binding;
+ String fqn = getFqn(scope);
+ Item item = ParameterItem.create(
+ (AbstractMethodDeclaration) referenceContext, argument, fqn,
+ binding, argument.binding);
+ if (item != null) {
+ assert fqn != null;
+ addItem(fqn, item);
+ addAnnotations(annotations, item);
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(ConstructorDeclaration constructorDeclaration, ClassScope scope) {
+ Annotation[] annotations = constructorDeclaration.annotations;
+ if (hasRelevantAnnotations(annotations)) {
+ MethodBinding constructorBinding = constructorDeclaration.binding;
+ if (constructorBinding == null) {
+ return false;
+ }
+
+ String fqn = getFqn(scope);
+ Item item = MethodItem.create(fqn, constructorBinding);
+ if (item != null) {
+ assert fqn != null;
+ addItem(fqn, item);
+ addAnnotations(annotations, item);
+ }
+ }
+
+ Argument[] arguments = constructorDeclaration.arguments;
+ if (arguments != null) {
+ for (Argument argument : arguments) {
+ argument.traverse(this, constructorDeclaration.scope);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(FieldDeclaration fieldDeclaration, MethodScope scope) {
+ Annotation[] annotations = fieldDeclaration.annotations;
+ if (hasRelevantAnnotations(annotations)) {
+ FieldBinding fieldBinding = fieldDeclaration.binding;
+ if (fieldBinding == null) {
+ return false;
+ }
+
+ String fqn = getFqn(scope);
+ Item item = FieldItem.create(fqn, fieldBinding);
+ if (item != null) {
+ assert fqn != null;
+ addItem(fqn, item);
+ addAnnotations(annotations, item);
+ }
+
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(MethodDeclaration methodDeclaration, ClassScope scope) {
+ Annotation[] annotations = methodDeclaration.annotations;
+ if (hasRelevantAnnotations(annotations)) {
+ MethodBinding methodBinding = methodDeclaration.binding;
+ if (methodBinding == null) {
+ return false;
+ }
+
+ String fqn = getFqn(scope);
+ Item item = MethodItem.create(fqn, methodDeclaration.binding);
+ if (item != null) {
+ assert fqn != null;
+ addItem(fqn, item);
+ addAnnotations(annotations, item);
+ }
+ }
+
+ Argument[] arguments = methodDeclaration.arguments;
+ if (arguments != null) {
+ for (Argument argument : arguments) {
+ argument.traverse(this, methodDeclaration.scope);
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java
new file mode 100644
index 0000000..1e141e6
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.annotations;
+
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.INT_DEF_ANNOTATION;
+import static com.android.SdkConstants.STRING_DEF_ANNOTATION;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.eclipse.jdt.internal.compiler.ASTVisitor;
+import org.eclipse.jdt.internal.compiler.ClassFile;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Javadoc;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
+import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/** Gathers information about typedefs (@IntDef and @StringDef */
+public class TypedefCollector extends ASTVisitor {
+ private Map<String,Annotation> mMap = Maps.newHashMap();
+
+ private boolean mRequireHide;
+ private boolean mRequireSourceRetention;
+ private CompilationUnitDeclaration mCurrentUnit;
+ private List<File> mClassFiles = Lists.newArrayList();
+
+ public TypedefCollector(
+ @NonNull Collection<CompilationUnitDeclaration> units,
+ boolean requireHide,
+ boolean requireSourceRetention) {
+ mRequireHide = requireHide;
+ mRequireSourceRetention = requireSourceRetention;
+
+ for (CompilationUnitDeclaration unit : units) {
+ mCurrentUnit = unit;
+ unit.traverse(this, unit.scope);
+ mCurrentUnit = null;
+ }
+ }
+
+ public List<File> getNonPublicTypedefClassFiles() {
+ return mClassFiles;
+ }
+
+ public Map<String,Annotation> getTypedefs() {
+ return mMap;
+ }
+
+ @Override
+ public boolean visit(TypeDeclaration memberTypeDeclaration, ClassScope scope) {
+ return recordTypedefs(memberTypeDeclaration);
+
+ }
+
+ @Override
+ public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
+ return recordTypedefs(typeDeclaration);
+ }
+
+ private boolean recordTypedefs(TypeDeclaration declaration) {
+ SourceTypeBinding binding = declaration.binding;
+ if (binding == null) {
+ return false;
+ }
+ Annotation[] annotations = declaration.annotations;
+ if (annotations != null) {
+ if (declaration.binding.isAnnotationType()) {
+ for (Annotation annotation : annotations) {
+ String typeName = Extractor.getFqn(annotation);
+ if (typeName == null) {
+ continue;
+ }
+
+ if (typeName.equals(INT_DEF_ANNOTATION) ||
+ typeName.equals(STRING_DEF_ANNOTATION) ||
+ typeName.equals(Extractor.ANDROID_INT_DEF) ||
+ typeName.equals(Extractor.ANDROID_STRING_DEF)) {
+ String fqn = new String(binding.readableName());
+ mMap.put(fqn, annotation);
+ if (mRequireHide) {
+ Javadoc javadoc = declaration.javadoc;
+ if (javadoc != null) {
+ StringBuffer stringBuffer = new StringBuffer(200);
+ javadoc.print(0, stringBuffer);
+ String documentation = stringBuffer.toString();
+ if (!documentation.contains("@hide")) {
+ Extractor.display(getFileName()
+ + ": The typedef annotation " + fqn
+ + " should specify @hide in a doc comment");
+ }
+ }
+ }
+ if (mRequireSourceRetention
+ && !Extractor.hasSourceRetention(annotations)) {
+ Extractor.display(getFileName()
+ + ": The typedef annotation " + fqn
+ + " must have @Retention(RetentionPolicy.SOURCE)");
+ }
+ if (declaration.binding != null
+ && (declaration.modifiers & ClassFileConstants.AccPublic) == 0) {
+ StringBuilder sb = new StringBuilder(100);
+ for (char c : declaration.binding.qualifiedPackageName()) {
+ if (c == '.') {
+ sb.append(File.separatorChar);
+ } else {
+ sb.append(c);
+ }
+ }
+ sb.append(File.separatorChar);
+ for (char c : declaration.binding.qualifiedSourceName()) {
+ if (c == '.') {
+ sb.append('$');
+ } else {
+ sb.append(c);
+ }
+ }
+ sb.append(DOT_CLASS);
+ File file = new File(sb.toString());
+ mClassFiles.add(file);
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ private String getFileName() {
+ return new String(mCurrentUnit.getFileName());
+ }
+}
diff --git a/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.application.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.application.properties
new file mode 100644
index 0000000..88dd73d
--- /dev/null
+++ b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.application.properties
@@ -0,0 +1 @@
+implementation-class=com.android.build.gradle.AppPlugin
\ No newline at end of file
diff --git a/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.library.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.library.properties
new file mode 100644
index 0000000..c3e8a16
--- /dev/null
+++ b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.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/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy
index bef2ec7..f6d27b7 100644
--- 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
@@ -35,7 +35,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
@@ -60,7 +60,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion = 15
@@ -82,7 +82,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion "android-15"
@@ -104,7 +104,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "multires")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
@@ -125,7 +125,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
@@ -157,7 +157,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
@@ -192,29 +192,29 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
- flavorGroups "group1", "group2"
+ flavorDimensions "dimension1", "dimension2"
productFlavors {
f1 {
- flavorGroup "group1"
+ flavorDimension "dimension1"
}
f2 {
- flavorGroup "group1"
+ flavorDimension "dimension1"
}
fa {
- flavorGroup "group2"
+ flavorDimension "dimension2"
}
fb {
- flavorGroup "group2"
+ flavorDimension "dimension2"
}
fc {
- flavorGroup "group2"
+ flavorDimension "dimension2"
}
}
}
@@ -247,7 +247,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
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
index 4578945..c43a70c 100644
--- 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
@@ -20,10 +20,10 @@
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.core.BuilderConstants
+import com.android.builder.core.DefaultBuildType
import com.android.builder.model.SigningConfig
-import com.android.builder.signing.KeystoreHelper
+import com.android.ide.common.signing.KeystoreHelper
import org.gradle.api.Project
import org.gradle.testfixtures.ProjectBuilder
@@ -42,19 +42,20 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
+ buildToolsVersion "19"
}
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())
+ assertEquals(2, plugin.variantManager.buildTypes.size())
+ assertNotNull(plugin.variantManager.buildTypes.get(BuilderConstants.DEBUG))
+ assertNotNull(plugin.variantManager.buildTypes.get(BuilderConstants.RELEASE))
+ assertEquals(0, plugin.variantManager.productFlavors.size())
List<BaseVariantData> variants = plugin.variantDataList
@@ -69,10 +70,11 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
+ buildToolsVersion "19"
signingConfigs {
fakeConfig {
@@ -97,8 +99,10 @@
plugin.createAndroidTasks(true /*force*/)
assertEquals(1, plugin.extension.defaultConfig.versionCode)
- assertEquals(2, plugin.extension.defaultConfig.minSdkVersion)
- assertEquals(3, plugin.extension.defaultConfig.targetSdkVersion)
+ assertNotNull(plugin.extension.defaultConfig.minSdkVersion)
+ assertEquals(2, plugin.extension.defaultConfig.minSdkVersion.apiLevel)
+ assertNotNull(plugin.extension.defaultConfig.targetSdkVersion)
+ assertEquals(3, plugin.extension.defaultConfig.targetSdkVersion.apiLevel)
assertEquals("2.0", plugin.extension.defaultConfig.versionName)
assertEquals(new File(project.projectDir, "aa"),
@@ -112,10 +116,11 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
+ buildToolsVersion "19"
testBuildType "staging"
buildTypes {
@@ -128,7 +133,7 @@
AppPlugin plugin = AppPlugin.pluginHolder.plugin
plugin.createAndroidTasks(true /*force*/)
- assertEquals(3, plugin.buildTypes.size())
+ assertEquals(3, plugin.variantManager.buildTypes.size())
List<BaseVariantData> variants = plugin.variantDataList
assertEquals(4, variants.size()) // includes the test variant(s)
@@ -148,10 +153,11 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
+ buildToolsVersion "19"
productFlavors {
flavor1 {
@@ -166,7 +172,7 @@
AppPlugin plugin = AppPlugin.pluginHolder.plugin
plugin.createAndroidTasks(true /*force*/)
- assertEquals(2, plugin.productFlavors.size())
+ assertEquals(2, plugin.variantManager.productFlavors.size())
List<BaseVariantData> variants = plugin.variantDataList
assertEquals(6, variants.size()) // includes the test variant(s)
@@ -184,29 +190,30 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
+ buildToolsVersion "19"
- flavorGroups "group1", "group2"
+ flavorDimensions "dimension1", "dimension2"
productFlavors {
f1 {
- flavorGroup "group1"
+ flavorDimension "dimension1"
}
f2 {
- flavorGroup "group1"
+ flavorDimension "dimension1"
}
fa {
- flavorGroup "group2"
+ flavorDimension "dimension2"
}
fb {
- flavorGroup "group2"
+ flavorDimension "dimension2"
}
fc {
- flavorGroup "group2"
+ flavorDimension "dimension2"
}
}
}
@@ -214,7 +221,7 @@
AppPlugin plugin = AppPlugin.pluginHolder.plugin
plugin.createAndroidTasks(true /*force*/)
- assertEquals(5, plugin.productFlavors.size())
+ assertEquals(5, plugin.variantManager.productFlavors.size())
List<BaseVariantData> variants = plugin.variantDataList
assertEquals(18, variants.size()) // includes the test variant(s)
@@ -248,10 +255,11 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
+ buildToolsVersion "19"
signingConfigs {
one {
@@ -348,10 +356,11 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
+ buildToolsVersion "19"
signingConfigs {
debug {
@@ -363,9 +372,9 @@
AppPlugin plugin = AppPlugin.pluginHolder.plugin
// check that the debug buildType has the updated debug signing config.
- DefaultBuildType buildType = plugin.buildTypes.get(BuilderConstants.DEBUG).buildType
+ DefaultBuildType buildType = plugin.variantManager.buildTypes.get(BuilderConstants.DEBUG).buildType
SigningConfig signingConfig = buildType.signingConfig
- assertEquals(plugin.signingConfigs.get(BuilderConstants.DEBUG), signingConfig)
+ assertEquals(plugin.variantManager.signingConfigs.get(BuilderConstants.DEBUG), signingConfig)
assertEquals("foo", signingConfig.storePassword)
}
@@ -373,7 +382,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
@@ -385,8 +394,8 @@
AppPlugin plugin = AppPlugin.pluginHolder.plugin
- SigningConfig debugSC = plugin.signingConfigs.get(BuilderConstants.DEBUG)
- SigningConfig fooSC = plugin.signingConfigs.get("foo")
+ SigningConfig debugSC = plugin.variantManager.signingConfigs.get(BuilderConstants.DEBUG)
+ SigningConfig fooSC = plugin.variantManager.signingConfigs.get("foo")
assertNotNull(fooSC);
@@ -400,11 +409,12 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.apply plugin: 'java'
project.android {
compileSdkVersion 15
+ buildToolsVersion "19"
}
AppPlugin plugin = AppPlugin.pluginHolder.plugin
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
index f76b7a97..44daf53 100644
--- 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
@@ -24,7 +24,7 @@
import org.gradle.testfixtures.ProjectBuilder
/**
- * Tests for the public DSL of the App plugin ("android-library")
+ * Tests for the public DSL of the Lib plugin ('com.android.library')
*/
public class LibraryPluginDslTest extends BaseTest {
@@ -37,7 +37,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android-library'
+ project.apply plugin: 'com.android.library'
project.android {
compileSdkVersion 15
@@ -63,19 +63,21 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android-library'
+ project.apply plugin: 'com.android.library'
project.android {
compileSdkVersion 15
- debugSigningConfig {
- storePassword = "foo"
+ signingConfigs {
+ debug {
+ storePassword = "foo"
+ }
}
}
- SigningConfig signingConfig = project.android.debug.signingConfig
+ SigningConfig signingConfig = project.android.buildTypes.debug.signingConfig
- assertEquals(project.android.debugSigningConfig, signingConfig)
+ assertEquals(project.android.signingConfigs.debug, signingConfig)
assertEquals("foo", signingConfig.storePassword)
}
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
index 3744b79..cb0fd0f 100644
--- 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
@@ -18,8 +18,8 @@
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 com.android.builder.core.DefaultBuildType
+import com.android.builder.core.BuilderConstants
import org.gradle.api.Project
import org.gradle.testfixtures.ProjectBuilder
@@ -32,7 +32,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
@@ -40,21 +40,21 @@
AppPlugin plugin = AppPlugin.pluginHolder.plugin
- DefaultBuildType type = plugin.buildTypes.get(BuilderConstants.DEBUG).buildType
+ DefaultBuildType type = plugin.variantManager.buildTypes.get(BuilderConstants.DEBUG).buildType
assertTrue(type.isDebuggable())
assertFalse(type.isJniDebugBuild())
assertFalse(type.isRenderscriptDebugBuild())
assertNotNull(type.getSigningConfig())
assertTrue(type.getSigningConfig().isSigningReady())
- assertFalse(type.isZipAlign())
+ assertTrue(type.isZipAlign())
}
public void testRelease() {
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- project.apply plugin: 'android'
+ project.apply plugin: 'com.android.application'
project.android {
compileSdkVersion 15
@@ -62,7 +62,7 @@
AppPlugin plugin = AppPlugin.pluginHolder.plugin
- DefaultBuildType type = plugin.buildTypes.get(BuilderConstants.RELEASE).buildType
+ DefaultBuildType type = plugin.variantManager.buildTypes.get(BuilderConstants.RELEASE).buildType
assertFalse(type.isDebuggable())
assertFalse(type.isJniDebugBuild())
@@ -74,7 +74,7 @@
Project project = ProjectBuilder.builder().withProjectDir(
new File(testDir, "basic")).build()
- BuildTypeDsl object1 = new BuildTypeDsl("foo", project.fileResolver)
+ BuildTypeDsl object1 = new BuildTypeDsl("foo", project, project.getLogger())
// change every value from their default.
object1.setDebuggable(true)
@@ -87,7 +87,7 @@
object1.setSigningConfig(new SigningConfigDsl("blah"))
object1.setZipAlign(false)
- BuildTypeDsl object2 = new BuildTypeDsl(object1.name, project.fileResolver)
+ BuildTypeDsl object2 = new BuildTypeDsl(object1.name, project, project.getLogger())
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
index 5a3bc66..3b3fd10 100644
--- 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
@@ -16,7 +16,7 @@
package com.android.build.gradle.internal.dsl;
-import com.android.builder.BuilderConstants;
+import com.android.builder.core.BuilderConstants;
import junit.framework.TestCase;
public class SigningConfigDslTest extends TestCase {
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
index fc35099..f4c8c52 100755
--- 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
@@ -19,6 +19,7 @@
import com.android.annotations.Nullable
import com.android.sdklib.internal.project.ProjectProperties
import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy
+import com.google.common.collect.Lists
import junit.framework.TestCase
import org.gradle.tooling.GradleConnector
import org.gradle.tooling.ProjectConnection
@@ -40,7 +41,7 @@
File dir = new File(location.toURI())
assertTrue(dir.getPath(), dir.exists())
- File f= dir.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile();
+ File f= dir.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())
@@ -131,17 +132,25 @@
return (File) localProp.file
}
- protected File runTasksOn(String name, String gradleVersion, String... tasks) {
+ protected File runTasksOn(
+ @NonNull String name,
+ @NonNull String gradleVersion,
+ @NonNull String... tasks) {
File project = new File(testDir, name)
- runGradleTasks(sdkDir, ndkDir, gradleVersion, project, tasks)
+ runGradleTasks(sdkDir, ndkDir, gradleVersion, project,
+ Collections.<String>emptyList(), tasks)
return project;
}
- protected static void runGradleTasks(File sdkDir, File ndkDir,
- String gradleVersion,
- File project, String... tasks) {
+ protected static void runGradleTasks(
+ @NonNull File sdkDir,
+ @NonNull File ndkDir,
+ @NonNull String gradleVersion,
+ @NonNull File project,
+ @NonNull List<String> arguments,
+ @NonNull String... tasks) {
File localProp = createLocalProp(project, sdkDir, ndkDir)
try {
@@ -153,7 +162,12 @@
.forProjectDirectory(project)
.connect()
try {
- connection.newBuild().forTasks(tasks).withArguments("-i").run()
+ List<String> args = Lists.newArrayListWithCapacity(2 + arguments.size());
+ args.add("-i");
+ args.add("-u");
+ args.addAll(arguments);
+
+ connection.newBuild().forTasks(tasks).withArguments(args.toArray() as String[]).run()
} finally {
connection.close()
}
diff --git a/build-system/manifest-merger/.classpath b/build-system/manifest-merger/.classpath
index cff9ae0..f750db5 100644
--- a/build-system/manifest-merger/.classpath
+++ b/build-system/manifest-merger/.classpath
@@ -1,10 +1,10 @@
<?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 combineaccessrules="false" kind="src" path="/sdklib"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/sdk-common"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/build-system/manifest-merger/build.gradle b/build-system/manifest-merger/build.gradle
index ffc78f3..1fc969e 100644
--- a/build-system/manifest-merger/build.gradle
+++ b/build-system/manifest-merger/build.gradle
@@ -1,18 +1,21 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
-evaluationDependsOn(':sdklib')
+evaluationDependsOn(':base:sdklib')
group = 'com.android.tools.build'
archivesBaseName = 'manifest-merger'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':common')
- compile project(':sdklib')
+ compile project(':base:common')
+ compile project(':base:sdklib')
+ compile project(':base:sdk-common')
compile 'kxml2:kxml2:2.3.0'
- testCompile project(':sdklib').sourceSets.test.output
+ testCompile project(':base:sdklib').sourceSets.test.output
testCompile 'junit:junit:3.8.1'
+ testCompile 'org.mockito:mockito-all:1.9.5'
}
sourceSets {
@@ -20,14 +23,9 @@
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'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/build-system/manifest-merger/manifest-merger.iml b/build-system/manifest-merger/manifest-merger.iml
index 77b77e8..dc95302 100644
--- a/build-system/manifest-merger/manifest-merger.iml
+++ b/build-system/manifest-merger/manifest-merger.iml
@@ -12,6 +12,8 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="library" scope="TEST" name="JUnit3" level="project" />
<orderEntry type="module" module-name="sdklib" exported="" />
+ <orderEntry type="module" module-name="sdk-common" exported="" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
</component>
</module>
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ActionRecorder.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ActionRecorder.java
new file mode 100644
index 0000000..f7fbcd4
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ActionRecorder.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.XmlNode.NodeKey;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.utils.PositionXmlParser;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Records all the actions taken by the merging tool.
+ * <p>
+ * Each action generates at least one {@link com.android.manifmerger.Actions.Record}
+ * containing enough information to generate a machine or human readable report.
+ * <p>
+ *
+ * The records are not organized in a temporal structure as the merging tool takes such decisions
+ * but are keyed by xml elements and attributes. For each node (elements or attributes), a linked
+ * list of actions that happened to the node is recorded to display all decisions that were made
+ * for that particular node.
+ * <p>
+ *
+ * This structure will permit displaying logs with co-located decisions records for each element,
+ * for instance :
+ * <pre>
+ * activity:com.foo.bar.MyApp
+ * Added from manifest.xml:31
+ * Rejected from lib1_manifest.xml:65
+ * </pre>
+ *
+ * <p>
+ * Each record for a node (element or attribute) will contain the following metadata :
+ * <p>
+ *
+ * <ul>
+ * <li>{@link com.android.manifmerger.Actions.ActionType} to identify whether the action
+ * applies to an attribute or an element.</li>
+ * <li>{@link com.android.manifmerger.Actions.ActionLocation} to identify the source xml
+ * location for the node.</li>
+ * </ul>
+ *
+ * <p>
+ * Elements will also contain:
+ * <ul>
+ * <li>Element name : a name composed of the element type and its key.</li>
+ * <li>{@link NodeOperationType} the highest priority tool annotation justifying the merging
+ * tool decision.</li>
+ * </ul>
+ *
+ * <p>
+ * While attributes will have:
+ * <ul>
+ * <li>element name</li>
+ * <li>attribute name : the namespace aware xml name</li>
+ * <li>{@link AttributeOperationType} the highest priority annotation justifying the merging
+ * tool decision.</li>
+ * </ul>
+ */
+public class ActionRecorder {
+
+ // defines all the records for the merging tool activity, indexed by element name+key.
+ // iterator should be ordered by the key insertion order. This is not a concurrent map so we
+ // will need to guard multi-threaded access when adding/removing elements.
+ @GuardedBy("this")
+ private final Map<NodeKey, Actions.DecisionTreeRecord> mRecords =
+ new LinkedHashMap<NodeKey, Actions.DecisionTreeRecord>();
+
+ /**
+ * When the first xml file is loaded, there is nothing to merge with, however, each xml element
+ * and attribute added to the initial merged file need to be recorded.
+ *
+ * @param xmlElement xml element added to the initial merged document.
+ */
+ void recordDefaultNodeAction(XmlElement xmlElement) {
+ if (!mRecords.containsKey(xmlElement.getId())) {
+ recordNodeAction(xmlElement, Actions.ActionType.ADDED);
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+ AttributeOperationType attributeOperation = xmlElement
+ .getAttributeOperationType(xmlAttribute.getName());
+ recordAttributeAction(
+ xmlAttribute, Actions.ActionType.ADDED,
+ attributeOperation);
+ }
+ for (XmlElement childNode : xmlElement.getMergeableElements()) {
+ recordDefaultNodeAction(childNode);
+ }
+ }
+ }
+
+ /**
+ * Record a node that was added due to an implicit presence in earlier SDK release but requires
+ * an explicit declaration in the application targeted SDK.
+ * @param xmlElement the implied element that was added to the resulting xml.
+ * @param reason optional contextual information whey the implied element was added.
+ */
+ void recordImpliedNodeAction(XmlElement xmlElement, String reason) {
+ NodeKey storageKey = xmlElement.getId();
+ Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey);
+ if (nodeDecisionTree == null) {
+ nodeDecisionTree = new Actions.DecisionTreeRecord();
+ mRecords.put(storageKey, nodeDecisionTree);
+ }
+ Actions.NodeRecord record = new Actions.NodeRecord(Actions.ActionType.IMPLIED,
+ new Actions.ActionLocation(
+ xmlElement.getDocument().getSourceLocation(),
+ xmlElement.getDocument().getRootNode().getPosition()),
+ xmlElement.getId(),
+ reason,
+ xmlElement.getOperationType()
+ );
+ nodeDecisionTree.addNodeRecord(record);
+ }
+
+ /**
+ * Record a node action taken by the merging tool.
+ *
+ * @param xmlElement the action's target xml element
+ * @param actionType the action's type
+ */
+ synchronized void recordNodeAction(
+ XmlElement xmlElement,
+ Actions.ActionType actionType) {
+ recordNodeAction(xmlElement, actionType, xmlElement);
+ }
+
+ /**
+ * Record a node action taken by the merging tool.
+ *
+ * @param mergedElement the merged xml element
+ * @param actionType the action's type
+ * @param targetElement the action's target when the action is rejected or replaced, it
+ * indicates what is the element being rejected or replaced.
+ */
+ synchronized void recordNodeAction(
+ XmlElement mergedElement,
+ Actions.ActionType actionType,
+ XmlElement targetElement) {
+
+ Actions.NodeRecord record = new Actions.NodeRecord(actionType,
+ new Actions.ActionLocation(
+ targetElement.getDocument().getSourceLocation(),
+ targetElement.getPosition()),
+ targetElement.getId(),
+ null, /* reason */
+ mergedElement.getOperationType()
+ );
+ recordNodeAction(mergedElement, record);
+ }
+
+ /**
+ * Records a {@link com.android.manifmerger.Actions.NodeRecord} action on a xml element.
+ * @param mergedElement the target element of the action.
+ * @param nodeRecord the record of the action.
+ */
+ synchronized void recordNodeAction(
+ XmlElement mergedElement,
+ Actions.NodeRecord nodeRecord) {
+
+ NodeKey storageKey = mergedElement.getId();
+ Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey);
+ if (nodeDecisionTree == null) {
+ nodeDecisionTree = new Actions.DecisionTreeRecord();
+ mRecords.put(storageKey, nodeDecisionTree);
+ }
+ nodeDecisionTree.addNodeRecord(nodeRecord);
+ }
+
+ /**
+ * Records an attribute action taken by the merging tool
+ *
+ * @param attribute the attribute in question.
+ * @param actionType the action's type
+ * @param attributeOperationType the original tool annotation leading to the merging tool
+ * decision.
+ */
+ synchronized void recordAttributeAction(
+ @NonNull XmlAttribute attribute,
+ @NonNull Actions.ActionType actionType,
+ @Nullable AttributeOperationType attributeOperationType) {
+
+ recordAttributeAction(
+ attribute, attribute.getPosition(), actionType, attributeOperationType);
+ }
+
+ /**
+ * Records an attribute action taken by the merging tool
+ *
+ * @param attribute the attribute in question.
+ * @param attributePosition the attribute's position.
+ * @param actionType the action's type
+ * @param attributeOperationType the original tool annotation leading to the merging tool
+ * decision.
+ */
+ synchronized void recordAttributeAction(
+ @NonNull XmlAttribute attribute,
+ @NonNull PositionXmlParser.Position attributePosition,
+ @NonNull Actions.ActionType actionType,
+ @Nullable AttributeOperationType attributeOperationType) {
+
+ XmlElement originElement = attribute.getOwnerElement();
+ Actions.AttributeRecord attributeRecord = new Actions.AttributeRecord(
+ actionType,
+ new Actions.ActionLocation(
+ originElement.getDocument().getSourceLocation(),
+ attributePosition),
+ attribute.getId(),
+ null, /* reason */
+ attributeOperationType
+ );
+ recordAttributeAction(attribute, attributeRecord);
+ }
+
+ /**
+ * Record a {@link com.android.manifmerger.Actions.AttributeRecord} action for an attribute of
+ * an xml element.
+ * @param attribute the attribute in question.
+ * @param attributeRecord the record of the action.
+ */
+ synchronized void recordAttributeAction(
+ XmlAttribute attribute,
+ Actions.AttributeRecord attributeRecord) {
+
+ List<Actions.AttributeRecord> attributeRecords = getAttributeRecords(attribute);
+ attributeRecords.add(attributeRecord);
+ }
+
+ /**
+ * Records when a default value that should be merged was rejected due to a tools:replace
+ * annotation.
+ *
+ * @param attribute the attribute which default value was ignored.
+ * @param implicitAttributeOwner the element owning the implicit default value.
+ */
+ synchronized void recordImplicitRejection(
+ @NonNull XmlAttribute attribute,
+ @NonNull XmlElement implicitAttributeOwner) {
+
+ List<Actions.AttributeRecord> attributeRecords = getAttributeRecords(attribute);
+ Actions.AttributeRecord attributeRecord = new Actions.AttributeRecord(
+ Actions.ActionType.REJECTED,
+ new Actions.ActionLocation(
+ implicitAttributeOwner.getDocument().getSourceLocation(),
+ implicitAttributeOwner.getPosition()),
+ attribute.getId(),
+ null, /* reason */
+ AttributeOperationType.REPLACE
+ );
+ attributeRecords.add(attributeRecord);
+ }
+
+ /**
+ * Returns the record for an attribute creation event. The attribute is "created" when it is
+ * added for the first time into the resulting merged xml document.
+ */
+ @Nullable
+ synchronized Actions.AttributeRecord getAttributeCreationRecord(XmlAttribute attribute) {
+ for (Actions.AttributeRecord attributeRecord : getAttributeRecords(attribute)) {
+ if (attributeRecord.getActionType() == Actions.ActionType.ADDED) {
+ return attributeRecord;
+ }
+ }
+ return null;
+ }
+
+ private List<Actions.AttributeRecord> getAttributeRecords(XmlAttribute attribute) {
+ XmlElement originElement = attribute.getOwnerElement();
+ NodeKey storageKey = originElement.getId();
+ Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey);
+ // by now the node should have been added for this element.
+ assert (nodeDecisionTree != null);
+ List<Actions.AttributeRecord> attributeRecords =
+ nodeDecisionTree.mAttributeRecords.get(attribute.getName());
+ if (attributeRecords == null) {
+ attributeRecords = new ArrayList<Actions.AttributeRecord>();
+ nodeDecisionTree.mAttributeRecords.put(attribute.getName(), attributeRecords);
+ }
+ return attributeRecords;
+ }
+
+ Actions build() {
+ return new Actions(new ImmutableMap.Builder<NodeKey, Actions.DecisionTreeRecord>()
+ .putAll(mRecords).build());
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/Actions.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Actions.java
new file mode 100644
index 0000000..b733a66
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Actions.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotations.concurrency.Immutable;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.utils.ILogger;
+import com.android.utils.PositionXmlParser;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.LineReader;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Contains all actions taken during a merging invocation.
+ */
+@Immutable
+public class Actions {
+
+ // TODO: i18n
+ @VisibleForTesting
+ static final String HEADER = "-- Merging decision tree log ---\n";
+
+ // defines all the records for the merging tool activity, indexed by element name+key.
+ // iterator should be ordered by the key insertion order.
+ private final ImmutableMap<XmlNode.NodeKey, DecisionTreeRecord> mRecords;
+
+ public Actions(ImmutableMap<XmlNode.NodeKey, DecisionTreeRecord> records) {
+ mRecords = records;
+ }
+
+ /**
+ * Returns a {@link com.google.common.collect.ImmutableList} of {@link NodeRecord}s for the
+ * passed xml {@link Element}
+ * @return the node records for that element or an empty list if none exist.
+ */
+ @NonNull
+ public ImmutableList<NodeRecord> getNodeRecords(Element element) {
+ XmlNode.NodeKey nodeKey = XmlNode.NodeKey.fromXml(element);
+ return mRecords.containsKey(nodeKey)
+ ? mRecords.get(nodeKey).getNodeRecords()
+ : ImmutableList.<NodeRecord>of();
+
+ }
+
+ /**
+ * Returns a {@link com.google.common.collect.ImmutableSet} of all the element's keys that have
+ * at least one {@link NodeRecord}.
+ */
+ @NonNull
+ public ImmutableSet<XmlNode.NodeKey> getNodeKeys() {
+ return mRecords.keySet();
+ }
+
+ /**
+ * Returns an {@link ImmutableList} of {@link NodeRecord} for the element identified with the
+ * passed key.
+ */
+ @NonNull
+ public ImmutableList<NodeRecord> getNodeRecords(XmlNode.NodeKey key) {
+ return mRecords.containsKey(key)
+ ? mRecords.get(key).getNodeRecords()
+ : ImmutableList.<NodeRecord>of();
+ }
+
+ /**
+ * Returns a {@link ImmutableList} of all attributes names that have at least one record for
+ * the element identified with the passed key.
+ */
+ @NonNull
+ public ImmutableList<XmlNode.NodeName> getRecordedAttributeNames(XmlNode.NodeKey nodeKey) {
+ DecisionTreeRecord decisionTreeRecord = mRecords.get(nodeKey);
+ if (decisionTreeRecord == null) {
+ return ImmutableList.of();
+ }
+ return decisionTreeRecord.getAttributesRecords().keySet().asList();
+ }
+
+ /**
+ * Returns the {@link com.google.common.collect.ImmutableList} of {@link AttributeRecord} for
+ * the attribute identified by attributeName of the element identified by elementKey.
+ */
+ @NonNull
+ public ImmutableList<AttributeRecord> getAttributeRecords(XmlNode.NodeKey elementKey,
+ XmlNode.NodeName attributeName) {
+
+ DecisionTreeRecord decisionTreeRecord = mRecords.get(elementKey);
+ if (decisionTreeRecord == null) {
+ return ImmutableList.of();
+ }
+ return decisionTreeRecord.getAttributeRecords(attributeName);
+ }
+
+ /**
+ * Initial dump of the merging tool actions, need to be refined and spec'ed out properly.
+ * @param logger logger to log to at INFO level.
+ */
+ void log(ILogger logger) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(HEADER);
+ for (Map.Entry<XmlNode.NodeKey, Actions.DecisionTreeRecord> record : mRecords.entrySet()) {
+ stringBuilder.append(record.getKey()).append("\n");
+ for (Actions.NodeRecord nodeRecord : record.getValue().getNodeRecords()) {
+ nodeRecord.print(stringBuilder);
+ stringBuilder.append('\n');
+ }
+ for (Map.Entry<XmlNode.NodeName, List<Actions.AttributeRecord>> attributeRecords :
+ record.getValue().mAttributeRecords.entrySet()) {
+ stringBuilder.append('\t').append(attributeRecords.getKey()).append('\n');
+ for (Actions.AttributeRecord attributeRecord : attributeRecords.getValue()) {
+ stringBuilder.append("\t\t");
+ attributeRecord.print(stringBuilder);
+ stringBuilder.append('\n');
+ }
+
+ }
+ }
+ logger.verbose(stringBuilder.toString());
+ }
+
+ /**
+ * Defines all possible actions taken from the merging tool for an xml element or attribute.
+ */
+ enum ActionType {
+ /**
+ * The element was added into the resulting merged manifest.
+ */
+ ADDED,
+ /**
+ * The element was injected from the merger invocation parameters.
+ */
+ INJECTED,
+ /**
+ * The element was merged with another element into the resulting merged manifest.
+ */
+ MERGED,
+ /**
+ * The element was rejected.
+ */
+ REJECTED,
+ /**
+ * The implied element was added was added when importing a library that expected the
+ * element to be present by default while targeted SDK requires its declaration.
+ */
+ IMPLIED,
+ }
+
+ /**
+ * Defines an abstract record contain common metadata for elements and attributes actions.
+ */
+ public abstract static class Record {
+
+ @NonNull protected final ActionType mActionType;
+ @NonNull protected final ActionLocation mActionLocation;
+ @NonNull protected final XmlNode.NodeKey mTargetId;
+ @Nullable protected final String mReason;
+
+ private Record(@NonNull ActionType actionType,
+ @NonNull ActionLocation actionLocation,
+ @NonNull XmlNode.NodeKey targetId,
+ @Nullable String reason) {
+ mActionType = Preconditions.checkNotNull(actionType);
+ mActionLocation = Preconditions.checkNotNull(actionLocation);
+ mTargetId = Preconditions.checkNotNull(targetId);
+ mReason = reason;
+ }
+
+ private Record(@NonNull Element xml) {
+ mActionType = ActionType.valueOf(xml.getAttribute("action-type"));
+ mActionLocation = new ActionLocation(getFirstChildElement(xml));
+ mTargetId = new XmlNode.NodeKey(xml.getAttribute("target-id"));
+ String reason = xml.getAttribute("reason");
+ mReason = Strings.isNullOrEmpty(reason) ? null : reason;
+ }
+
+ public ActionType getActionType() {
+ return mActionType;
+ }
+
+ public ActionLocation getActionLocation() {
+ return mActionLocation;
+ }
+
+ public XmlNode.NodeKey getTargetId() {
+ return mTargetId;
+ }
+
+ public void print(StringBuilder stringBuilder) {
+ stringBuilder.append(mActionType)
+ .append(" from ")
+ .append(mActionLocation);
+ if (mReason != null) {
+ stringBuilder.append(" reason: ")
+ .append(mReason);
+ }
+ }
+
+ public Element toXml(Document document) {
+ Element record = document.createElement("record");
+ record.setAttribute("action-type", mActionType.toString());
+ record.setAttribute("target-id", mTargetId.toString());
+ if (mReason != null) {
+ record.setAttribute("reason", mReason);
+ }
+ addAttributes(record);
+ Element location = document.createElement("location");
+ record.appendChild(mActionLocation.toXml(location));
+ record.appendChild(location);
+ return record;
+ }
+
+ protected abstract void addAttributes(Element element);
+ }
+
+ /**
+ * Defines a merging tool action for an xml element.
+ */
+ public static class NodeRecord extends Record {
+
+ private final NodeOperationType mNodeOperationType;
+
+ NodeRecord(@NonNull ActionType actionType,
+ @NonNull ActionLocation actionLocation,
+ @NonNull XmlNode.NodeKey targetId,
+ @Nullable String reason,
+ @NonNull NodeOperationType nodeOperationType) {
+ super(actionType, actionLocation, targetId, reason);
+ this.mNodeOperationType = Preconditions.checkNotNull(nodeOperationType);
+ }
+
+ NodeRecord(@NonNull Element xml) {
+ super(xml);
+ mNodeOperationType = NodeOperationType.valueOf(xml.getAttribute("opType"));
+ }
+
+ @Override
+ protected void addAttributes(Element element) {
+ element.setAttribute("opType", mNodeOperationType.toString());
+ }
+
+ @Override
+ public String toString() {
+ return "Id=" + mTargetId.toString() + " actionType=" + getActionType()
+ + " location=" + getActionLocation()
+ + " opType=" + mNodeOperationType;
+ }
+ }
+
+ /**
+ * Defines a merging tool action for an xml attribute
+ */
+ public static class AttributeRecord extends Record {
+
+ // first in wins which should be fine, the first
+ // operation type will be the highest priority one
+ private final AttributeOperationType mOperationType;
+
+ AttributeRecord(
+ @NonNull ActionType actionType,
+ @NonNull ActionLocation actionLocation,
+ @NonNull XmlNode.NodeKey targetId,
+ @Nullable String reason,
+ @Nullable AttributeOperationType operationType) {
+ super(actionType, actionLocation, targetId, reason);
+ this.mOperationType = operationType;
+ }
+
+ AttributeRecord(@NonNull Element xml) {
+ super(xml);
+ mOperationType = AttributeOperationType.valueOf(xml.getAttribute("opType"));
+ }
+
+ @Nullable
+ public AttributeOperationType getOperationType() {
+ return mOperationType;
+ }
+
+ @Override
+ protected void addAttributes(Element element) {
+ if (mOperationType != null) {
+ element.setAttribute("opType", mOperationType.toString());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Id=" + mTargetId + " actionType=" + getActionType()
+ + " location=" + getActionLocation()
+ + " opType=" + getOperationType();
+ }
+ }
+
+ /**
+ * Defines an action location which is composed of a pointer to the source location (e.g. a
+ * file) and a position within that source location.
+ */
+ public static final class ActionLocation {
+ private final XmlLoader.SourceLocation mSourceLocation;
+ private final PositionXmlParser.Position mPosition;
+
+ public ActionLocation(@NonNull XmlLoader.SourceLocation sourceLocation,
+ @NonNull PositionXmlParser.Position position) {
+ mSourceLocation = Preconditions.checkNotNull(sourceLocation);
+ mPosition = Preconditions.checkNotNull(position);
+ }
+
+ ActionLocation(Element xml) {
+ final Element location = getFirstChildElement(xml);
+ mSourceLocation = XmlLoader.locationFromXml(location);
+ mPosition = PositionImpl.fromXml(getNextSiblingElement(location));
+ }
+
+ public PositionXmlParser.Position getPosition() {
+ return mPosition;
+ }
+
+ public XmlLoader.SourceLocation getSourceLocation() {
+ return mSourceLocation;
+ }
+
+ @Override
+ public String toString() {
+ return mSourceLocation.print(true)
+ + ":" + mPosition.getLine() + ":" + mPosition.getColumn();
+ }
+
+ public Node toXml(Element location) {
+ location.appendChild(mSourceLocation.toXml(location.getOwnerDocument()));
+ location.appendChild(PositionImpl.toXml(mPosition, location.getOwnerDocument()));
+ return location;
+ }
+ }
+
+ public String persist()
+ throws ParserConfigurationException, IOException, SAXException {
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ Document document = documentBuilderFactory.newDocumentBuilder().newDocument();
+ Element rootElement = document.createElement("manifest-merger-mappings");
+ document.appendChild(rootElement);
+
+ for (Map.Entry<XmlNode.NodeKey, DecisionTreeRecord> decisionTreeRecordEntry :
+ mRecords.entrySet()) {
+
+ Element elementActions = document.createElement("element-actions");
+ elementActions.setAttribute("id", decisionTreeRecordEntry.getKey().toString());
+ decisionTreeRecordEntry.getValue().toXml(elementActions);
+ rootElement.appendChild(elementActions);
+ }
+
+ return XmlPrettyPrinter.prettyPrint(document, false);
+ }
+
+ @Nullable
+ public static Actions load(InputStream inputStream)
+ throws IOException, SAXException, ParserConfigurationException {
+
+ return load(new PositionXmlParser().parse(inputStream));
+ }
+
+ @Nullable
+ public static Actions load(String xml)
+ throws IOException, SAXException, ParserConfigurationException {
+
+ return load(new PositionXmlParser().parse(xml));
+ }
+
+ @Nullable
+ private static Actions load(Document document) throws IOException {
+ if (document == null) return null;
+
+ Element rootElement = document.getDocumentElement();
+ if (!rootElement.getNodeName().equals("manifest-merger-mappings")) {
+ throw new IOException("File is not a manifest-merger-mappings");
+ }
+ ImmutableMap.Builder<XmlNode.NodeKey, DecisionTreeRecord> records = ImmutableMap.builder();
+ NodeList elementActions = rootElement.getChildNodes();
+ for (int i = 0; i < elementActions.getLength(); i++) {
+ if (elementActions.item(i).getNodeType() != Node.ELEMENT_NODE) continue;
+ Element elementAction = (Element) elementActions.item(i);
+ XmlNode.NodeKey key = new XmlNode.NodeKey(elementAction.getAttribute("id"));
+ DecisionTreeRecord decisionTreeRecord = new DecisionTreeRecord(elementAction);
+ records.put(key, decisionTreeRecord);
+ }
+ return new Actions(records.build());
+ }
+
+ private static Element getFirstChildElement(Element element) {
+ Node child = element.getFirstChild();
+ while(child.getNodeType() != Node.ELEMENT_NODE) {
+ child = child.getNextSibling();
+ }
+ return (Element) child;
+ }
+
+ private static Element getNextSiblingElement(Element element) {
+ Node sibling = element.getNextSibling();
+ while(sibling != null && sibling.getNodeType() != Node.ELEMENT_NODE) {
+ sibling = sibling.getNextSibling();
+ }
+ return (Element) sibling;
+ }
+
+ public ImmutableMultimap<Integer, Record> getResultingSourceMapping(XmlDocument xmlDocument)
+ throws ParserConfigurationException, SAXException, IOException {
+
+ XmlLoader.SourceLocation inMemory = XmlLoader.UNKNOWN;
+
+ XmlDocument loadedWithLineNumbers = XmlLoader.load(
+ xmlDocument.getSelectors(),
+ xmlDocument.getSystemPropertyResolver(),
+ inMemory,
+ xmlDocument.prettyPrint(),
+ XmlDocument.Type.MAIN,
+ Optional.<String>absent() /* mainManifestPackageName */);
+
+ ImmutableMultimap.Builder<Integer, Record> mappingBuilder = ImmutableMultimap.builder();
+ for (XmlElement xmlElement : loadedWithLineNumbers.getRootNode().getMergeableElements()) {
+ parse(xmlElement, mappingBuilder);
+ }
+ return mappingBuilder.build();
+ }
+
+ private void parse(XmlElement element,
+ ImmutableMultimap.Builder<Integer, Record> mappings) {
+ DecisionTreeRecord decisionTreeRecord = mRecords.get(element.getId());
+ if (decisionTreeRecord != null) {
+ Actions.NodeRecord nodeRecord = findNodeRecord(decisionTreeRecord);
+ if (nodeRecord != null) {
+ mappings.put(element.getPosition().getLine(), nodeRecord);
+ }
+ for (XmlAttribute xmlAttribute : element.getAttributes()) {
+ Actions.AttributeRecord attributeRecord = findAttributeRecord(decisionTreeRecord,
+ xmlAttribute);
+ if (attributeRecord != null) {
+ mappings.put(xmlAttribute.getPosition().getLine(), attributeRecord);
+ }
+ }
+ }
+ for (XmlElement xmlElement : element.getMergeableElements()) {
+ parse(xmlElement, mappings);
+ }
+ }
+
+ public String blame(XmlDocument xmlDocument)
+ throws IOException, SAXException, ParserConfigurationException {
+
+ ImmutableMultimap<Integer, Record> resultingSourceMapping =
+ getResultingSourceMapping(xmlDocument);
+ LineReader lineReader = new LineReader(
+ new StringReader(xmlDocument.prettyPrint()));
+
+ StringBuilder actualMappings = new StringBuilder();
+ String line;
+ int count = 1;
+ while ((line = lineReader.readLine()) != null) {
+ actualMappings.append(count).append(line).append("\n");
+ if (resultingSourceMapping.containsKey(count)) {
+ for (Record record : resultingSourceMapping.get(count)) {
+ actualMappings.append(count).append("-->")
+ .append(record.getActionLocation().toString())
+ .append("\n");
+ }
+ }
+ count++;
+ }
+ return actualMappings.toString();
+ }
+
+ @Nullable
+ private static Actions.NodeRecord findNodeRecord(DecisionTreeRecord decisionTreeRecord) {
+ for (Actions.NodeRecord nodeRecord : decisionTreeRecord.getNodeRecords()) {
+ if (nodeRecord.getActionType() == Actions.ActionType.ADDED) {
+ return nodeRecord;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static Actions.AttributeRecord findAttributeRecord(
+ DecisionTreeRecord decisionTreeRecord,
+ XmlAttribute xmlAttribute) {
+ for (Actions.AttributeRecord attributeRecord : decisionTreeRecord
+ .getAttributeRecords(xmlAttribute.getName())) {
+ if (attributeRecord.getActionType() == Actions.ActionType.ADDED) {
+ return attributeRecord;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Internal structure on how {@link com.android.manifmerger.Actions.Record}s are kept for an
+ * xml element.
+ *
+ * Each xml element should have an associated DecisionTreeRecord which keeps a list of
+ * {@link com.android.manifmerger.Actions.NodeRecord} for all the node actions related
+ * to this xml element.
+ *
+ * It will also contain a map indexed by attribute name on all the attribute actions related
+ * to that particular attribute within the xml element.
+ *
+ */
+ static class DecisionTreeRecord {
+ // all other occurrences of the nodes decisions, in order of decisions.
+ private final List<NodeRecord> mNodeRecords = new ArrayList<NodeRecord>();
+
+ // all attributes decisions indexed by attribute name.
+ final Map<XmlNode.NodeName, List<AttributeRecord>> mAttributeRecords =
+ new HashMap<XmlNode.NodeName, List<AttributeRecord>>();
+
+ ImmutableList<NodeRecord> getNodeRecords() {
+ return ImmutableList.copyOf(mNodeRecords);
+ }
+
+ ImmutableMap<XmlNode.NodeName, List<AttributeRecord>> getAttributesRecords() {
+ return ImmutableMap.copyOf(mAttributeRecords);
+ }
+
+ DecisionTreeRecord() {
+ }
+
+ DecisionTreeRecord(Element elementAction) {
+ Preconditions.checkArgument(elementAction.getNodeName().equals("element-actions"));
+ NodeList childNodes = elementAction.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeName().equals("node-records")) {
+ NodeList nodeRecords = child.getChildNodes();
+ for (int j = 0; j < nodeRecords.getLength(); j++) {
+ if (nodeRecords.item(j).getNodeType() != Node.ELEMENT_NODE) continue;
+ NodeRecord nodeRecord = new NodeRecord((Element) nodeRecords.item(j));
+ mNodeRecords.add(nodeRecord);
+ }
+ } else if (child.getNodeName().equals("attribute-records")) {
+ // id, record*
+ Element id = getFirstChildElement((Element) child);
+ XmlNode.NodeName nodeName = Strings.isNullOrEmpty(id.getAttribute("name"))
+ ? XmlNode.fromNSName(
+ id.getAttribute("namespace-uri"),
+ id.getAttribute("prefix"),
+ id.getAttribute("local-name"))
+ : XmlNode.fromXmlName(id.getAttribute("name"));
+ Element record = id;
+ ImmutableList.Builder<AttributeRecord> attributeRecords =
+ ImmutableList.builder();
+ while ((record = getNextSiblingElement(record)) != null) {
+ AttributeRecord attributeRecord = new AttributeRecord(record);
+ attributeRecords.add(attributeRecord);
+ }
+ mAttributeRecords.put(nodeName, attributeRecords.build());
+ }
+ }
+ }
+
+ void addNodeRecord(NodeRecord nodeRecord) {
+ mNodeRecords.add(nodeRecord);
+ }
+
+ ImmutableList<AttributeRecord> getAttributeRecords(XmlNode.NodeName attributeName) {
+ List<AttributeRecord> attributeRecords = mAttributeRecords.get(attributeName);
+ return attributeRecords == null
+ ? ImmutableList.<AttributeRecord>of()
+ : ImmutableList.copyOf(attributeRecords);
+ }
+
+ public void toXml(Element elementAction) {
+ Document document = elementAction.getOwnerDocument();
+ Element nodeRecords = document.createElement("node-records");
+ elementAction.appendChild(nodeRecords);
+ for (NodeRecord nodeRecord : mNodeRecords) {
+ Element xmlNode = nodeRecord.toXml(document);
+ nodeRecords.appendChild(xmlNode);
+ }
+ for (Map.Entry<XmlNode.NodeName, List<AttributeRecord>> nodeNameListEntry :
+ mAttributeRecords.entrySet()) {
+ Element attributeRecords = document.createElement("attribute-records");
+ elementAction.appendChild(attributeRecords);
+ Element id = document.createElement("id");
+ nodeNameListEntry.getKey().persistTo(id);
+ attributeRecords.appendChild(id);
+
+ for (AttributeRecord attributeRecord : nodeNameListEntry.getValue()) {
+ Element xmlAttributeRecord = attributeRecord.toXml(document);
+ attributeRecords.appendChild(xmlAttributeRecord);
+ }
+ }
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeModel.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeModel.java
new file mode 100644
index 0000000..2adb164
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeModel.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.google.common.base.Joiner;
+import com.google.common.base.Optional;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Describes an attribute characteristics like if it supports smart package name replacement, has
+ * a default value and a validator for its values.
+ */
+class AttributeModel {
+
+ @NonNull private final XmlNode.NodeName mName;
+ private final boolean mIsPackageDependent;
+ @Nullable private final String mDefaultValue;
+ @Nullable private final Validator mOnReadValidator;
+ @Nullable private final Validator mOnWriteValidator;
+ @NonNull private final MergingPolicy mMergingPolicy;
+
+ /**
+ * Define a new attribute with specific characteristics.
+ *
+ * @param name name of the attribute, so far assumed to be in the
+ * {@link com.android.SdkConstants#ANDROID_URI} namespace.
+ * @param isPackageDependent true if the attribute support smart substitution of package name.
+ * @param defaultValue an optional default value.
+ * @param onReadValidator an optional validator to validate values against.
+ */
+ private AttributeModel(@NonNull XmlNode.NodeName name,
+ boolean isPackageDependent,
+ @Nullable String defaultValue,
+ @Nullable Validator onReadValidator,
+ @Nullable Validator onWriteValidator,
+ @NonNull MergingPolicy mergingPolicy) {
+ mName = name;
+ mIsPackageDependent = isPackageDependent;
+ mDefaultValue = defaultValue;
+ mOnReadValidator = onReadValidator;
+ mOnWriteValidator = onWriteValidator;
+ mMergingPolicy = mergingPolicy;
+ }
+
+ @NonNull
+ XmlNode.NodeName getName() {
+ return mName;
+ }
+
+ /**
+ * Return true if the attribute support smart substitution of partially fully qualified
+ * class names with package settings as provided by the manifest node's package attribute
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-element.html>}
+ *
+ * @return true if this attribute supports smart substitution or false if not.
+ */
+ boolean isPackageDependent() {
+ return mIsPackageDependent;
+ }
+
+ /**
+ * Returns the attribute's default value or null if none.
+ */
+ @Nullable
+ String getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ /**
+ * Returns the attribute's {@link com.android.manifmerger.AttributeModel.Validator} to
+ * validate its value when read from xml files or null if no validation is necessary.
+ */
+ @Nullable
+ public Validator getOnReadValidator() {
+ return mOnReadValidator;
+ }
+
+ /**
+ * Returns the attribute's {@link com.android.manifmerger.AttributeModel.Validator} to
+ * validate its value when the merged file is about to be persisted.
+ */
+ @Nullable
+ public Validator getOnWriteValidator() {
+ return mOnWriteValidator;
+ }
+
+ /**
+ * Returns the {@link com.android.manifmerger.AttributeModel.MergingPolicy} for this
+ * attribute.
+ */
+ @NonNull
+ public MergingPolicy getMergingPolicy() {
+ return mMergingPolicy;
+ }
+
+ /**
+ * Creates a new {@link Builder} to describe an attribute.
+ * @param attributeName the to be described attribute name
+ */
+ static Builder newModel(String attributeName) {
+ return new Builder(attributeName);
+ }
+
+
+ static class Builder {
+
+ private final String mName;
+ private boolean mIsPackageDependent = false;
+ private String mDefaultValue;
+ private Validator mOnReadValidator;
+ private Validator mOnWriteValidator;
+ private MergingPolicy mMergingPolicy = STRICT_MERGING_POLICY;
+
+ Builder(String name) {
+ this.mName = name;
+ }
+
+ /**
+ * Sets the attribute support for smart substitution of partially fully qualified
+ * class names with package settings as provided by the manifest node's package attribute
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-element.html>}
+ */
+ Builder setIsPackageDependent() {
+ mIsPackageDependent = true;
+ return this;
+ }
+
+ /**
+ * Sets the attribute default value.
+ */
+ Builder setDefaultValue(String value) {
+ mDefaultValue = value;
+ return this;
+ }
+
+ /**
+ * Sets a {@link com.android.manifmerger.AttributeModel.Validator} to validate the
+ * attribute's values coming from xml files.
+ */
+ Builder setOnReadValidator(Validator validator) {
+ mOnReadValidator = validator;
+ return this;
+ }
+
+ /**
+ * Sets a {@link com.android.manifmerger.AttributeModel.Validator} to validate values
+ * before they are written to the final merged document.
+ */
+ Builder setOnWriteValidator(Validator validator) {
+ mOnWriteValidator = validator;
+ return this;
+ }
+
+ Builder setMergingPolicy(MergingPolicy mergingPolicy) {
+ mMergingPolicy = mergingPolicy;
+ return this;
+ }
+
+ /**
+ * Build an immutable {@link com.android.manifmerger.AttributeModel}
+ */
+ AttributeModel build() {
+ return new AttributeModel(
+ XmlNode.fromXmlName("android:" + mName),
+ mIsPackageDependent,
+ mDefaultValue,
+ mOnReadValidator,
+ mOnWriteValidator,
+ mMergingPolicy);
+ }
+ }
+
+ /**
+ * Defines a merging policy between two attribute values. Example of merging policies can be
+ * strict when it is illegal to try to merge or override a value by another. Another example
+ * is a OR merging policy on boolean attribute values.
+ */
+ static interface MergingPolicy {
+
+ /**
+ * Returns true if it should be attempted to merge this attribute value with
+ * the attribute default value when merging with a node that does not contain
+ * the attribute declaration.
+ */
+ boolean shouldMergeDefaultValues();
+
+ /**
+ * Merges the two attributes values and returns the merged value. If the values cannot be
+ * merged, return null.
+ */
+ @Nullable
+ String merge(@NonNull String higherPriority, @NonNull String lowerPriority);
+ }
+
+ /**
+ * Standard attribute value merging policy, generates an error unless both values are equal.
+ */
+ static final MergingPolicy STRICT_MERGING_POLICY = new MergingPolicy() {
+
+ @Override
+ public boolean shouldMergeDefaultValues() {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public String merge(@NonNull String higherPriority, @NonNull String lowerPriority) {
+ // it's ok if the values are equal, otherwise it's not.
+ return higherPriority.equals(lowerPriority)
+ ? higherPriority
+ : null;
+ }
+ };
+
+ /**
+ * Boolean OR merging policy.
+ */
+ static final MergingPolicy OR_MERGING_POLICY = new MergingPolicy() {
+ @Override
+ public boolean shouldMergeDefaultValues() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public String merge(@NonNull String higherPriority, @NonNull String lowerPriority) {
+ return Boolean.toString(BooleanValidator.isTrue(higherPriority) ||
+ BooleanValidator.isTrue(lowerPriority));
+ }
+ };
+
+ /**
+ * Merging policy that will return the higher priority value regardless of the lower priority
+ * value
+ */
+ static final MergingPolicy NO_MERGING_POLICY = new MergingPolicy() {
+
+ @Override
+ public boolean shouldMergeDefaultValues() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public String merge(@NonNull String higherPriority, @NonNull String lowerPriority) {
+ return higherPriority;
+ }
+ };
+
+ /**
+ * Decode a decimal or hexadecimal {@link String} into an {@link Integer}.
+ * String starting with 0 will be considered decimal, not octal.
+ */
+ private static int decodeDecOrHexString(String s) {
+ long decodedValue = s.startsWith("0x") || s.startsWith("0X")
+ ? Long.decode(s)
+ : Long.parseLong(s);
+ if (decodedValue < 0xFFFFFFFFL) {
+ return (int) decodedValue;
+ } else {
+ throw new IllegalArgumentException("Value " + s + " too big for 32 bits.");
+ }
+ }
+
+ /**
+ * Validates an attribute value.
+ *
+ * The validator can be called when xml documents are read to ensure the xml file contains
+ * valid statements.
+ *
+ * This is a poor-mans replacement for not having a proper XML Schema do perform such
+ * validations.
+ */
+ static interface Validator {
+
+ /**
+ * Validates a value, issuing a warning or error in case of failure through the passed
+ * merging report.
+ * @param mergingReport to report validation warnings or error
+ * @param attribute the attribute to validate.
+ * @param value the proposed or existing attribute value.
+ * @return true if the value is legal for this attribute.
+ */
+ boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute,
+ @NonNull String value);
+ }
+
+ /**
+ * Validates a boolean attribute type.
+ */
+ static class BooleanValidator implements Validator {
+
+ // TODO: check with @xav where to find the acceptable values by runtime.
+ private static final Pattern TRUE_PATTERN = Pattern.compile("true|True|TRUE");
+ private static final Pattern FALSE_PATTERN = Pattern.compile("false|False|FALSE");
+
+ private static boolean isTrue(String value) {
+ return TRUE_PATTERN.matcher(value).matches();
+ }
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute,
+ @NonNull String value) {
+ boolean matches = TRUE_PATTERN.matcher(value).matches() ||
+ FALSE_PATTERN.matcher(value).matches();
+ if (!matches) {
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Attribute %1$s at %2$s has an illegal value=(%3$s), "
+ + "expected 'true' or 'false'",
+ attribute.getId(),
+ attribute.printPosition(),
+ value
+ )
+ );
+ }
+ return matches;
+ }
+ }
+
+ /**
+ * A {@link com.android.manifmerger.AttributeModel.Validator} for verifying that a proposed
+ * value is part of the acceptable list of possible values.
+ */
+ static class MultiValueValidator implements Validator {
+
+ private final String[] multiValues;
+ private final String allValues;
+
+ MultiValueValidator(String... multiValues) {
+ this.multiValues = multiValues;
+ allValues = Joiner.on(',').join(multiValues);
+ }
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute, @NonNull String value) {
+ for (String multiValue : multiValues) {
+ if (multiValue.equals(value)) {
+ return true;
+ }
+ }
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Invalid value for attribute %1$s at %2$s, value=(%3$s), "
+ + "acceptable values are (%4$s)",
+ attribute.getId(),
+ attribute.printPosition(),
+ value,
+ allValues
+ )
+ );
+ return false;
+ }
+ }
+
+ /**
+ * A {@link com.android.manifmerger.AttributeModel.Validator} for verifying that a proposed
+ * value is a numerical integer value.
+ */
+ static class IntegerValueValidator implements Validator {
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute, @NonNull String value) {
+ try {
+ return Integer.parseInt(value) > 0;
+ } catch (NumberFormatException e) {
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Attribute %1$s at %2$s must be an integer, found %3$s",
+ attribute.getId(),
+ attribute.printPosition(),
+ value)
+ );
+ return false;
+ }
+ }
+ }
+
+ /**
+ * A {@link com.android.manifmerger.AttributeModel.Validator} to validate that a string is
+ * a valid 32 bits hexadecimal representation.
+ */
+ static class Hexadecimal32Bits implements Validator {
+ protected static final Pattern PATTERN = Pattern.compile("0[xX]([0-9a-fA-F]+)");
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute, @NonNull String value) {
+ Matcher matcher = PATTERN.matcher(value);
+ boolean valid = matcher.matches() && matcher.group(1).length() <= 8;
+ if (!valid) {
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Attribute %1$s at %2$s is not a valid hexadecimal 32 bit value,"
+ + " found %3$s",
+ attribute.getId(),
+ attribute.printPosition(),
+ value
+ ));
+ }
+ return valid;
+ }
+ }
+
+ /**
+ * A {@link com.android.manifmerger.AttributeModel.Validator} to validate that a string is
+ * a valid 32 positive hexadecimal representation with a minimum value requirement.
+ */
+ static class Hexadecimal32BitsWithMinimumValue extends Hexadecimal32Bits {
+
+ private final int mMinimumValue;
+
+ Hexadecimal32BitsWithMinimumValue(int minimumValue) {
+ mMinimumValue = minimumValue;
+ }
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute, @NonNull String value) {
+ boolean valid = super.validates(mergingReport, attribute, value);
+ if (valid) {
+ try {
+ Long decodedValue = Long.decode(value);
+ valid = decodedValue >= mMinimumValue && decodedValue < 0xFFFFFFFFL;
+ } catch(NumberFormatException e) {
+ valid = false;
+ }
+ if (!valid) {
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Attribute %1$s at %2$s is not a valid hexadecimal value,"
+ + " minimum is 0x%3$08X, maximum is 0x%4$08X, found %5$s",
+ attribute.getId(),
+ attribute.printPosition(),
+ mMinimumValue,
+ Integer.MAX_VALUE,
+ value
+ ));
+ }
+ return valid;
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeOperationType.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeOperationType.java
new file mode 100644
index 0000000..ca308dd
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeOperationType.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Defines attributes operations as it can be provided by users through attributes on the target
+ * xml element.
+ * <p>
+ *
+ * For instance:
+ * <pre>
+ * {@code
+ * <uses-permission
+ * android:name="android.permission.CAMERA"
+ * android:maxSdkVersion=25
+ * tools:replace="android:maxSdkVersion"/>
+ * }
+ * </pre>
+ *
+ * <p>
+ *
+ * The operation type is provided as part of the tools attribute name itself, so you can find
+ * tools:remove, tools:replace, tools:strict. The value of that attribute is a comma separated list
+ * of attribute names on which the operation applies.
+ *
+ * <p>
+ * For instance:
+ * <pre>
+ * {@code
+ * <permission
+ * android:name="android.permission.CAMERA"
+ * android:icon="@Res/foo"
+ * android:protectionLevel="dangerous"
+ * tools:replace="android:maxSdkVersion, protectionLevel"/>
+ * }
+ * </pre>
+ * will replace maxSdkVersion and protectionLevel attributes values when merging lower level xml
+ * elements.
+ */
+enum AttributeOperationType {
+
+ /**
+ * Removes the attributes from all further merging activities.
+ */
+ REMOVE,
+
+ /**
+ * Replace the attributes values with the provided one. (Will generate a merging error if no
+ * new value is provided).
+ */
+ REPLACE,
+
+ /**
+ * The attributes should not be specified by any lower priority xml elements.
+ */
+ STRICT
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ConvertibleName.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ConvertibleName.java
new file mode 100644
index 0000000..7ffde16
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ConvertibleName.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Defines conversion routines for named types that can be converted into Xml name or Camel case
+ * names.
+ */
+public interface ConvertibleName {
+
+ /**
+ * Returns a xml lower-hyphen separated name of itself.
+ */
+ String toXmlName();
+
+ /**
+ * Returns a camel case version of itself.
+ */
+ String toCamelCaseName();
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ElementsTrimmer.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ElementsTrimmer.java
new file mode 100644
index 0000000..d71cea3
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ElementsTrimmer.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.SdkConstants.ANDROID_URI;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.xml.AndroidManifest;
+
+import org.w3c.dom.Attr;
+
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+/**
+ * Trims the document from unwanted, repeated elements.
+ */
+public class ElementsTrimmer {
+
+ /**
+ * Trims unwanted, duplicated elements from the merged document.
+ * <p>
+ * Current trimmed elements are :
+ * <p>
+ * <ul>
+ * <li>uses-features with glEsVersion key</li>
+ * <ul>
+ * <li>The highest 1.x version element will be kept regardless of 'required' flag value</li>
+ * <li>If the above element is present and has a 'false' required flag, there can be at most
+ * one element of a lesser version with 'required' attribute set to true.</li>
+ * <li>The highest 2.x or superior element will be kept regardless of 'required' flag value
+ * </li>
+ * <li>If the above element is present and has a 'false' required flag, there can be at
+ * most one element of a lesser version (but higher than 2.0) with a 'required' attribute
+ * set to true.</li>
+ * </ul>
+ * </ul>
+ *
+ * @param xmlDocument the xml document to trim.
+ * @param mergingReport the report to log errors and actions.
+ */
+ public static void trim(
+ @NonNull XmlDocument xmlDocument,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ // I sort the glEsVersion declaration by value.
+ NavigableMap<Integer, XmlElement> glEsVersionDeclarations = new TreeMap<Integer, XmlElement>();
+
+ for (XmlElement childElement : xmlDocument.getRootNode().getMergeableElements()) {
+ if (childElement.getType().equals(ManifestModel.NodeTypes.USES_FEATURE)) {
+ Integer value = getGlEsVersion(childElement);
+ if (value != null) {
+ glEsVersionDeclarations.put(value, childElement);
+ }
+ }
+ }
+
+ // now eliminate all unwanted declarations, revert the sorted map, so we get the
+ // higher elements first.
+ glEsVersionDeclarations = glEsVersionDeclarations.descendingMap();
+ boolean doneWithAboveTwoTrue = false;
+ boolean doneWithAboveTwoFalse = false;
+ boolean doneWithBelowTwoTrue = false;
+ boolean doneWithBelowTwoFalse = false;
+ for (Map.Entry<Integer, XmlElement> glEsVersionDeclaration :
+ glEsVersionDeclarations.entrySet()) {
+
+ boolean removeElement;
+
+ Attr requiredAttribute = glEsVersionDeclaration.getValue().getXml().getAttributeNodeNS(
+ ANDROID_URI, AndroidManifest.ATTRIBUTE_REQUIRED);
+
+ boolean isRequired = requiredAttribute == null ||
+ Boolean.parseBoolean(requiredAttribute.getValue());
+
+ if (glEsVersionDeclaration.getKey() < 0x20000) {
+ // version one.
+ removeElement = (doneWithBelowTwoFalse && doneWithBelowTwoTrue)
+ || (isRequired && doneWithBelowTwoTrue)
+ || (!isRequired && doneWithBelowTwoFalse);
+
+ if (!removeElement) {
+ doneWithBelowTwoFalse = true;
+ doneWithBelowTwoTrue = isRequired;
+ }
+ } else {
+ // version two or above.
+ removeElement = (doneWithAboveTwoFalse && doneWithAboveTwoTrue)
+ || (isRequired && doneWithAboveTwoTrue)
+ || (!isRequired && doneWithAboveTwoFalse);
+
+ if (!removeElement) {
+ doneWithAboveTwoFalse = true;
+ doneWithAboveTwoTrue = isRequired;
+ }
+ }
+ if (removeElement) {
+ // if the node only contains glEsVersion, then remove the entire node,
+ // if it also contains android:name, just remove the glEsVersion attribute
+ if (glEsVersionDeclaration.getValue().getXml().getAttributeNodeNS(ANDROID_URI,
+ SdkConstants.ATTR_NAME) != null) {
+ glEsVersionDeclaration.getValue().getXml().removeAttributeNS(ANDROID_URI,
+ AndroidManifest.ATTRIBUTE_GLESVERSION);
+ mergingReport.getActionRecorder().recordAttributeAction(
+ glEsVersionDeclaration.getValue().getAttribute(XmlNode.fromXmlName(
+ "android:" + AndroidManifest.ATTRIBUTE_GLESVERSION)).get(),
+ Actions.ActionType.REJECTED,
+ null /* attributeOperationType */);
+ } else {
+ xmlDocument.getRootNode().getXml().removeChild(
+ glEsVersionDeclaration.getValue().getXml());
+ mergingReport.getActionRecorder().recordNodeAction(
+ glEsVersionDeclaration.getValue(),
+ Actions.ActionType.REJECTED);
+
+ }
+ }
+
+ }
+
+ }
+
+ private static Integer getGlEsVersion(XmlElement xmlElement) {
+ Attr glEsVersion = xmlElement.getXml()
+ .getAttributeNodeNS(ANDROID_URI, AndroidManifest.ATTRIBUTE_GLESVERSION);
+ if (glEsVersion == null) {
+ return null;
+ }
+ return getHexValue(glEsVersion);
+ }
+
+ private static Integer getHexValue(Attr attribute) {
+ return Integer.decode(attribute.getValue());
+ }
+}
+
+
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/KeyResolver.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/KeyResolver.java
new file mode 100644
index 0000000..f86bb56
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/KeyResolver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Nullable;
+
+import java.util.List;
+
+/**
+ * Facility to identify an element from its key.
+ */
+public interface KeyResolver<T> {
+
+ /**
+ * Returns an element identified with the passed key.
+ * @param key key to resolve.
+ * @return the element identified by the passed key or null if there is no key of that name.
+ */
+ @Nullable
+ T resolve(String key);
+
+ Iterable<String> getKeys();
+}
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
index 7a01d48..c4f95e5 100755
--- 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
@@ -167,6 +167,7 @@
"activity/name",
"activity/parentActivityName",
"activity-alias/name",
+ "activity-alias/targetActivity",
"receiver/name",
"service/name",
"provider/name",
@@ -341,6 +342,10 @@
cleanupToolsAttributes(mainDoc);
+ if (mExtractPackagePrefix) {
+ extractFqcns(mainDoc);
+ }
+
if (mInsertSourceMarkers) {
insertSourceMarkers(mainDoc);
}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java
new file mode 100644
index 0000000..1ec3473
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java
@@ -0,0 +1,859 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+import com.android.utils.SdkUtils;
+import com.android.utils.StdLogger;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * merges android manifest files, idempotent.
+ */
+@Immutable
+public class ManifestMerger2 {
+
+ @NonNull
+ private final File mManifestFile;
+
+ @NonNull
+ private final KeyBasedValueResolver<String> mPlaceHolderValueResolver;
+
+ @NonNull
+ private final KeyBasedValueResolver<SystemProperty> mSystemPropertyResolver;
+
+ private final ILogger mLogger;
+ private final ImmutableList<Pair<String, File>> mLibraryFiles;
+ private final ImmutableList<File> mFlavorsAndBuildTypeFiles;
+ private final ImmutableList<Invoker.Feature> mOptionalFeatures;
+ private final MergeType mMergeType;
+
+ private ManifestMerger2(
+ @NonNull ILogger logger,
+ @NonNull File mainManifestFile,
+ @NonNull ImmutableList<Pair<String, File>> libraryFiles,
+ @NonNull ImmutableList<File> flavorsAndBuildTypeFiles,
+ @NonNull ImmutableList<Invoker.Feature> optionalFeatures,
+ @NonNull KeyBasedValueResolver<String> placeHolderValueResolver,
+ @NonNull KeyBasedValueResolver<SystemProperty> systemPropertiesResolver,
+ @NonNull MergeType mergeType) {
+ this.mSystemPropertyResolver = systemPropertiesResolver;
+ this.mPlaceHolderValueResolver = placeHolderValueResolver;
+ this.mManifestFile = mainManifestFile;
+ this.mLogger = logger;
+ this.mLibraryFiles = libraryFiles;
+ this.mFlavorsAndBuildTypeFiles = flavorsAndBuildTypeFiles;
+ this.mOptionalFeatures = optionalFeatures;
+ this.mMergeType = mergeType;
+ }
+
+ /**
+ * Perform high level ordering of files merging and delegates actual merging to
+ * {@link XmlDocument#merge(XmlDocument, com.android.manifmerger.MergingReport.Builder)}
+ *
+ * @return the merging activity report.
+ * @throws MergeFailureException if the merging cannot be completed (for instance, if xml
+ * files cannot be loaded).
+ */
+ private MergingReport merge() throws MergeFailureException {
+ // initiate a new merging report
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+
+ SelectorResolver selectors = new SelectorResolver();
+ // load all the libraries xml files up front to have a list of all possible node:selector
+ // values.
+ List<LoadedManifestInfo> loadedLibraryDocuments = loadLibraries(selectors);
+
+ // load the main manifest file to do some checking along the way.
+ LoadedManifestInfo loadedMainManifestInfo = load(
+ new ManifestInfo(
+ mManifestFile.getName(),
+ mManifestFile,
+ XmlDocument.Type.MAIN,
+ Optional.<String>absent() /* mainManifestPackageName */),
+ selectors);
+
+ // first do we have a package declaration in the main manifest ?
+ Optional<XmlAttribute> mainPackageAttribute =
+ loadedMainManifestInfo.getXmlDocument().getPackage();
+ if (!mainPackageAttribute.isPresent()) {
+ mergingReportBuilder.addMessage(
+ loadedMainManifestInfo.getXmlDocument().getSourceLocation(), 0, 0,
+ MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Main AndroidManifest.xml at %1$s manifest:package attribute is not declared",
+ loadedMainManifestInfo.getXmlDocument().getSourceLocation().print(true)));
+ return mergingReportBuilder.build();
+ }
+
+ // invariant : xmlDocumentOptional holds the higher priority document and we try to
+ // merge in lower priority documents.
+ Optional<XmlDocument> xmlDocumentOptional = Optional.absent();
+ for (File inputFile : mFlavorsAndBuildTypeFiles) {
+ mLogger.info("Merging flavors and build manifest %s \n", inputFile.getPath());
+ LoadedManifestInfo overlayDocument = load(
+ new ManifestInfo(null, inputFile, XmlDocument.Type.OVERLAY,
+ Optional.of(mainPackageAttribute.get().getValue())), selectors);
+
+ // check package declaration.
+ Optional<XmlAttribute> packageAttribute =
+ overlayDocument.getXmlDocument().getPackage();
+ // if both files declare a package name, it should be the same.
+ if (mainPackageAttribute.isPresent() && packageAttribute.isPresent()
+ && !mainPackageAttribute.get().getValue().equals(
+ packageAttribute.get().getValue())) {
+ // no suggestion for library since this is actually forbidden to change the
+ // the package name per flavor.
+ String message = mMergeType == MergeType.APPLICATION
+ ? String.format(
+ "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
+ + "\thas a different value=(%3$s) "
+ + "declared in main manifest at %4$s \n"
+ + "\tSuggestion : remove the overlay declaration at %5$s "
+ + "\tand place it in the build.gradle:\n"
+ + "\t\tflavorName {\n"
+ + "\t\t\tapplicationId = \"%2$s\"\n"
+ + "\t\t}",
+ packageAttribute.get().printPosition(),
+ packageAttribute.get().getValue(),
+ mainPackageAttribute.get().getValue(),
+ mainPackageAttribute.get().printPosition(),
+ packageAttribute.get().getSourceLocation().print(true))
+ : String.format(
+ "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
+ + "\thas a different value=(%3$s) "
+ + "declared in main manifest at %4$s",
+ packageAttribute.get().printPosition(),
+ packageAttribute.get().getValue(),
+ mainPackageAttribute.get().getValue(),
+ mainPackageAttribute.get().printPosition());
+ mergingReportBuilder.addMessage(
+ overlayDocument.getXmlDocument().getSourceLocation(), 0, 0,
+ MergingReport.Record.Severity.ERROR,
+ message);
+ return mergingReportBuilder.build();
+ }
+
+ xmlDocumentOptional = merge(xmlDocumentOptional, overlayDocument, mergingReportBuilder);
+
+ if (!xmlDocumentOptional.isPresent()) {
+ return mergingReportBuilder.build();
+ }
+ }
+
+ mLogger.info("Merging main manifest %s\n", mManifestFile.getPath());
+ xmlDocumentOptional =
+ merge(xmlDocumentOptional, loadedMainManifestInfo, mergingReportBuilder);
+
+ if (!xmlDocumentOptional.isPresent()) {
+ return mergingReportBuilder.build();
+ }
+
+ // force main manifest package into resulting merged file when creating a library manifest.
+ if (mMergeType == MergeType.LIBRARY) {
+ // extract the package name...
+ String mainManifestPackageName = loadedMainManifestInfo.getXmlDocument().getRootNode()
+ .getXml().getAttribute("package");
+ // save it in the selector instance.
+ if (!Strings.isNullOrEmpty(mainManifestPackageName)) {
+ xmlDocumentOptional.get().getRootNode().getXml()
+ .setAttribute("package", mainManifestPackageName);
+ }
+ }
+ for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
+ mLogger.info("Merging library manifest " + libraryDocument.getLocation());
+ xmlDocumentOptional = merge(
+ xmlDocumentOptional, libraryDocument, mergingReportBuilder);
+ if (!xmlDocumentOptional.isPresent()) {
+ return mergingReportBuilder.build();
+ }
+ }
+
+ // done with proper merging phase, now we need to trim unwanted elements, placeholder
+ // substitution and system properties injection.
+ ElementsTrimmer.trim(xmlDocumentOptional.get(), mergingReportBuilder);
+ if (mergingReportBuilder.hasErrors()) {
+ return mergingReportBuilder.build();
+ }
+
+ // do placeholder substitution
+ PlaceholderHandler placeholderHandler = new PlaceholderHandler();
+ placeholderHandler.visit(
+ xmlDocumentOptional.get(),
+ mPlaceHolderValueResolver,
+ mergingReportBuilder);
+ if (mergingReportBuilder.hasErrors()) {
+ return mergingReportBuilder.build();
+ }
+
+ // perform system property injection.
+ performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get());
+
+ XmlDocument finalMergedDocument = xmlDocumentOptional.get();
+ PostValidator.validate(finalMergedDocument, mergingReportBuilder);
+ if (mergingReportBuilder.hasErrors()) {
+ finalMergedDocument.getRootNode().addMessage(mergingReportBuilder,
+ MergingReport.Record.Severity.WARNING,
+ "Post merge validation failed");
+ }
+
+ // only remove tools annotations if we are packaging an application.
+ if (mMergeType == MergeType.APPLICATION) {
+ finalMergedDocument =
+ ToolsInstructionsCleaner.cleanToolsReferences(finalMergedDocument, mLogger);
+ }
+
+ if (finalMergedDocument != null) {
+ mergingReportBuilder.setMergedDocument(finalMergedDocument);
+ }
+
+ MergingReport build = mergingReportBuilder.build();
+ StdLogger stdLogger = new StdLogger(StdLogger.Level.INFO);
+ build.log(stdLogger);
+ stdLogger.verbose(build.getMergedDocument().get().prettyPrint());
+
+ return build;
+ }
+
+ // merge the optionally existing xmlDocument with a lower priority xml file.
+ private LoadedManifestInfo load(
+ ManifestInfo lowerPriorityManifest,
+ KeyResolver<String> selectors) throws MergeFailureException {
+
+ XmlDocument lowerPriorityDocument;
+ try {
+ lowerPriorityDocument = XmlLoader.load(selectors,
+ mSystemPropertyResolver,
+ lowerPriorityManifest.mName,
+ lowerPriorityManifest.mLocation,
+ lowerPriorityManifest.getType(),
+ lowerPriorityManifest.getMainManifestPackageName());
+ } catch (Exception e) {
+ throw new MergeFailureException(e);
+ }
+ return new LoadedManifestInfo(lowerPriorityManifest, lowerPriorityDocument);
+ }
+
+ // merge the optionally existing xmlDocument with a lower priority xml file.
+ private Optional<XmlDocument> merge(
+ Optional<XmlDocument> xmlDocument,
+ LoadedManifestInfo lowerPriorityDocument,
+ MergingReport.Builder mergingReportBuilder) throws MergeFailureException {
+
+ MergingReport.Result validationResult = PreValidator
+ .validate(mergingReportBuilder, lowerPriorityDocument.getXmlDocument());
+ if (validationResult == MergingReport.Result.ERROR) {
+ mergingReportBuilder.addMessage(
+ lowerPriorityDocument.getXmlDocument().getSourceLocation(), 0, 0,
+ MergingReport.Record.Severity.ERROR,
+ "Validation failed, exiting");
+ return Optional.absent();
+ }
+ Optional<XmlDocument> result;
+ if (xmlDocument.isPresent()) {
+ result = xmlDocument.get().merge(
+ lowerPriorityDocument.getXmlDocument(), mergingReportBuilder);
+ } else {
+ mergingReportBuilder.getActionRecorder().recordDefaultNodeAction(
+ lowerPriorityDocument.getXmlDocument().getRootNode());
+ result = Optional.of(lowerPriorityDocument.getXmlDocument());
+ }
+
+ // if requested, dump each intermediary merging stage into the report.
+ if (mOptionalFeatures.contains(Invoker.Feature.KEEP_INTERMEDIARY_STAGES)
+ && result.isPresent()) {
+ mergingReportBuilder.addMergingStage(result.get().prettyPrint());
+ }
+
+ return result;
+ }
+
+ private List<LoadedManifestInfo> loadLibraries(SelectorResolver selectors)
+ throws MergeFailureException {
+
+ ImmutableList.Builder<LoadedManifestInfo> loadedLibraryDocuments = ImmutableList.builder();
+ for (Pair<String, File> libraryFile : mLibraryFiles) {
+ mLogger.info("Loading library manifest " + libraryFile.getSecond().getPath());
+ ManifestInfo manifestInfo = new ManifestInfo(libraryFile.getFirst(), libraryFile.getSecond(),
+ XmlDocument.Type.LIBRARY, Optional.<String>absent());
+ XmlDocument libraryDocument;
+ try {
+ libraryDocument = XmlLoader.load(selectors,
+ mSystemPropertyResolver,
+ manifestInfo.mName, manifestInfo.mLocation,
+ XmlDocument.Type.LIBRARY,
+ Optional.<String>absent() /* mainManifestPackageName */);
+ } catch (Exception e) {
+ throw new MergeFailureException(e);
+ }
+ // extract the package name...
+ String libraryPackage = libraryDocument.getRootNode().getXml().getAttribute("package");
+ // save it in the selector instance.
+ if (!Strings.isNullOrEmpty(libraryPackage)) {
+ selectors.addSelector(libraryPackage, libraryFile.getFirst());
+ }
+
+ loadedLibraryDocuments.add(new LoadedManifestInfo(manifestInfo, libraryDocument));
+ }
+ return loadedLibraryDocuments.build();
+ }
+
+ /**
+ * Creates a new {@link com.android.manifmerger.ManifestMerger2.Invoker} instance to invoke
+ * the merging tool to merge manifest files for an application.
+ *
+ * @param mainManifestFile application main manifest file.
+ * @param logger the logger interface to use.
+ * @return an {@link com.android.manifmerger.ManifestMerger2.Invoker} instance that will allow
+ * further customization and trigger the merging tool.
+ */
+ public static Invoker newMerger(@NonNull File mainManifestFile,
+ @NonNull ILogger logger, @NonNull MergeType mergeType) {
+ return new Invoker(mainManifestFile, logger, mergeType);
+ }
+
+ /**
+ * List of manifest files properties that can be directly overridden without using a
+ * placeholder.
+ */
+ public static enum SystemProperty implements AutoAddingProperty {
+
+ /**
+ * Allow setting the merged manifest file package name.
+ */
+ PACKAGE {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElement(this, actionRecorder, value, document.getRootNode());
+ }
+ },
+ /**
+ * http://developer.android.com/guide/topics/manifest/manifest-element.html#vcode
+ */
+ VERSION_CODE {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElementInAndroidNS(this, actionRecorder, value, document.getRootNode());
+ }
+ },
+ /**
+ * http://developer.android.com/guide/topics/manifest/manifest-element.html#vname
+ */
+ VERSION_NAME {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElementInAndroidNS(this, actionRecorder, value, document.getRootNode());
+ }
+ },
+ /**
+ * http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#min
+ */
+ MIN_SDK_VERSION {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElementInAndroidNS(this, actionRecorder, value,
+ createOrGetUseSdk(actionRecorder, document));
+ }
+ },
+ /**
+ * http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#target
+ */
+ TARGET_SDK_VERSION {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElementInAndroidNS(this, actionRecorder, value,
+ createOrGetUseSdk(actionRecorder, document));
+ }
+ };
+
+ public String toCamelCase() {
+ return SdkUtils.constantNameToCamelCase(name());
+ }
+
+ // utility method to add an attribute which name is derived from the enum name().
+ private static void addToElement(
+ SystemProperty systemProperty,
+ ActionRecorder actionRecorder,
+ String value,
+ XmlElement to) {
+
+ to.getXml().setAttribute(systemProperty.toCamelCase(), value);
+ XmlAttribute xmlAttribute = new XmlAttribute(to,
+ to.getXml().getAttributeNode(systemProperty.toCamelCase()), null);
+ actionRecorder.recordAttributeAction(xmlAttribute, new Actions.AttributeRecord(
+ Actions.ActionType.INJECTED,
+ new Actions.ActionLocation(
+ to.getSourceLocation(),
+ PositionImpl.UNKNOWN),
+ xmlAttribute.getId(),
+ null, /* reason */
+ null /* attributeOperationType */));
+ }
+
+ // utility method to add an attribute in android namespace which local name is derived from
+ // the enum name().
+ private static void addToElementInAndroidNS(
+ SystemProperty systemProperty,
+ ActionRecorder actionRecorder,
+ String value,
+ XmlElement to) {
+
+ String toolsPrefix = XmlUtils.lookupNamespacePrefix(
+ to.getXml(), SdkConstants.ANDROID_URI, SdkConstants.ANDROID_NS_NAME, false);
+ to.getXml().setAttributeNS(SdkConstants.ANDROID_URI,
+ toolsPrefix + XmlUtils.NS_SEPARATOR + systemProperty.toCamelCase(),
+ value);
+ Attr attr = to.getXml().getAttributeNodeNS(SdkConstants.ANDROID_URI,
+ systemProperty.toCamelCase());
+
+ XmlAttribute xmlAttribute = new XmlAttribute(to, attr, null);
+ actionRecorder.recordAttributeAction(xmlAttribute,
+ new Actions.AttributeRecord(
+ Actions.ActionType.INJECTED,
+ new Actions.ActionLocation(
+ to.getSourceLocation(),
+ PositionImpl.UNKNOWN),
+ xmlAttribute.getId(),
+ null, /* reason */
+ null /* attributeOperationType */
+ )
+ );
+
+ }
+
+ // utility method to create or get an existing use-sdk xml element under manifest.
+ // this could be made more generic by adding more metadata to the enum but since there is
+ // only one case so far, keep it simple.
+ private static XmlElement createOrGetUseSdk(
+ ActionRecorder actionRecorder, XmlDocument document) {
+
+ Element manifest = document.getXml().getDocumentElement();
+ NodeList usesSdks = manifest
+ .getElementsByTagName(ManifestModel.NodeTypes.USES_SDK.toXmlName());
+ if (usesSdks.getLength() == 0) {
+ // create it first.
+ Element useSdk = manifest.getOwnerDocument().createElement(
+ ManifestModel.NodeTypes.USES_SDK.toXmlName());
+ manifest.appendChild(useSdk);
+ XmlElement xmlElement = new XmlElement(useSdk, document);
+ Actions.NodeRecord nodeRecord = new Actions.NodeRecord(
+ Actions.ActionType.INJECTED,
+ new Actions.ActionLocation(xmlElement.getSourceLocation(),
+ PositionImpl.UNKNOWN),
+ xmlElement.getId(),
+ "use-sdk injection requested",
+ NodeOperationType.STRICT);
+ actionRecorder.recordNodeAction(xmlElement, nodeRecord);
+ return xmlElement;
+ } else {
+ return new XmlElement((Element) usesSdks.item(0), document);
+ }
+ }
+ }
+
+ /**
+ * Defines the merging type expected from the tool.
+ */
+ public enum MergeType {
+ /**
+ * Application merging type is used when packaging an application with a set of imported
+ * libraries. The resulting merged android manifest is final and is not expected to be
+ * imported in another application.
+ */
+ APPLICATION,
+
+ /**
+ * Library merging typee is used when packaging a library. The resulting android manifest
+ * file will not merge in all the imported libraries this library depends on. Also the tools
+ * annotations will not be removed as they can be useful when later importing the resulting
+ * merged android manifest into an application.
+ */
+ LIBRARY
+ }
+
+ /**
+ * Defines a property that can add or override itself into an XML document.
+ */
+ public interface AutoAddingProperty {
+
+ /**
+ * Add itself (possibly just override the current value) with the passed value
+ * @param actionRecorder to record actions.
+ * @param document the xml document to add itself to.
+ * @param value the value to set of this property.
+ */
+ void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value);
+ }
+
+ /**
+ * Perform {@link com.android.manifmerger.ManifestMerger2.SystemProperty} injection.
+ * @param mergingReport to log actions and errors.
+ * @param xmlDocument the xml document to inject into.
+ */
+ protected void performSystemPropertiesInjection(
+ MergingReport.Builder mergingReport,
+ XmlDocument xmlDocument) {
+ for (SystemProperty systemProperty : SystemProperty.values()) {
+ String propertyOverride = mSystemPropertyResolver.getValue(systemProperty);
+ if (propertyOverride != null) {
+ systemProperty.addTo(mergingReport.getActionRecorder(), xmlDocument, propertyOverride);
+ }
+ }
+ }
+
+ /**
+ * This class will hold all invocation parameters for the manifest merging tool.
+ *
+ * There are broadly three types of input to the merging tool :
+ * <ul>
+ * <li>Build types and flavors overriding manifests</li>
+ * <li>Application main manifest</li>
+ * <li>Library manifest files</li></lib>
+ * </ul>
+ *
+ * Only the main manifest file is a mandatory parameter.
+ *
+ * High level description of the merging will be as follow :
+ * <ol>
+ * <li>Build type and flavors will be merged first in the order they were added. Highest
+ * priority file added first, lowest added last.</li>
+ * <li>Resulting document is merged with lower priority application main manifest file.</li>
+ * <li>Resulting document is merged with each library file manifest file in the order
+ * they were added. Highest priority added first, lowest added last.</li>
+ * <li>Resulting document is returned as results of the merging process.</li>
+ * </ol>
+ *
+ */
+ public static final class Invoker<T extends Invoker<T>>{
+
+ protected final File mMainManifestFile;
+
+ protected final ImmutableMap.Builder<SystemProperty, String> mSystemProperties =
+ new ImmutableMap.Builder<SystemProperty, String>();
+
+ protected final ILogger mLogger;
+
+ protected final ImmutableMap.Builder<String, String> mPlaceHolders =
+ new ImmutableMap.Builder<String, String>();
+
+ /**
+ * Sets a value for a {@link com.android.manifmerger.ManifestMerger2.SystemProperty}
+ * @param override the property to set
+ * @param value the value for the property
+ * @return itself.
+ */
+ public Invoker setOverride(SystemProperty override, String value) {
+ mSystemProperties.put(override, value);
+ return thisAsT();
+ }
+
+ /**
+ * Adds placeholders names and associated values for substitution.
+ * @return itself.
+ */
+ public Invoker setPlaceHolderValues(Map<String, String> keyValuePairs) {
+ mPlaceHolders.putAll(keyValuePairs);
+ return thisAsT();
+ }
+
+ /**
+ * Adds a new placeholder name and value for substitution.
+ * @return itself.
+ */
+ public Invoker setPlaceHolderValue(String placeHolderName, String value) {
+ mPlaceHolders.put(placeHolderName, value);
+ return thisAsT();
+ }
+
+ /**
+ * Optional behavior of the merging tool can be turned on by setting these Feature.
+ */
+ public enum Feature {
+
+ /**
+ * Keep all intermediary merged files during the merging process. This is particularly
+ * useful for debugging/tracing purposes.
+ */
+ KEEP_INTERMEDIARY_STAGES,
+
+ /**
+ * When logging file names, use {@link java.io.File#getName()} rather than
+ * {@link java.io.File#getPath()}
+ */
+ PRINT_SIMPLE_FILENAMES
+ }
+
+ private final ImmutableList.Builder<Pair<String, File>> mLibraryFilesBuilder =
+ new ImmutableList.Builder<Pair<String, File>>();
+ private final ImmutableList.Builder<File> mFlavorsAndBuildTypeFiles =
+ new ImmutableList.Builder<File>();
+ private final ImmutableList.Builder<Feature> mFeaturesBuilder =
+ new ImmutableList.Builder<Feature>();
+ private final MergeType mMergeType;
+
+ /**
+ * Creates a new builder with the mandatory main manifest file.
+ * @param mainManifestFile application main manifest file.
+ * @param logger the logger interface to use.
+ */
+ private Invoker(
+ @NonNull File mainManifestFile, @NonNull ILogger logger, MergeType mergeType) {
+ this.mMainManifestFile = Preconditions.checkNotNull(mainManifestFile);
+ this.mLogger = logger;
+ this.mMergeType = mergeType;
+ }
+
+ /**
+ * Add one library file manifest, will be added last in the list of library files which will
+ * make the parameter the lowest priority library manifest file.
+ * @param file the library manifest file to add.
+ * @return itself.
+ */
+ public Invoker addLibraryManifest(File file) {
+ if (mMergeType == MergeType.LIBRARY) {
+ throw new IllegalStateException(
+ "Cannot add library dependencies manifests when creating a library");
+ }
+ mLibraryFilesBuilder.add(Pair.of(file.getName(), file));
+ return thisAsT();
+ }
+
+ public Invoker addLibraryManifests(List<Pair<String, File>> namesAndFiles) {
+ if (mMergeType == MergeType.LIBRARY && !namesAndFiles.isEmpty()) {
+ throw new IllegalStateException(
+ "Cannot add library dependencies manifests when creating a library");
+ }
+ mLibraryFilesBuilder.addAll(namesAndFiles);
+ return thisAsT();
+ }
+
+ /**
+ * Add several library file manifests at then end of the list which will make them the
+ * lowest priority manifest files. The relative priority between all the files passed as
+ * parameters will be respected.
+ * @param files library manifest files to add last.
+ * @return itself.
+ */
+ public Invoker addLibraryManifests(File... files) {
+ for (File file : files) {
+ addLibraryManifest(file);
+ }
+ return thisAsT();
+ }
+
+ /**
+ * Add a flavor or build type manifest file last in the list.
+ * @param file build type or flavor manifest file
+ * @return itself.
+ */
+ public Invoker addFlavorAndBuildTypeManifest(File file) {
+ this.mFlavorsAndBuildTypeFiles.add(file);
+ return thisAsT();
+ }
+
+ /**
+ * Add several flavor or build type manifest files last in the list. Relative priorities
+ * between the passed files as parameters will be respected.
+ * @param files build type of flavor manifest files to add.
+ * @return itself.
+ */
+ public Invoker addFlavorAndBuildTypeManifests(File... files) {
+ this.mFlavorsAndBuildTypeFiles.add(files);
+ return thisAsT();
+ }
+
+ /**
+ * Sets some optional features for the merge tool.
+ *
+ * @param features one to many features to set.
+ * @return itself.
+ */
+ public Invoker withFeatures(Feature...features) {
+ mFeaturesBuilder.add(features);
+ return thisAsT();
+ }
+
+ /**
+ * Perform the merging and return the result.
+ *
+ * @return an instance of {@link com.android.manifmerger.MergingReport} that will give
+ * access to all the logging and merging records.
+ *
+ * This method can be invoked several time and will re-do the file merges.
+ *
+ * @throws com.android.manifmerger.ManifestMerger2.MergeFailureException if the merging cannot be completed successfully.
+ */
+ public MergingReport merge() throws MergeFailureException {
+
+ // provide some free placeholders values.
+ ImmutableMap<SystemProperty, String> systemProperties = mSystemProperties.build();
+ if (systemProperties.containsKey(SystemProperty.PACKAGE)) {
+ mPlaceHolders.put("packageName", systemProperties.get(SystemProperty.PACKAGE));
+ mPlaceHolders.put("applicationId", systemProperties.get(SystemProperty.PACKAGE));
+ }
+
+ ManifestMerger2 manifestMerger =
+ new ManifestMerger2(
+ mLogger,
+ mMainManifestFile,
+ mLibraryFilesBuilder.build(),
+ mFlavorsAndBuildTypeFiles.build(),
+ mFeaturesBuilder.build(),
+ new MapBasedKeyBasedValueResolver<String>(mPlaceHolders.build()),
+ new MapBasedKeyBasedValueResolver<SystemProperty>(
+ systemProperties),
+ mMergeType);
+ return manifestMerger.merge();
+ }
+
+ @SuppressWarnings("unchecked")
+ private T thisAsT() {
+ return (T) this;
+ }
+ }
+
+ /**
+ * Helper class for map based placeholders key value pairs.
+ */
+ public static class MapBasedKeyBasedValueResolver<T> implements KeyBasedValueResolver<T> {
+
+ private final ImmutableMap<T, String> keyValues;
+
+ public MapBasedKeyBasedValueResolver(Map<T, String> keyValues) {
+ this.keyValues = ImmutableMap.copyOf(keyValues);
+ }
+
+ @Nullable
+ @Override
+ public String getValue(@NonNull T key) {
+ return keyValues.get(key);
+ }
+ }
+
+ private static class ManifestInfo {
+
+ private ManifestInfo(
+ String name,
+ File location,
+ XmlDocument.Type type,
+ Optional<String> mainManifestPackageName) {
+ mName = name;
+ mLocation = location;
+ mType = type;
+ mMainManifestPackageName = mainManifestPackageName;
+ }
+
+ private final String mName;
+ private final File mLocation;
+ private final XmlDocument.Type mType;
+ private final Optional<String> mMainManifestPackageName;
+
+ File getLocation() {
+ return mLocation;
+ }
+
+ XmlDocument.Type getType() {
+ return mType;
+ }
+
+ Optional<String> getMainManifestPackageName() {
+ return mMainManifestPackageName;
+ }
+ }
+
+ private static class LoadedManifestInfo extends ManifestInfo {
+
+ private final XmlDocument mXmlDocument;
+
+ private LoadedManifestInfo(ManifestInfo manifestInfo ,XmlDocument xmlDocument) {
+ super(manifestInfo.mName,
+ manifestInfo.mLocation,
+ manifestInfo.mType,
+ manifestInfo.getMainManifestPackageName());
+ mXmlDocument = xmlDocument;
+ }
+
+ public XmlDocument getXmlDocument() {
+ return mXmlDocument;
+ }
+ }
+
+ /**
+ * Implementation a {@link com.android.manifmerger.KeyResolver} capable of resolving all
+ * selectors value in the context of the passed libraries to this merging activities.
+ */
+ static class SelectorResolver implements KeyResolver<String> {
+
+ private final Map<String, String> mSelectors = new HashMap<String, String>();
+
+ protected void addSelector(String key, String value) {
+ mSelectors.put(key, value);
+ }
+
+ @Nullable
+ @Override
+ public String resolve(String key) {
+ return mSelectors.get(key);
+ }
+
+ @Override
+ public Iterable<String> getKeys() {
+ return mSelectors.keySet();
+ }
+ }
+
+ // a wrapper exception to all sorts of failure exceptions that can be thrown during merging.
+ public static class MergeFailureException extends Exception {
+
+ protected MergeFailureException(Exception cause) {
+ super(cause);
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestModel.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestModel.java
new file mode 100644
index 0000000..5a4d9d2
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestModel.java
@@ -0,0 +1,681 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.manifmerger.AttributeModel.Hexadecimal32BitsWithMinimumValue;
+import static com.android.manifmerger.AttributeModel.MultiValueValidator;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.android.utils.SdkUtils;
+import com.android.xml.AndroidManifest;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Model for the manifest file merging activities.
+ * <p>
+ *
+ * This model will describe each element that is eligible for merging and associated merging
+ * policies. It is not reusable as most of its interfaces are private but a future enhancement
+ * could easily make this more generic/reusable if we need to merge more than manifest files.
+ *
+ */
+@Immutable
+class ManifestModel {
+
+ /**
+ * Interface responsible for providing a key extraction capability from a xml element.
+ * Some elements store their keys as an attribute, some as a sub-element attribute, some don't
+ * have any key.
+ */
+ @Immutable
+ interface NodeKeyResolver {
+
+ /**
+ * Returns the key associated with this xml element.
+ * @param xmlElement the xml element to get the key from
+ * @return the key as a string to uniquely identify xmlElement from similarly typed elements
+ * in the xml document or null if there is no key.
+ */
+ @Nullable String getKey(Element xmlElement);
+
+ /**
+ * Returns the attribute(s) used to store the xml element key.
+ * @return the key attribute(s) name(s) or null of this element does not have a key.
+ */
+ @NonNull
+ ImmutableList<String> getKeyAttributesNames();
+ }
+
+ /**
+ * Implementation of {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} that do not
+ * provide any key (the element has to be unique in the xml document).
+ */
+ private static class NoKeyNodeResolver implements NodeKeyResolver {
+
+ @Override
+ @Nullable
+ public String getKey(Element xmlElement) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of();
+ }
+ }
+
+ /**
+ * Implementation of {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} that uses an
+ * attribute to resolve the key value.
+ */
+ private static class AttributeBasedNodeKeyResolver implements NodeKeyResolver {
+
+ @Nullable private final String mNamespaceUri;
+ private final String mAttributeName;
+
+ /**
+ * Build a new instance capable of resolving an xml element key from the passed attribute
+ * namespace and local name.
+ * @param namespaceUri optional namespace for the attribute name.
+ * @param attributeName attribute name
+ */
+ private AttributeBasedNodeKeyResolver(@Nullable String namespaceUri,
+ @NonNull String attributeName) {
+ this.mNamespaceUri = namespaceUri;
+ this.mAttributeName = Preconditions.checkNotNull(attributeName);
+ }
+
+ @Override
+ @Nullable
+ public String getKey(Element xmlElement) {
+ String key = mNamespaceUri == null
+ ? xmlElement.getAttribute(mAttributeName)
+ : xmlElement.getAttributeNS(mNamespaceUri, mAttributeName);
+ if (Strings.isNullOrEmpty(key)) return null;
+ return key;
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of(mAttributeName);
+ }
+ }
+
+ /**
+ * Subclass of {@link com.android.manifmerger.ManifestModel.AttributeBasedNodeKeyResolver} that
+ * uses "android:name" as the attribute.
+ */
+ private static final NodeKeyResolver DEFAULT_NAME_ATTRIBUTE_RESOLVER =
+ new AttributeBasedNodeKeyResolver(ANDROID_URI, SdkConstants.ATTR_NAME);
+
+ private static final NoKeyNodeResolver DEFAULT_NO_KEY_NODE_RESOLVER = new NoKeyNodeResolver();
+
+ /**
+ * A {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} capable of extracting the
+ * element key first in an "android:name" attribute and if not value found there, in the
+ * "android:glEsVersion" attribute.
+ */
+ private static final NodeKeyResolver NAME_AND_GLESVERSION_KEY_RESOLVER = new NodeKeyResolver() {
+ private final NodeKeyResolver nameAttrResolver = DEFAULT_NAME_ATTRIBUTE_RESOLVER;
+ private final NodeKeyResolver glEsVersionResolver =
+ new AttributeBasedNodeKeyResolver(ANDROID_URI,
+ AndroidManifest.ATTRIBUTE_GLESVERSION);
+
+ @Nullable
+ @Override
+ public String getKey(Element xmlElement) {
+ String key = nameAttrResolver.getKey(xmlElement);
+ return Strings.isNullOrEmpty(key)
+ ? glEsVersionResolver.getKey(xmlElement)
+ : key;
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of(SdkConstants.ATTR_NAME, AndroidManifest.ATTRIBUTE_GLESVERSION);
+ }
+ };
+
+ /**
+ * Specific {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} for intent-filter
+ * elements.
+ * Intent filters do not have a proper key, therefore their identity is really carried by
+ * the presence of the action and category sub-elements.
+ * We concatenate such elements sub-keys (after sorting them to work around declaration order)
+ * and use that for the intent-filter unique key.
+ */
+ private static final NodeKeyResolver INTENT_FILTER_KEY_RESOLVER = new NodeKeyResolver() {
+ @Nullable
+ @Override
+ public String getKey(Element element) {
+ OrphanXmlElement xmlElement = new OrphanXmlElement(element);
+ assert(xmlElement.getType() == NodeTypes.INTENT_FILTER);
+ // concatenate all actions and categories attribute names.
+ List<String> allSubElementKeys = new ArrayList<String>();
+ NodeList childNodes = element.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() != Node.ELEMENT_NODE) continue;
+ OrphanXmlElement subElement = new OrphanXmlElement((Element) child);
+ if (subElement.getType() == NodeTypes.ACTION
+ || subElement.getType() == NodeTypes.CATEGORY) {
+ Attr nameAttribute = subElement.getXml()
+ .getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (nameAttribute != null) {
+ allSubElementKeys.add(nameAttribute.getValue());
+ }
+ }
+ }
+ Collections.sort(allSubElementKeys);
+ return Joiner.on('+').join(allSubElementKeys);
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of("action#name", "category#name");
+ }
+ };
+
+ /**
+ * Implementation of {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} that
+ * combined two attributes values to create the key value.
+ */
+ private static final class TwoAttributesBasedKeyResolver implements NodeKeyResolver {
+ private final NodeKeyResolver firstAttributeKeyResolver;
+ private final NodeKeyResolver secondAttributeKeyResolver;
+
+ private TwoAttributesBasedKeyResolver(NodeKeyResolver firstAttributeKeyResolver,
+ NodeKeyResolver secondAttributeKeyResolver) {
+ this.firstAttributeKeyResolver = firstAttributeKeyResolver;
+ this.secondAttributeKeyResolver = secondAttributeKeyResolver;
+ }
+
+ @Nullable
+ @Override
+ public String getKey(Element xmlElement) {
+ String firstKey = firstAttributeKeyResolver.getKey(xmlElement);
+ String secondKey = secondAttributeKeyResolver.getKey(xmlElement);
+
+ return Strings.isNullOrEmpty(firstKey)
+ ? secondKey
+ : Strings.isNullOrEmpty(secondKey)
+ ? firstKey
+ : firstKey + "+" + secondKey;
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of(firstAttributeKeyResolver.getKeyAttributesNames().get(0),
+ secondAttributeKeyResolver.getKeyAttributesNames().get(0));
+ }
+ }
+
+ private static final AttributeModel.BooleanValidator BOOLEAN_VALIDATOR =
+ new AttributeModel.BooleanValidator();
+
+ private static final boolean MULTIPLE_DECLARATION_FOR_SAME_KEY_ALLOWED = true;
+
+ /**
+ * Definitions of the support node types in the Android Manifest file.
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-intro.html/>}
+ * for more details about the xml format.
+ *
+ * There is no DTD or schema associated with the file type so this is best effort in providing
+ * some metadata on the elements of the Android's xml file.
+ *
+ * Each xml element is defined as an enum value and for each node, extra metadata is added
+ * <ul>
+ * <li>{@link com.android.manifmerger.MergeType} to identify how the merging engine
+ * should process this element.</li>
+ * <li>{@link com.android.manifmerger.ManifestModel.NodeKeyResolver} to resolve the
+ * element's key. Elements can have an attribute like "android:name", others can use
+ * a sub-element, and finally some do not have a key and are meant to be unique.</li>
+ * <li>List of attributes models with special behaviors :
+ * <ul>
+ * <li>Smart substitution of class names to fully qualified class names using the
+ * document's package declaration. The list's size can be 0..n</li>
+ * <li>Implicit default value when no defined on the xml element.</li>
+ * <li>{@link AttributeModel.Validator} to validate attribute value against.</li>
+ * </ul>
+ * </ul>
+ *
+ * It is of the outermost importance to keep this model correct as it is used by the merging
+ * engine to make all its decisions. There should not be special casing in the engine, all
+ * decisions must be represented here.
+ *
+ * If you find yourself needing to extend the model to support future requirements, do it here
+ * and modify the engine to make proper decision based on the added metadata.
+ */
+ enum NodeTypes {
+
+ /**
+ * Action (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/action-element.html>
+ * Action Xml documentation</a>}
+ */
+ ACTION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Activity (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/activity-element.html>
+ * Activity Xml documentation</a>}
+ */
+ ACTIVITY(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel("parentActivityName").setIsPackageDependent(),
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Activity-alias (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/activity-alias-element.html>
+ * Activity-alias Xml documentation</a>}
+ */
+ ACTIVITY_ALIAS(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel("targetActivity").setIsPackageDependent(),
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Application (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/application-element.html>
+ * Application Xml documentation</a>}
+ */
+ APPLICATION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER,
+ AttributeModel.newModel("backupAgent").setIsPackageDependent(),
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Category (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/category-element.html>
+ * Category Xml documentation</a>}
+ */
+ CATEGORY(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Compatible-screens (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/compatible-screens-element.html>
+ * Category Xml documentation</a>}
+ */
+ COMPATIBLE_SCREENS(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Data (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/data-element.html>
+ * Category Xml documentation</a>}
+ */
+ DATA(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Grant-uri-permission (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/grant-uri-permission-element.html>
+ * Category Xml documentation</a>}
+ */
+ GRANT_URI_PERMISSION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Instrumentation (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/instrumentation-element.html>
+ * Instrunentation Xml documentation</a>}
+ */
+ INSTRUMENTATION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Intent-filter (contained in activity, activity-alias, service, receiver)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/intent-filter-element.html>
+ * Intent-filter Xml documentation</a>}
+ */
+ INTENT_FILTER(MergeType.ALWAYS, INTENT_FILTER_KEY_RESOLVER,
+ MULTIPLE_DECLARATION_FOR_SAME_KEY_ALLOWED),
+
+ /**
+ * Manifest (top level node)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-element.html>
+ * Manifest Xml documentation</a>}
+ */
+ MANIFEST(MergeType.MERGE_CHILDREN_ONLY, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Meta-data (contained in activity, activity-alias, application, provider, receiver)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/meta-data-element.html>
+ * Meta-data Xml documentation</a>}
+ */
+ META_DATA(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Path-permission (contained in provider)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/path-permission-element.html>
+ * Meta-data Xml documentation</a>}
+ */
+ PATH_PERMISSION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Permission-group (contained in manifest).
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/permission-group-element.html>
+ * Permission-group Xml documentation</a>}
+ *
+ */
+ PERMISSION_GROUP(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME)),
+
+ /**
+ * Permission (contained in manifest).
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/permission-element.html>
+ * Permission Xml documentation</a>}
+ *
+ */
+ PERMISSION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME),
+ AttributeModel.newModel("protectionLevel")
+ .setDefaultValue("normal")
+ // TODO : this will need to be populated from
+ // sdk/platforms/android-19/data/res/values.attrs_manifest.xml
+ .setOnReadValidator(new MultiValueValidator(
+ "normal", "dangerous", "signature", "signatureOrSystem"))),
+
+ /**
+ * Permission-tree (contained in manifest).
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/permission-tree-element.html>
+ * Permission-tree Xml documentation</a>}
+ *
+ */
+ PERMISSION_TREE(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME)),
+
+ /**
+ * Provider (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/provider-element.html>
+ * Provider Xml documentation</a>}
+ */
+ PROVIDER(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME)
+ .setIsPackageDependent()),
+
+ /**
+ * Receiver (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/receiver-element.html>
+ * Receiver Xml documentation</a>}
+ */
+ RECEIVER(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Screen (contained in compatible-screens)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/compatible-screens-element.html>
+ * Receiver Xml documentation</a>}
+ */
+ SCREEN(MergeType.MERGE, new TwoAttributesBasedKeyResolver(
+ new AttributeBasedNodeKeyResolver(ANDROID_URI, "screenSize"),
+ new AttributeBasedNodeKeyResolver(ANDROID_URI, "screenDensity"))),
+
+ /**
+ * Service (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/application-element.html>
+ * Service Xml documentation</a>}
+ */
+ SERVICE(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Supports-gl-texture (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/supports-gl-texture-element.html>
+ * Support-screens Xml documentation</a>}
+ */
+ SUPPORTS_GL_TEXTURE(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Support-screens (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/supports-screens-element.html>
+ * Support-screens Xml documentation</a>}
+ */
+ SUPPORTS_SCREENS(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Uses-configuration (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-configuration-element.html>
+ * Support-screens Xml documentation</a>}
+ */
+ USES_CONFIGURATION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Uses-feature (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-feature-element.html>
+ * Uses-feature Xml documentation</a>}
+ */
+ USES_FEATURE(MergeType.MERGE, NAME_AND_GLESVERSION_KEY_RESOLVER,
+ AttributeModel.newModel(AndroidManifest.ATTRIBUTE_REQUIRED)
+ .setDefaultValue(SdkConstants.VALUE_TRUE)
+ .setOnReadValidator(BOOLEAN_VALIDATOR)
+ .setMergingPolicy(AttributeModel.OR_MERGING_POLICY),
+ AttributeModel.newModel(AndroidManifest.ATTRIBUTE_GLESVERSION)
+ .setDefaultValue("0x00010000")
+ .setOnReadValidator(new Hexadecimal32BitsWithMinimumValue(0x00010000))),
+
+ /**
+ * Use-library (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-library-element.html>
+ * Use-library Xml documentation</a>}
+ */
+ USES_LIBRARY(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(AndroidManifest.ATTRIBUTE_REQUIRED)
+ .setDefaultValue(SdkConstants.VALUE_TRUE)
+ .setOnReadValidator(BOOLEAN_VALIDATOR)
+ .setMergingPolicy(AttributeModel.OR_MERGING_POLICY)),
+
+ /**
+ * Uses-permission (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-permission-element.html>
+ * Uses-permission Xml documentation</a>}
+ */
+ USES_PERMISSION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Uses-sdk (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-sdk-element.html>
+ * Uses-sdk Xml documentation</a>}
+ */
+ USES_SDK(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER,
+ AttributeModel.newModel("minSdkVersion")
+ .setDefaultValue(SdkConstants.VALUE_1)
+ .setMergingPolicy(AttributeModel.NO_MERGING_POLICY),
+ AttributeModel.newModel("maxSdkVersion")
+ .setMergingPolicy(AttributeModel.NO_MERGING_POLICY),
+ // TODO : model target's default value is minSdkVersion value.
+ AttributeModel.newModel("targetSdkVersion")
+ .setMergingPolicy(AttributeModel.NO_MERGING_POLICY)
+ ),
+
+ /**
+ * Custom tag for any application specific element
+ */
+ CUSTOM(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER);
+
+
+ private final MergeType mMergeType;
+ private final NodeKeyResolver mNodeKeyResolver;
+ private final ImmutableList<AttributeModel> mAttributeModels;
+ private final boolean mMultipleDeclarationAllowed;
+
+ private NodeTypes(
+ @NonNull MergeType mergeType,
+ @NonNull NodeKeyResolver nodeKeyResolver,
+ @Nullable AttributeModel.Builder... attributeModelBuilders) {
+ this(mergeType, nodeKeyResolver, false, attributeModelBuilders);
+ }
+
+ private NodeTypes(
+ @NonNull MergeType mergeType,
+ @NonNull NodeKeyResolver nodeKeyResolver,
+ boolean mutipleDeclarationAllowed,
+ @Nullable AttributeModel.Builder... attributeModelBuilders) {
+ this.mMergeType = Preconditions.checkNotNull(mergeType);
+ this.mNodeKeyResolver = Preconditions.checkNotNull(nodeKeyResolver);
+ ImmutableList.Builder<AttributeModel> attributeModels =
+ new ImmutableList.Builder<AttributeModel>();
+ if (attributeModelBuilders != null) {
+ for (AttributeModel.Builder attributeModelBuilder : attributeModelBuilders) {
+ attributeModels.add(attributeModelBuilder.build());
+ }
+ }
+ this.mAttributeModels = attributeModels.build();
+ this.mMultipleDeclarationAllowed = mutipleDeclarationAllowed;
+ }
+
+ @NonNull
+ NodeKeyResolver getNodeKeyResolver() {
+ return mNodeKeyResolver;
+ }
+
+ ImmutableList<AttributeModel> getAttributeModels() {
+ return mAttributeModels.asList();
+ }
+
+ @Nullable
+ AttributeModel getAttributeModel(XmlNode.NodeName attributeName) {
+ // mAttributeModels could be replaced with a Map if the number of models grows.
+ for (AttributeModel attributeModel : mAttributeModels) {
+ if (attributeModel.getName().equals(attributeName)) {
+ return attributeModel;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the Xml name for this node type
+ */
+ String toXmlName() {
+ return SdkUtils.constantNameToXmlName(this.name());
+ }
+
+ /**
+ * Returns the {@link NodeTypes} instance from an xml element name (without namespace
+ * decoration). For instance, an xml element
+ * <pre>
+ * {@code
+ * <activity android:name="foo">
+ * ...
+ * </activity>}
+ * </pre>
+ * has a xml simple name of "activity" which will resolve to {@link NodeTypes#ACTIVITY} value.
+ *
+ * Note : a runtime exception will be generated if no mapping from the simple name to a
+ * {@link com.android.manifmerger.ManifestModel.NodeTypes} exists.
+ *
+ * @param xmlSimpleName the xml (lower-hyphen separated words) simple name.
+ * @return the {@link NodeTypes} associated with that element name.
+ */
+ static NodeTypes fromXmlSimpleName(String xmlSimpleName) {
+ String constantName = SdkUtils.xmlNameToConstantName(xmlSimpleName);
+
+ try {
+ return NodeTypes.valueOf(constantName);
+ } catch (IllegalArgumentException e) {
+ // if this element name is not a known tag, we categorize it as 'custom' which will
+ // be simply merged. It will prevent us from catching simple spelling mistakes but
+ // extensibility is a must have feature.
+ return NodeTypes.CUSTOM;
+ }
+ }
+
+ MergeType getMergeType() {
+ return mMergeType;
+ }
+
+ /**
+ * Returns true if multiple declaration for the same type and key are allowed or false if
+ * there must be only one declaration of this element for a particular key value.
+ */
+ boolean areMultipleDeclarationAllowed() {
+ return mMultipleDeclarationAllowed;
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergeType.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergeType.java
new file mode 100644
index 0000000..dbe99e4
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergeType.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Defines the default merging activity for same type.
+ *
+ * WIP more work needed.
+ */
+public enum MergeType {
+
+ /**
+ * Merge this element's children with lower priority element's children. Do not merge
+ * element's attributes.
+ */
+ MERGE_CHILDREN_ONLY,
+
+ /**
+ * Merge this element with lower priority elements.
+ */
+ MERGE,
+
+ /**
+ * Always generate a merging failure when encountering lower priority elements.
+ */
+ CONFLICT,
+
+ /**
+ * Do not attempt to merge with lower priority elements.
+ */
+ IGNORE,
+
+ /**
+ * Always consume lower priority elements unless it is strictly equals to the higher priority
+ * element.
+ */
+ ALWAYS,
+}
\ No newline at end of file
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
index 46093ba..9438dda 100755
--- 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
@@ -33,12 +33,9 @@
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;
@@ -85,7 +82,7 @@
@NonNull ManifestMerger merger) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- Reader reader = new BufferedReader(new FileReader(xmlFile));
+ Reader reader = XmlUtils.getUtfReader(xmlFile);
InputSource is = new InputSource(reader);
factory.setNamespaceAware(true);
factory.setValidating(false);
@@ -156,12 +153,7 @@
@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);
+ Document doc = XmlUtils.parseDocument(xml, true);
findLineNumbers(doc, 1);
if (errorContext.getFileName() != null) {
setSource(doc, new File(errorContext.getFileName()));
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergingReport.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergingReport.java
new file mode 100644
index 0000000..c4ec1d1
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergingReport.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.XmlLoader.SourceLocation;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.Immutable;
+import com.android.utils.ILogger;
+import com.android.utils.SdkUtils;
+import com.android.utils.XmlUtils;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Contains the result of 2 files merging.
+ *
+ * TODO: more work necessary, this is pretty raw as it stands.
+ */
+@Immutable
+public class MergingReport {
+
+ private final Optional<XmlDocument> mMergedDocument;
+ private final Result mResult;
+ // list of logging events, ordered by their recording time.
+ private final ImmutableList<Record> mRecords;
+ private final ImmutableList<String> mIntermediaryStages;
+ private final Actions mActions;
+
+ private MergingReport(Optional<XmlDocument> mergedDocument,
+ @NonNull Result result,
+ @NonNull ImmutableList<Record> records,
+ @NonNull ImmutableList<String> intermediaryStages,
+ @NonNull Actions actions) {
+ mMergedDocument = mergedDocument;
+ mResult = result;
+ mRecords = records;
+ mIntermediaryStages = intermediaryStages;
+ mActions = actions;
+ }
+
+ /**
+ * dumps all logging records to a logger.
+ */
+ public void log(ILogger logger) {
+ for (Record record : mRecords) {
+ switch(record.mSeverity) {
+ case WARNING:
+ logger.warning(record.toString());
+ break;
+ case ERROR:
+ logger.error(null /* throwable */, record.toString());
+ break;
+ case INFO:
+ logger.verbose(record.toString());
+ break;
+ default:
+ logger.error(null /* throwable */, "Unhandled record type " + record.mSeverity);
+ }
+ }
+ mActions.log(logger);
+ }
+
+ /**
+ * Return the resulting merged document.
+ */
+ public Optional<XmlDocument> getMergedDocument() {
+ return mMergedDocument;
+ }
+
+ /**
+ * Returns all the merging intermediary stages if
+ * {@link com.android.manifmerger.ManifestMerger2.Invoker.Feature#KEEP_INTERMEDIARY_STAGES}
+ * is set.
+ */
+ public ImmutableList<String> getIntermediaryStages() {
+ return mIntermediaryStages;
+ }
+
+ /**
+ * Overall result of the merging process.
+ */
+ public enum Result {
+ SUCCESS,
+
+ WARNING,
+
+ ERROR;
+
+ public boolean isSuccess() {
+ return this == SUCCESS || this == WARNING;
+ }
+
+ public boolean isWarning() {
+ return this == WARNING;
+ }
+
+ public boolean isError() {
+ return this == ERROR;
+ }
+ }
+
+ @NonNull
+ public Result getResult() {
+ return mResult;
+ }
+
+ @NonNull
+ public ImmutableList<Record> getLoggingRecords() {
+ return mRecords;
+ }
+
+ @NonNull
+ public Actions getActions() {
+ return mActions;
+ }
+
+ @NonNull
+ public String getReportString() {
+ switch (mResult) {
+ case SUCCESS:
+ return "Manifest merger executed successfully";
+ case WARNING:
+ return mRecords.size() > 1
+ ? "Manifest merger exited with warnings, see logs"
+ : "Manifest merger warning : " + mRecords.get(0).mLog;
+ case ERROR:
+ return mRecords.size() > 1
+ ? "Manifest merger failed with multiple errors, see logs"
+ : "Manifest merger failed : " + mRecords.get(0).mLog;
+ default:
+ return "Manifest merger returned an invalid result " + mResult;
+ }
+ }
+
+ /**
+ * Log record. This is used to give users some information about what is happening and
+ * what might have gone wrong.
+ *
+ * TODO: need to enhance to add SourceLocation, and make this more machine readable.
+ */
+ public static class Record {
+
+ public enum Severity {WARNING, ERROR, INFO }
+
+ private final Severity mSeverity;
+ private final String mLog;
+ private final SourceLocation mSourceLocation;
+ private final int mLineNumber;
+ private final int mColumnNumber;
+
+ private Record(
+ @NonNull SourceLocation sourceLocation,
+ int lineNumber,
+ int columnNumber,
+ @NonNull Severity severity,
+ @NonNull String mLog) {
+ this.mSourceLocation = sourceLocation;
+ this.mLineNumber = lineNumber;
+ this.mColumnNumber = columnNumber;
+ this.mSeverity = severity;
+ this.mLog = mLog;
+ }
+
+ public Severity getSeverity() {
+ return mSeverity;
+ }
+
+ public String getMessage() {
+ return mLog;
+ }
+
+ @Override
+ public String toString() {
+ return mSourceLocation.print(false)
+ + ":" + mLineNumber + ":" + mColumnNumber + " "
+ + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, mSeverity.toString())
+ + ":\n\t"
+ + mLog;
+ }
+ }
+
+ /**
+ * This builder is used to accumulate logging, action recording and intermediary results as
+ * well as final result of the merging activity.
+ *
+ * Once the merging is finished, the {@link #build()} is called to return an immutable version
+ * of itself with all the logging, action recordings and xml files obtainable.
+ *
+ */
+ static class Builder {
+
+ private Optional<XmlDocument> mMergedDocument = Optional.absent();
+ private ImmutableList.Builder<Record> mRecordBuilder = new ImmutableList.Builder<Record>();
+ private ImmutableList.Builder<String> mIntermediaryStages = new ImmutableList.Builder<String>();
+ private boolean mHasWarnings = false;
+ private boolean mHasErrors = false;
+ private ActionRecorder mActionRecorder = new ActionRecorder();
+ private final ILogger mLogger;
+
+ Builder(ILogger logger) {
+ mLogger = logger;
+ }
+
+
+ Builder setMergedDocument(@NonNull XmlDocument mergedDocument) {
+ mMergedDocument = Optional.of(mergedDocument);
+ return this;
+ }
+
+ Builder addMessage(@NonNull SourceLocation errorLocation,
+ int line,
+ int column,
+ @NonNull Record.Severity severity,
+ @NonNull String message) {
+
+ switch (severity) {
+ case ERROR:
+ mHasErrors = true;
+ break;
+ case WARNING:
+ mHasWarnings = true;
+ break;
+ }
+ mRecordBuilder.add(new Record(
+ errorLocation, line, column, severity, message));
+ return this;
+ }
+
+ Builder addMergingStage(String xml) {
+ mIntermediaryStages.add(xml);
+ return this;
+ }
+
+ /**
+ * Returns true if some fatal errors were reported.
+ */
+ boolean hasErrors() {
+ return mHasErrors;
+ }
+
+ ActionRecorder getActionRecorder() {
+ return mActionRecorder;
+ }
+
+ MergingReport build() {
+ Result result = mHasErrors
+ ? Result.ERROR
+ : mHasWarnings
+ ? Result.WARNING
+ : Result.SUCCESS;
+
+ return new MergingReport(
+ mMergedDocument,
+ result,
+ mRecordBuilder.build(),
+ mIntermediaryStages.build(),
+ mActionRecorder.build());
+ }
+
+ public ILogger getLogger() {
+ return mLogger;
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/NodeOperationType.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/NodeOperationType.java
new file mode 100644
index 0000000..cea0df0
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/NodeOperationType.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.utils.SdkUtils;
+
+/**
+ * Defines node operation types as it can be provided by user's through attributes on the
+ * target xml element.
+ *
+ * <p>
+ * Example:
+ * <pre>
+ * <activity android:name="com.foo.bar.ActivityUI"
+ * tools:node="remove_children">
+ * </activity>
+ * </pre>
+ *
+ */
+public enum NodeOperationType implements ConvertibleName {
+
+ /**
+ * Merges further definitions of the same element with this one.
+ */
+ MERGE(false),
+
+ /**
+ * Remove all children from the target element before merging it into the resulting merged
+ * manifest. This basically merges attributes only (attributes annotation still applies).
+ */
+ MERGE_ONLY_ATTRIBUTES(false),
+
+ /**
+ * Replace further definitions of the same element with this one. There can be 0..n similar
+ * elements replaced with the annotated xml element.
+ */
+ REPLACE(false),
+
+ /**
+ * Remove the next definition of the same element from the resulting merged manifest. There can
+ * be only one similar element removed. If further definition are encountered, a merging
+ * failure will be initiated.
+ */
+ REMOVE(true),
+
+ /**
+ * Remove all definitions of the same element from the resulting merged manifest.
+ */
+ REMOVE_ALL(true),
+
+ /**
+ * Remove all children from the target element before merging it into the resulting merged
+ * manifest. This basically merges all attributes only (attributes annotation still applies).
+ */
+ REMOVE_CHILDREN(false),
+
+ /**
+ * No further definition of this element should be encountered. A merging tool failure will be
+ * generated if there is one.
+ */
+ STRICT(false);
+
+ // specifies whether the node operation can support an associated {@link Selector}
+ private final boolean mIsSelectable;
+
+ private NodeOperationType(boolean isSelectable) {
+ mIsSelectable = isSelectable;
+ }
+
+ /**
+ * Returns true if this operation supports a {@link com.android.manifmerger.Selector}
+ */
+ public boolean isSelectable() {
+ return mIsSelectable;
+ }
+
+ @Override
+ public String toXmlName() {
+ return SdkUtils.constantNameToXmlName(name());
+ }
+
+ @Override
+ public String toCamelCaseName() {
+ return SdkUtils.constantNameToCamelCase(name());
+ }
+
+ /**
+ * Returns true if the element will override (remove or replace) lower priority elements.
+ */
+ public boolean isOverriding() {
+ return this == REMOVE || this == REMOVE_ALL || this == REPLACE;
+ }
+
+ /**
+ * Local xml name of node operation types.
+ */
+ static final String NODE_LOCAL_NAME = "node"; //$NON-NLS-1$
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/OrphanXmlElement.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/OrphanXmlElement.java
new file mode 100644
index 0000000..33520e6
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/OrphanXmlElement.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.ManifestModel.NodeTypes;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.PositionXmlParser;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+import org.w3c.dom.Element;
+
+/**
+ * An xml element that does not belong to a {@link com.android.manifmerger.XmlDocument}
+ */
+public class OrphanXmlElement extends XmlNode {
+
+ @NonNull
+ private final Element mXml;
+
+ @NonNull
+ private final NodeTypes mType;
+
+ public OrphanXmlElement(@NonNull Element xml) {
+
+ mXml = Preconditions.checkNotNull(xml);
+ NodeTypes nodeType;
+ String elementName = mXml.getNodeName();
+ // this is bit more complicated than it should be. Look first if there is a namespace
+ // prefix in the name, most elements don't. If they do, however, strip it off if it is the
+ // android prefix, but if it's custom namespace prefix, classify the node as CUSTOM.
+ int indexOfColon = elementName.indexOf(':');
+ if (indexOfColon != -1) {
+ String androidPrefix = XmlUtils.lookupNamespacePrefix(xml, SdkConstants.ANDROID_URI);
+ if (androidPrefix.equals(elementName.substring(0, indexOfColon))) {
+ nodeType = NodeTypes.fromXmlSimpleName(elementName.substring(indexOfColon + 1));
+ } else {
+ nodeType = NodeTypes.CUSTOM;
+ }
+ } else {
+ nodeType = NodeTypes.fromXmlSimpleName(elementName);
+ }
+ mType = nodeType;
+ }
+
+ /**
+ * Returns true if this xml element's {@link NodeTypes} is
+ * the passed one.
+ */
+ public boolean isA(NodeTypes type) {
+ return this.mType == type;
+ }
+
+ @NonNull
+ @Override
+ public Element getXml() {
+ return mXml;
+ }
+
+
+ @Override
+ public NodeKey getId() {
+ return new NodeKey(Strings.isNullOrEmpty(getKey())
+ ? getName().toString()
+ : getName().toString() + "#" + getKey());
+ }
+
+ @Override
+ public NodeName getName() {
+ return XmlNode.unwrapName(mXml);
+ }
+
+ /**
+ * Returns this xml element {@link NodeTypes}
+ */
+ @NonNull
+ public NodeTypes getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the unique key for this xml element within the xml file or null if there can be only
+ * one element of this type.
+ */
+ @Nullable
+ public String getKey() {
+ return mType.getNodeKeyResolver().getKey(mXml);
+ }
+
+ @Override
+ public PositionXmlParser.Position getPosition() {
+ return PositionImpl.UNKNOWN;
+ }
+
+ @Override
+ @NonNull
+ public XmlLoader.SourceLocation getSourceLocation() {
+ return XmlLoader.UNKNOWN;
+ }
+}
+
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/OtherOperationType.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/OtherOperationType.java
new file mode 100644
index 0000000..d0ca70b
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/OtherOperationType.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * List of other http://schemas.android.com/tools namespace instructions that can be present in a
+ * manifest file.
+ */
+public enum OtherOperationType {
+
+ // used to direct lint
+ ignore,
+
+ // used to direct lint
+ targetAPI
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderHandler.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderHandler.java
new file mode 100644
index 0000000..0699cae
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderHandler.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Replaces all placeholders of the form ${name} with a tool invocation provided value
+ */
+public class PlaceholderHandler {
+
+ // regular expression to recognize placeholders like ${name}, potentially surrounded by a
+ // prefix and suffix string. this will split in 3 groups, the prefix, the placeholder name, and
+ // the suffix.
+ private final Pattern mPattern = Pattern.compile("([^\\$]*)\\$\\{([^\\}]*)\\}(.*)");
+
+ /**
+ * Interface to provide a value for a placeholder key.
+ * @param <T> the key type
+ */
+ public interface KeyBasedValueResolver<T> {
+
+ /**
+ * Returns a placeholder value for the placeholder key or null if none exists.
+ */
+ @Nullable
+ String getValue(@NonNull T key);
+ }
+
+ /**
+ * Visits a document's entire tree and check each attribute for a placeholder existence.
+ * If one is found, delegate to the provided {@link KeyBasedValueResolver} to provide a value
+ * for the placeholder.
+ * <p>
+ * If no value is provided, an error will be generated.
+ *
+ * @param xmlDocument the xml document to visit
+ * @param valueProvider the placeholder value provider.
+ * @param mergingReportBuilder to report errors and log actions.
+ */
+ public void visit(@NonNull XmlDocument xmlDocument,
+ @NonNull KeyBasedValueResolver<String> valueProvider,
+ @NonNull MergingReport.Builder mergingReportBuilder) {
+
+ visit(xmlDocument.getRootNode(), valueProvider, mergingReportBuilder);
+ }
+
+ private void visit(XmlElement xmlElement,
+ KeyBasedValueResolver<String> valueProvider,
+ MergingReport.Builder mergingReportBuilder) {
+
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+
+ Matcher matcher = mPattern.matcher(xmlAttribute.getValue());
+ if (matcher.matches()) {
+ String placeholderValue = valueProvider.getValue(matcher.group(2));
+ if (placeholderValue == null) {
+ xmlAttribute.addMessage(mergingReportBuilder, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Attribute %1$s at %2$s requires a placeholder substitution"
+ + " but no value for <%3$s> is provided.",
+ xmlAttribute.getId(),
+ xmlAttribute.printPosition(),
+ matcher.group(2)
+ ));
+ } else {
+ // record the attribute set
+ mergingReportBuilder.getActionRecorder().recordAttributeAction(
+ xmlAttribute,
+ PositionImpl.UNKNOWN,
+ Actions.ActionType.INJECTED,
+ null /* attributeOperationType */);
+
+ String attrValue = matcher.group(1) + placeholderValue + matcher.group(3);
+ xmlAttribute.getXml().setValue(attrValue);
+ }
+ }
+ }
+ for (XmlElement childElement : xmlElement.getMergeableElements()) {
+ visit(childElement, valueProvider, mergingReportBuilder);
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/PositionImpl.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PositionImpl.java
new file mode 100644
index 0000000..2d2eabd
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PositionImpl.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.utils.PositionXmlParser;
+import com.google.common.base.Preconditions;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Implementation of {@link com.android.utils.PositionXmlParser.Position} capable of initializing
+ * from xml definition or with given line, column and offset.
+*/
+final class PositionImpl implements PositionXmlParser.Position {
+
+ private static final String POSITION_ELEMENT = "position";
+ private static final String LINE_ATTRIBUTE = "line";
+ private static final String COLUMN_ATTRIBUTE = "col";
+ private static final String OFFSET_ATTRIBUTE = "offset";
+
+ /**
+ * Unknown position on an action happens when the action did not originate from any source file
+ * but from environmental factors like placeholder injection, implicit permissions when
+ * upgrading, etc...
+ */
+ static final PositionXmlParser.Position UNKNOWN = new PositionImpl(0, 0, 0);
+
+ private final int mLine;
+ private final int mColumn;
+ private final int mOffset;
+
+ private PositionImpl(int line, int column, int offset) {
+ mLine = line;
+ mColumn = column;
+ mOffset = offset;
+ }
+
+ /**
+ * Creates a {@link com.android.utils.PositionXmlParser.Position} from its Xml reprentation.
+ * @param xml the xml representation of the element
+ * @return the {link Position} initialized from its Xml representation.
+ */
+ public static PositionXmlParser.Position fromXml(Element xml) {
+ Preconditions.checkArgument(xml.getNodeName().equals(POSITION_ELEMENT));
+ return new PositionImpl(
+ Integer.parseInt(xml.getAttribute(LINE_ATTRIBUTE)),
+ Integer.parseInt(xml.getAttribute(COLUMN_ATTRIBUTE)),
+ Integer.parseInt(xml.getAttribute(OFFSET_ATTRIBUTE)));
+ }
+
+ /**
+ * Persists a position to an xml representation.
+ * @param position the position to be persisted.
+ * @param document the document to persist into.
+ * @return the xml {@link Element} containing the persisted position.
+ */
+ public static Element toXml(PositionXmlParser.Position position, Document document) {
+ Element xml = document.createElement(PositionImpl.POSITION_ELEMENT);
+ xml.setAttribute(LINE_ATTRIBUTE, String.valueOf(position.getLine()));
+ xml.setAttribute(COLUMN_ATTRIBUTE, String.valueOf(position.getColumn()));
+ xml.setAttribute(OFFSET_ATTRIBUTE, String.valueOf(position.getOffset()));
+ return xml;
+
+ }
+
+ @Nullable
+ @Override
+ public PositionXmlParser.Position getEnd() {
+ return null;
+ }
+
+ @Override
+ public void setEnd(@NonNull PositionXmlParser.Position end) {
+
+ }
+
+ @Override
+ public int getLine() {
+ return mLine;
+ }
+
+ @Override
+ public int getOffset() {
+ return mOffset;
+ }
+
+ @Override
+ public int getColumn() {
+ return mColumn;
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/PostValidator.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PostValidator.java
new file mode 100644
index 0000000..2ff2845
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PostValidator.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.Actions.ActionType;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+
+import org.w3c.dom.Node;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Validator that runs post merging activities and verifies that all "tools:" instructions
+ * triggered an action by the merging tool.
+ * <p>
+ *
+ * This is primarily to catch situations like a user entered a tools:remove="foo" directory on one
+ * of its elements and that particular attribute was never removed during the merges possibly
+ * indicating an unforeseen change of configuration.
+ * <p>
+ *
+ * Most of the output from this validation should be warnings.
+ */
+public class PostValidator {
+
+ /**
+ * Post validation of the merged document. This will essentially check that all merging
+ * instructions were applied at least once.
+ *
+ * @param xmlDocument merged document to check.
+ * @param mergingReport report for errors and warnings.
+ */
+ public static void validate(
+ @NonNull XmlDocument xmlDocument,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ Preconditions.checkNotNull(xmlDocument);
+ Preconditions.checkNotNull(mergingReport);
+ enforceAndroidNamespaceDeclaration(xmlDocument);
+ reOrderElements(xmlDocument.getRootNode());
+ validate(xmlDocument.getRootNode(),
+ mergingReport.getActionRecorder().build(),
+ mergingReport);
+ }
+
+ /**
+ * Enforces {@link com.android.SdkConstants#ANDROID_URI} declaration in the top level element.
+ * It is possible that the original manifest file did not contain any attribute declaration,
+ * therefore not requiring a xmlns: declaration. Yet the implicit elements handling may have
+ * added attributes requiring the namespace declaration.
+ */
+ private static void enforceAndroidNamespaceDeclaration(@NonNull XmlDocument xmlDocument) {
+ XmlElement manifest = xmlDocument.getRootNode();
+ for (XmlAttribute xmlAttribute : manifest.getAttributes()) {
+ if (xmlAttribute.getXml().getName().startsWith(SdkConstants.XMLNS) &&
+ xmlAttribute.getValue().equals(SdkConstants.ANDROID_URI)) {
+ return;
+ }
+ }
+ // if we are here, we did not find the namespace declaration, add it.
+ manifest.getXml().setAttribute(SdkConstants.XMLNS + ":" + "android",
+ SdkConstants.ANDROID_URI);
+ }
+
+ /**
+ * Reorder child elements :
+ * <li>
+ * <ul> <application> is moved last in the list of children
+ * of the <manifest> element.
+ * <ul> uses-sdk is moved first in the list of children of the <manifest> element </ul>
+ * </li>
+ * @param xmlElement the root element of the manifest document.
+ */
+ private static void reOrderElements(XmlElement xmlElement) {
+
+ reOrderApplication(xmlElement);
+ reOrderUsesSdk(xmlElement);
+ }
+
+ /**
+ * Reorder application element
+ *
+ * @param xmlElement the root element of the manifest document.
+ */
+ private static void reOrderApplication(XmlElement xmlElement) {
+
+ // look up application element.
+ Optional<XmlElement> element = xmlElement
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
+ if (!element.isPresent()) {
+ return;
+ }
+ XmlElement applicationElement = element.get();
+
+ List<Node> comments = XmlElement.getLeadingComments(applicationElement.getXml());
+
+ // move the application's comments if any.
+ for (Node comment : comments) {
+ xmlElement.getXml().removeChild(comment);
+ xmlElement.getXml().appendChild(comment);
+ }
+ // remove the application element and add it back, it will be automatically placed last.
+ xmlElement.getXml().removeChild(applicationElement.getXml());
+ xmlElement.getXml().appendChild(applicationElement.getXml());
+ }
+
+ /**
+ * Reorder uses-sdk element
+ *
+ * @param xmlElement the root element of the manifest document.
+ */
+ private static void reOrderUsesSdk(XmlElement xmlElement) {
+
+ // look up application element.
+ Optional<XmlElement> element = xmlElement
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.USES_SDK, null);
+ if (!element.isPresent()) {
+ return;
+ }
+
+ XmlElement usesSdk = element.get();
+ Node firstChild = xmlElement.getXml().getFirstChild();
+ // already the first element ?
+ if (firstChild == usesSdk) {
+ return;
+ }
+
+ List<Node> comments = XmlElement.getLeadingComments(usesSdk.getXml());
+
+ // move the application's comments if any.
+ for (Node comment : comments) {
+ xmlElement.getXml().removeChild(comment);
+ xmlElement.getXml().insertBefore(comment, firstChild);
+ }
+ // remove the application element and add it back, it will be automatically placed last.
+ xmlElement.getXml().removeChild(usesSdk.getXml());
+ xmlElement.getXml().insertBefore(usesSdk.getXml(), firstChild);
+ }
+
+ /**
+ * Validate an xml element and recursively its children elements, ensuring that all merging
+ * instructions were applied.
+ *
+ * @param xmlElement xml element to validate.
+ * @param actions the actions recorded during the merging activities.
+ * @param mergingReport report for errors and warnings.
+ * instructions were applied once or {@link MergingReport.Result#WARNING} otherwise.
+ */
+ private static void validate(
+ XmlElement xmlElement,
+ Actions actions,
+ MergingReport.Builder mergingReport) {
+
+ NodeOperationType operationType = xmlElement.getOperationType();
+ switch (operationType) {
+ case REPLACE:
+ // we should find at least one rejected twin.
+ if (!isNodeOperationPresent(xmlElement, actions, ActionType.REJECTED)) {
+ xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
+ String.format(
+ "%1$s was tagged at %2$s:%3$d to replace another declaration "
+ + "but no other declaration present",
+ xmlElement.getId(),
+ xmlElement.getDocument().getSourceLocation().print(true),
+ xmlElement.getPosition().getLine()
+ ));
+ }
+ break;
+ case REMOVE:
+ case REMOVE_ALL:
+ // we should find at least one rejected twin.
+ if (!isNodeOperationPresent(xmlElement, actions, ActionType.REJECTED)) {
+ xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
+ String.format(
+ "%1$s was tagged at %2$s:%3$d to remove other declarations "
+ + "but no other declaration present",
+ xmlElement.getId(),
+ xmlElement.getDocument().getSourceLocation().print(true),
+ xmlElement.getPosition().getLine()
+ ));
+ }
+ break;
+ }
+ validateAttributes(xmlElement, actions, mergingReport);
+ validateAndroidAttributes(xmlElement, mergingReport);
+ for (XmlElement child : xmlElement.getMergeableElements()) {
+ validate(child, actions, mergingReport);
+ }
+ }
+
+
+ /**
+ * Verifies that all merging attributes on a passed xml element were applied.
+ */
+ private static void validateAttributes(
+ XmlElement xmlElement,
+ Actions actions,
+ MergingReport.Builder mergingReport) {
+
+ Collection<Map.Entry<XmlNode.NodeName, AttributeOperationType>> attributeOperations
+ = xmlElement.getAttributeOperations();
+ for (Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperation :
+ attributeOperations) {
+ switch (attributeOperation.getValue()) {
+ case REMOVE:
+ if (!isAttributeOperationPresent(
+ xmlElement, attributeOperation, actions, ActionType.REJECTED)) {
+ xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
+ String.format(
+ "%1$s@%2$s was tagged at %3$s:%4$d to remove other"
+ + " declarations but no other declaration present",
+ xmlElement.getId(),
+ attributeOperation.getKey(),
+ xmlElement.getDocument().getSourceLocation().print(true),
+ xmlElement.getPosition().getLine()
+ ));
+ }
+ break;
+ case REPLACE:
+ if (!isAttributeOperationPresent(
+ xmlElement, attributeOperation, actions, ActionType.REJECTED)) {
+ xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
+ String.format(
+ "%1$s@%2$s was tagged at %3$s:%4$d to replace other"
+ + " declarations but no other declaration present",
+ xmlElement.getId(),
+ attributeOperation.getKey(),
+ xmlElement.getDocument().getSourceLocation().print(true),
+ xmlElement.getPosition().getLine()
+ ));
+ }
+ break;
+ }
+ }
+
+ }
+
+ /**
+ * Check in our list of applied actions that a particular
+ * {@link com.android.manifmerger.Actions.ActionType} action was recorded on the passed element.
+ * @return true if it was applied, false otherwise.
+ */
+ private static boolean isNodeOperationPresent(XmlElement xmlElement,
+ Actions actions,
+ ActionType action) {
+
+ for (Actions.NodeRecord nodeRecord : actions.getNodeRecords(xmlElement.getId())) {
+ if (nodeRecord.getActionType() == action) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check in our list of attribute actions that a particular
+ * {@link com.android.manifmerger.Actions.ActionType} action was recorded on the passed element.
+ * @return true if it was applied, false otherwise.
+ */
+ private static boolean isAttributeOperationPresent(XmlElement xmlElement,
+ Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperation,
+ Actions actions,
+ ActionType action) {
+
+ for (Actions.AttributeRecord attributeRecord : actions.getAttributeRecords(
+ xmlElement.getId(), attributeOperation.getKey())) {
+ if (attributeRecord.getActionType() == action) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Validates all {@link com.android.manifmerger.XmlElement} attributes belonging to the
+ * {@link com.android.SdkConstants#ANDROID_URI} namespace.
+ *
+ * @param xmlElement xml element to check the attributes from.
+ * @param mergingReport report for errors and warnings.
+ */
+ private static void validateAndroidAttributes(XmlElement xmlElement,
+ MergingReport.Builder mergingReport) {
+
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+ if (xmlAttribute.getModel() != null) {
+ AttributeModel.Validator onWriteValidator = xmlAttribute.getModel()
+ .getOnWriteValidator();
+ if (onWriteValidator != null) {
+ onWriteValidator.validates(
+ mergingReport, xmlAttribute, xmlAttribute.getValue());
+ }
+ }
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/PreValidator.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PreValidator.java
new file mode 100644
index 0000000..f4b0d81
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PreValidator.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.MergingReport.Record.Severity.*;
+import static com.android.manifmerger.XmlNode.NodeKey;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.utils.SdkUtils;
+import com.android.utils.XmlUtils;
+import com.android.xml.AndroidManifest;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Validates a loaded {@link XmlDocument} and check for potential inconsistencies in the model due
+ * to user error or omission.
+ *
+ * This is implemented as a separate class so it can be invoked by tools independently from the
+ * merging process.
+ *
+ * This validator will check the state of the loaded xml document before any merging activity is
+ * attempted. It verifies things like a "tools:replace="foo" attribute has a "android:foo"
+ * attribute also declared on the same element (since we want to replace its value).
+ */
+public class PreValidator {
+
+ private PreValidator(){
+ }
+
+ /**
+ * Validates a loaded {@link com.android.manifmerger.XmlDocument} and return a status of the
+ * merging model.
+ *
+ * Will return one the following status :
+ * <ul>
+ * <li>{@link com.android.manifmerger.MergingReport.Result#SUCCESS} : the merging model is
+ * correct, merging should be attempted</li>
+ * <li>{@link com.android.manifmerger.MergingReport.Result#WARNING} : the merging model
+ * contains non fatal error, user should be notified, merging can be attempted</li>
+ * <li>{@link com.android.manifmerger.MergingReport.Result#ERROR} : the merging model
+ * contains errors, user must be notified, merging should not be attempted</li>
+ * </ul>
+ *
+ * A successful validation does not mean that the merging will be successful, it only means
+ * that the {@link com.android.SdkConstants#TOOLS_URI} instructions are correct and consistent.
+ *
+ * @param mergingReport report to log warnings and errors.
+ * @param xmlDocument the loaded xml part.
+ * @return one the {@link com.android.manifmerger.MergingReport.Result} value.
+ */
+ @NonNull
+ public static MergingReport.Result validate(
+ @NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlDocument xmlDocument) {
+
+ validateManifestAttribute(
+ mergingReport, xmlDocument.getRootNode(), xmlDocument.getFileType());
+ return validate(mergingReport, xmlDocument.getRootNode());
+ }
+
+ private static MergingReport.Result validate(MergingReport.Builder mergingReport,
+ XmlElement xmlElement) {
+
+ validateAttributeInstructions(mergingReport, xmlElement);
+
+ validateAndroidAttributes(mergingReport, xmlElement);
+
+ checkSelectorPresence(mergingReport, xmlElement);
+
+ // create a temporary hash map of children indexed by key to ensure key uniqueness.
+ Map<NodeKey, XmlElement> childrenKeys = new HashMap<NodeKey, XmlElement>();
+ for (XmlElement childElement : xmlElement.getMergeableElements()) {
+
+ // if this element is tagged with 'tools:node=removeAll', ensure it has no other
+ // attributes.
+ if (childElement.getOperationType() == NodeOperationType.REMOVE_ALL) {
+ validateRemoveAllOperation(mergingReport, childElement);
+ } else {
+ if (checkKeyPresence(mergingReport, childElement)) {
+ XmlElement twin = childrenKeys.get(childElement.getId());
+ if (twin != null && !childElement.getType().areMultipleDeclarationAllowed()) {
+ // we have 2 elements with the same identity, if they are equals,
+ // issue a warning, if not, issue an error.
+ String message = String.format(
+ "Element %1$s at %2$s duplicated with element declared at %3$s",
+ childElement.getId(),
+ childElement.printPosition(),
+ childrenKeys.get(childElement.getId()).printPosition());
+ if (twin.compareTo(childElement).isPresent()) {
+ childElement.addMessage(mergingReport, ERROR, message);
+ } else {
+ childElement.addMessage(mergingReport, WARNING, message);
+ }
+ }
+ childrenKeys.put(childElement.getId(), childElement);
+ }
+ validate(mergingReport, childElement);
+ }
+ }
+ return mergingReport.hasErrors()
+ ? MergingReport.Result.ERROR : MergingReport.Result.SUCCESS;
+ }
+
+ /**
+ * Validate an xml declaration with 'tools:node="removeAll" annotation. There should not
+ * be any other attribute declaration on this element.
+ */
+ private static void validateRemoveAllOperation(MergingReport.Builder mergingReport,
+ XmlElement element) {
+
+ NamedNodeMap attributes = element.getXml().getAttributes();
+ if (attributes.getLength() > 1) {
+ List<String> extraAttributeNames = new ArrayList<String>();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Node item = attributes.item(i);
+ if (!(SdkConstants.TOOLS_URI.equals(item.getNamespaceURI()) &&
+ NodeOperationType.NODE_LOCAL_NAME.equals(item.getLocalName()))) {
+ extraAttributeNames.add(item.getNodeName());
+ }
+ }
+ String message = String.format(
+ "Element %1$s at %2$s annotated with 'tools:node=\"removeAll\"' cannot "
+ + "have other attributes : %3$s",
+ element.getId(),
+ element.printPosition(),
+ Joiner.on(',').join(extraAttributeNames)
+ );
+ element.addMessage(mergingReport, ERROR, message);
+ }
+ }
+
+ private static void checkSelectorPresence(MergingReport.Builder mergingReport,
+ XmlElement element) {
+
+ Attr selectorAttribute =
+ element.getXml().getAttributeNodeNS(SdkConstants.TOOLS_URI, Selector.SELECTOR_LOCAL_NAME);
+ if (selectorAttribute!=null && !element.getOperationType().isSelectable()) {
+ String message = String.format(
+ "Unsupported tools:selector=\"%1$s\" found on node %2$s at %3$s",
+ selectorAttribute.getValue(),
+ element.getId(),
+ element.printPosition());
+ element.addMessage(mergingReport, ERROR, message);
+ }
+ }
+
+ private static void validateManifestAttribute(
+ MergingReport.Builder mergingReport, XmlElement manifest, XmlDocument.Type fileType) {
+ Attr attributeNode = manifest.getXml().getAttributeNode(AndroidManifest.ATTRIBUTE_PACKAGE);
+ // it's ok for an overlay to not have a package name, it's not ok for a main manifest
+ // and it's a warning for a library.
+ if (attributeNode == null && fileType != XmlDocument.Type.OVERLAY) {
+ manifest.addMessage(mergingReport,
+ fileType == XmlDocument.Type.MAIN ? ERROR : WARNING,
+ String.format(
+ "Missing 'package' declaration in manifest at %1$s",
+ manifest.printPosition()));
+ }
+ }
+
+ /**
+ * Checks that an element which is supposed to have a key does have one.
+ * @param mergingReport report to log warnings and errors.
+ * @param xmlElement xml element to check for key presence.
+ * @return true if the element has a valid key or false it does not need one or it is invalid.
+ */
+ private static boolean checkKeyPresence(
+ MergingReport.Builder mergingReport,
+ XmlElement xmlElement) {
+ ManifestModel.NodeKeyResolver nodeKeyResolver = xmlElement.getType().getNodeKeyResolver();
+ ImmutableList<String> keyAttributesNames = nodeKeyResolver.getKeyAttributesNames();
+ if (keyAttributesNames.isEmpty()) {
+ return false;
+ }
+ if (Strings.isNullOrEmpty(xmlElement.getKey())) {
+ // we should have a key but we don't.
+ String message = keyAttributesNames.size() > 1
+ ? String.format(
+ "Missing one of the key attributes '%1$s' on element %2$s at %3$s",
+ Joiner.on(',').join(keyAttributesNames),
+ xmlElement.getId(),
+ xmlElement.printPosition())
+ : String.format(
+ "Missing '%1$s' key attribute on element %2$s at %3$s",
+ keyAttributesNames.get(0),
+ xmlElement.getId(),
+ xmlElement.printPosition());
+ xmlElement.addMessage(mergingReport, ERROR, message);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Validate attributes part of the {@link com.android.SdkConstants#ANDROID_URI}
+ * @param mergingReport report to log warnings and errors.
+ * @param xmlElement xml element to check its attributes.
+ */
+ private static void validateAndroidAttributes(MergingReport.Builder mergingReport,
+ XmlElement xmlElement) {
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+ AttributeModel model = xmlAttribute.getModel();
+ if (model != null && model.getOnReadValidator() != null) {
+ model.getOnReadValidator().validates(
+ mergingReport, xmlAttribute, xmlAttribute.getValue());
+ }
+ }
+ }
+
+ /**
+ * Validates attributes part of the {@link com.android.SdkConstants#TOOLS_URI}
+ * @param mergingReport report to log warnings and errors.
+ * @param xmlElement xml element to check its attributes.
+ */
+ private static void validateAttributeInstructions(
+ MergingReport.Builder mergingReport,
+ XmlElement xmlElement) {
+
+ for (Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperationTypeEntry :
+ xmlElement.getAttributeOperations()) {
+
+ Optional<XmlAttribute> attribute = xmlElement
+ .getAttribute(attributeOperationTypeEntry.getKey());
+ switch(attributeOperationTypeEntry.getValue()) {
+ case STRICT:
+ break;
+ case REMOVE:
+ // check we are not provided a new value.
+ if (attribute.isPresent()) {
+ xmlElement.addMessage(mergingReport, ERROR, String.format(
+ "tools:remove specified at line:%d for attribute %s, but "
+ + "attribute also declared at line:%d, "
+ + "do you want to use tools:replace instead ?",
+ xmlElement.getLine(),
+ attributeOperationTypeEntry.getKey(),
+ attribute.get().getPosition().getLine()
+ ));
+ }
+ break;
+ case REPLACE:
+ // check we are provided a new value
+ if (!attribute.isPresent()) {
+ xmlElement.addMessage(mergingReport, ERROR, String.format(
+ "tools:replace specified at line:%d for attribute %s, but "
+ + "no new value specified",
+ xmlElement.getLine(),
+ attributeOperationTypeEntry.getKey()
+ ));
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unhandled AttributeOperationType " +
+ attributeOperationTypeEntry.getValue());
+ }
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/Selector.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Selector.java
new file mode 100644
index 0000000..73ece80
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Selector.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.concurrency.Immutable;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+
+/**
+ * Represents a selector to be able to identify manifest file xml elements.
+ */
+@Immutable
+public class Selector {
+
+ /**
+ * local name for tools:selector attributes.
+ */
+ public static final String SELECTOR_LOCAL_NAME = "selector";
+
+ @NonNull private final String mPackageName;
+
+ public Selector(@NonNull String packageName) {
+ mPackageName = Preconditions.checkNotNull(packageName);
+ }
+
+ /**
+ * Returns true if the passed element is "selected" by this selector. If so, any action this
+ * selector decorated will be applied to the element.
+ */
+ boolean appliesTo(XmlElement element) {
+ Optional<XmlAttribute> packageName = element.getDocument().getPackage();
+ return packageName.isPresent() && mPackageName.equals(packageName.get().getValue());
+ }
+
+ /**
+ * Returns true if the passed resolver can resolve this selector, false otherwise.
+ */
+ boolean isResolvable(KeyResolver<String> resolver) {
+ return resolver.resolve(mPackageName) != null;
+ }
+
+ @Override
+ public String toString() {
+ return mPackageName;
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ToolsInstructionsCleaner.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ToolsInstructionsCleaner.java
new file mode 100644
index 0000000..4d0e21d
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ToolsInstructionsCleaner.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.MergingReport.Result.ERROR;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.android.utils.ILogger;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Removes all "tools:" statements from the resulting xml.
+ *
+ * All attributes belonging to the {@link com.android.SdkConstants#ANDROID_URI} namespace will be
+ * removed. If an element contained a "tools:node=\"remove\"" attribute, the element will be
+ * deleted.
+ */
+@Immutable
+public class ToolsInstructionsCleaner {
+
+ private static final String REMOVE_OPERATION_XML_MAME =
+ NodeOperationType.REMOVE.toCamelCaseName();
+ private static final String REMOVE_ALL_OPERATION_XML_MAME =
+ NodeOperationType.REMOVE_ALL.toCamelCaseName();
+
+ /**
+ * Cleans all attributes belonging to the {@link com.android.SdkConstants#TOOLS_URI} namespace.
+ *
+ * @param document the xml document to clean
+ * @param logger logger to use in case of errors and warnings.
+ * @return the cleaned document or null if an error occurred.
+ */
+ @Nullable
+ public static XmlDocument cleanToolsReferences(
+ @NonNull XmlDocument document,
+ @NonNull ILogger logger) {
+
+ document = Preconditions.checkNotNull(document);
+ logger = Preconditions.checkNotNull(logger);
+ MergingReport.Result result = cleanToolsReferences(document.getRootNode().getXml(),
+ logger);
+ return result == MergingReport.Result.SUCCESS
+ ? document.reparse()
+ : null;
+ }
+
+ private static MergingReport.Result cleanToolsReferences(
+ Element element,
+ ILogger logger) {
+
+ NamedNodeMap namedNodeMap = element.getAttributes();
+ if (namedNodeMap != null) {
+ // make a copy of the original list of attributes as we will remove some during this
+ // process.
+ List<Node> attributes = new ArrayList<Node>();
+ for (int i = 0; i < namedNodeMap.getLength(); i++) {
+ attributes.add(namedNodeMap.item(i));
+ }
+ for (Node attribute : attributes) {
+ if (SdkConstants.TOOLS_URI.equals(attribute.getNamespaceURI())) {
+ // we need to special case when the element contained tools:node="remove"
+ // since it also needs to be deleted unless it had a selector.
+ // if this is ools:node="removeAll", we always delete the element whether or
+ // not there is a tools:selector.
+ boolean hasSelector = namedNodeMap.getNamedItemNS(
+ SdkConstants.TOOLS_URI, "selector") != null;
+ if (attribute.getLocalName().equals(NodeOperationType.NODE_LOCAL_NAME)
+ && (attribute.getNodeValue().equals(REMOVE_ALL_OPERATION_XML_MAME)
+ || (attribute.getNodeValue().equals(REMOVE_OPERATION_XML_MAME))
+ && !hasSelector)) {
+
+ if (element.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
+ logger.error(null /* Throwable */,
+ String.format(
+ "tools:node=\"%1$s\" not allowed on top level %2$s element",
+ attribute.getNodeValue(),
+ XmlNode.unwrapName(element)));
+ return ERROR;
+ } else {
+ element.getParentNode().removeChild(element);
+ }
+ } else {
+ // anything else, we just clean the attribute.
+ element.removeAttributeNS(
+ attribute.getNamespaceURI(), attribute.getLocalName());
+ }
+ }
+ // this could also be the xmlns:tools declaration.
+ if (attribute.getNodeName().startsWith(SdkConstants.XMLNS_PREFIX)
+ && SdkConstants.TOOLS_URI.equals(attribute.getNodeValue())) {
+ element.removeAttribute(attribute.getNodeName());
+ }
+ }
+ }
+ // make a copy of the element children since we will be removing some during
+ // this process, we don't want side effects.
+ NodeList childNodes = element.getChildNodes();
+ ImmutableList.Builder<Element> childElements = ImmutableList.builder();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node node = childNodes.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ childElements.add((Element) node);
+ }
+ }
+ for (Element childElement : childElements.build()) {
+ if (cleanToolsReferences(childElement, logger) == ERROR) {
+ return ERROR;
+ }
+ }
+ return MergingReport.Result.SUCCESS;
+ }
+
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlAttribute.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlAttribute.java
new file mode 100644
index 0000000..5690ae4
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlAttribute.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.utils.PositionXmlParser;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+
+import org.w3c.dom.Attr;
+
+/**
+ * Defines an XML attribute inside a {@link XmlElement}.
+ *
+ * Basically a facade object on {@link Attr} objects with some added features like automatic
+ * namespace handling, manifest merger friendly identifiers and smart replacement of shortened
+ * full qualified class names using manifest node's package setting from the the owning Android's
+ * document.
+ */
+public class XmlAttribute extends XmlNode {
+
+ private final XmlElement mOwnerElement;
+ private final Attr mXml;
+ @Nullable
+ private final AttributeModel mAttributeModel;
+
+ /**
+ * Creates a new facade object to a {@link Attr} xml attribute in a
+ * {@link XmlElement}.
+ *
+ * @param ownerElement the xml node object owning this attribute.
+ * @param xml the xml definition of the attribute.
+ */
+ public XmlAttribute(
+ @NonNull XmlElement ownerElement,
+ @NonNull Attr xml,
+ @Nullable AttributeModel attributeModel) {
+ this.mOwnerElement = Preconditions.checkNotNull(ownerElement);
+ this.mXml = Preconditions.checkNotNull(xml);
+ this.mAttributeModel = attributeModel;
+ if (mAttributeModel != null && mAttributeModel.isPackageDependent()) {
+ String value = mXml.getValue();
+ String pkg = mOwnerElement.getDocument().getPackageName();
+ // We know it's a shortened FQCN if it starts with a dot
+ // or does not contain any dot.
+ if (value != null && !value.isEmpty() &&
+ (value.indexOf('.') == -1 || value.charAt(0) == '.')) {
+ if (value.charAt(0) == '.') {
+ value = pkg + value;
+ } else {
+ value = pkg + '.' + value;
+ }
+ mXml.setValue(value);
+ }
+ }
+ }
+
+ /**
+ * Returns the attribute's name, providing isolation from details like namespaces handling.
+ */
+ @Override
+ public NodeName getName() {
+ return XmlNode.unwrapName(mXml);
+ }
+
+ /**
+ * Returns the attribute's value
+ */
+ public String getValue() {
+ return mXml.getValue();
+ }
+
+ /**
+ * Returns a display friendly identification string that can be used in machine and user
+ * readable messages.
+ */
+ @Override
+ public NodeKey getId() {
+ // (Id of the parent element)@(my name)
+ String myName = mXml.getNamespaceURI() == null ? mXml.getName() : mXml.getLocalName();
+ return new NodeKey(mOwnerElement.getId() + "@" + myName);
+ }
+
+ @NonNull
+ @Override
+ public PositionXmlParser.Position getPosition() {
+ return mOwnerElement.getDocument().getNodePosition(this);
+ }
+
+ @NonNull
+ @Override
+ public Attr getXml() {
+ return mXml;
+ }
+
+ @Nullable
+ public AttributeModel getModel() {
+ return mAttributeModel;
+ }
+
+ XmlElement getOwnerElement() {
+ return mOwnerElement;
+ }
+
+ void mergeInHigherPriorityElement(XmlElement higherPriorityElement,
+ MergingReport.Builder mergingReport) {
+
+ // does the higher priority has the same attribute as myself ?
+ Optional<XmlAttribute> higherPriorityAttributeOptional =
+ higherPriorityElement.getAttribute(getName());
+
+ AttributeOperationType attributeOperationType =
+ higherPriorityElement.getAttributeOperationType(getName());
+
+ if (higherPriorityAttributeOptional.isPresent()) {
+
+ XmlAttribute higherPriorityAttribute = higherPriorityAttributeOptional.get();
+ handleBothAttributePresent(
+ mergingReport, higherPriorityAttribute, attributeOperationType);
+ return;
+ }
+
+ // it does not exist, verify if we are supposed to remove it.
+ if (attributeOperationType == AttributeOperationType.REMOVE) {
+ // record the fact the attribute was actively removed.
+ mergingReport.getActionRecorder().recordAttributeAction(
+ this,
+ Actions.ActionType.REJECTED,
+ AttributeOperationType.REMOVE);
+ return;
+ }
+
+ // the node is not defined in the higher priority element, it's defined in this lower
+ // priority element, we need to merge this lower priority attribute value with a potential
+ // higher priority default value (implicitly set on the higher priority element).
+ String mergedValue = mergeThisAndDefaultValue(mergingReport, higherPriorityElement);
+ if (mergedValue == null) {
+ return;
+ }
+
+ // ok merge it in the higher priority element.
+ getName().addToNode(higherPriorityElement.getXml(), mergedValue);
+
+ // and record the action.
+ mergingReport.getActionRecorder().recordAttributeAction(
+ this,
+ Actions.ActionType.ADDED,
+ getOwnerElement().getAttributeOperationType(getName()));
+ }
+
+ /**
+ * Handles merging of two attributes value explicitly declared in xml elements.
+ *
+ * @param report report to log errors and actions.
+ * @param higherPriority higher priority attribute we should merge this attribute with.
+ * @param operationType user operation type optionally requested by the user.
+ */
+ private void handleBothAttributePresent(
+ MergingReport.Builder report,
+ XmlAttribute higherPriority,
+ AttributeOperationType operationType) {
+
+ // handles tools: attribute separately.
+
+ if (getXml().getNamespaceURI() != null
+ && getXml().getNamespaceURI().equals(SdkConstants.TOOLS_URI)) {
+ handleBothToolsAttributePresent(higherPriority);
+ return;
+ }
+
+ // the attribute is present on both elements, there are 2 possibilities :
+ // 1. tools:replace was specified, replace the value.
+ // 2. nothing was specified, the values should be equal or this is an error.
+ if (operationType == AttributeOperationType.REPLACE) {
+ // record the fact the lower priority attribute was rejected.
+ report.getActionRecorder().recordAttributeAction(
+ this,
+ Actions.ActionType.REJECTED,
+ AttributeOperationType.REPLACE);
+ return;
+ }
+ // if the values are the same, then it's fine, otherwise flag the error.
+ if (mAttributeModel != null) {
+ String mergedValue = mAttributeModel.getMergingPolicy()
+ .merge(higherPriority.getValue(), getValue());
+ if (mergedValue != null) {
+ higherPriority.mXml.setValue(mergedValue);
+ } else {
+ addConflictingValueMessage(report, higherPriority);
+ }
+ return;
+ }
+ // no merging policy, for now revert on checking manually for equality.
+ if (!getValue().equals(higherPriority.getValue())) {
+ addConflictingValueMessage(report, higherPriority);
+ }
+ }
+
+ /**
+ * Handles tools: namespace attributes presence in both documents.
+ * @param higherPriority the higherPriority attribute
+ */
+ private void handleBothToolsAttributePresent(
+ XmlAttribute higherPriority) {
+
+ // do not merge tools:node attributes, the higher priority one wins.
+ if (getName().getLocalName().equals(NodeOperationType.NODE_LOCAL_NAME)) {
+ return;
+ }
+
+ // everything else should be merged, duplicates should be eliminated.
+ Splitter splitter = Splitter.on(',');
+ ImmutableSet.Builder<String> targetValues = ImmutableSet.builder();
+ targetValues.addAll(splitter.split(higherPriority.getValue()));
+ targetValues.addAll(splitter.split(getValue()));
+ higherPriority.getXml().setValue(Joiner.on(',').join(targetValues.build()));
+ }
+
+ /**
+ * Merge this attribute value (on a lower priority element) with a implicit default value
+ * (implicitly declared on the implicitNode).
+ * @param mergingReport report to log errors and actions.
+ * @param implicitNode the lower priority node where the implicit attribute value resides.
+ * @return the merged value that should be stored in the attribute or null if nothing should
+ * be stored.
+ */
+ private String mergeThisAndDefaultValue(MergingReport.Builder mergingReport,
+ XmlElement implicitNode) {
+
+ String mergedValue = getValue();
+ if (mAttributeModel == null || mAttributeModel.getDefaultValue() == null
+ || !mAttributeModel.getMergingPolicy().shouldMergeDefaultValues()) {
+ return mergedValue;
+ }
+ String defaultValue = mAttributeModel.getDefaultValue();
+ if (defaultValue.equals(mergedValue)) {
+ // even though the lower priority attribute is only declared and its value is the same
+ // as the default value, ensure it gets added to the higher priority node.
+ return mergedValue;
+ } else {
+ // ok, the default value and actual declaration are different, delegate to the
+ // merging policy to figure out what value should be used if any.
+ mergedValue = mAttributeModel.getMergingPolicy().merge(defaultValue, mergedValue);
+ if (mergedValue == null) {
+ addIllegalImplicitOverrideMessage(mergingReport, mAttributeModel, implicitNode);
+ return null;
+ }
+ if (mergedValue.equals(defaultValue)) {
+ // no need to forcefully add an attribute to the parent with its default value
+ // since it was not declared to start with.
+ return null;
+ }
+ }
+ return mergedValue;
+ }
+
+ /**
+ * Merge this attribute value with a lower priority node attribute default value.
+ * The attribute is not explicitly set on the implicitNode, yet it exist on this attribute
+ * {@link com.android.manifmerger.XmlElement} higher priority owner.
+ *
+ * @param mergingReport report to log errors and actions.
+ * @param implicitNode the lower priority node where the implicit attribute value resides.
+ */
+ void mergeWithLowerPriorityDefaultValue(
+ MergingReport.Builder mergingReport, XmlElement implicitNode) {
+
+ if (mAttributeModel == null || mAttributeModel.getDefaultValue() == null
+ || !mAttributeModel.getMergingPolicy().shouldMergeDefaultValues()) {
+ return;
+ }
+ // if this value has been explicitly set to replace the implicit default value, just
+ // log the action.
+ if (mOwnerElement.getAttributeOperationType(getName()) == AttributeOperationType.REPLACE) {
+ mergingReport.getActionRecorder().recordImplicitRejection(this, implicitNode);
+ return;
+ }
+ String mergedValue = mAttributeModel.getMergingPolicy().merge(
+ getValue(), mAttributeModel.getDefaultValue());
+ if (mergedValue == null) {
+ addIllegalImplicitOverrideMessage(mergingReport, mAttributeModel, implicitNode);
+ } else {
+ getXml().setValue(mergedValue);
+ mergingReport.getActionRecorder().recordAttributeAction(
+ this,
+ Actions.ActionType.MERGED,
+ null /* attributeOperationType */);
+ }
+ }
+
+ private void addIllegalImplicitOverrideMessage(
+ @NonNull MergingReport.Builder mergingReport,
+ @NonNull AttributeModel attributeModel,
+ @NonNull XmlElement implicitNode) {
+ String error = String.format("Attribute %1$s value=(%2$s) at %3$s"
+ + " cannot override implicit default value=(%4$s) at %5$s",
+ getId(),
+ getValue(),
+ printPosition(),
+ attributeModel.getDefaultValue(),
+ implicitNode.printPosition());
+ addMessage(mergingReport, MergingReport.Record.Severity.ERROR, error);
+ }
+
+ private void addConflictingValueMessage(
+ MergingReport.Builder report,
+ XmlAttribute higherPriority) {
+
+ Actions.AttributeRecord attributeRecord = report.getActionRecorder()
+ .getAttributeCreationRecord(higherPriority);
+
+ String error = String.format(
+ "Attribute %1$s value=(%2$s) from %3$s\n"
+ + "\tis also present at %4$s value=(%5$s)\n"
+ + "\tSuggestion: add 'tools:replace=\"%6$s\"' to <%7$s> element "
+ + "at %8$s to override",
+ higherPriority.getId(),
+ higherPriority.getValue(),
+ attributeRecord != null
+ ? attributeRecord.getActionLocation().toString()
+ : "(unknown)",
+ printPosition(),
+ getValue(),
+ mXml.getName(),
+ getOwnerElement().getType().toXmlName(),
+ higherPriority.getOwnerElement().printPosition(true)
+ );
+ higherPriority.addMessage(report, MergingReport.Record.Severity.ERROR, error);
+ }
+
+ void addMessage(MergingReport.Builder report,
+ MergingReport.Record.Severity severity,
+ String message) {
+ report.addMessage(getOwnerElement().getDocument().getSourceLocation(),
+ getLine(), getColumn(), severity, message);
+ }
+
+ @NonNull
+ @Override
+ public XmlLoader.SourceLocation getSourceLocation() {
+ return getOwnerElement().getSourceLocation();
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlDocument.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlDocument.java
new file mode 100644
index 0000000..eff5914
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlDocument.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.ManifestMerger2.SystemProperty;
+import static com.android.manifmerger.ManifestModel.NodeTypes.USES_PERMISSION;
+import static com.android.manifmerger.ManifestModel.NodeTypes.USES_SDK;
+import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.sdk.SdkVersionInfo;
+import com.android.ide.common.xml.XmlFormatPreferences;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.utils.Pair;
+import com.android.utils.PositionXmlParser;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Represents a loaded xml document.
+ *
+ * Has pointers to the root {@link XmlElement} element and provides services to persist the document
+ * to an external format. Also provides abilities to be merged with other
+ * {@link com.android.manifmerger.XmlDocument} as well as access to the line numbers for all
+ * document's xml elements and attributes.
+ *
+ */
+public class XmlDocument {
+
+ private static final String DEFAULT_SDK_VERSION = "1";
+
+ /**
+ * The document type.
+ */
+ enum Type {
+ /**
+ * A manifest overlay as found in the build types and variants.
+ */
+ OVERLAY,
+ /**
+ * The main android manifest file.
+ */
+ MAIN,
+ /**
+ * A library manifest that is imported in the application.
+ */
+ LIBRARY
+ }
+
+ private final Element mRootElement;
+ // this is initialized lazily to avoid un-necessary early parsing.
+ private final AtomicReference<XmlElement> mRootNode = new AtomicReference<XmlElement>(null);
+ private final PositionXmlParser mPositionXmlParser;
+ private final XmlLoader.SourceLocation mSourceLocation;
+ private final KeyResolver<String> mSelectors;
+ private final KeyBasedValueResolver<SystemProperty> mSystemPropertyResolver;
+ private final Type mType;
+ private final Optional<String> mMainManifestPackageName;
+
+ public XmlDocument(@NonNull PositionXmlParser positionXmlParser,
+ @NonNull XmlLoader.SourceLocation sourceLocation,
+ @NonNull KeyResolver<String> selectors,
+ @NonNull KeyBasedValueResolver<SystemProperty> systemPropertyResolver,
+ @NonNull Element element,
+ @NonNull Type type,
+ @NonNull Optional<String> mainManifestPackageName) {
+ this.mPositionXmlParser = Preconditions.checkNotNull(positionXmlParser);
+ this.mSourceLocation = Preconditions.checkNotNull(sourceLocation);
+ this.mRootElement = Preconditions.checkNotNull(element);
+ this.mSelectors = Preconditions.checkNotNull(selectors);
+ this.mSystemPropertyResolver = Preconditions.checkNotNull(systemPropertyResolver);
+ this.mType = type;
+ this.mMainManifestPackageName = mainManifestPackageName;
+ }
+
+ public Type getFileType() {
+ return mType;
+ }
+
+ /**
+ * Returns a pretty string representation of this document.
+ */
+ public String prettyPrint() {
+ return XmlPrettyPrinter.prettyPrint(
+ getXml(),
+ XmlFormatPreferences.defaults(),
+ XmlFormatStyle.get(getRootNode().getXml()),
+ null, /* endOfLineSeparator */
+ false /* endWithNewLine */);
+ }
+
+ /**
+ * merge this higher priority document with a higher priority document.
+ * @param lowerPriorityDocument the lower priority document to merge in.
+ * @param mergingReportBuilder the merging report to record errors and actions.
+ * @return a new merged {@link com.android.manifmerger.XmlDocument} or
+ * {@link Optional#absent()} if there were errors during the merging activities.
+ */
+ public Optional<XmlDocument> merge(
+ XmlDocument lowerPriorityDocument,
+ MergingReport.Builder mergingReportBuilder) {
+
+ mergingReportBuilder.getActionRecorder().recordDefaultNodeAction(getRootNode());
+
+ getRootNode().mergeWithLowerPriorityNode(
+ lowerPriorityDocument.getRootNode(), mergingReportBuilder);
+
+ addImplicitElements(lowerPriorityDocument, mergingReportBuilder);
+
+ // force re-parsing as new nodes may have appeared.
+ return mergingReportBuilder.hasErrors()
+ ? Optional.<XmlDocument>absent()
+ : Optional.of(reparse());
+ }
+
+ /**
+ * Forces a re-parsing of the document
+ * @return a new {@link com.android.manifmerger.XmlDocument} with up to date information.
+ */
+ public XmlDocument reparse() {
+ return new XmlDocument(mPositionXmlParser,
+ mSourceLocation,
+ mSelectors,
+ mSystemPropertyResolver,
+ mRootElement,
+ mType,
+ mMainManifestPackageName);
+ }
+
+ /**
+ * Returns a {@link com.android.manifmerger.KeyResolver} capable of resolving all selectors
+ * types
+ */
+ public KeyResolver<String> getSelectors() {
+ return mSelectors;
+ }
+
+ /**
+ * Returns the {@link com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver} capable
+ * of resolving all injected {@link com.android.manifmerger.ManifestMerger2.SystemProperty}
+ */
+ public KeyBasedValueResolver<SystemProperty> getSystemPropertyResolver() {
+ return mSystemPropertyResolver;
+ }
+
+ /**
+ * Compares this document to another {@link com.android.manifmerger.XmlDocument} ignoring all
+ * attributes belonging to the {@link com.android.SdkConstants#TOOLS_URI} namespace.
+ *
+ * @param other the other document to compare against.
+ * @return a {@link String} describing the differences between the two XML elements or
+ * {@link Optional#absent()} if they are equals.
+ */
+ public Optional<String> compareTo(XmlDocument other) {
+ return getRootNode().compareTo(other.getRootNode());
+ }
+
+ /**
+ * Returns a {@link XmlNode} position automatically offsetting the line and number
+ * columns by one (for PositionXmlParser, document starts at line 0, however for the common
+ * understanding, document should start at line 1).
+ */
+ @NonNull
+ PositionXmlParser.Position getNodePosition(XmlNode node) {
+ return getNodePosition(node.getXml());
+ }
+
+ /**
+ * Returns a {@link org.w3c.dom.Node} position automatically offsetting the line and number
+ * columns by one (for PositionXmlParser, document starts at line 0, however for the common
+ * understanding, document should start at line 1).
+ */
+ @NonNull
+ PositionXmlParser.Position getNodePosition(Node xml) {
+
+ final PositionXmlParser.Position position = mPositionXmlParser.getPosition(xml);
+ if (position == null) {
+ return PositionImpl.UNKNOWN;
+ }
+ return new PositionXmlParser.Position() {
+ @Nullable
+ @Override
+ public PositionXmlParser.Position getEnd() {
+ return position.getEnd();
+ }
+
+ @Override
+ public void setEnd(@NonNull PositionXmlParser.Position end) {
+ position.setEnd(end);
+ }
+
+ @Override
+ public int getLine() {
+ return position.getLine() + 1;
+ }
+
+ @Override
+ public int getOffset() {
+ return position.getOffset();
+ }
+
+ @Override
+ public int getColumn() {
+ return position.getColumn() +1;
+ }
+ };
+ }
+
+ public XmlLoader.SourceLocation getSourceLocation() {
+ return mSourceLocation;
+ }
+
+ public synchronized XmlElement getRootNode() {
+ if (mRootNode.get() == null) {
+ this.mRootNode.set(new XmlElement(mRootElement, this));
+ }
+ return mRootNode.get();
+ }
+
+ public Optional<XmlElement> getByTypeAndKey(
+ ManifestModel.NodeTypes type,
+ @Nullable String keyValue) {
+
+ return getRootNode().getNodeByTypeAndKey(type, keyValue);
+ }
+
+ /**
+ * Package name for this android manifest which will be used to resolve
+ * partial path. In the case of Overlays, this is absent and the main
+ * manifest packageName must be used.
+ * @return the package name to do partial class names resolution.
+ */
+ public String getPackageName() {
+ return mMainManifestPackageName.or(mRootElement.getAttribute("package"));
+ }
+
+ public Optional<XmlAttribute> getPackage() {
+ Optional<XmlAttribute> packageAttribute =
+ getRootNode().getAttribute(XmlNode.fromXmlName("package"));
+ return packageAttribute.isPresent()
+ ? packageAttribute
+ : getRootNode().getAttribute(XmlNode.fromNSName(
+ SdkConstants.ANDROID_URI, "android", "package"));
+ }
+
+ public Document getXml() {
+ return mRootElement.getOwnerDocument();
+ }
+
+ /**
+ * Returns the minSdk version specified in the uses_sdk element if present or the
+ * default value.
+ */
+ private String getRawMinSdkVersion() {
+ Optional<XmlElement> usesSdk = getByTypeAndKey(
+ ManifestModel.NodeTypes.USES_SDK, null);
+ if (usesSdk.isPresent()) {
+ Optional<XmlAttribute> minSdkVersion = usesSdk.get()
+ .getAttribute(XmlNode.fromXmlName("android:minSdkVersion"));
+ if (minSdkVersion.isPresent()) {
+ return minSdkVersion.get().getValue();
+ }
+ }
+ return DEFAULT_SDK_VERSION;
+ }
+
+ /**
+ * Returns the minSdk version for this manifest file. It can be injected from the outer
+ * build.gradle or can be expressed in the uses_sdk element.
+ */
+ private String getMinSdkVersion() {
+ // check for system properties.
+ String injectedMinSdk = mSystemPropertyResolver.getValue(SystemProperty.MIN_SDK_VERSION);
+ if (injectedMinSdk != null) {
+ return injectedMinSdk;
+ }
+ return getRawMinSdkVersion();
+ }
+
+ /**
+ * Returns the targetSdk version specified in the uses_sdk element if present or the
+ * default value.
+ */
+ private String getRawTargetSdkVersion() {
+
+ Optional<XmlElement> usesSdk = getByTypeAndKey(
+ ManifestModel.NodeTypes.USES_SDK, null);
+ if (usesSdk.isPresent()) {
+ Optional<XmlAttribute> targetSdkVersion = usesSdk.get()
+ .getAttribute(XmlNode.fromXmlName("android:targetSdkVersion"));
+ if (targetSdkVersion.isPresent()) {
+ return targetSdkVersion.get().getValue();
+ }
+ }
+ return getMinSdkVersion();
+ }
+
+ /**
+ * Returns the targetSdk version for this manifest file. It can be injected from the outer
+ * build.gradle or can be expressed in the uses_sdk element.
+ */
+ private String getTargetSdkVersion() {
+
+ // check for system properties.
+ String injectedTargetVersion = mSystemPropertyResolver
+ .getValue(SystemProperty.TARGET_SDK_VERSION);
+ if (injectedTargetVersion != null) {
+ return injectedTargetVersion;
+ }
+ return getRawTargetSdkVersion();
+ }
+
+ /**
+ * Decodes a sdk version from either its decimal representation or from a platform code name.
+ * @param attributeVersion the sdk version attribute as specified by users.
+ * @return the integer representation of the platform level.
+ */
+ private static int getApiLevelFromAttribute(String attributeVersion) {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(attributeVersion));
+ if (Character.isDigit(attributeVersion.charAt(0))) {
+ return Integer.parseInt(attributeVersion);
+ }
+ return SdkVersionInfo.getApiByPreviewName(attributeVersion, true);
+ }
+
+ /**
+ * Add all implicit elements from the passed lower priority document that are
+ * required in the target SDK.
+ */
+ @SuppressWarnings("unchecked") // compiler confused about varargs and generics.
+ private void addImplicitElements(XmlDocument lowerPriorityDocument,
+ MergingReport.Builder mergingReport) {
+
+ // if this document is an overlay, tolerate the absence of uses-sdk and do not
+ // assume implicit minimum versions.
+ Optional<XmlElement> usesSdk = getByTypeAndKey(
+ ManifestModel.NodeTypes.USES_SDK, null);
+ if (mType == Type.OVERLAY && !usesSdk.isPresent()) {
+ return;
+ }
+
+ int thisTargetSdk = getApiLevelFromAttribute(getTargetSdkVersion());
+ int libraryTargetSdk = getApiLevelFromAttribute(
+ lowerPriorityDocument.getTargetSdkVersion());
+
+ // if library is using a code name rather than an API level, make sure this document target
+ // sdk version is using the same code name.
+ String libraryTargetSdkVersion = lowerPriorityDocument.getTargetSdkVersion();
+ if (!Character.isDigit(libraryTargetSdkVersion.charAt(0))) {
+ // this is a code name, ensure this document uses the same code name.
+ if (!libraryTargetSdkVersion.equals(getTargetSdkVersion())) {
+ mergingReport.addMessage(getSourceLocation(), 0, 0, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "uses-sdk:targetSdkVersion %1$s cannot be different than version "
+ + "%2$s declared in library %3$s",
+ getTargetSdkVersion(),
+ libraryTargetSdkVersion,
+ lowerPriorityDocument.getSourceLocation().print(true)
+ )
+ );
+ return;
+ }
+ }
+ // same for minSdkVersion, if the library is using a code name, the application must
+ // also be using the same code name.
+ String libraryMinSdkVersion = lowerPriorityDocument.getMinSdkVersion();
+ if (!Character.isDigit(libraryMinSdkVersion.charAt(0))) {
+ // this is a code name, ensure this document uses the same code name.
+ if (!libraryMinSdkVersion.equals(getMinSdkVersion())) {
+ mergingReport.addMessage(getSourceLocation(), 0, 0, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "uses-sdk:minSdkVersion %1$s cannot be different than version "
+ + "%2$s declared in library %3$s",
+ getMinSdkVersion(),
+ libraryMinSdkVersion,
+ lowerPriorityDocument.getSourceLocation().print(true)
+ )
+ );
+ return;
+ }
+ }
+
+ if (!checkUsesSdkMinVersion(lowerPriorityDocument)) {
+ mergingReport.addMessage(getSourceLocation(), 0, 0, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "uses-sdk:minSdkVersion %1$s cannot be smaller than version "
+ + "%2$s declared in library %3$s",
+ getMinSdkVersion(),
+ lowerPriorityDocument.getRawMinSdkVersion(),
+ lowerPriorityDocument.getSourceLocation().print(true)
+ )
+ );
+ return;
+ }
+
+ // if the merged document target SDK is equal or smaller than the library's, nothing to do.
+ if (thisTargetSdk <= libraryTargetSdk) {
+ return;
+ }
+
+ // There is no need to add any implied permissions when targeting an old runtime.
+ if (thisTargetSdk < 4) {
+ return;
+ }
+
+ boolean hasWriteToExternalStoragePermission =
+ getByTypeAndKey(USES_PERMISSION, permission("WRITE_EXTERNAL_STORAGE")).isPresent();
+
+ if (libraryTargetSdk < 4) {
+ Optional<Element> permission = addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION,
+ permission("WRITE_EXTERNAL_STORAGE"),
+ "targetSdkVersion < 4",
+ Pair.of("maxSdkVersion", "18") // permission became implied at 19.
+ );
+ hasWriteToExternalStoragePermission = permission.isPresent();
+
+ addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION,
+ permission("READ_PHONE_STATE"),
+ "targetSdkVersion < 4");
+ }
+ // If the application has requested WRITE_EXTERNAL_STORAGE, we will
+ // force them to always take READ_EXTERNAL_STORAGE as well. We always
+ // do this (regardless of target API version) because we can't have
+ // an app with write permission but not read permission.
+ if (hasWriteToExternalStoragePermission
+ && !getByTypeAndKey(USES_PERMISSION, permission("READ_EXTERNAL_STORAGE"))
+ .isPresent()) {
+
+ addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION,
+ permission("READ_EXTERNAL_STORAGE"),
+ "requested WRITE_EXTERNAL_STORAGE",
+ // NOTE TO @xav, where can we find the list of implied permissions at versions X
+ Pair.of("maxSdkVersion", "18") // permission became implied at 19, DID IT ???
+ );
+ }
+
+ // Pre-JellyBean call log permission compatibility.
+ if (libraryTargetSdk < 16) {
+ if (getByTypeAndKey(USES_PERMISSION, permission("READ_CONTACTS")).isPresent()) {
+ addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION, permission("READ_CALL_LOG"),
+ "targetSdkVersion < 16 and requested READ_CONTACTS");
+ }
+ if (getByTypeAndKey(USES_PERMISSION, permission("WRITE_CONTACTS")).isPresent()) {
+ addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION, permission("WRITE_CALL_LOG"),
+ "targetSdkVersion < 16 and requested WRITE_CONTACTS");
+ }
+ }
+ }
+
+ /**
+ * Returns true if the minSdkVersion of the application and the library are compatible, false
+ * otherwise.
+ */
+ private boolean checkUsesSdkMinVersion(XmlDocument lowerPriorityDocument) {
+
+ int thisMinSdk = getApiLevelFromAttribute(getMinSdkVersion());
+ int libraryMinSdk = getApiLevelFromAttribute(
+ lowerPriorityDocument.getRawMinSdkVersion());
+
+ // the merged document minSdk cannot be lower than a library
+ if (thisMinSdk < libraryMinSdk) {
+
+ // check if this higher priority document has any tools instructions for the node
+ // or the attribute.
+ Optional<XmlElement> xmlElementOptional = getByTypeAndKey(USES_SDK, null);
+ if (!xmlElementOptional.isPresent()) {
+ return false;
+ }
+ XmlElement xmlElement = xmlElementOptional.get();
+
+ if (!xmlElement.getOperationType().isOverriding()) {
+ // last chance, check the attribute.
+ if (xmlElement.getAttributeOperationType(XmlNode.fromXmlName(
+ "android:minSdkVersion")) == AttributeOperationType.STRICT) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Adds a new element of type nodeType with a specific keyValue if the element is absent in this
+ * document. Will also add attributes expressed through key value pairs.
+ *
+ * @param actionRecorder to records creation actions.
+ * @param nodeType the node type to crete
+ * @param keyValue the optional key for the element.
+ * @param attributes the optional array of key value pairs for extra element attribute.
+ * @return the Xml element whether it was created or existed or {@link Optional#absent()} if
+ * it does not exist in this document.
+ */
+ private Optional<Element> addIfAbsent(
+ @NonNull ActionRecorder actionRecorder,
+ @NonNull ManifestModel.NodeTypes nodeType,
+ @Nullable String keyValue,
+ @Nullable String reason,
+ @Nullable Pair<String, String>... attributes) {
+
+ Optional<XmlElement> xmlElementOptional = getByTypeAndKey(nodeType, keyValue);
+ if (xmlElementOptional.isPresent()) {
+ return Optional.absent();
+ }
+ Element elementNS = getXml()
+ .createElementNS(SdkConstants.ANDROID_URI, "android:" + nodeType.toXmlName());
+
+
+ ImmutableList<String> keyAttributesNames = nodeType.getNodeKeyResolver()
+ .getKeyAttributesNames();
+ if (keyAttributesNames.size() == 1) {
+ elementNS.setAttributeNS(
+ SdkConstants.ANDROID_URI, "android:" + keyAttributesNames.get(0), keyValue);
+ }
+ if (attributes != null) {
+ for (Pair<String, String> attribute : attributes) {
+ elementNS.setAttributeNS(
+ SdkConstants.ANDROID_URI, "android:" + attribute.getFirst(),
+ attribute.getSecond());
+ }
+ }
+
+ // record creation.
+ XmlElement xmlElement = new XmlElement(elementNS, this);
+ actionRecorder.recordImpliedNodeAction(xmlElement, reason);
+
+ getRootNode().getXml().appendChild(elementNS);
+ return Optional.of(elementNS);
+ }
+
+ private static String permission(String permissionName) {
+ return "android.permission." + permissionName;
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlElement.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlElement.java
new file mode 100644
index 0000000..84a33ce
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlElement.java
@@ -0,0 +1,839 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ide.common.res2.MergingException;
+import com.android.utils.ILogger;
+import com.android.utils.PositionXmlParser;
+import com.android.utils.SdkUtils;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Xml {@link org.w3c.dom.Element} which is mergeable.
+ *
+ * A mergeable element can contains 3 types of children :
+ * <ul>
+ * <li>a child element, which itself may or may not be mergeable.</li>
+ * <li>xml attributes which are related to the element.</li>
+ * <li>tools oriented attributes to trigger specific behaviors from the merging tool</li>
+ * </ul>
+ *
+ * The two main responsibilities of this class is to be capable of comparing itself against
+ * another instance of the same type as well as providing XML element merging capabilities.
+ */
+public class XmlElement extends OrphanXmlElement {
+
+ @NonNull private final XmlDocument mDocument;
+
+ private final NodeOperationType mNodeOperationType;
+ // list of non tools related attributes.
+ private final ImmutableList<XmlAttribute> mAttributes;
+ // map of all tools related attributes keyed by target attribute name
+ private final Map<NodeName, AttributeOperationType> mAttributesOperationTypes;
+ // list of mergeable children elements.
+ private final ImmutableList<XmlElement> mMergeableChildren;
+ // optional selector declared on this xml element.
+ @Nullable private final Selector mSelector;
+
+ public XmlElement(@NonNull Element xml, @NonNull XmlDocument document) {
+ super(xml);
+
+ mDocument = Preconditions.checkNotNull(document);
+ Selector selector = null;
+
+ ImmutableMap.Builder<NodeName, AttributeOperationType> attributeOperationTypeBuilder =
+ ImmutableMap.builder();
+ ImmutableList.Builder<XmlAttribute> attributesListBuilder = ImmutableList.builder();
+ NamedNodeMap namedNodeMap = getXml().getAttributes();
+ NodeOperationType lastNodeOperationType = null;
+ for (int i = 0; i < namedNodeMap.getLength(); i++) {
+ Node attribute = namedNodeMap.item(i);
+ if (SdkConstants.TOOLS_URI.equals(attribute.getNamespaceURI())) {
+ String instruction = attribute.getLocalName();
+ if (instruction.equals(NodeOperationType.NODE_LOCAL_NAME)) {
+ // should we flag an error when there are more than one operation type on a node ?
+ lastNodeOperationType = NodeOperationType.valueOf(
+ SdkUtils.camelCaseToConstantName(
+ attribute.getNodeValue()));
+ } else if (instruction.equals(Selector.SELECTOR_LOCAL_NAME)) {
+ selector = new Selector(attribute.getNodeValue());
+ } else {
+ AttributeOperationType attributeOperationType;
+ try {
+ attributeOperationType =
+ AttributeOperationType.valueOf(
+ SdkUtils.xmlNameToConstantName(instruction));
+ } catch (IllegalArgumentException e) {
+ try {
+ // is this another tool's operation type that we do not care about.
+ OtherOperationType.valueOf(instruction);
+ break;
+ } catch (IllegalArgumentException e1) {
+ String errorMessage =
+ String.format("[%1$s:%2$s] Invalid instruction '%3$s', "
+ + "valid instructions are : %4$s",
+ mDocument.getSourceLocation().print(false),
+ mDocument.getNodePosition(xml).getLine(),
+ instruction,
+ Joiner.on(',').join(AttributeOperationType.values())
+ );
+ throw new RuntimeException(new MergingException(errorMessage, e));
+ }
+ }
+ for (String attributeName : Splitter.on(',').trimResults()
+ .split(attribute.getNodeValue())) {
+ if (attributeName.indexOf(XmlUtils.NS_SEPARATOR) == -1) {
+ String toolsPrefix = XmlUtils
+ .lookupNamespacePrefix(getXml(), SdkConstants.TOOLS_URI,
+ SdkConstants.ANDROID_NS_NAME, false);
+ // automatically provide the prefix.
+ attributeName = toolsPrefix + XmlUtils.NS_SEPARATOR + attributeName;
+ }
+ NodeName nodeName = XmlNode.fromXmlName(attributeName);
+ attributeOperationTypeBuilder.put(nodeName, attributeOperationType);
+ }
+ }
+ }
+ }
+ mAttributesOperationTypes = attributeOperationTypeBuilder.build();
+ for (int i = 0; i < namedNodeMap.getLength(); i++) {
+ Node attribute = namedNodeMap.item(i);
+ XmlAttribute xmlAttribute = new XmlAttribute(
+ this, (Attr) attribute, getType().getAttributeModel(XmlNode.fromXmlName(
+ ((Attr) attribute).getName())));
+ attributesListBuilder.add(xmlAttribute);
+ }
+ mNodeOperationType = lastNodeOperationType;
+ mAttributes = attributesListBuilder.build();
+ mMergeableChildren = initMergeableChildren();
+ mSelector = selector;
+ }
+
+ /**
+ * Returns the owning {@link com.android.manifmerger.XmlDocument}
+ */
+ @NonNull
+ public XmlDocument getDocument() {
+ return mDocument;
+ }
+
+ /**
+ * Returns the list of attributes for this xml element.
+ */
+ public List<XmlAttribute> getAttributes() {
+ return mAttributes;
+ }
+
+ /**
+ * Returns the {@link com.android.manifmerger.XmlAttribute} for an attribute present on this
+ * xml element, or {@link com.google.common.base.Optional#absent} if not present.
+ * @param attributeName the attribute name.
+ */
+ public Optional<XmlAttribute> getAttribute(NodeName attributeName) {
+ for (XmlAttribute xmlAttribute : mAttributes) {
+ if (xmlAttribute.getName().equals(attributeName)) {
+ return Optional.of(xmlAttribute);
+ }
+ }
+ return Optional.absent();
+ }
+
+ /**
+ * Get the node operation type as optionally specified by the user. If the user did not
+ * explicitly specify how conflicting elements should be handled, a
+ * {@link com.android.manifmerger.NodeOperationType#MERGE} will be returned.
+ */
+ public NodeOperationType getOperationType() {
+ return mNodeOperationType != null
+ ? mNodeOperationType
+ : NodeOperationType.MERGE;
+ }
+
+ /**
+ * Get the attribute operation type as optionally specified by the user. If the user did not
+ * explicitly specify how conflicting attributes should be handled, a
+ * {@link AttributeOperationType#STRICT} will be returned.
+ */
+ public AttributeOperationType getAttributeOperationType(NodeName attributeName) {
+ return mAttributesOperationTypes.containsKey(attributeName)
+ ? mAttributesOperationTypes.get(attributeName)
+ : AttributeOperationType.STRICT;
+ }
+
+ public Collection<Map.Entry<NodeName, AttributeOperationType>> getAttributeOperations() {
+ return mAttributesOperationTypes.entrySet();
+ }
+
+
+ @NonNull
+ @Override
+ public PositionXmlParser.Position getPosition() {
+ return mDocument.getNodePosition(this);
+ }
+
+ @NonNull
+ @Override
+ public XmlLoader.SourceLocation getSourceLocation() {
+ return getDocument().getSourceLocation();
+ }
+
+ /**
+ * Merge this xml element with a lower priority node.
+ *
+ * For now, attributes will be merged. If present on both xml elements, a warning will be
+ * issued and the attribute merge will be rejected.
+ *
+ * @param lowerPriorityNode lower priority Xml element to merge with.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ public void mergeWithLowerPriorityNode(
+ XmlElement lowerPriorityNode,
+ MergingReport.Builder mergingReport) {
+
+
+ if (mSelector != null && !mSelector.isResolvable(getDocument().getSelectors())) {
+ mergingReport.addMessage(getSourceLocation(), getLine(), getColumn(),
+ MergingReport.Record.Severity.ERROR,
+ String.format("'tools:selector=\"%1$s\"' is not a valid library identifier, "
+ + "valid identifiers are : %2$s",
+ mSelector.toString(),
+ Joiner.on(',').join(mDocument.getSelectors().getKeys())));
+ return;
+
+ }
+ mergingReport.getLogger().info("Merging " + getId()
+ + " with lower " + lowerPriorityNode.printPosition());
+
+ // workaround for 0.12 release and overlay treatment of manifest entries. This will
+ // need to be expressed in the model instead.
+ MergeType mergeType = getType().getMergeType();
+ // if element we are merging in is not a library (an overlay or an application), we should
+ // always merge the <manifest> attributes otherwise, we do not merge the libraries
+ // <manifest> attributes.
+ if (isA(ManifestModel.NodeTypes.MANIFEST)
+ && lowerPriorityNode.getDocument().getFileType() != XmlDocument.Type.LIBRARY) {
+ mergeType = MergeType.MERGE;
+ }
+
+ if (mergeType != MergeType.MERGE_CHILDREN_ONLY) {
+ // make a copy of all the attributes metadata, it will eliminate elements from this
+ // list as it finds them explicitly defined in the lower priority node.
+ // At the end of the explicit attributes processing, the remaining elements of this
+ // list will need to be checked for default value that may clash with a locally
+ // defined attribute.
+ List<AttributeModel> attributeModels =
+ new ArrayList<AttributeModel>(lowerPriorityNode.getType().getAttributeModels());
+
+ // merge explicit attributes from lower priority node.
+ for (XmlAttribute lowerPriorityAttribute : lowerPriorityNode.getAttributes()) {
+ lowerPriorityAttribute.mergeInHigherPriorityElement(this, mergingReport);
+ if (lowerPriorityAttribute.getModel() != null) {
+ attributeModels.remove(lowerPriorityAttribute.getModel());
+ }
+ }
+ // merge implicit default values from lower priority node when we have an explicit
+ // attribute declared on this node.
+ for (AttributeModel attributeModel : attributeModels) {
+ if (attributeModel.getDefaultValue() != null) {
+ Optional<XmlAttribute> myAttribute = getAttribute(attributeModel.getName());
+ if (myAttribute.isPresent()) {
+ myAttribute.get().mergeWithLowerPriorityDefaultValue(
+ mergingReport, lowerPriorityNode);
+ }
+ }
+ }
+ }
+ // are we supposed to merge children ?
+ if (mNodeOperationType != NodeOperationType.MERGE_ONLY_ATTRIBUTES) {
+ mergeChildren(lowerPriorityNode, mergingReport);
+ } else {
+ // record rejection of the lower priority node's children .
+ for (XmlElement lowerPriorityChild : lowerPriorityNode.getMergeableElements()) {
+ mergingReport.getActionRecorder().recordNodeAction(this,
+ Actions.ActionType.REJECTED,
+ lowerPriorityChild);
+ }
+ }
+ }
+
+ public ImmutableList<XmlElement> getMergeableElements() {
+ return mMergeableChildren;
+ }
+
+ /**
+ * Returns a child of a particular type and a particular key.
+ * @param type the requested child type.
+ * @param keyValue the requested child key.
+ * @return the child of {@link com.google.common.base.Optional#absent()} if no child of this
+ * type and key exist.
+ */
+ public Optional<XmlElement> getNodeByTypeAndKey(
+ ManifestModel.NodeTypes type,
+ @Nullable String keyValue) {
+
+ for (XmlElement xmlElement : mMergeableChildren) {
+ if (xmlElement.isA(type) &&
+ (keyValue == null || keyValue.equals(xmlElement.getKey()))) {
+ return Optional.of(xmlElement);
+ }
+ }
+ return Optional.absent();
+ }
+
+ /**
+ * Returns all immediate children of this node for a particular type, irrespective of their
+ * key.
+ * @param type the type of children element requested.
+ * @return the list (potentially empty) of children.
+ */
+ public ImmutableList<XmlElement> getAllNodesByType(ManifestModel.NodeTypes type) {
+ ImmutableList.Builder<XmlElement> listBuilder = ImmutableList.builder();
+ for (XmlElement mergeableChild : initMergeableChildren()) {
+ if (mergeableChild.isA(type)) {
+ listBuilder.add(mergeableChild);
+ }
+ }
+ return listBuilder.build();
+ }
+
+ // merge this higher priority node with a lower priority node.
+ public void mergeChildren(XmlElement lowerPriorityNode,
+ MergingReport.Builder mergingReport) {
+
+ // read all lower priority mergeable nodes.
+ // if the same node is not defined in this document merge it in.
+ // if the same is defined, so far, give an error message.
+ for (XmlElement lowerPriorityChild : lowerPriorityNode.getMergeableElements()) {
+
+ if (shouldIgnore(lowerPriorityChild, mergingReport)) {
+ continue;
+ }
+ mergeChild(lowerPriorityChild, mergingReport);
+ }
+ }
+
+ // merge a child of a lower priority node into this higher priority node.
+ private void mergeChild(XmlElement lowerPriorityChild, MergingReport.Builder mergingReport) {
+
+ ILogger logger = mergingReport.getLogger();
+
+ // If this a custom element, we just blindly merge it in.
+ if (lowerPriorityChild.getType() == ManifestModel.NodeTypes.CUSTOM) {
+ handleCustomElement(lowerPriorityChild, mergingReport);
+ return;
+ }
+
+ Optional<XmlElement> thisChildOptional =
+ getNodeByTypeAndKey(lowerPriorityChild.getType(),lowerPriorityChild.getKey());
+
+ // only in the lower priority document ?
+ if (!thisChildOptional.isPresent()) {
+ addElement(lowerPriorityChild, mergingReport);
+ return;
+ }
+ // it's defined in both files.
+ logger.verbose(lowerPriorityChild.getId() + " defined in both files...");
+
+ XmlElement thisChild = thisChildOptional.get();
+ switch (thisChild.getType().getMergeType()) {
+ case CONFLICT:
+ addMessage(mergingReport, MergingReport.Record.Severity.ERROR, String.format(
+ "Node %1$s cannot be present in more than one input file and it's "
+ + "present at %2$s and %3$s",
+ thisChild.getType(),
+ thisChild.printPosition(),
+ lowerPriorityChild.printPosition()
+ ));
+ break;
+ case ALWAYS:
+
+ // no merging, we consume the lower priority node unmodified.
+ // if the two elements are equal, just skip it.
+
+ // but check first that we are not supposed to replace or remove it.
+ NodeOperationType operationType =
+ calculateNodeOperationType(thisChild, lowerPriorityChild);
+ if (operationType == NodeOperationType.REMOVE ||
+ operationType == NodeOperationType.REPLACE) {
+ mergingReport.getActionRecorder().recordNodeAction(thisChild,
+ Actions.ActionType.REJECTED, lowerPriorityChild);
+ break;
+ }
+
+ if (thisChild.getType().areMultipleDeclarationAllowed()) {
+ mergeChildrenWithMultipleDeclarations(lowerPriorityChild, mergingReport);
+ } else {
+ if (!thisChild.isEquals(lowerPriorityChild)) {
+ addElement(lowerPriorityChild, mergingReport);
+ }
+ }
+ break;
+ default:
+ // 2 nodes exist, some merging need to happen
+ handleTwoElementsExistence(thisChild, lowerPriorityChild, mergingReport);
+ break;
+ }
+ }
+
+ /**
+ * Handles presence of custom elements (elements not part of the android or tools
+ * namespaces). Such elements are merged unchanged into the resulting document, and
+ * optionally, the namespace definition is added to the merged document root element.
+ * @param customElement the custom element present in the lower priority document.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ private void handleCustomElement(XmlElement customElement,
+ MergingReport.Builder mergingReport) {
+ addElement(customElement, mergingReport);
+
+ // add the custom namespace to the document generation.
+ String nodeName = customElement.getXml().getNodeName();
+ if (!nodeName.contains(":")) {
+ return;
+ }
+ String prefix = nodeName.substring(0, nodeName.indexOf(':'));
+ String namespace = customElement.getDocument().getRootNode()
+ .getXml().getAttribute(SdkConstants.XMLNS_PREFIX + prefix);
+
+ if (namespace != null) {
+ getDocument().getRootNode().getXml().setAttributeNS(
+ SdkConstants.XMLNS_URI, SdkConstants.XMLNS_PREFIX + prefix, namespace);
+ }
+ }
+
+ /**
+ * Merges two children when this children's type allow multiple elements declaration with the
+ * same key value. In that case, we only merge the lower priority child if there is not already
+ * an element with the same key value that is equal to the lower priority child. Two children
+ * are equals if they have the same attributes and children declared irrespective of the
+ * declaration order.
+ *
+ * @param lowerPriorityChild the lower priority element's child.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ private void mergeChildrenWithMultipleDeclarations(
+ XmlElement lowerPriorityChild,
+ MergingReport.Builder mergingReport) {
+
+ Preconditions.checkArgument(lowerPriorityChild.getType().areMultipleDeclarationAllowed());
+ if (lowerPriorityChild.getType().areMultipleDeclarationAllowed()) {
+ for (XmlElement sameTypeChild : getAllNodesByType(lowerPriorityChild.getType())) {
+ if (sameTypeChild.getId().equals(lowerPriorityChild.getId()) &&
+ sameTypeChild.isEquals(lowerPriorityChild)) {
+ return;
+ }
+ }
+ }
+ // if we end up here, we never found a child of this element with the same key and strictly
+ // equals to the lowerPriorityChild so we should merge it in.
+ addElement(lowerPriorityChild, mergingReport);
+ }
+
+ /**
+ * Determine if we should completely ignore a child from any merging activity.
+ * There are 2 situations where we should ignore a lower priority child :
+ * <p>
+ * <ul>
+ * <li>The associate {@link com.android.manifmerger.ManifestModel.NodeTypes} is
+ * annotated with {@link com.android.manifmerger.MergeType#IGNORE}</li>
+ * <li>This element has a child of the same type with no key that has a '
+ * tools:node="removeAll' attribute.</li>
+ * </ul>
+ * @param lowerPriorityChild the lower priority child we should determine eligibility for
+ * merging.
+ * @return true if the element should be ignored, false otherwise.
+ */
+ private boolean shouldIgnore(
+ XmlElement lowerPriorityChild,
+ MergingReport.Builder mergingReport) {
+
+ if (lowerPriorityChild.getType().getMergeType() == MergeType.IGNORE) {
+ return true;
+ }
+
+ // do we have an element of the same type of that child with no key ?
+ Optional<XmlElement> thisChildElementOptional =
+ getNodeByTypeAndKey(lowerPriorityChild.getType(), null /* keyValue */);
+ if (!thisChildElementOptional.isPresent()) {
+ return false;
+ }
+ XmlElement thisChild = thisChildElementOptional.get();
+
+ // are we supposed to delete all occurrences and if yes, is there a selector defined to
+ // filter which elements should be deleted.
+ boolean shouldDelete = thisChild.mNodeOperationType == NodeOperationType.REMOVE_ALL
+ && (thisChild.mSelector == null
+ || thisChild.mSelector.appliesTo(lowerPriorityChild));
+ // if we should discard this child element, record the action.
+ if (shouldDelete) {
+ mergingReport.getActionRecorder().recordNodeAction(thisChildElementOptional.get(),
+ Actions.ActionType.REJECTED,
+ lowerPriorityChild);
+ }
+ return shouldDelete;
+ }
+
+ /**
+ * Handle 2 elements (of same identity) merging.
+ * higher priority one has a tools:node="remove", remove the low priority one
+ * higher priority one has a tools:node="replace", replace the low priority one
+ * higher priority one has a tools:node="strict", flag the error if not equals.
+ * default or tools:node="merge", merge the two elements.
+ * @param higherPriority the higher priority node.
+ * @param lowerPriority the lower priority element.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ private void handleTwoElementsExistence(
+ XmlElement higherPriority,
+ XmlElement lowerPriority,
+ MergingReport.Builder mergingReport) {
+
+ NodeOperationType operationType = calculateNodeOperationType(higherPriority, lowerPriority);
+ // 2 nodes exist, 3 possibilities :
+ // higher priority one has a tools:node="remove", remove the low priority one
+ // higher priority one has a tools:node="replace", replace the low priority one
+ // higher priority one has a tools:node="strict", flag the error if not equals.
+ switch(operationType) {
+ case MERGE:
+ case MERGE_ONLY_ATTRIBUTES:
+ // record the action
+ mergingReport.getActionRecorder().recordNodeAction(higherPriority,
+ Actions.ActionType.MERGED, lowerPriority);
+ // and perform the merge
+ higherPriority.mergeWithLowerPriorityNode(lowerPriority, mergingReport);
+ break;
+ case REMOVE:
+ case REPLACE:
+ // so far remove and replace and similar, the post validation will take
+ // care of removing this node in the case of REMOVE.
+
+ // just don't import the lower priority node and record the action.
+ mergingReport.getActionRecorder().recordNodeAction(higherPriority,
+ Actions.ActionType.REJECTED, lowerPriority);
+ break;
+ case STRICT:
+ Optional<String> compareMessage = higherPriority.compareTo(lowerPriority);
+ if (compareMessage.isPresent()) {
+ // flag error.
+ addMessage(mergingReport, MergingReport.Record.Severity.ERROR, String.format(
+ "Node %1$s at %2$s is tagged with tools:node=\"strict\", yet "
+ + "%3$s at %4$s is different : %5$s",
+ higherPriority.getId(),
+ higherPriority.printPosition(),
+ lowerPriority.getId(),
+ lowerPriority.printPosition(),
+ compareMessage.get()
+ ));
+ }
+ break;
+ default:
+ mergingReport.getLogger().error(null /* throwable */,
+ "Unhandled node operation type %s", higherPriority.getOperationType());
+ break;
+ }
+ }
+
+ /**
+ * Calculate the effective node operation type for a higher priority node when a lower priority
+ * node is queried for merge.
+ * @param higherPriority the higher priority node which may have a {@link NodeOperationType}
+ * declaration and may also have a {@link Selector} declaration.
+ * @param lowerPriority the lower priority node that is elected for merging with the higher
+ * priority node.
+ * @return the effective {@link NodeOperationType} that should be used to affect higher and
+ * lower priority nodes merging.
+ */
+ private static NodeOperationType calculateNodeOperationType(
+ @NonNull XmlElement higherPriority,
+ @NonNull XmlElement lowerPriority) {
+
+ NodeOperationType operationType = higherPriority.getOperationType();
+ // if the operation's selector exists and the lower priority node is not selected,
+ // we revert to default operation type which is merge.
+ if (operationType.isSelectable()
+ && higherPriority.mSelector != null
+ && !higherPriority.mSelector.appliesTo(lowerPriority)) {
+ operationType = NodeOperationType.MERGE;
+ }
+ return operationType;
+ }
+
+ /**
+ * Add an element and its leading comments as the last sub-element of the current element.
+ * @param elementToBeAdded xml element to be added to the current element.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ private void addElement(XmlElement elementToBeAdded, MergingReport.Builder mergingReport) {
+
+ List<Node> comments = getLeadingComments(elementToBeAdded.getXml());
+ // record all the actions before the node is moved from the library document to the main
+ // merged document.
+ mergingReport.getActionRecorder().recordDefaultNodeAction(elementToBeAdded);
+
+ // only in the new file, just import it.
+ Node node = getXml().getOwnerDocument().adoptNode(elementToBeAdded.getXml());
+ getXml().appendChild(node);
+
+ // also adopt the child's comments if any.
+ for (Node comment : comments) {
+ Node newComment = getXml().getOwnerDocument().adoptNode(comment);
+ getXml().insertBefore(newComment, node);
+ }
+
+ mergingReport.getLogger().verbose("Adopted " + node);
+ }
+
+ public boolean isEquals(XmlElement otherNode) {
+ return !compareTo(otherNode).isPresent();
+ }
+
+ /**
+ * Compares this element with another {@link XmlElement} ignoring all attributes belonging to
+ * the {@link com.android.SdkConstants#TOOLS_URI} namespace.
+ *
+ * @param other the other element to compare against.
+ * @return a {@link String} describing the differences between the two XML elements or
+ * {@link Optional#absent()} if they are equals.
+ */
+ public Optional<String> compareTo(Object other) {
+
+ if (!(other instanceof XmlElement)) {
+ return Optional.of("Wrong type");
+ }
+ XmlElement otherNode = (XmlElement) other;
+
+ // compare element names
+ if (getXml().getNamespaceURI() != null) {
+ if (!getXml().getLocalName().equals(otherNode.getXml().getLocalName())) {
+ return Optional.of(
+ String.format("Element names do not match: %1$s versus %2$s",
+ getXml().getLocalName(),
+ otherNode.getXml().getLocalName()));
+ }
+ // compare element ns
+ String thisNS = getXml().getNamespaceURI();
+ String otherNS = otherNode.getXml().getNamespaceURI();
+ if ((thisNS == null && otherNS != null)
+ || (thisNS != null && !thisNS.equals(otherNS))) {
+ return Optional.of(
+ String.format("Element namespaces names do not match: %1$s versus %2$s",
+ thisNS, otherNS));
+ }
+ } else {
+ if (!getXml().getNodeName().equals(otherNode.getXml().getNodeName())) {
+ return Optional.of(String.format("Element names do not match: %1$s versus %2$s",
+ getXml().getNodeName(),
+ otherNode.getXml().getNodeName()));
+ }
+ }
+
+ // compare attributes, we do it twice to identify added/missing elements in both lists.
+ Optional<String> message = checkAttributes(this, otherNode);
+ if (message.isPresent()) {
+ return message;
+ }
+ message = checkAttributes(otherNode, this);
+ if (message.isPresent()) {
+ return message;
+ }
+
+ // compare children
+ List<Node> expectedChildren = filterUninterestingNodes(getXml().getChildNodes());
+ List<Node> actualChildren = filterUninterestingNodes(otherNode.getXml().getChildNodes());
+ if (expectedChildren.size() != actualChildren.size()) {
+
+ if (expectedChildren.size() > actualChildren.size()) {
+ // missing some.
+ List<String> missingChildrenNames =
+ Lists.transform(expectedChildren, NODE_TO_NAME);
+ missingChildrenNames.removeAll(Lists.transform(actualChildren, NODE_TO_NAME));
+ return Optional.of(String.format(
+ "%1$s: Number of children do not match up: "
+ + "expected %2$d versus %3$d at %4$s, missing %5$s",
+ getId(),
+ expectedChildren.size(),
+ actualChildren.size(),
+ otherNode.printPosition(),
+ Joiner.on(",").join(missingChildrenNames)));
+ } else {
+ // extra ones.
+ List<String> extraChildrenNames = Lists.transform(actualChildren, NODE_TO_NAME);
+ extraChildrenNames.removeAll(Lists.transform(expectedChildren, NODE_TO_NAME));
+ return Optional.of(String.format(
+ "%1$s: Number of children do not match up: "
+ + "expected %2$d versus %3$d at %4$s, extra elements found : %5$s",
+ getId(),
+ expectedChildren.size(),
+ actualChildren.size(),
+ otherNode.printPosition(),
+ Joiner.on(",").join(expectedChildren)));
+ }
+ }
+ for (Node expectedChild : expectedChildren) {
+ if (expectedChild.getNodeType() == Node.ELEMENT_NODE) {
+ XmlElement expectedChildNode = new XmlElement((Element) expectedChild, mDocument);
+ message = findAndCompareNode(otherNode, actualChildren, expectedChildNode);
+ if (message.isPresent()) {
+ return message;
+ }
+ }
+ }
+ return Optional.absent();
+ }
+
+ private Optional<String> findAndCompareNode(
+ XmlElement otherElement,
+ List<Node> otherElementChildren,
+ XmlElement childNode) {
+
+ for (Node potentialNode : otherElementChildren) {
+ if (potentialNode.getNodeType() == Node.ELEMENT_NODE) {
+ XmlElement otherChildNode = new XmlElement((Element) potentialNode, mDocument);
+ if (childNode.getType() == otherChildNode.getType()) {
+ // check if this element uses a key.
+ if (childNode.getType().getNodeKeyResolver().getKeyAttributesNames()
+ .isEmpty()) {
+ // no key... try all the other elements, if we find one equal, we are done.
+ if (!childNode.compareTo(otherChildNode).isPresent()) {
+ return Optional.absent();
+ }
+ } else {
+ // key...
+ if (childNode.getKey() == null) {
+ // other key MUST also be null.
+ if (otherChildNode.getKey() == null) {
+ return childNode.compareTo(otherChildNode);
+ }
+ } else {
+ if (childNode.getKey().equals(otherChildNode.getKey())) {
+ return childNode.compareTo(otherChildNode);
+ }
+ }
+ }
+ }
+ }
+ }
+ return Optional.of(String.format("Child %1$s not found in document %2$s",
+ childNode.getId(),
+ otherElement.printPosition()));
+ }
+
+ private static List<Node> filterUninterestingNodes(NodeList nodeList) {
+ List<Node> interestingNodes = new ArrayList<Node>();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ if (node.getNodeType() == Node.TEXT_NODE) {
+ Text t = (Text) node;
+ if (!t.getData().trim().isEmpty()) {
+ interestingNodes.add(node);
+ }
+ } else if (node.getNodeType() != Node.COMMENT_NODE) {
+ interestingNodes.add(node);
+ }
+
+ }
+ return interestingNodes;
+ }
+
+ private static Optional<String> checkAttributes(
+ XmlElement expected,
+ XmlElement actual) {
+
+ for (XmlAttribute expectedAttr : expected.getAttributes()) {
+ XmlAttribute.NodeName attributeName = expectedAttr.getName();
+ if (attributeName.isInNamespace(SdkConstants.TOOLS_URI)) {
+ continue;
+ }
+ Optional<XmlAttribute> actualAttr = actual.getAttribute(attributeName);
+ if (actualAttr.isPresent()) {
+ if (!expectedAttr.getValue().equals(actualAttr.get().getValue())) {
+ return Optional.of(
+ String.format("Attribute %1$s do not match: %2$s versus %3$s at %4$s",
+ expectedAttr.getId(),
+ expectedAttr.getValue(),
+ actualAttr.get().getValue(),
+ actual.printPosition()));
+ }
+ } else {
+ return Optional.of(String.format("Attribute %1$s not found at %2$s",
+ expectedAttr.getId(), actual.printPosition()));
+ }
+ }
+ return Optional.absent();
+ }
+
+ private ImmutableList<XmlElement> initMergeableChildren() {
+ ImmutableList.Builder<XmlElement> mergeableNodes = new ImmutableList.Builder<XmlElement>();
+ NodeList nodeList = getXml().getChildNodes();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ if (node instanceof Element) {
+ XmlElement xmlElement = new XmlElement((Element) node, mDocument);
+ mergeableNodes.add(xmlElement);
+ }
+ }
+ return mergeableNodes.build();
+ }
+
+ /**
+ * Returns all leading comments in the source xml before the node to be adopted.
+ * @param nodeToBeAdopted node that will be added as a child to this node.
+ */
+ static List<Node> getLeadingComments(Node nodeToBeAdopted) {
+ ImmutableList.Builder<Node> nodesToAdopt = new ImmutableList.Builder<Node>();
+ Node previousSibling = nodeToBeAdopted.getPreviousSibling();
+ while (previousSibling != null
+ && (previousSibling.getNodeType() == Node.COMMENT_NODE
+ || previousSibling.getNodeType() == Node.TEXT_NODE)) {
+ // we really only care about comments.
+ if (previousSibling.getNodeType() == Node.COMMENT_NODE) {
+ nodesToAdopt.add(previousSibling);
+ }
+ previousSibling = previousSibling.getPreviousSibling();
+ }
+ return nodesToAdopt.build().reverse();
+ }
+
+ void addMessage(MergingReport.Builder mergingReport,
+ MergingReport.Record.Severity severity,
+ String message) {
+ mergingReport.addMessage(getDocument().getSourceLocation(),
+ getLine(), getColumn(), severity, message);
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlLoader.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlLoader.java
new file mode 100644
index 0000000..1184e25
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlLoader.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.ManifestMerger2.SystemProperty;
+import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+
+import com.android.annotations.Nullable;
+import com.android.utils.PositionXmlParser;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Responsible for loading XML files.
+ */
+public final class XmlLoader {
+
+ /**
+ * Abstraction for the notion of source location. This is useful for logging and records
+ * collection when a origin of an xml declaration is needed.
+ */
+ public interface SourceLocation {
+
+ /**
+ * print this source location in a human and machine readable format.
+ *
+ * @param shortFormat whether or not to use the short format. For instance, for a file, a
+ * short format is the file name while the long format is its path.
+ * @return the human and machine readable source location.
+ */
+ String print(boolean shortFormat);
+
+ /**
+ * Persist a location to an xml node.
+ *
+ * @param document the document in which the node will exist.
+ * @return the persisted location as a xml node.
+ */
+ Node toXml(Document document);
+ }
+
+ private XmlLoader() {}
+
+ /**
+ * Loads an xml file without doing xml validation and return a {@link XmlDocument}
+ *
+ * @param displayName the xml file display name.
+ * @param xmlFile the xml file.
+ * @return the initialized {@link com.android.manifmerger.XmlDocument}
+ */
+ public static XmlDocument load(
+ KeyResolver<String> selectors,
+ KeyBasedValueResolver<SystemProperty> systemPropertyResolver,
+ String displayName,
+ File xmlFile,
+ XmlDocument.Type type,
+ Optional<String> mainManifestPackageName)
+ throws IOException, SAXException, ParserConfigurationException {
+ InputStream inputStream = new BufferedInputStream(new FileInputStream(xmlFile));
+
+ PositionXmlParser positionXmlParser = new PositionXmlParser();
+ Document domDocument = positionXmlParser.parse(inputStream);
+ return domDocument != null
+ ? new XmlDocument(positionXmlParser,
+ new FileSourceLocation(displayName, xmlFile),
+ selectors,
+ systemPropertyResolver,
+ domDocument.getDocumentElement(),
+ type,
+ mainManifestPackageName)
+ : null;
+ }
+
+
+ /**
+ * Loads a xml document from its {@link String} representation without doing xml validation and
+ * return a {@link com.android.manifmerger.XmlDocument}
+ * @param sourceLocation the source location to use for logging and record collection.
+ * @param xml the persisted xml.
+ * @return the initialized {@link com.android.manifmerger.XmlDocument}
+ * @throws IOException this should never be thrown.
+ * @throws SAXException if the xml is incorrect
+ * @throws ParserConfigurationException if the xml engine cannot be configured.
+ */
+ public static XmlDocument load(
+ KeyResolver<String> selectors,
+ KeyBasedValueResolver<SystemProperty> systemPropertyResolver,
+ SourceLocation sourceLocation,
+ String xml,
+ XmlDocument.Type type,
+ Optional<String> mainManifestPackageName)
+ throws IOException, SAXException, ParserConfigurationException {
+ PositionXmlParser positionXmlParser = new PositionXmlParser();
+ Document domDocument = positionXmlParser.parse(xml);
+ return domDocument != null
+ ? new XmlDocument(
+ positionXmlParser,
+ sourceLocation,
+ selectors,
+ systemPropertyResolver,
+ domDocument.getDocumentElement(),
+ type,
+ mainManifestPackageName)
+ : null;
+ }
+
+ /**
+ * Implementation of {@link SourceLocation} describing a local file.
+ */
+ private static class FileSourceLocation implements SourceLocation {
+
+ private final File mFile;
+ private final String mName;
+
+ private FileSourceLocation(@Nullable String name, File file) {
+ this.mFile = file;
+ mName = Strings.isNullOrEmpty(name)
+ ? file.getName()
+ : name;
+ }
+
+ @Override
+ public String print(boolean shortFormat) {
+ return shortFormat ? mName : mFile.getAbsolutePath();
+ }
+
+ @Override
+ public Node toXml(Document document) {
+ Element location = document.createElement("source");
+ location.setAttribute("name", mName);
+ location.setAttribute("scheme", "file://");
+ location.setAttribute("value", mFile.getAbsolutePath());
+ return location;
+ }
+ }
+
+ public static SourceLocation locationFromXml(Element location) {
+ String scheme = location.getAttribute("scheme");
+ if (Strings.isNullOrEmpty(scheme)) {
+ return UNKNOWN;
+ }
+ if (scheme.equals("file://")) {
+ return new FileSourceLocation(
+ location.getAttribute("name"),
+ new File(location.getAttribute("value")));
+ }
+ throw new RuntimeException(scheme + " scheme unsupported");
+ }
+
+ public static final SourceLocation UNKNOWN = new SourceLocation() {
+ @Override
+ public String print(boolean shortFormat) {
+ return "Unknown location";
+ }
+
+ @Override
+ public Node toXml(Document document) {
+ // empty node.
+ return document.createElement("source");
+ }
+ };
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlNode.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlNode.java
new file mode 100644
index 0000000..892eb97
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlNode.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotations.concurrency.Immutable;
+import com.android.utils.PositionXmlParser;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * Common behavior of any xml declaration.
+ */
+public abstract class XmlNode {
+
+ private static final String UNKNOWN_POSITION = "Unknown position";
+
+ protected static final Function<Node, String> NODE_TO_NAME =
+ new Function<Node, String>() {
+ @Override
+ public String apply(Node input) {
+ return input.getNodeName();
+ }
+ };
+
+ /**
+ * Returns an unique id within the manifest file for the element.
+ */
+ public abstract NodeKey getId();
+
+ /**
+ * Returns the element's position
+ */
+ public abstract PositionXmlParser.Position getPosition();
+
+ /**
+ * Returns the element's document xml source file location.
+ */
+ @NonNull
+ public abstract XmlLoader.SourceLocation getSourceLocation();
+
+ /**
+ * Returns the element's xml
+ */
+ @NonNull
+ public abstract Node getXml();
+
+ /**
+ * Returns the name of this xml element or attribute.
+ */
+ public abstract NodeName getName();
+
+ /**
+ * Abstraction to an xml name to isolate whether the name has a namespace or not.
+ */
+ public interface NodeName {
+
+ /**
+ * Returns true if this attribute name has a namespace declaration and that namespapce is
+ * the same as provided, false otherwise.
+ */
+ boolean isInNamespace(String namespaceURI);
+
+ /**
+ * Adds a new attribute of this name to a xml element with a value.
+ * @param to the xml element to add the attribute to.
+ * @param withValue the new attribute's value.
+ */
+ void addToNode(Element to, String withValue);
+
+ /**
+ * Persist itself inside a {@link org.w3c.dom.Element}
+ */
+ void persistTo(Element node);
+
+ /**
+ * The local name.
+ */
+ String getLocalName();
+
+ }
+
+ /**
+ * Factory method to create an instance of {@link com.android.manifmerger.XmlNode.NodeName}
+ * for an existing xml node.
+ * @param node the xml definition.
+ * @return an instance of {@link com.android.manifmerger.XmlNode.NodeName} providing
+ * namespace handling.
+ */
+ public static NodeName unwrapName(Node node) {
+ return node.getNamespaceURI() == null
+ ? new Name(node.getNodeName())
+ : new NamespaceAwareName(node);
+ }
+
+ public static NodeName fromXmlName(String name) {
+ return (name.contains(":"))
+ ? new NamespaceAwareName(SdkConstants.ANDROID_URI,
+ name.substring(0, name.indexOf(':')),
+ name.substring(name.indexOf(':') + 1))
+ : new Name(name);
+ }
+
+ public static NodeName fromNSName(String namespaceUri, String prefix, String localName) {
+ return new NamespaceAwareName(namespaceUri, prefix, localName);
+ }
+
+ /**
+ * Return the line number in the original xml file this element or attribute was declared.
+ */
+ public int getLine() {
+ PositionXmlParser.Position position = getPosition();
+ return position != null ? position.getLine() : 0;
+ }
+
+ /**
+ * Return the column number in the original xml file this element or attribute was declared.
+ */
+ public int getColumn() {
+ PositionXmlParser.Position position = getPosition();
+ return position != null ? position.getColumn() : 0;
+ }
+
+ /**
+ * Returns the position of this attribute in the original xml file. This may return an invalid
+ * location as this xml fragment does not exist in any xml file but is the temporary result
+ * of the merging process.
+ * @return a human readable position or {@link #UNKNOWN_POSITION}
+ */
+ public String printPosition() {
+ return printPosition(true);
+ }
+
+ public String printPosition(boolean shortFormat) {
+ PositionXmlParser.Position position = getPosition();
+ if (position == null) {
+ return UNKNOWN_POSITION;
+ }
+ return new StringBuilder()
+ .append(getSourceLocation() != null
+ ? getSourceLocation().print(shortFormat)
+ : "Unknown location")
+ .append(":").append(position.getLine())
+ .append(":").append(position.getColumn())
+ .toString();
+ }
+
+ /**
+ * Implementation of {@link com.android.manifmerger.XmlNode.NodeName} for an
+ * node's declaration not using a namespace.
+ */
+ private static final class Name implements NodeName {
+ private final String mName;
+
+ private Name(@NonNull String name) {
+ this.mName = Preconditions.checkNotNull(name);
+ }
+
+ @Override
+ public boolean isInNamespace(String namespaceURI) {
+ return false;
+ }
+
+ @Override
+ public void addToNode(Element to, String withValue) {
+ to.setAttribute(mName, withValue);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o != null && o instanceof Name && ((Name) o).mName.equals(this.mName));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mName);
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+
+ @Override
+ public void persistTo(Element node) {
+ node.setAttribute("name", mName);
+ }
+
+ @Override
+ public String getLocalName() {
+ return mName;
+ }
+ }
+
+ /**
+ * Implementation of the {@link com.android.manifmerger.XmlNode.NodeName} for a namespace aware attribute.
+ */
+ private static final class NamespaceAwareName implements NodeName {
+ private final String mNamespaceURI;
+ // ignore for comparison and hashcoding since different documents can use different
+ // prefixes for the same namespace URI.
+ private final String mPrefix;
+ private final String mLocalName;
+
+ private NamespaceAwareName(@NonNull Node node) {
+ this.mNamespaceURI = Preconditions.checkNotNull(node.getNamespaceURI());
+ this.mPrefix = Preconditions.checkNotNull(node.getPrefix());
+ this.mLocalName = Preconditions.checkNotNull(node.getLocalName());
+ }
+
+ private NamespaceAwareName(@NonNull String namespaceURI,
+ @NonNull String prefix,
+ @NonNull String localName) {
+ mNamespaceURI = Preconditions.checkNotNull(namespaceURI);
+ mPrefix = Preconditions.checkNotNull(prefix);
+ mLocalName = Preconditions.checkNotNull(localName);
+ }
+
+ @Override
+ public boolean isInNamespace(String namespaceURI) {
+ return mNamespaceURI.equals(namespaceURI);
+ }
+
+ @Override
+ public void addToNode(Element to, String withValue) {
+ // TODO: consider standardizing everything on "android:"
+ to.setAttributeNS(mNamespaceURI, mPrefix + ":" + mLocalName, withValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mNamespaceURI, mLocalName);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o != null && o instanceof NamespaceAwareName
+ && ((NamespaceAwareName) o).mLocalName.equals(this.mLocalName)
+ && ((NamespaceAwareName) o).mNamespaceURI.equals(this.mNamespaceURI));
+ }
+
+ @Override
+ public String toString() {
+ return mPrefix + ":" + mLocalName;
+ }
+
+ @Override
+ public void persistTo(Element node) {
+ node.setAttribute("prefix", mPrefix);
+ node.setAttribute("local-name", mLocalName);
+ node.setAttribute("namespace-uri", mNamespaceURI);
+ }
+
+ @Override
+ public String getLocalName() {
+ return mLocalName;
+ }
+ }
+
+ /**
+ * A xml element or attribute key.
+ */
+ @Immutable
+ public static class NodeKey {
+
+ @NonNull
+ private final String mKey;
+
+ NodeKey(@NonNull String key) {
+ mKey = key;
+ }
+
+ public static NodeKey fromXml(Element element) {
+ return new OrphanXmlElement(element).getId();
+ }
+
+ @Override
+ public String toString() {
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o != null && o instanceof NodeKey && ((NodeKey) o).mKey.equals(this.mKey));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mKey);
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionRecorderTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionRecorderTest.java
new file mode 100644
index 0000000..5d0ba59
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionRecorderTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.utils.ILogger;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link ActionRecorder} class
+ */
+public class ActionRecorderTest extends TestCase {
+
+ private static final String REFERENCE = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/apk/res/android/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+
+ // this will be used as the source location for the "reference" xml string.
+ private static final String REFEFENCE_DOCUMENT = "ref_doc";
+
+ @Mock ILogger mLoggerMock;
+
+ ActionRecorder mActionRecorderBuilder = new ActionRecorder();
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testDoNothing() {
+ Actions actions = mActionRecorderBuilder.build();
+ actions.log(mLoggerMock);
+ Mockito.verify(mLoggerMock).verbose(Actions.HEADER);
+ Mockito.verifyNoMoreInteractions(mLoggerMock);
+ assertTrue(actions.getNodeKeys().isEmpty());
+ }
+
+ public void testSingleElement_withoutAttributes()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ REFEFENCE_DOCUMENT), REFERENCE);
+
+ XmlElement xmlElement = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get();
+ // added during the initial file loading
+ mActionRecorderBuilder.recordNodeAction(xmlElement, Actions.ActionType.ADDED);
+
+ Actions actions = mActionRecorderBuilder.build();
+ assertEquals(1, actions.getNodeKeys().size());
+ assertEquals(1, actions.getNodeRecords(xmlElement.getId()).size());
+ assertEquals(0, actions.getRecordedAttributeNames(xmlElement.getId()).size());
+ actions.log(mLoggerMock);
+
+ // check that output is consistent with spec.
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(Actions.HEADER)
+ .append(xmlElement.getId()).append("\n");
+ appendNode(stringBuilder, Actions.ActionType.ADDED, REFEFENCE_DOCUMENT, 6, 5);
+
+ Mockito.verify(mLoggerMock).verbose(stringBuilder.toString());
+ Mockito.verifyNoMoreInteractions(mLoggerMock);
+ }
+
+ public void testSingleElement_withoutAttributes_withRejection()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String other = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/apk/res/android/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ REFEFENCE_DOCUMENT), REFERENCE);
+
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ "other_document"), other);
+
+ XmlElement xmlElement = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get();
+ // added during initial document loading
+ mActionRecorderBuilder.recordNodeAction(xmlElement, Actions.ActionType.ADDED);
+ // rejected during second document merging.
+ mActionRecorderBuilder.recordNodeAction(xmlElement, Actions.ActionType.REJECTED,
+ otherDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get());
+
+ Actions actions = mActionRecorderBuilder.build();
+ assertEquals(1, actions.getNodeKeys().size());
+ assertEquals(2, actions.getNodeRecords(xmlElement.getId()).size());
+ assertEquals(Actions.ActionType.ADDED,
+ actions.getNodeRecords(xmlElement.getId()).get(0).mActionType);
+ assertEquals(Actions.ActionType.REJECTED,
+ actions.getNodeRecords(xmlElement.getId()).get(1).mActionType);
+ assertEquals(0, actions.getRecordedAttributeNames(xmlElement.getId()).size());
+ actions.log(mLoggerMock);
+
+ // check that output is consistent with spec.
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(Actions.HEADER)
+ .append(xmlElement.getId()).append("\n");
+ appendNode(stringBuilder, Actions.ActionType.ADDED, REFEFENCE_DOCUMENT, 6, 5);
+ appendNode(stringBuilder, Actions.ActionType.REJECTED, "other_document", 6, 5);
+
+ Mockito.verify(mLoggerMock).verbose(stringBuilder.toString());
+ Mockito.verifyNoMoreInteractions(mLoggerMock);
+ }
+
+ public void testSingleElement_withNoNamespaceAttributes()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ REFEFENCE_DOCUMENT), REFERENCE);
+
+ XmlElement xmlElement = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get();
+ // added during the initial file loading
+ mActionRecorderBuilder.recordNodeAction(xmlElement, Actions.ActionType.ADDED);
+ mActionRecorderBuilder.recordAttributeAction(
+ xmlElement.getAttribute(XmlNode.fromXmlName("android:name")).get(),
+ Actions.ActionType.ADDED, AttributeOperationType.STRICT);
+
+ Actions actions = mActionRecorderBuilder.build();
+ assertEquals(1, actions.getNodeKeys().size());
+ assertEquals(1, actions.getNodeRecords(xmlElement.getId()).size());
+ assertEquals(1, actions.getRecordedAttributeNames(xmlElement.getId()).size());
+ actions.log(mLoggerMock);
+
+ // check that output is consistent with spec.
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(Actions.HEADER)
+ .append(xmlElement.getId()).append("\n");
+ appendNode(stringBuilder, Actions.ActionType.ADDED, REFEFENCE_DOCUMENT, 6, 5);
+ appendAttribute(stringBuilder,
+ XmlNode.unwrapName(xmlElement.getXml().getAttributeNode("android:name")),
+ Actions.ActionType.ADDED,
+ REFEFENCE_DOCUMENT,
+ 6,
+ 15);
+
+ Mockito.verify(mLoggerMock).verbose(stringBuilder.toString());
+ Mockito.verifyNoMoreInteractions(mLoggerMock);
+ }
+
+ public void testSingleElement_withNamespaceAttributes()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ REFEFENCE_DOCUMENT), REFERENCE);
+
+ XmlElement xmlElement = xmlDocument.getRootNode();
+ // added during the initial file loading
+ mActionRecorderBuilder.recordNodeAction(xmlElement, Actions.ActionType.ADDED);
+ mActionRecorderBuilder.recordAttributeAction(
+ xmlElement.getAttribute(XmlNode.fromXmlName("package")).get(),
+ Actions.ActionType.ADDED, AttributeOperationType.STRICT);
+
+ Actions actions = mActionRecorderBuilder.build();
+ assertEquals(1, actions.getNodeKeys().size());
+ assertEquals(1, actions.getNodeRecords(xmlElement.getId()).size());
+ assertEquals(1, actions.getRecordedAttributeNames(xmlElement.getId()).size());
+ actions.log(mLoggerMock);
+
+ // check that output is consistent with spec.
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(Actions.HEADER)
+ .append(xmlElement.getId()).append("\n");
+ appendNode(stringBuilder, Actions.ActionType.ADDED, REFEFENCE_DOCUMENT, 1, 1);
+ appendAttribute(stringBuilder,
+ XmlNode.unwrapName(xmlElement.getXml().getAttributeNode("package")),
+ Actions.ActionType.ADDED,
+ REFEFENCE_DOCUMENT,
+ 4,
+ 5);
+
+ Mockito.verify(mLoggerMock).verbose(stringBuilder.toString());
+ Mockito.verifyNoMoreInteractions(mLoggerMock);
+ }
+
+ public void testMultipleElements_withRejection()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String other = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/apk/res/android/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\""
+ + " android:configChanges=\"locale\"/>\n"
+ + " <application android:name=\"applicationOne\"/>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ REFEFENCE_DOCUMENT), REFERENCE);
+
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ "other_document"), other);
+
+ XmlElement activityElement = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get();
+ // added during initial document loading
+ mActionRecorderBuilder.recordNodeAction(activityElement, Actions.ActionType.ADDED);
+ // rejected during second document merging.
+ mActionRecorderBuilder.recordNodeAction(activityElement, Actions.ActionType.REJECTED,
+ otherDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get());
+ XmlElement applicationElement = otherDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.APPLICATION, null).get();
+ mActionRecorderBuilder.recordNodeAction(applicationElement, Actions.ActionType.ADDED);
+
+ Actions actions = mActionRecorderBuilder.build();
+ assertEquals(2, actions.getNodeKeys().size());
+ assertEquals(2, actions.getNodeRecords(activityElement.getId()).size());
+ assertEquals(Actions.ActionType.ADDED,
+ actions.getNodeRecords(activityElement.getId()).get(0).mActionType);
+ assertEquals(Actions.ActionType.REJECTED,
+ actions.getNodeRecords(activityElement.getId()).get(1).mActionType);
+ assertEquals(0, actions.getRecordedAttributeNames(activityElement.getId()).size());
+ assertEquals(1, actions.getNodeRecords(applicationElement.getId()).size());
+ assertEquals(0, actions.getRecordedAttributeNames(applicationElement.getId()).size());
+ actions.log(mLoggerMock);
+
+ // check that output is consistent with spec.
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(Actions.HEADER)
+ .append(activityElement.getId()).append("\n");
+ appendNode(stringBuilder, Actions.ActionType.ADDED, REFEFENCE_DOCUMENT, 6, 5);
+ appendNode(stringBuilder, Actions.ActionType.REJECTED, "other_document", 6, 5);
+ stringBuilder.append(applicationElement.getId()).append("\n");
+ appendNode(stringBuilder, Actions.ActionType.ADDED, "other_document", 7, 5);
+
+ Mockito.verify(mLoggerMock).verbose(stringBuilder.toString());
+ Mockito.verifyNoMoreInteractions(mLoggerMock);
+ }
+
+ private void appendNode(StringBuilder out,
+ Actions.ActionType actionType,
+ String docString,
+ int lineNumber,
+ int columnNumber) {
+
+ out.append(actionType.toString())
+ .append(" from ")
+ .append(getClass().getSimpleName()).append('#').append(docString)
+ .append(":").append(lineNumber).append(":").append(columnNumber).append("\n");
+ }
+
+ private void appendAttribute(StringBuilder out,
+ XmlNode.NodeName attributeName,
+ Actions.ActionType actionType,
+ String docString,
+ int lineNumber,
+ int columnNumber) {
+
+ out.append("\t")
+ .append(attributeName.toString())
+ .append("\n\t\t")
+ .append(actionType.toString())
+ .append(" from ")
+ .append(getClass().getSimpleName()).append('#').append(docString)
+ .append(":").append(lineNumber)
+ .append(":").append(columnNumber).append("\n");
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionsTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionsTest.java
new file mode 100644
index 0000000..4d4332f
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionsTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.Actions.ActionLocation;
+import static com.android.manifmerger.Actions.DecisionTreeRecord;
+import static com.android.manifmerger.XmlNode.NodeKey;
+import static com.android.manifmerger.XmlNode.fromXmlName;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.SdkConstants;
+import com.android.sdklib.mock.MockLog;
+import com.android.utils.ILogger;
+import com.android.utils.PositionXmlParser;
+import com.android.utils.StdLogger;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import com.google.common.io.LineReader;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for {@link Actions} class
+ */
+public class ActionsTest extends TestCase {
+
+ public void testGetNodeKeys() {
+ Element xmlElement = mock(Element.class);
+ when(xmlElement.getNodeName()).thenReturn("activity");
+ when(xmlElement.getAttributeNS(SdkConstants.ANDROID_URI, "name")).thenReturn("keyOne");
+ NodeKey nodeKey = NodeKey.fromXml(xmlElement);
+ assertNotNull(nodeKey);
+
+ ImmutableMap.Builder<NodeKey, DecisionTreeRecord> records = ImmutableMap.builder();
+ DecisionTreeRecord activityDecisionTree = new DecisionTreeRecord();
+ records.put(nodeKey, activityDecisionTree);
+ Actions actions = new Actions(records.build());
+
+ assertEquals(1, actions.getNodeKeys().size());
+ assertTrue(actions.getNodeKeys().contains(nodeKey));
+ }
+
+ public void testGetNodeRecords() {
+ Element xmlElement = mock(Element.class);
+ when(xmlElement.getNodeName()).thenReturn("activity");
+ when(xmlElement.getAttributeNS(SdkConstants.ANDROID_URI, "name")).thenReturn("keyOne");
+ NodeKey nodeKey = NodeKey.fromXml(xmlElement);
+ assertNotNull(nodeKey);
+
+ ImmutableMap.Builder<NodeKey, DecisionTreeRecord> records = ImmutableMap.builder();
+ DecisionTreeRecord activityDecisionTree = new DecisionTreeRecord();
+ activityDecisionTree.addNodeRecord(new Actions.NodeRecord(
+ Actions.ActionType.ADDED,
+ new ActionLocation(mock(XmlLoader.SourceLocation.class), mock(
+ PositionXmlParser.Position.class)),
+ new NodeKey("nodeKey"),
+ null, /* reason */
+ NodeOperationType.MERGE));
+ records.put(nodeKey, activityDecisionTree);
+ Actions actions = new Actions(records.build());
+
+ // lookup using DOM element API.
+ ImmutableList<Actions.NodeRecord> nodeRecords = actions.getNodeRecords(xmlElement);
+ assertNotNull(nodeRecords);
+ assertEquals(1, nodeRecords.size());
+
+ // lookup using key
+ nodeRecords = actions.getNodeRecords(nodeKey);
+ assertNotNull(nodeRecords);
+ assertEquals(1, nodeRecords.size());
+ }
+
+ public void testGetAttributesRecords() {
+ Element xmlElement = mock(Element.class);
+ when(xmlElement.getNodeName()).thenReturn("activity");
+ when(xmlElement.getAttributeNS(SdkConstants.ANDROID_URI, "name")).thenReturn("keyOne");
+ NodeKey nodeKey = NodeKey.fromXml(xmlElement);
+ assertNotNull(nodeKey);
+
+ ImmutableMap.Builder<NodeKey, DecisionTreeRecord> records = ImmutableMap.builder();
+ DecisionTreeRecord activityDecisionTree = new DecisionTreeRecord();
+ activityDecisionTree.addNodeRecord(new Actions.NodeRecord(
+ Actions.ActionType.ADDED,
+ new ActionLocation(mock(XmlLoader.SourceLocation.class), mock(
+ PositionXmlParser.Position.class)),
+ new NodeKey("nodeKey"),
+ null, /* reason */
+ NodeOperationType.MERGE));
+ XmlNode.NodeName attributeName = XmlNode.fromXmlName("android:name");
+ activityDecisionTree.mAttributeRecords.put(attributeName,
+ ImmutableList.of(
+ new Actions.AttributeRecord(Actions.ActionType.INJECTED,
+ new ActionLocation(mock(XmlLoader.SourceLocation.class),
+ mock(PositionXmlParser.Position.class)),
+ new NodeKey("nodeKey"),
+ null, /* reason */
+ AttributeOperationType.STRICT)));
+ records.put(nodeKey, activityDecisionTree);
+ Actions actions = new Actions(records.build());
+
+ ImmutableList<XmlNode.NodeName> recordedAttributeNames = actions
+ .getRecordedAttributeNames(nodeKey);
+ assertTrue(recordedAttributeNames.contains(attributeName));
+
+ ImmutableList<Actions.AttributeRecord> attributeRecords = actions
+ .getAttributeRecords(nodeKey, attributeName);
+ assertEquals(1, attributeRecords.size());
+ }
+
+
+ /**
+ * test tools:node="removeAll" with several target elements to be removed.
+ */
+ public void testActionsPersistenceAndLoading()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n" // 1
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" // 2
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n" // 3
+ + " package=\"com.example.lib3\">\n" // 4
+ + "\n" // 5
+ + " <permission\n" // 6
+ + " android:name=\"permissionOne\"\n" // 7
+ + " tools:node=\"remove\">\n" // 8
+ + " </permission>\n" // 10
+ + " <permission \n" // 11
+ + " tools:node=\"removeAll\"\n" // 12
+ + " tools:selector=\"com.example.lib3\">\n" // 13
+ + " </permission>\n" // 14
+ + " <permission\n" // 15
+ + " android:name=\"permissionThree\"\n" // 16
+ + " android:protectionLevel=\"signature\"\n" // 17
+ + " tools:node=\"replace\">\n" // 18
+ + " </permission>\n" // 19
+ + "\n" // 20
+ + "</manifest>"; // 21
+
+ String lowerPriorityOne = ""
+ + "<manifest\n" // 1
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" // 2
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n" // 3
+ + " package=\"com.example.lib1\">\n" // 4
+ + "\n" // 5
+ + " <permission android:name=\"permissionOne\"\n" // 6
+ + " android:protectionLevel=\"signature\">\n" // 7
+ + " </permission>\n" // 8
+ + " <permission android:name=\"permissionTwo\"\n" // 9
+ + " android:protectionLevel=\"signature\">\n" // 10
+ + " </permission>\n" // 11
+ + "\n" // 12
+ + "</manifest>"; // 13
+
+ String lowerPriorityTwo = ""
+ + "<manifest\n" // 1
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" // 2
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n" // 3
+ + " package=\"com.example.lib2\">\n" // 4
+ + "\n" // 5
+ + " <permission android:name=\"permissionThree\"\n" // 6
+ + " android:protectionLevel=\"normal\">\n" // 7
+ + " </permission>\n" // 8
+ + " <permission android:name=\"permissionFour\"\n" // 9
+ + " android:protectionLevel=\"normal\">\n" // 10
+ + " </permission>\n" // 11
+ + "\n" // 12
+ + "</manifest>"; // 13
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument firstLibrary = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityOne"), lowerPriorityOne);
+ XmlDocument secondLibrary = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityTwo"), lowerPriorityTwo);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(firstLibrary, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ result = result.get().merge(secondLibrary, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ ILogger logger = new MockLog();
+ XmlDocument cleanedDocument =
+ ToolsInstructionsCleaner.cleanToolsReferences(result.get(), logger);
+ assertNotNull(cleanedDocument);
+
+ Actions actions = mergingReportBuilder.getActionRecorder().build();
+ String expectedMappings = "1<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "2<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "3 package=\"com.example.lib3\" >\n"
+ + "4\n"
+ + "5 <permission\n"
+ + "5-->ActionsTest#higherPriority:14:5\n"
+ + "6 android:name=\"permissionThree\"\n"
+ + "6-->ActionsTest#higherPriority:15:14\n"
+ + "7 android:protectionLevel=\"signature\" >\n"
+ + "7-->ActionsTest#higherPriority:16:14\n"
+ + "8 </permission>\n"
+ + "9 <permission\n"
+ + "9-->ActionsTest#lowerPriorityOne:9:5\n"
+ + "10 android:name=\"permissionTwo\"\n"
+ + "10-->ActionsTest#lowerPriorityOne:9:17\n"
+ + "11 android:protectionLevel=\"signature\" >\n"
+ + "11-->ActionsTest#lowerPriorityOne:10:14\n"
+ + "12 </permission>\n"
+ + "13 <permission\n"
+ + "13-->ActionsTest#lowerPriorityTwo:9:5\n"
+ + "14 android:name=\"permissionFour\"\n"
+ + "14-->ActionsTest#lowerPriorityTwo:9:17\n"
+ + "15 android:protectionLevel=\"normal\" >\n"
+ + "15-->ActionsTest#lowerPriorityTwo:10:14\n"
+ + "16 </permission>\n"
+ + "17\n"
+ + "18</manifest>\n";
+ assertEquals(expectedMappings, actions.blame(cleanedDocument));
+
+ // persist the records
+ String persistedMappings = actions.persist();
+ System.out.println(persistedMappings);
+
+ // and reload them from the persisted media.
+ Actions newActions = Actions.load(persistedMappings);
+ assertNotNull(newActions);
+
+ // check equality.
+ for (NodeKey nodeKey : actions.getNodeKeys()) {
+
+ ImmutableList<Actions.NodeRecord> allNodeRecords = newActions.getNodeRecords(nodeKey);
+ assertNotNull(allNodeRecords);
+ assertEquals(newActions.getNodeRecords(nodeKey).size(), allNodeRecords.size());
+
+ for (Actions.NodeRecord nodeRecord : newActions.getNodeRecords(nodeKey)) {
+ assertTrue("Cannot find node=" + nodeKey + "record=" + nodeRecord,
+ findNodeRecordInList(nodeRecord, allNodeRecords));
+ }
+
+ for (XmlNode.NodeName nodeName : actions
+ .getRecordedAttributeNames(nodeKey)) {
+ ImmutableList<Actions.AttributeRecord> expectedAttributeRecords =
+ actions.getAttributeRecords(nodeKey, nodeName);
+ ImmutableList<Actions.AttributeRecord> actualAttributeRecords =
+ newActions.getAttributeRecords(nodeKey, nodeName);
+ assertEquals(expectedAttributeRecords.size(), actualAttributeRecords.size());
+ for (Actions.AttributeRecord expectedAttributeRecord : expectedAttributeRecords) {
+ assertTrue("Cannot find attribute=" + nodeName
+ + " record=" + expectedAttributeRecord,
+ findAttributeRecordInList(
+ expectedAttributeRecord, actualAttributeRecords)
+ );
+ }
+ }
+
+ }
+ }
+
+ public static boolean findNodeRecordInList(
+ Actions.NodeRecord nodeRecord,
+ List<Actions.NodeRecord> nodeRecordList) {
+ for (Actions.NodeRecord record : nodeRecordList) {
+ if (record.getActionLocation().getPosition().getLine()
+ == nodeRecord.getActionLocation().getPosition().getLine()
+ && record.getActionLocation().getPosition().getColumn()
+ == nodeRecord.getActionLocation().getPosition().getColumn()
+ && record.getActionLocation().getPosition().getOffset()
+ == nodeRecord.getActionLocation().getPosition().getOffset()
+ && record.getActionType() == nodeRecord.getActionType()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean findAttributeRecordInList(
+ Actions.AttributeRecord attributeRecord,
+ List<Actions.AttributeRecord> attributeRecordList) {
+
+ for (Actions.AttributeRecord record : attributeRecordList) {
+ if (record.getOperationType() == attributeRecord.getOperationType()
+ && record.getActionType() == attributeRecord.getActionType()
+ && record.getActionLocation().getPosition().getLine()
+ == attributeRecord.getActionLocation().getPosition().getLine()
+ && record.getActionLocation().getPosition().getColumn()
+ == attributeRecord.getActionLocation().getPosition().getColumn()
+ && record.getActionLocation().getPosition().getOffset()
+ == attributeRecord.getActionLocation().getPosition().getOffset()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/AttributeModelTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/AttributeModelTest.java
new file mode 100644
index 0000000..7f33be9
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/AttributeModelTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.android.sdklib.mock.MockLog;
+import com.android.utils.ILogger;
+import com.google.common.base.Optional;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link com.android.manifmerger.AttributeModel} class
+ */
+public class AttributeModelTest extends TestCase {
+
+ @Mock
+ AttributeModel.Validator mValidator;
+
+ @Mock
+ XmlAttribute mXmlAttribute;
+
+ @Mock
+ ILogger mMockLog;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testGetters() {
+ AttributeModel attributeModel = AttributeModel.newModel("someName")
+ .setIsPackageDependent()
+ .setDefaultValue("default_value")
+ .setOnReadValidator(mValidator)
+ .build();
+
+ assertEquals(XmlNode.fromXmlName("android:someName"), attributeModel.getName());
+ assertTrue(attributeModel.isPackageDependent());
+ assertEquals("default_value", attributeModel.getDefaultValue());
+
+ attributeModel = AttributeModel.newModel("someName").build();
+
+ assertEquals(XmlNode.fromXmlName("android:someName"), attributeModel.getName());
+ assertFalse(attributeModel.isPackageDependent());
+ assertEquals(null, attributeModel.getDefaultValue());
+
+ Mockito.verifyZeroInteractions(mValidator);
+ }
+
+ public void testBooleanValidator() {
+
+ AttributeModel.BooleanValidator booleanValidator = new AttributeModel.BooleanValidator();
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mMockLog);
+ assertTrue(booleanValidator.validates(mergingReport, mXmlAttribute, "false"));
+ assertTrue(booleanValidator.validates(mergingReport, mXmlAttribute, "true"));
+ assertTrue(booleanValidator.validates(mergingReport, mXmlAttribute, "FALSE"));
+ assertTrue(booleanValidator.validates(mergingReport, mXmlAttribute, "TRUE"));
+ assertTrue(booleanValidator.validates(mergingReport, mXmlAttribute, "False"));
+ assertTrue(booleanValidator.validates(mergingReport, mXmlAttribute, "True"));
+
+ assertFalse(booleanValidator.validates(mergingReport, mXmlAttribute, "foo"));
+ verify(mXmlAttribute).printPosition();
+ }
+
+ public void testMultiValuesValidator() {
+ AttributeModel.MultiValueValidator multiValueValidator =
+ new AttributeModel.MultiValueValidator("foo", "bar", "doh !");
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mMockLog);
+ assertTrue(multiValueValidator.validates(mergingReport, mXmlAttribute, "foo"));
+ assertTrue(multiValueValidator.validates(mergingReport, mXmlAttribute, "bar"));
+ assertTrue(multiValueValidator.validates(mergingReport, mXmlAttribute, "doh !"));
+
+ assertFalse(multiValueValidator.validates(mergingReport, mXmlAttribute, "oh no !"));
+ }
+
+ public void testIntegerValueValidator() {
+ AttributeModel.IntegerValueValidator integerValueValidator =
+ new AttributeModel.IntegerValueValidator();
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mMockLog);
+ assertFalse(integerValueValidator.validates(mergingReport, mXmlAttribute, "abcd"));
+ assertFalse(integerValueValidator.validates(mergingReport, mXmlAttribute,
+ "123456789123456789"));
+ assertFalse(integerValueValidator.validates(mergingReport, mXmlAttribute,
+ "0xFFFFFFFFFFFFFFFF"));
+ }
+
+ public void testStrictMergingPolicy() {
+ assertEquals("ok", AttributeModel.STRICT_MERGING_POLICY.merge("ok", "ok"));
+ assertNull(AttributeModel.STRICT_MERGING_POLICY.merge("one", "two"));
+ }
+
+ public void testOrMergingPolicy() {
+ assertEquals("true", AttributeModel.OR_MERGING_POLICY.merge("true", "true"));
+ assertEquals("true", AttributeModel.OR_MERGING_POLICY.merge("true", "false"));
+ assertEquals("true", AttributeModel.OR_MERGING_POLICY.merge("false", "true"));
+ assertEquals("false", AttributeModel.OR_MERGING_POLICY.merge("false", "false"));
+ }
+
+ public void testNumericalSuperiorityPolicy() {
+ assertEquals("5", AttributeModel.NO_MERGING_POLICY.merge("5", "10"));
+ assertEquals("10", AttributeModel.NO_MERGING_POLICY.merge("10", "5"));
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ElementsTrimmerTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ElementsTrimmerTest.java
new file mode 100644
index 0000000..83bc78d
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ElementsTrimmerTest.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.SdkConstants;
+import com.android.utils.ILogger;
+import com.android.xml.AndroidManifest;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link ElementsTrimmer} class
+ */
+public class ElementsTrimmerTest extends TestCase {
+
+ @Mock
+ ILogger mILogger;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testNoUseFeaturesDeclaration()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\" "
+ + " permissionGroup=\"permissionGroupOne\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNoUseFeaturesDeclaration"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mILogger);
+ ElementsTrimmer.trim(xmlDocument, mergingReport);
+ assertFalse(mergingReport.hasErrors());
+ Mockito.verifyZeroInteractions(mILogger);
+ assertEquals(0, mergingReport.getActionRecorder().build().getNodeKeys().size());
+ }
+
+
+ public void testNothingToTrim()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature"
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x0002000\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNothingToTrim"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mILogger);
+ ElementsTrimmer.trim(xmlDocument, mergingReport);
+ assertFalse(mergingReport.hasErrors());
+ Mockito.verifyZeroInteractions(mILogger);
+ assertEquals(0, mergingReport.getActionRecorder().build().getNodeKeys().size());
+ }
+
+
+ public void testMultipleAboveTwoResults()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature"
+ + " android:required=\"true\""
+ + " android:glEsVersion=\"0x00020000\"/>\n"
+ + " <uses-feature"
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x00021000\"/>\n"
+ + " <uses-feature"
+ + " android:glEsVersion=\"0x00022000\"/>\n"
+ + " <uses-feature"
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x00030000\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testMultipleAboveTwoResults"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mILogger);
+ ElementsTrimmer.trim(xmlDocument, mergingReport);
+ assertFalse(mergingReport.hasErrors());
+ Mockito.verifyZeroInteractions(mILogger);
+
+ // check action recording.
+ checkActionsRecording(mergingReport, 2);
+
+ // check results.
+ checkResults(xmlDocument, ImmutableList.of("0x00030000", "0x00022000"));
+ }
+
+ public void testSingleAboveTwoResults()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature android:glEsVersion=\"0x00020000\"/>\n"
+ + " <uses-feature android:glEsVersion=\"0x00021000\"/>\n"
+ + " <uses-feature android:glEsVersion=\"0x00030000\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testSingleAboveTwoResults"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mILogger);
+ ElementsTrimmer.trim(xmlDocument, mergingReport);
+ assertFalse(mergingReport.hasErrors());
+ Mockito.verifyZeroInteractions(mILogger);
+
+ // check action recording.
+ checkActionsRecording(mergingReport, 2);
+
+ // check results.
+ checkResults(xmlDocument, ImmutableList.of("0x00030000"));
+ }
+
+ public void testMultipleBelowTwoResults()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature"
+ + " android:required=\"true\""
+ + " android:glEsVersion=\"0x00010000\"/>\n"
+ + " <uses-feature"
+ + " android:glEsVersion=\"0x00011000\"/>\n"
+ + " <uses-feature"
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x00012000\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testMultipleBelowTwoResults"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mILogger);
+ ElementsTrimmer.trim(xmlDocument, mergingReport);
+ assertFalse(mergingReport.hasErrors());
+ Mockito.verifyZeroInteractions(mILogger);
+
+ // check action recording.
+ checkActionsRecording(mergingReport, 1);
+
+ // check results.
+ checkResults(xmlDocument, ImmutableList.of("0x00011000", "0x00012000"));
+ }
+
+ public void testSingleBelowTwoResults()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature android:glEsVersion=\"0x00010000\"/>\n"
+ + " <uses-feature android:glEsVersion=\"0x00011000\"/>\n"
+ + " <uses-feature android:glEsVersion=\"0x00012000\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testSingleBelowTwoResults"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mILogger);
+ ElementsTrimmer.trim(xmlDocument, mergingReport);
+ assertFalse(mergingReport.hasErrors());
+ Mockito.verifyZeroInteractions(mILogger);
+
+ // check action recording.
+ checkActionsRecording(mergingReport, 2);
+
+ // check results.
+ checkResults(xmlDocument, ImmutableList.of("0x00012000"));
+ }
+
+ public void testMultipleAboveAndBelowTwoResults()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature"
+ + " android:required=\"true\""
+ + " android:glEsVersion=\"0x00010000\"/>\n"
+ + " <uses-feature"
+ + " android:glEsVersion=\"0x00011000\"/>\n"
+ + " <uses-feature"
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x00012000\"/>\n"
+ + " <uses-feature"
+ + " android:required=\"true\""
+ + " android:glEsVersion=\"0x00020000\"/>\n"
+ + " <uses-feature"
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x00021000\"/>\n"
+ + " <uses-feature"
+ + " android:glEsVersion=\"0x00022000\"/>\n"
+ + " <uses-feature"
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x00030000\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testMultipleAboveAndBelowTwoResults"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mILogger);
+ ElementsTrimmer.trim(xmlDocument, mergingReport);
+ assertFalse(mergingReport.hasErrors());
+ Mockito.verifyZeroInteractions(mILogger);
+
+ // check action recording.
+ checkActionsRecording(mergingReport, 3);
+
+ // check results.
+ checkResults(xmlDocument,
+ ImmutableList.of("0x00011000", "0x00012000", "0x00022000", "0x00030000"));
+ }
+
+ public void testUsesFeatureSplit_attributeDeleted()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature android:name=\"@string/lib_name\""
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x00020000\"/>\n"
+ + " <uses-feature"
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x00030000\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testUsesFeatureSplit"), input);
+
+ ActionRecorder mockActionRecorder = Mockito.mock(ActionRecorder.class);
+ MergingReport.Builder mockReport = Mockito.mock(MergingReport.Builder.class);
+ when(mockReport.getActionRecorder()).thenReturn(mockActionRecorder);
+ ElementsTrimmer.trim(xmlDocument, mockReport);
+
+ // check that we have now 2 uses-feature with separated keys.
+ NodeList elementsByTagName = xmlDocument.getRootNode().getXml()
+ .getElementsByTagName("uses-feature");
+ assertEquals(2, elementsByTagName.getLength());
+
+ // verify the action was recorded.
+ verify(mockActionRecorder).recordAttributeAction(any(XmlAttribute.class),
+ eq(Actions.ActionType.REJECTED), (AttributeOperationType) eq(null));
+
+ for (int i = 0; i < elementsByTagName.getLength(); i++) {
+ NamedNodeMap attributes = elementsByTagName.item(i).getAttributes();
+ assertEquals(2, attributes.getLength());
+ ensureOnlyOneKey(attributes, ManifestModel.NodeTypes.USES_FEATURE);
+ }
+ }
+
+ public void testUsesFeatureSplit_elementDeleted()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature android:required=\"false\""
+ + " android:glEsVersion=\"0x00020000\"/>\n"
+ + " <uses-feature"
+ + " android:required=\"false\""
+ + " android:glEsVersion=\"0x00030000\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testUsesFeatureSplit"), input);
+
+ ActionRecorder mockActionRecorder = Mockito.mock(ActionRecorder.class);
+ MergingReport.Builder mockReport = Mockito.mock(MergingReport.Builder.class);
+ when(mockReport.getActionRecorder()).thenReturn(mockActionRecorder);
+ ElementsTrimmer.trim(xmlDocument, mockReport);
+
+ // check that we have now 2 uses-feature with separated keys.
+ NodeList elementsByTagName = xmlDocument.getRootNode().getXml()
+ .getElementsByTagName("uses-feature");
+ assertEquals(1, elementsByTagName.getLength());
+
+ // verify the action was recorded.
+ verify(mockActionRecorder).recordNodeAction(any(XmlElement.class),
+ eq(Actions.ActionType.REJECTED));
+
+ for (int i = 0; i < elementsByTagName.getLength(); i++) {
+ NamedNodeMap attributes = elementsByTagName.item(i).getAttributes();
+ assertEquals(2, attributes.getLength());
+ ensureOnlyOneKey(attributes, ManifestModel.NodeTypes.USES_FEATURE);
+ }
+ }
+
+ private void ensureOnlyOneKey(NamedNodeMap namedNodeMap, ManifestModel.NodeTypes nodeType) {
+ String firstKey = null;
+ ImmutableList<String> keyAttributesNames =
+ nodeType.getNodeKeyResolver().getKeyAttributesNames();
+ for (String keyAttributesName : keyAttributesNames) {
+ if (namedNodeMap.getNamedItemNS(SdkConstants. ANDROID_URI, keyAttributesName) != null) {
+ if (firstKey != null) {
+ fail("Found 2 keys : " + firstKey + " and " + keyAttributesName);
+ }
+ firstKey = keyAttributesName;
+ }
+ }
+ }
+
+
+ private static void checkActionsRecording(
+ MergingReport.Builder mergingReport,
+ int expectedActionsNumber) {
+
+ Actions actions = mergingReport.build().getActions();
+ assertEquals(expectedActionsNumber, actions.getNodeKeys().size());
+ for (XmlNode.NodeKey nodeKey : actions.getNodeKeys()) {
+ assertEquals(1, actions.getNodeRecords(nodeKey).size());
+ assertEquals(Actions.ActionType.REJECTED,
+ actions.getNodeRecords(nodeKey).get(0).getActionType());
+ }
+ }
+
+ private static void checkResults(XmlDocument xmlDocument, List<String> expectedVersions) {
+ NodeList elementsByTagName = xmlDocument.getRootNode().getXml()
+ .getElementsByTagName("uses-feature");
+ assertEquals(expectedVersions.size(), elementsByTagName.getLength());
+ for (int i = 0; i < elementsByTagName.getLength(); i++) {
+ Element item = (Element) elementsByTagName.item(i);
+ Attr glEsVersion = item.getAttributeNodeNS(SdkConstants.ANDROID_URI,
+ AndroidManifest.ATTRIBUTE_GLESVERSION);
+ assertNotNull(glEsVersion);
+ assertTrue(expectedVersions.contains(glEsVersion.getValue()));
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2SmallTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2SmallTest.java
new file mode 100644
index 0000000..6f42710
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2SmallTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sdklib.mock.MockLog;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link com.android.manifmerger.ManifestMergerTest} class
+ */
+public class ManifestMerger2SmallTest extends TestCase {
+
+ @Mock
+ ActionRecorder mActionRecorder;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testValidationFailure()
+ throws ParserConfigurationException, SAXException, IOException,
+ ManifestMerger2.MergeFailureException {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:replace=\"exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ File tmpFile = inputAsFile("ManifestMerger2Test_testValidationFailure", input);
+ assertTrue(tmpFile.exists());
+
+ try {
+ MergingReport mergingReport = ManifestMerger2.newMerger(tmpFile, mockLog,
+ ManifestMerger2.MergeType.APPLICATION).merge();
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ // check the log complains about the incorrect "tools:replace"
+ assertStringPresenceInLogRecords(mergingReport, "tools:replace");
+ assertFalse(mergingReport.getMergedDocument().isPresent());
+ } finally {
+ assertTrue(tmpFile.delete());
+ }
+ }
+
+ public void testToolsAnnotationRemoval()
+ throws ParserConfigurationException, SAXException, IOException,
+ ManifestMerger2.MergeFailureException {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" "
+ + " tools:replace=\"label\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ File tmpFile = inputAsFile("testToolsAnnotationRemoval", input);
+ assertTrue(tmpFile.exists());
+
+ try {
+ MergingReport mergingReport = ManifestMerger2.newMerger(tmpFile, mockLog,
+ ManifestMerger2.MergeType.APPLICATION).merge();
+ assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
+ // ensure tools annotation removal.
+ XmlDocument mergedDocument = mergingReport.getMergedDocument().get();
+ Optional<XmlElement> applicationNode = mergedDocument
+ .getByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
+ assertTrue(applicationNode.isPresent());
+ String replaceAttribute = applicationNode.get().getXml().getAttributeNS(
+ SdkConstants.TOOLS_URI, "replace");
+ assertTrue(Strings.isNullOrEmpty(replaceAttribute));
+ System.out.println(mergedDocument.prettyPrint());
+ mergedDocument.prettyPrint();
+ } finally {
+ assertTrue(tmpFile.delete());
+ }
+ }
+
+ public void testToolsAnnotationPresence()
+ throws ParserConfigurationException, SAXException, IOException,
+ ManifestMerger2.MergeFailureException {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" "
+ + " tools:replace=\"label\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ File tmpFile = inputAsFile("testToolsAnnotationRemoval", input);
+ assertTrue(tmpFile.exists());
+
+ try {
+ MergingReport mergingReport = ManifestMerger2.newMerger(tmpFile, mockLog,
+ ManifestMerger2.MergeType.LIBRARY).merge();
+ assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
+ // ensure tools annotation removal.
+ XmlDocument mergedDocument = mergingReport.getMergedDocument().get();
+ Optional<XmlElement> applicationNode = mergedDocument
+ .getByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
+ assertTrue(applicationNode.isPresent());
+ String replaceAttribute = applicationNode.get().getXml().getAttributeNS(
+ SdkConstants.TOOLS_URI, "replace");
+ assertEquals("label", replaceAttribute);
+ System.out.println(mergedDocument.prettyPrint());
+ } finally {
+ assertTrue(tmpFile.delete());
+ }
+ }
+
+
+ public void testPackageOverride()
+ throws ParserConfigurationException, SAXException, IOException {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\""
+ + " package=\"com.foo.old\" >\n"
+ + " <activity android:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testPackageOverride#xml"), xml);
+
+ ManifestMerger2.SystemProperty.PACKAGE.addTo(mActionRecorder, refDocument, "com.bar.new");
+ // verify the package value was overriden.
+ assertEquals("com.bar.new", refDocument.getRootNode().getXml().getAttribute("package"));
+ }
+
+ public void testMissingPackageOverride()
+ throws ParserConfigurationException, SAXException, IOException {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testMissingPackageOverride#xml"), xml);
+
+ ManifestMerger2.SystemProperty.PACKAGE.addTo(mActionRecorder, refDocument, "com.bar.new");
+ // verify the package value was added.
+ assertEquals("com.bar.new", refDocument.getRootNode().getXml().getAttribute("package"));
+ }
+
+ public void testAddingSystemProperties()
+ throws ParserConfigurationException, SAXException, IOException {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument document = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ "testAddingSystemProperties#xml"), xml);
+
+ ManifestMerger2.SystemProperty.VERSION_CODE.addTo(mActionRecorder, document, "101");
+ assertEquals("101",
+ document.getXml().getDocumentElement().getAttribute("android:versionCode"));
+
+ ManifestMerger2.SystemProperty.VERSION_NAME.addTo(mActionRecorder, document, "1.0.1");
+ assertEquals("1.0.1",
+ document.getXml().getDocumentElement().getAttribute("android:versionName"));
+
+ ManifestMerger2.SystemProperty.MIN_SDK_VERSION.addTo(mActionRecorder, document, "10");
+ Element usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("10", usesSdk.getAttribute("android:minSdkVersion"));
+
+ ManifestMerger2.SystemProperty.TARGET_SDK_VERSION.addTo(mActionRecorder, document, "14");
+ usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("14", usesSdk.getAttribute("android:targetSdkVersion"));
+ }
+
+ public void testAddingSystemProperties_withDifferentPrefix()
+ throws ParserConfigurationException, SAXException, IOException {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity t:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument document = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ "testAddingSystemProperties#xml"), xml
+ );
+
+ ManifestMerger2.SystemProperty.VERSION_CODE.addTo(mActionRecorder, document, "101");
+ // using the non namespace aware API to make sure the prefix is the expected one.
+ assertEquals("101",
+ document.getXml().getDocumentElement().getAttribute("t:versionCode"));
+ }
+
+ public void testOverridingSystemProperties()
+ throws ParserConfigurationException, SAXException, IOException {
+ String xml = ""
+ + "<manifest versionCode=\"34\" versionName=\"3.4\"\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <uses-sdk minSdkVersion=\"9\" targetSdkVersion=\".9\"/>\n"
+ + " <activity android:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument document = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),
+ "testAddingSystemProperties#xml"), xml);
+ // check initial state.
+ assertEquals("34", document.getXml().getDocumentElement().getAttribute("versionCode"));
+ assertEquals("3.4", document.getXml().getDocumentElement().getAttribute("versionName"));
+ Element usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("9", usesSdk.getAttribute("minSdkVersion"));
+ assertEquals(".9", usesSdk.getAttribute("targetSdkVersion"));
+
+
+ ManifestMerger2.SystemProperty.VERSION_CODE.addTo(mActionRecorder, document, "101");
+ assertEquals("101",
+ document.getXml().getDocumentElement().getAttribute("android:versionCode"));
+
+ ManifestMerger2.SystemProperty.VERSION_NAME.addTo(mActionRecorder, document, "1.0.1");
+ assertEquals("1.0.1",
+ document.getXml().getDocumentElement().getAttribute("android:versionName"));
+
+ ManifestMerger2.SystemProperty.MIN_SDK_VERSION.addTo(mActionRecorder, document, "10");
+ usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("10", usesSdk.getAttribute("android:minSdkVersion"));
+
+ ManifestMerger2.SystemProperty.TARGET_SDK_VERSION.addTo(mActionRecorder, document, "14");
+ usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("14", usesSdk.getAttribute("android:targetSdkVersion"));
+ }
+
+ public void testPlaceholderSubstitution()
+ throws ParserConfigurationException, SAXException, IOException,
+ ManifestMerger2.MergeFailureException {
+ String xml = ""
+ + "<manifest package=\"foo\" versionCode=\"34\" versionName=\"3.4\"\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\".activityOne\" android:label=\"${labelName}\"/>\n"
+ + "</manifest>";
+
+ Map<String, String> placeholders = ImmutableMap.of("labelName", "injectedLabelName");
+ MockLog mockLog = new MockLog();
+ File inputFile = inputAsFile("testPlaceholderSubstitution", xml);
+ try {
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .setPlaceHolderValues(placeholders)
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ assertTrue(mergingReport.getMergedDocument().isPresent());
+ XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
+ Optional<XmlElement> activityOne = xmlDocument
+ .getByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "foo.activityOne");
+ assertTrue(activityOne.isPresent());
+ Optional<XmlAttribute> attribute = activityOne.get()
+ .getAttribute(XmlNode.fromXmlName("android:label"));
+ assertTrue(attribute.isPresent());
+ assertEquals("injectedLabelName", attribute.get().getValue());
+ } finally {
+ inputFile.delete();
+ }
+ }
+
+ public void testApplicationIdSubstitution()
+ throws ManifestMerger2.MergeFailureException, IOException {
+ String xml = ""
+ + "<manifest package=\"foo\" versionCode=\"34\" versionName=\"3.4\"\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"${applicationId}.activityOne\"/>\n"
+ + "</manifest>";
+
+ MockLog mockLog = new MockLog();
+ File inputFile = inputAsFile("testPlaceholderSubstitution", xml);
+ try {
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .setOverride(ManifestMerger2.SystemProperty.PACKAGE, "bar")
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ assertTrue(mergingReport.getMergedDocument().isPresent());
+ XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
+ assertEquals("bar", xmlDocument.getPackageName());
+ Optional<XmlElement> activityOne = xmlDocument
+ .getByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "bar.activityOne");
+ assertTrue(activityOne.isPresent());
+ } finally {
+ inputFile.delete();
+ }
+ }
+
+ /**
+ * Utility method to save a {@link String} XML into a file.
+ */
+ private static File inputAsFile(String testName, String input) throws IOException {
+ File tmpFile = File.createTempFile(testName, ".xml");
+ FileWriter fw = null;
+ try {
+ fw = new FileWriter(tmpFile);
+ fw.append(input);
+ } finally {
+ if (fw != null) fw.close();
+ }
+ return tmpFile;
+ }
+
+ private static void assertStringPresenceInLogRecords(MergingReport mergingReport, String s) {
+ for (MergingReport.Record record : mergingReport.getLoggingRecords()) {
+ if (record.toString().contains(s)) {
+ return;
+ }
+ }
+ // failed, dump the records
+ for (MergingReport.Record record : mergingReport.getLoggingRecords()) {
+ Logger.getAnonymousLogger().info(record.toString());
+ }
+ fail("could not find " + s + " in logging records");
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2Test.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2Test.java
new file mode 100644
index 0000000..3b067b0
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2Test.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.ManifestMerger2.SystemProperty;
+import static com.android.manifmerger.MergingReport.Record;
+
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.android.utils.StdLogger;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * Tests for the {@link com.android.manifmerger.ManifestMerger2} class
+ */
+public class ManifestMerger2Test extends ManifestMergerTest {
+
+ // so far, I only support 3 original tests.
+ private static String[] sDataFiles = new String[]{
+ "00_noop",
+ "03_inject_attributes.xml",
+ "05_inject_package.xml",
+ "05_inject_package_placeholder.xml",
+ "06_inject_attributes_with_specific_prefix.xml",
+ "07_no_package_provided.xml",
+ "08_no_library_package_provided.xml",
+ "10_activity_merge",
+ "11_activity_dup",
+ "12_alias_dup",
+ "13_service_dup",
+ "14_receiver_dup",
+ "15_provider_dup",
+ "16_fqcn_merge",
+ "17_fqcn_conflict",
+ "18_fqcn_success",
+ "20_uses_lib_merge",
+ "21_uses_main_errors",
+ "22_uses_lib_errors",
+ "25_permission_merge",
+ "26_permission_dup",
+ "28_uses_perm_merge",
+ "29_uses_perm_selector",
+ "29b_uses_perm_invalidSelector",
+ "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",
+ "78_removeAll",
+ "79_custom_node.xml",
+ };
+
+ @Override
+ protected String getTestDataDirectory() {
+ return "data2";
+ }
+
+ /**
+ * 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 junit.framework.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(ManifestMerger2Test.class, fileName));
+ }
+
+ return suite;
+ }
+
+ public ManifestMerger2Test(String testName) {
+ super(testName);
+ }
+
+ /**
+ * Processes the data from the given
+ * {@link com.android.manifmerger.ManifestMergerTest.TestFiles} by invoking {@link
+ * ManifestMerger#process(java.io.File, java.io.File, java.io.File[], java.util.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.
+ */
+ @Override
+ void processTestFiles(TestFiles testFiles) throws Exception {
+
+ StdLogger stdLogger = new StdLogger(StdLogger.Level.VERBOSE);
+ ManifestMerger2.Invoker invoker = ManifestMerger2.newMerger(testFiles.getMain(),
+ stdLogger, ManifestMerger2.MergeType.APPLICATION)
+ .addLibraryManifests(testFiles.getLibs())
+ .withFeatures(ManifestMerger2.Invoker.Feature.KEEP_INTERMEDIARY_STAGES);
+
+ if (!Strings.isNullOrEmpty(testFiles.getPackageOverride())) {
+ invoker.setOverride(SystemProperty.PACKAGE, testFiles.getPackageOverride());
+ }
+
+ for (Map.Entry<String, String> injectable : testFiles.getInjectAttributes().entrySet()) {
+ SystemProperty systemProperty = getSystemProperty(injectable.getKey());
+ if (systemProperty != null) {
+ invoker.setOverride(systemProperty, injectable.getValue());
+ } else {
+ invoker.setPlaceHolderValue(injectable.getKey(), injectable.getValue());
+ }
+ }
+
+ MergingReport mergeReport = invoker.merge();
+
+
+ // this is obviously quite hacky, refine once merge output is better defined.
+ boolean notExpectingError = !isExpectingError(testFiles.getExpectedErrors());
+ mergeReport.log(stdLogger);
+ if (mergeReport.getMergedDocument().isPresent()) {
+
+ XmlDocument actualResult = mergeReport.getMergedDocument().get();
+ String prettyResult = actualResult.prettyPrint();
+ stdLogger.info(prettyResult);
+
+ if (testFiles.getActualResult() != null) {
+ FileWriter writer = new FileWriter(testFiles.getActualResult());
+ try {
+ writer.append(prettyResult);
+ } finally {
+ writer.close();
+ }
+ }
+
+ if (!notExpectingError) {
+ fail("Did not get expected error : " + testFiles.getExpectedErrors());
+ }
+
+ XmlDocument expectedResult = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), testFiles.getMain().getName()),
+ testFiles.getExpectedResult());
+ Optional<String> comparingMessage =
+ expectedResult.compareTo(actualResult);
+
+ if (comparingMessage.isPresent()) {
+ Logger.getAnonymousLogger().severe(comparingMessage.get());
+ fail(comparingMessage.get());
+ }
+ // process any warnings.
+ if (mergeReport.getResult() == MergingReport.Result.WARNING) {
+ compareExpectedAndActualErrors(mergeReport, testFiles.getExpectedErrors());
+ }
+ } else {
+ for (Record record : mergeReport.getLoggingRecords()) {
+ Logger.getAnonymousLogger().info("Returned log: " + record);
+ }
+ compareExpectedAndActualErrors(mergeReport, testFiles.getExpectedErrors());
+ assertFalse(notExpectingError);
+ }
+ }
+
+ private boolean isExpectingError(String expectedOutput) throws IOException {
+ StringReader stringReader = new StringReader(expectedOutput);
+ BufferedReader reader = new BufferedReader(stringReader);
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("ERROR")) return true;
+ }
+ return false;
+ }
+
+ private void compareExpectedAndActualErrors(
+ MergingReport mergeReport,
+ String expectedOutput) throws IOException {
+
+ StringReader stringReader = new StringReader(expectedOutput);
+ BufferedReader reader = new BufferedReader(stringReader);
+ String line = reader.readLine();
+ List<Record> records = new ArrayList<Record>(mergeReport.getLoggingRecords());
+ while (line != null) {
+ if (line.startsWith("WARNING") || line.startsWith("ERROR")) {
+ String message = line;
+ do {
+ line = reader.readLine();
+ if (line != null && line.startsWith(" ")) {
+ message = message + "\n" + line;
+ }
+ } while (line != null && line.startsWith(" "));
+
+ // next might generate an exception which will make the test fail when we
+ // get unexpected error message.
+ if (!findLineInRecords(message, records)) {
+
+ StringBuilder errorMessage = new StringBuilder();
+ dumpRecords(records, errorMessage);
+ errorMessage.append("Cannot find expected error : \n").append(message);
+ fail(errorMessage.toString());
+ }
+ }
+ }
+ // check that we do not have any unexpected error messages.
+ if (!records.isEmpty()) {
+ StringBuilder message = new StringBuilder();
+ dumpRecords(records, message);
+ message.append("Unexpected error message(s)");
+ fail(message.toString());
+ }
+ }
+
+ private boolean findLineInRecords(String errorLine, List<Record> records) {
+ String severity = errorLine.substring(0, errorLine.indexOf(':'));
+ String message = errorLine.substring(errorLine.indexOf(':') + 1);
+ for (Record record : records) {
+ int indexOfSuggestions = record.getMessage().indexOf("\n\tSuggestion:");
+ String messageRecord = indexOfSuggestions != -1
+ ? record.getMessage().substring(0, indexOfSuggestions)
+ : record.getMessage();
+ if (messageRecord.replaceAll("\t", " ").equals(message)
+ && record.getSeverity() == Record.Severity.valueOf(severity)) {
+ records.remove(record);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private SystemProperty getSystemProperty(String name) {
+ for (SystemProperty systemProperty : SystemProperty.values()) {
+ if (systemProperty.toCamelCase().equals(name)) {
+ return systemProperty;
+ }
+ }
+ return null;
+ }
+
+ private void dumpRecords(List<Record> records, StringBuilder stringBuilder) {
+ stringBuilder.append("\n------------ Records : \n");
+ for (Record record : records) {
+ stringBuilder.append(record.toString());
+ stringBuilder.append("\n");
+ }
+ stringBuilder.append("------------ End of records.\n");
+ }
+}
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
index bf2de43..5a77ec0 100755
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2013 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.
@@ -16,10 +16,12 @@
package com.android.manifmerger;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.manifmerger.IMergerLog.FileAndLine;
import com.android.sdklib.mock.MockLog;
+import junit.framework.ComparisonFailure;
import junit.framework.TestCase;
import org.w3c.dom.Document;
@@ -169,7 +171,7 @@
boolean ok = merger.process(mainDoc, library1, library2, library3);
assertTrue(ok);
String actual = MergerXmlUtils.printXmlString(mainDoc, mergerLog);
- assertEquals("Encountered unexpected errors/warnings", "[]", log.toString());
+ 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"
@@ -228,15 +230,29 @@
+ "\n"
+ "</manifest>\n";
- if (!expected.equals(actual)) {
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ // Adjust mock paths & EOLs for windows
+ actual = actual.replace("\r\n", "\n");
+ expected = expected.replace("file:/path/to/", "file:/C:/path/to/");
+ }
+
+ try {
+ assertEquals(expected, actual);
+
+ } catch (Exception originalFailure) {
// 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);
+ try {
+ assertEquals(expected, actual);
+ } catch (Throwable ignore) {
+ // If the second test fails too, throw the *original* exception,
+ // before we tried to tweak the EOL.
+ throw originalFailure;
+ }
}
-
- 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
index a8473b2..e5f6f6f 100755
--- 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
@@ -42,6 +42,8 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Some utilities to reduce repetitions in the {@link ManifestMergerTest}s.
@@ -217,7 +219,7 @@
@NonNull Map<String, Boolean> features,
@NonNull Map<String, String> injectAttributes,
@Nullable String packageOverride,
- @NonNull File actualResult,
+ @Nullable File actualResult,
@NonNull String expectedResult,
@NonNull String expectedErrors) {
mShouldFail = shouldFail;
@@ -260,7 +262,7 @@
return mPackageOverride;
}
- @NonNull
+ @Nullable
public File getActualResult() {
return mActualResult;
}
@@ -321,6 +323,13 @@
}
/**
+ * Returns the relative path the test data directory
+ */
+ protected String getTestDataDirectory() {
+ return "data";
+ }
+
+ /**
* 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.
@@ -345,7 +354,7 @@
@NonNull
TestFiles loadTestData(@NonNull String filename) throws Exception {
- String resName = "data" + File.separator + filename;
+ String resName = getTestDataDirectory() + File.separator + filename;
InputStream is = null;
BufferedReader reader = null;
BufferedWriter writer = null;
@@ -489,10 +498,8 @@
}
assertNotNull("Missing @" + DELIM_MAIN + " in " + filename, mainFile);
- assertNotNull("Missing @" + DELIM_RESULT + " in " + filename, actualResultFile);
assert mainFile != null;
- assert actualResultFile != null;
Collections.sort(libFiles);
@@ -585,10 +592,13 @@
// Convert relative pathnames to absolute.
String expectedErrors = testFiles.getExpectedErrors().trim();
- expectedErrors = expectedErrors.replaceAll(testFiles.getMain().getName(),
- testFiles.getMain().getAbsolutePath());
+ expectedErrors = expectedErrors.replaceAll(
+ Pattern.quote(testFiles.getMain().getName()),
+ Matcher.quoteReplacement(testFiles.getMain().getAbsolutePath()));
for (File file : testFiles.getLibs()) {
- expectedErrors = expectedErrors.replaceAll(file.getName(), file.getAbsolutePath());
+ expectedErrors = expectedErrors.replaceAll(
+ Pattern.quote(file.getName()),
+ Matcher.quoteReplacement(file.getAbsolutePath()));
}
StringBuilder actualErrors = new StringBuilder();
@@ -615,7 +625,7 @@
assertNotNull(document);
assert document != null; // for Eclipse null analysis
String actual = MergerXmlUtils.printXmlString(document, mergerLog);
- assertEquals("Error parsing actual result XML", "[]", log.toString());
+ assertEquals("Error parsing actual result XML", "", log.toString());
log.clear();
document = MergerXmlUtils.parseDocument(
testFiles.getExpectedResult(),
@@ -624,7 +634,7 @@
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 parsing expected result XML", "", log.toString());
assertEquals("Error comparing expected to actual result", expected, actual);
testFiles.cleanup();
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestModelTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestModelTest.java
new file mode 100644
index 0000000..8705254ad
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestModelTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.XmlNode.NodeKey;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.android.sdklib.mock.MockLog;
+import com.android.xml.AndroidManifest;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link com.android.manifmerger.ManifestModel} class.
+ */
+public class ManifestModelTest extends TestCase {
+
+ public void testNameResolution()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature android:name=\"camera\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNoUseFeaturesDeclaration"), input);
+
+ XmlElement xmlElement = xmlDocument.getRootNode().getMergeableElements().get(0);
+ assertEquals("uses-feature",xmlElement.getXml().getNodeName());
+ assertEquals("camera", xmlElement.getKey());
+ }
+
+ public void testGlEsKeyResolution()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature android:glEsVersion=\"0x00030000\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNoUseFeaturesDeclaration"), input);
+
+ XmlElement xmlElement = xmlDocument.getRootNode().getMergeableElements().get(0);
+ assertEquals("uses-feature",xmlElement.getXml().getNodeName());
+ assertEquals("0x00030000", xmlElement.getKey());
+ }
+
+
+ public void testInvalidGlEsVersion()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ AttributeModel.Hexadecimal32Bits validator =
+ new AttributeModel.Hexadecimal32Bits();
+ XmlAttribute xmlAttribute = Mockito.mock(XmlAttribute.class);
+ MergingReport.Builder mergingReport = Mockito.mock(MergingReport.Builder.class);
+ when(xmlAttribute.getId()).thenReturn(new NodeKey(AndroidManifest.ATTRIBUTE_GLESVERSION));
+
+ ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
+ Mockito.doNothing().when(xmlAttribute).addMessage(
+ Mockito.any(MergingReport.Builder.class),
+ eq(MergingReport.Record.Severity.ERROR),
+ argumentCaptor.capture());
+ when(xmlAttribute.printPosition()).thenReturn("unknown");
+ assertFalse(validator.validates(mergingReport, xmlAttribute, "0xFFFFFFFFFFFF"));
+ assertEquals("Attribute glEsVersion at unknown is not a valid hexadecimal "
+ + "32 bit value, found 0xFFFFFFFFFFFF",
+ argumentCaptor.getValue());
+ }
+
+ public void testTooLowGlEsVersion()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ AttributeModel.Hexadecimal32BitsWithMinimumValue validator =
+ new AttributeModel.Hexadecimal32BitsWithMinimumValue(0x00010000);
+ XmlAttribute xmlAttribute = Mockito.mock(XmlAttribute.class);
+ MergingReport.Builder mergingReport = Mockito.mock(MergingReport.Builder.class);
+
+ ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
+ Mockito.doNothing().when(xmlAttribute).addMessage(
+ Mockito.any(MergingReport.Builder.class),
+ eq(MergingReport.Record.Severity.ERROR),
+ argumentCaptor.capture());
+
+ when(xmlAttribute.getId()).thenReturn(new NodeKey(AndroidManifest.ATTRIBUTE_GLESVERSION));
+ when(xmlAttribute.printPosition()).thenReturn("unknown");
+ assertFalse(validator.validates(mergingReport, xmlAttribute, "0xFFF"));
+ assertEquals("Attribute glEsVersion at unknown is not a valid hexadecimal value, "
+ + "minimum is 0x00010000, maximum is 0x7FFFFFFF, found 0xFFF",
+ argumentCaptor.getValue());
+ }
+
+ public void testOkGlEsVersion()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ AttributeModel.Hexadecimal32BitsWithMinimumValue validator =
+ new AttributeModel.Hexadecimal32BitsWithMinimumValue(0x00010000);
+ XmlAttribute xmlAttribute = Mockito.mock(XmlAttribute.class);
+ MergingReport.Builder mergingReport = Mockito.mock(MergingReport.Builder.class);
+
+ when(xmlAttribute.getId()).thenReturn(new NodeKey(AndroidManifest.ATTRIBUTE_GLESVERSION));
+ when(xmlAttribute.printPosition()).thenReturn("unknown");
+ assertTrue(validator.validates(mergingReport, xmlAttribute, "0x00020001"));
+ verifyNoMoreInteractions(xmlAttribute);
+ }
+
+ public void testTooBigGlEsVersion()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ AttributeModel.Hexadecimal32BitsWithMinimumValue validator =
+ new AttributeModel.Hexadecimal32BitsWithMinimumValue(0x00010000);
+ XmlAttribute xmlAttribute = Mockito.mock(XmlAttribute.class);
+ MergingReport.Builder mergingReport = Mockito.mock(MergingReport.Builder.class);
+
+ ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
+ Mockito.doNothing().when(xmlAttribute).addMessage(
+ Mockito.any(MergingReport.Builder.class),
+ eq(MergingReport.Record.Severity.ERROR),
+ argumentCaptor.capture());
+
+ when(xmlAttribute.getId()).thenReturn(new NodeKey(AndroidManifest.ATTRIBUTE_GLESVERSION));
+ when(xmlAttribute.printPosition()).thenReturn("unknown");
+ assertFalse(validator.validates(mergingReport, xmlAttribute, "0xFFFFFFFF"));
+ assertEquals("Attribute glEsVersion at unknown is not a valid hexadecimal value,"
+ + " minimum is 0x00010000, maximum is 0x7FFFFFFF, found 0xFFFFFFFF",
+ argumentCaptor.getValue());
+ }
+
+ public void testNoKeyResolution()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature android:required=\"false\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNoUseFeaturesDeclaration"), input);
+
+ XmlElement xmlElement = xmlDocument.getRootNode().getMergeableElements().get(0);
+ assertEquals("uses-feature",xmlElement.getXml().getNodeName());
+ assertNull(xmlElement.getKey());
+ }
+
+ public void testTwoAttributesKeyResolution()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <compatible-screens>\n"
+ + " <screen/>\n"
+ + " <screen android:screenDensity=\"mdpi\"/>\n"
+ + " <screen android:screenSize=\"normal\"/>\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"mdpi\"/>\n"
+ + " </compatible-screens>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNoUseFeaturesDeclaration"), input);
+
+ XmlElement xmlElement = xmlDocument.getRootNode().getMergeableElements().get(0);
+ ImmutableList<XmlElement> screenDefinitions = xmlElement.getMergeableElements();
+ assertNull(screenDefinitions.get(0).getKey());
+ assertEquals("mdpi", screenDefinitions.get(1).getKey());
+ assertEquals("normal", screenDefinitions.get(2).getKey());
+ assertEquals("normal+mdpi", screenDefinitions.get(3).getKey());
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergingReportTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergingReportTest.java
new file mode 100644
index 0000000..9758fda
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergingReportTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.MergingReport.Record.Severity;
+import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
+import com.android.utils.ILogger;
+import com.android.utils.PositionXmlParser;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.w3c.dom.Element;
+
+/**
+ * Tests for the {@link com.android.manifmerger.MergingReport} class
+ */
+public class MergingReportTest extends TestCase {
+
+ @Mock ILogger mLoggerMock;
+ @Mock Element mElement;
+ @Mock XmlLoader.SourceLocation mSourceLocation;
+ @Mock KeyResolver<String> mKeyResolver;
+ @Mock KeyBasedValueResolver<ManifestMerger2.SystemProperty> mPropertyResolver;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testJustError() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.ERROR,"Something bad happened")
+ .build();
+
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ }
+
+ public void testJustWarning() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.WARNING, "Something weird happened")
+ .build();
+
+ assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
+ }
+
+ public void testJustInfo() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
+ .build();
+
+ assertEquals(MergingReport.Result.SUCCESS, mergingReport.getResult());
+ }
+
+
+ public void testJustInfoAndWarning() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
+ .addMessage(mSourceLocation,0, 0, Severity.WARNING, "Something weird happened")
+ .build();
+
+ assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
+ }
+
+ public void testJustInfoAndError() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
+ .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
+ .build();
+
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ }
+
+ public void testJustWarningAndError() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.WARNING, "something weird happened")
+ .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
+ .build();
+
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ }
+ public void testAllTypes() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
+ .addMessage(mSourceLocation,0, 0, Severity.WARNING, "something weird happened")
+ .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
+ .build();
+
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ }
+
+ public void testLogging() {
+ when(mSourceLocation.print(any(boolean.class))).thenReturn("location");
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
+ .addMessage(mSourceLocation,0, 0, Severity.WARNING, "something weird happened")
+ .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
+ .build();
+
+ mergingReport.log(mLoggerMock);
+ Mockito.verify(mLoggerMock).verbose("location:0:0 Info:\n\tmerging info");
+ Mockito.verify(mLoggerMock).warning("location:0:0 Warning:\n\tsomething weird happened");
+ Mockito.verify(mLoggerMock).error(null /* throwable */,
+ "location:0:0 Error:\n\tsomething bad happened");
+ Mockito.verify(mLoggerMock).verbose(Actions.HEADER);
+ Mockito.verifyNoMoreInteractions(mLoggerMock);
+ }
+
+ public void testItermediaryMerges() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMergingStage("<first/>")
+ .addMergingStage("<second/>")
+ .addMergingStage("<third/>")
+ .build();
+
+ ImmutableList<String> intermediaryStages = mergingReport.getIntermediaryStages();
+ assertEquals(3, intermediaryStages.size());
+ assertEquals("<first/>", intermediaryStages.get(0));
+ assertEquals("<second/>", intermediaryStages.get(1));
+ assertEquals("<third/>", intermediaryStages.get(2));
+ }
+
+ public void testGetMergedDocument() {
+ XmlDocument xmlDocument =
+ new XmlDocument(new PositionXmlParser(),
+ mSourceLocation,
+ mKeyResolver,
+ mPropertyResolver,
+ mElement,
+ XmlDocument.Type.MAIN,
+ Optional.<String>absent() /* mainManifestPackageName */);
+
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .setMergedDocument(xmlDocument)
+ .build();
+
+ assertTrue(mergingReport.getMergedDocument().isPresent());
+ assertEquals(xmlDocument, mergingReport.getMergedDocument().get());
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/PlaceholderHandlerTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PlaceholderHandlerTest.java
new file mode 100644
index 0000000..1a4735e
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PlaceholderHandlerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.mock.MockLog;
+import com.google.common.base.Optional;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Source;
+
+/**
+ * Tests for the {@link com.android.manifmerger.PlaceholderHandler}
+ */
+public class PlaceholderHandlerTest extends TestCase {
+
+ @Mock
+ ActionRecorder mActionRecorder;
+
+ @Mock
+ MergingReport.Builder mBuilder;
+
+ MockLog mMockLog = new MockLog();
+
+ KeyBasedValueResolver<String> nullResolver = new KeyBasedValueResolver<String>() {
+ @Override
+ public String getValue(@NonNull String key) {
+ // not provided a placeholder value should generate an error.
+ return null;
+ }
+ };
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ when(mBuilder.getLogger()).thenReturn(mMockLog);
+ when(mBuilder.getActionRecorder()).thenReturn(mActionRecorder);
+ }
+
+ public void testPlaceholders() throws ParserConfigurationException, SAXException, IOException {
+
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:attr1=\"${landscapePH}\"\n"
+ + " android:attr2=\"prefix.${landscapePH}\"\n"
+ + " android:attr3=\"${landscapePH}.suffix\"\n"
+ + " android:attr4=\"prefix${landscapePH}suffix\">\n"
+ + " </activity>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testPlaceholders#xml"), xml);
+
+ PlaceholderHandler handler = new PlaceholderHandler();
+ handler.visit(refDocument, new KeyBasedValueResolver<String>() {
+ @Override
+ public String getValue(@NonNull String key) {
+ return "newValue";
+ }
+ }, mBuilder);
+
+ Optional<XmlElement> activityOne = refDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, ".activityOne");
+ assertTrue(activityOne.isPresent());
+ assertEquals(5, activityOne.get().getAttributes().size());
+ // check substitution.
+ assertEquals("newValue",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr1")).get().getValue());
+ assertEquals("prefix.newValue",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr2")).get().getValue());
+ assertEquals("newValue.suffix",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr3")).get().getValue());
+ assertEquals("prefixnewValuesuffix",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr4")).get().getValue());
+
+ for (XmlAttribute xmlAttribute : activityOne.get().getAttributes()) {
+ // any attribute other than android:name should have been injected.
+ if (!xmlAttribute.getName().toString().contains("name")) {
+ verify(mActionRecorder).recordAttributeAction(
+ xmlAttribute,
+ PositionImpl.UNKNOWN,
+ Actions.ActionType.INJECTED,
+ null);
+ }
+ }
+ }
+
+ public void testPlaceHolder_notProvided()
+ throws ParserConfigurationException, SAXException, IOException {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:attr1=\"${landscapePH}\"/>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testPlaceholders#xml"), xml);
+
+ PlaceholderHandler handler = new PlaceholderHandler();
+ handler.visit(refDocument, nullResolver, mBuilder);
+ // verify the error was recorded.
+ verify(mBuilder).addMessage(
+ any(XmlLoader.SourceLocation.class), anyInt(), anyInt(),
+ eq(MergingReport.Record.Severity.ERROR), anyString());
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/PostValidatorTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PostValidatorTest.java
new file mode 100644
index 0000000..0551651
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PostValidatorTest.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.utils.ILogger;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link com.android.manifmerger.PostValidator} class.
+ */
+public class PostValidatorTest extends TestCase {
+
+ @Mock
+ ILogger mILogger;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testIncorrectRemove()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" tools:remove=\"exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"/>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testIncorrectRemoveMain"), main);
+
+ XmlDocument libraryDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testIncorrectRemoveLib"), library);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ PostValidator.validate(mainDocument, mergingReportBuilder);
+ for (MergingReport.Record record : mergingReportBuilder.build().getLoggingRecords()) {
+ if (record.getSeverity() == MergingReport.Record.Severity.WARNING
+ && record.toString().contains("PostValidatorTest#testIncorrectRemoveMain:8")) {
+ return;
+ }
+ }
+ fail("No reference to faulty PostValidatorTest#testIncorrectRemoveMain:8 found");
+ }
+
+ public void testIncorrectReplace()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"false\""
+ + " tools:replace=\"exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"/>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testIncorrectReplaceMain"), main);
+
+ XmlDocument libraryDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testIncorrectReplaceLib"), library);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ PostValidator.validate(mainDocument, mergingReportBuilder);
+ for (MergingReport.Record record : mergingReportBuilder.build().getLoggingRecords()) {
+ if (record.getSeverity() == MergingReport.Record.Severity.WARNING
+ && record.toString().contains("PostValidatorTest#testIncorrectReplaceMain:8")) {
+ return;
+ }
+ }
+ fail("No reference to faulty PostValidatorTest#testIncorrectRemoveMain:8 found");
+ }
+
+ public void testApplicationInvalidOrder()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"/>"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <uses-sdk minSdkVersion=\"14\"/>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testApplicationInvalidOrder"), input);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ PostValidator.validate(xmlDocument, mergingReportBuilder);
+ // ensure application element is last.
+ Node lastChild = xmlDocument.getRootNode().getXml().getLastChild();
+ while(lastChild.getNodeType() != Node.ELEMENT_NODE) {
+ lastChild = lastChild.getPreviousSibling();
+ }
+ OrphanXmlElement xmlElement = new OrphanXmlElement((Element) lastChild);
+ assertEquals(ManifestModel.NodeTypes.APPLICATION, xmlElement.getType());
+ }
+
+ public void testApplicationInvalidOrder_withComments()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"/>"
+ + "\n"
+ + " <!-- with comments ! -->"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <uses-sdk minSdkVersion=\"14\"/>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testApplicationInvalidOrder"), input);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ PostValidator.validate(xmlDocument, mergingReportBuilder);
+ System.out.println(xmlDocument.prettyPrint());
+ // ensure application element is last.
+ Node lastChild = xmlDocument.getRootNode().getXml().getLastChild();
+ while(lastChild.getNodeType() != Node.ELEMENT_NODE) {
+ lastChild = lastChild.getPreviousSibling();
+ }
+ OrphanXmlElement xmlElement = new OrphanXmlElement((Element) lastChild);
+ assertEquals(ManifestModel.NodeTypes.APPLICATION, xmlElement.getType());
+ // check the comment was also moved.
+ assertEquals(Node.COMMENT_NODE, lastChild.getPreviousSibling().getNodeType());
+ }
+
+ public void testApplicationValidOrder()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"/>"
+ + "\n"
+ + " <uses-sdk minSdkVersion=\"14\"/>"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testApplicationValidOrder"), input);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ PostValidator.validate(xmlDocument, mergingReportBuilder);
+ // ensure application element is last.
+ Node lastChild = xmlDocument.getRootNode().getXml().getLastChild();
+ while(lastChild.getNodeType() != Node.ELEMENT_NODE) {
+ lastChild = lastChild.getPreviousSibling();
+ }
+ OrphanXmlElement xmlElement = new OrphanXmlElement((Element) lastChild);
+ assertEquals(ManifestModel.NodeTypes.APPLICATION, xmlElement.getType());
+ }
+
+ public void testUsesSdkInvalidOrder()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"/>"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <uses-sdk minSdkVersion=\"14\"/>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testUsesSdkInvalidOrder"), input);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ PostValidator.validate(xmlDocument, mergingReportBuilder);
+ // ensure uses-sdk element is first.
+ Node firstChild = xmlDocument.getRootNode().getXml().getFirstChild();
+ while(firstChild.getNodeType() != Node.ELEMENT_NODE) {
+ firstChild = firstChild.getNextSibling();
+ }
+ OrphanXmlElement xmlElement = new OrphanXmlElement((Element) firstChild);
+ assertEquals(ManifestModel.NodeTypes.USES_SDK, xmlElement.getType());
+ }
+
+ public void testUsesSdkInvalidOrder_withComments()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"/>"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <!-- with comments ! -->"
+ + " <uses-sdk minSdkVersion=\"14\"/>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testUsesSdkInvalidOrder"), input);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ PostValidator.validate(xmlDocument, mergingReportBuilder);
+ System.out.println(xmlDocument.prettyPrint());
+ // ensure uses-sdk element is first.
+ Node firstChild = xmlDocument.getRootNode().getXml().getFirstChild();
+ while(firstChild.getNodeType() != Node.ELEMENT_NODE) {
+ firstChild = firstChild.getNextSibling();
+ }
+ OrphanXmlElement xmlElement = new OrphanXmlElement((Element) firstChild);
+ assertEquals(ManifestModel.NodeTypes.USES_SDK, xmlElement.getType());
+ // check the comment was also moved.
+ assertEquals(Node.COMMENT_NODE, firstChild.getPreviousSibling().getNodeType());
+ }
+
+ public void testUsesSdkValidOrder()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-sdk minSdkVersion=\"14\"/>"
+ + "\n"
+ + " <activity android:name=\"activityOne\"/>"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testUsesSdkValidOrder"), input);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ PostValidator.validate(xmlDocument, mergingReportBuilder);
+ // ensure uses-sdk element is first.
+ Node firstChild = xmlDocument.getRootNode().getXml().getFirstChild();
+ while(firstChild.getNodeType() != Node.ELEMENT_NODE) {
+ firstChild = firstChild.getNextSibling();
+ }
+ OrphanXmlElement xmlElement = new OrphanXmlElement((Element) firstChild);
+ assertEquals(ManifestModel.NodeTypes.USES_SDK, xmlElement.getType());
+ }
+
+ public void testAndroidNamespacePresence()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-sdk minSdkVersion=\"14\"/>"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testApplicationInvalidOrder"), input);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ PostValidator.validate(xmlDocument, mergingReportBuilder);
+ // ensure application element is last.
+ String attribute = xmlDocument.getRootNode().getXml().getAttribute("xmlns:android");
+ assertEquals(SdkConstants.ANDROID_URI, attribute);
+ }
+
+ public void testAndroidNamespacePresence_differentPrefix()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:A=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-sdk A:minSdkVersion=\"14\"/>"
+ + "\n"
+ + " <application A:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testApplicationInvalidOrder"), input);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ PostValidator.validate(xmlDocument, mergingReportBuilder);
+ // ensure application element is last.
+ String attribute = xmlDocument.getRootNode().getXml().getAttribute("xmlns:A");
+ assertEquals(SdkConstants.ANDROID_URI, attribute);
+ }
+
+ public void testAndroidNamespaceAbsence()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testApplicationInvalidOrder"), input);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mILogger);
+ PostValidator.validate(xmlDocument, mergingReportBuilder);
+ // ensure application element is last.
+ String attribute = xmlDocument.getRootNode().getXml().getAttribute("xmlns:android");
+ assertEquals(SdkConstants.ANDROID_URI, attribute);
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/PreValidatorTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PreValidatorTest.java
new file mode 100644
index 0000000..307ee01
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PreValidatorTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sdklib.mock.MockLog;
+import com.google.common.base.Optional;
+
+import junit.framework.TestCase;
+
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link com.android.manifmerger.PreValidator} class.
+ */
+public class PreValidatorTest extends TestCase {
+
+ public void testCorrectInstructions()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"false\""
+ + " tools:replace=\"exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testIncorrectRemove"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mockLog);
+ MergingReport.Result validated = PreValidator.validate(mergingReport, xmlDocument);
+ assertEquals(MergingReport.Result.SUCCESS, validated);
+ assertTrue(mockLog.toString().isEmpty());
+ }
+
+ public void testIncorrectReplace()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:replace=\"exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testIncorrectRemove"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mockLog);
+ MergingReport.Result validated = PreValidator.validate(mergingReport, xmlDocument);
+ assertEquals(MergingReport.Result.ERROR, validated);
+ // assert the error message complains about the bad instruction usage.
+ assertStringPresenceInLogRecords(mergingReport, "tools:replace");
+ }
+
+ public void testIncorrectRemove()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"true\""
+ + " tools:remove=\"exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testIncorrectRemove"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mockLog);
+ MergingReport.Result validated = PreValidator.validate(mergingReport, xmlDocument);
+ assertEquals(MergingReport.Result.ERROR, validated);
+ // assert the error message complains about the bad instruction usage.
+ assertStringPresenceInLogRecords(mergingReport, "tools:remove");
+ }
+
+ public void testIncorrectRemoveAll()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission "
+ + " android:label=\"@string/lib_name\""
+ + " android:name=\"permissionOne\""
+ + " tools:node=\"removeAll\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testIncorrectRemove"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mockLog);
+ MergingReport.Result validated = PreValidator.validate(mergingReport, xmlDocument);
+ assertEquals(MergingReport.Result.ERROR, validated);
+ // assert the error message complains about the bad instruction usage.
+ assertStringPresenceInLogRecords(mergingReport, "tools:node=\"removeAll\"");
+ }
+
+
+ public void testIncorrectSelector()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission "
+ + " android:label=\"@string/lib_name\""
+ + " android:name=\"permissionOne\""
+ + " tools:node=\"replace\" tools:selector=\"foo\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testIncorrectRemove"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mockLog);
+ MergingReport.Result validated = PreValidator.validate(mergingReport, xmlDocument);
+ assertEquals(MergingReport.Result.ERROR, validated);
+ // assert the error message complains about the bad instruction usage.
+ assertStringPresenceInLogRecords(mergingReport, "tools:selector=\"foo\"");
+ }
+
+ public void testNoKeyElement()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <compatible-screens>\n"
+ + " <!-- all small size screens -->\n"
+ + " <screen android:screenSize=\"small\" android:screenDensity=\"ldpi\" />\n"
+ + " <screen android:screenSize=\"small\" android:screenDensity=\"mdpi\" />\n"
+ + " <screen android:screenSize=\"small\" android:screenDensity=\"xhdpi\" />\n"
+ + " <!-- all normal size screens -->\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"ldpi\" />\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"hdpi\" />\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"xhdpi\" />\n"
+ + " </compatible-screens>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testScreenMerging"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mockLog);
+ MergingReport.Result validated = PreValidator.validate(mergingReport, xmlDocument);
+ assertEquals(MergingReport.Result.SUCCESS, validated);
+ }
+
+ public void testMultipleIntentFilterWithSameKeyValue()
+ throws ParserConfigurationException, SAXException, IOException {
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"com.foo.bar.DeepLinkRouterActivity\" android:theme=\"@android:style/Theme.NoDisplay\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"myspecialdeeplinkscheme\"/>\n"
+ + " <data android:host=\"home\"/>\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.foo.com\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testMultipleIntentFilterWithSameKeyValue"), input);
+
+ MergingReport.Builder mergingReport = new MergingReport.Builder(mockLog);
+ MergingReport.Result validated = PreValidator.validate(mergingReport, xmlDocument);
+ assertEquals(MergingReport.Result.SUCCESS, validated);
+ }
+
+ private static void assertStringPresenceInLogRecords(MergingReport.Builder mergingReport, String s) {
+ for (MergingReport.Record record : mergingReport.build().getLoggingRecords()) {
+ if (record.toString().contains(s)) {
+ return;
+ }
+ }
+ // failed, dump the records
+ for (MergingReport.Record record : mergingReport.build().getLoggingRecords()) {
+ Logger.getAnonymousLogger().info(record.toString());
+ }
+ fail("could not find " + s + " in logging records");
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/TestUtils.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/TestUtils.java
new file mode 100644
index 0000000..cd1ce50
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/TestUtils.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.ManifestMerger2.SystemProperty;
+import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Optional;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Utilities for testing ManifestMerge classes.
+ */
+public class TestUtils {
+
+ private static final KeyResolver<String> NULL_RESOLVER = new KeyResolver<String>() {
+ @Nullable
+ @Override
+ public String resolve(String key) {
+ return null;
+ }
+
+ @Override
+ public List<String> getKeys() {
+ return Collections.emptyList();
+ }
+ };
+
+ private static final KeyBasedValueResolver<SystemProperty> NO_PROPERTY_RESOLVER =
+ new KeyBasedValueResolver<SystemProperty>() {
+ @Nullable
+ @Override
+ public String getValue(@NonNull SystemProperty key) {
+ return null;
+ }
+ };
+
+ static class TestSourceLocation implements XmlLoader.SourceLocation {
+
+ private final String mLocation;
+
+ TestSourceLocation(Class sourceClass, String location) {
+ this.mLocation = sourceClass.getSimpleName() + "#" + location;
+ }
+
+ @Override
+ public String print(boolean shortFormat) {
+ return mLocation;
+ }
+
+ @Override
+ public Node toXml(Document document) {
+ Element location = document.createElement("source");
+ location.setAttribute("value", mLocation);
+ return location;
+ }
+ }
+
+ static XmlDocument xmlDocumentFromString(
+ XmlLoader.SourceLocation location,
+ String input) throws IOException, SAXException, ParserConfigurationException {
+
+ return XmlLoader.load(
+ NULL_RESOLVER, NO_PROPERTY_RESOLVER, location, input, XmlDocument.Type.MAIN,
+ Optional.<String>absent() /* mainManifestPackageName */);
+ }
+
+ static XmlDocument xmlLibraryFromString(
+ XmlLoader.SourceLocation location,
+ String input) throws IOException, SAXException, ParserConfigurationException {
+
+ return XmlLoader.load(
+ NULL_RESOLVER, NO_PROPERTY_RESOLVER, location, input, XmlDocument.Type.LIBRARY,
+ Optional.<String>absent() /* mainManifestPackageName */);
+ }
+
+ static XmlDocument xmlDocumentFromString(
+ XmlLoader.SourceLocation location,
+ String input,
+ XmlDocument.Type type,
+ Optional<String> mainManifestPackageName) throws IOException, SAXException, ParserConfigurationException {
+
+ return XmlLoader.load(NULL_RESOLVER, NO_PROPERTY_RESOLVER, location, input, type, mainManifestPackageName);
+ }
+
+ static XmlDocument xmlDocumentFromString(
+ @NonNull KeyResolver<String> selectors,
+ @NonNull XmlLoader.SourceLocation location,
+ String input) throws IOException, SAXException, ParserConfigurationException {
+
+ return XmlLoader.load(selectors, NO_PROPERTY_RESOLVER, location, input,
+ XmlDocument.Type.LIBRARY, Optional.<String>absent() /* mainManifestPackageName */);
+ }
+
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ToolsInstructionsCleanerTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ToolsInstructionsCleanerTest.java
new file mode 100644
index 0000000..592bd0c
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ToolsInstructionsCleanerTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sdklib.mock.MockLog;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link com.android.manifmerger.ToolsInstructionsCleaner} class.
+ */
+public class ToolsInstructionsCleanerTest extends TestCase {
+
+ public void testNodeRemoveOperation()
+ throws ParserConfigurationException, SAXException, IOException {
+ MockLog mockLog = new MockLog();
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" tools:node=\"remove\"/>\n"
+ + " </application>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNodeRemoveOperation"), main);
+
+ Element rootElement = mainDocument.getRootNode().getXml();
+ ToolsInstructionsCleaner.cleanToolsReferences(mainDocument, mockLog);
+
+ Optional<Element> application = getChildElementByName(rootElement, "application");
+ assertTrue(application.isPresent());
+
+ Optional<Element> activity = getChildElementByName(application.get(), "activity");
+ // ensure the activity did get deleted.
+ assertFalse(activity.isPresent());
+ }
+
+ public void testNodeWithChildrenRemoveOperation()
+ throws ParserConfigurationException, SAXException, IOException {
+ MockLog mockLog = new MockLog();
+ String main = "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\" >\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"com.example.lib3.activityOne\" >\n"
+ + " <intent-filter tools:node=\"remove\" >\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNodeRemoveWithChildrenOperation"), main);
+
+ Element rootElement = mainDocument.getRootNode().getXml();
+ ToolsInstructionsCleaner.cleanToolsReferences(mainDocument, mockLog);
+
+ Optional<Element> application = getChildElementByName(rootElement, "application");
+ assertTrue(application.isPresent());
+
+ Optional<Element> activity = getChildElementByName(application.get(), "activity");
+ assertTrue(activity.isPresent());
+ Optional<Element> intentFilter = getChildElementByName(application.get(), "intent-filter");
+
+ // ensure the intent-filter did get deleted.
+ assertFalse(intentFilter.isPresent());
+ }
+
+ public void testInvalidToolsRemoveOperation()
+ throws ParserConfigurationException, SAXException, IOException {
+ MockLog mockLog = new MockLog();
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\""
+ + " tools:node=\"remove\">\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNodeRemoveOperation"), main);
+
+ assertNull(ToolsInstructionsCleaner.cleanToolsReferences(mainDocument, mockLog));
+ }
+
+ public void testInvalidToolsRemoveAllOperation()
+ throws ParserConfigurationException, SAXException, IOException {
+ MockLog mockLog = new MockLog();
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\""
+ + " tools:node=\"removeAll\">\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNodeRemoveOperation"), main);
+
+ assertNull(ToolsInstructionsCleaner.cleanToolsReferences(mainDocument, mockLog));
+ }
+
+ public void testNodeReplaceOperation()
+ throws ParserConfigurationException, SAXException, IOException {
+ MockLog mockLog = new MockLog();
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\">\n"
+ + " <activity android:name=\"activityOne\" tools:node=\"replace\"/>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNodeReplaceOperation"), main);
+
+ Element rootElement = mainDocument.getRootNode().getXml();
+ ToolsInstructionsCleaner.cleanToolsReferences(mainDocument, mockLog);
+
+ Optional<Element> application = getChildElementByName(rootElement, "application");
+ assertTrue(application.isPresent());
+
+ Optional<Element> activity = getChildElementByName(application.get(), "activity");
+ // ensure the activity did not get deleted.
+ assertTrue(activity.isPresent());
+ }
+
+ public void testAttributeRemoveOperation()
+ throws ParserConfigurationException, SAXException, IOException {
+ MockLog mockLog = new MockLog();
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\">\n"
+ + " <activity android:name=\"activityOne\" tools:remove=\"exported\"/>\n"
+ + " </application>\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testAttributeRemoveOperation"), main);
+
+ Element rootElement = mainDocument.getRootNode().getXml();
+ ToolsInstructionsCleaner.cleanToolsReferences(mainDocument, mockLog);
+
+ Optional<Element> application = getChildElementByName(rootElement, "application");
+ assertTrue(application.isPresent());
+
+ Optional<Element> activity = getChildElementByName(application.get(), "activity");
+ // ensure the activity did not get deleted.
+ assertTrue(activity.isPresent());
+ assertEquals(1, activity.get().getAttributes().getLength());
+ assertNotNull(activity.get().getAttribute("android:name"));
+ }
+
+ public void testSelectorRemoval()
+ throws ParserConfigurationException, SAXException, IOException {
+ MockLog mockLog = new MockLog();
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\">\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:node=\"remove\" tools:selector=\"foo\"/>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testSelectorRemoval"), main);
+
+ Element rootElement = mainDocument.getRootNode().getXml();
+ ToolsInstructionsCleaner.cleanToolsReferences(mainDocument, mockLog);
+
+ Optional<Element> application = getChildElementByName(rootElement, "application");
+ assertTrue(application.isPresent());
+
+ Optional<Element> activity = getChildElementByName(application.get(), "activity");
+ // ensure the activity did not get deleted since it has a selector
+ assertTrue(activity.isPresent());
+ assertTrue(Strings.isNullOrEmpty(activity.get().getAttribute("tools:selector")));
+ }
+
+ public void testOtherToolInstructionRemoval()
+ throws ParserConfigurationException, SAXException, IOException {
+ MockLog mockLog = new MockLog();
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\">\n"
+ + " <activity android:name=\"activityOne\" tools:ignore=\"value\"/>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testNodeReplaceOperation"), main);
+
+ Element rootElement = mainDocument.getRootNode().getXml();
+ ToolsInstructionsCleaner.cleanToolsReferences(mainDocument, mockLog);
+
+ Optional<Element> application = getChildElementByName(rootElement, "application");
+ assertTrue(application.isPresent());
+
+ Optional<Element> activity = getChildElementByName(application.get(), "activity");
+ // ensure the activity did not get deleted.
+ assertTrue(activity.isPresent());
+ // ensure tools:ignore got deleted.
+ assertNull(activity.get().getAttributeNodeNS("http://schemas.android.com/tools", "ignore"));
+ }
+
+ private static Optional<Element> getChildElementByName(Element parent, String name) {
+ NodeList childNodes = parent.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node item = childNodes.item(i);
+ if (item.getNodeType() == Node.ELEMENT_NODE
+ && item.getNodeName().equals(name)) {
+ return Optional.of((Element) item);
+ }
+ }
+ return Optional.absent();
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlAttributeTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlAttributeTest.java
new file mode 100644
index 0000000..21233dc
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlAttributeTest.java
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static org.mockito.Mockito.when;
+
+import com.android.SdkConstants;
+import com.android.utils.StdLogger;
+import com.google.common.base.Optional;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.w3c.dom.Attr;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for {@link com.android.manifmerger.XmlAttribute} class
+ */
+public class XmlAttributeTest extends TestCase {
+
+ @Mock
+ XmlDocument mXmlDocument;
+
+ @Mock
+ XmlElement mXmlElement;
+
+ @Mock
+ Attr mAttr;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+
+ when(mAttr.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mAttr.getPrefix()).thenReturn("android");
+ when(mAttr.getLocalName()).thenReturn("name");
+
+ when(mXmlElement.getType()).thenReturn(ManifestModel.NodeTypes.ACTIVITY);
+ when(mXmlElement.getDocument()).thenReturn(mXmlDocument);
+ when(mXmlDocument.getPackageName()).thenReturn("com.foo.bar");
+ }
+
+ public void testPackageSubstitution_noDot() {
+
+ when(mAttr.getValue()).thenReturn("ActivityOne");
+ // this will reset the package.
+ assertNotNull(new XmlAttribute(mXmlElement, mAttr,
+ AttributeModel.newModel("ActivityOne").setIsPackageDependent().build()));
+ Mockito.verify(mAttr).setValue("com.foo.bar.ActivityOne");
+ }
+
+ public void testPackageSubstitution_withDot() {
+
+ when(mAttr.getValue()).thenReturn(".ActivityOne");
+ // this will reset the package.
+ assertNotNull(new XmlAttribute(mXmlElement, mAttr,
+ AttributeModel.newModel("ActivityOne").setIsPackageDependent().build()));
+ Mockito.verify(mAttr).setValue("com.foo.bar.ActivityOne");
+ }
+
+ public void testNoPackageSubstitution() {
+
+ when(mAttr.getValue()).thenReturn("com.foo.foo2.ActivityOne");
+ // this will NOT reset the package.
+ assertNotNull(new XmlAttribute(mXmlElement, mAttr,
+ AttributeModel.newModel("ActivityOne").setIsPackageDependent().build()));
+ Mockito.verify(mAttr).getValue();
+ Mockito.verifyNoMoreInteractions(mAttr);
+ }
+
+ public void testAttributeRemoval()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " tools:remove=\"theme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" \n"
+ + " android:theme=\"@oldtheme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ XmlDocument resultDocument = result.get();
+
+ Optional<XmlElement> activityOne = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ // verify that only android:name and tools:remove remains in the result.
+ List<XmlAttribute> attributes = activityOne.get().getAttributes();
+ assertEquals(2, attributes.size());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:name")).isPresent());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove")).isPresent());
+
+ Actions actions = mergingReportBuilder.getActionRecorder().build();
+ // check the recorded actions.
+ List<Actions.AttributeRecord> attributeRecords =
+ actions.getAttributeRecords(activityOne.get().getId(),
+ XmlNode.fromXmlName("android:theme"));
+ assertNotNull(attributeRecords);
+ assertEquals(1, attributeRecords.size());
+ Actions.AttributeRecord attributeRecord = attributeRecords.get(0);
+ assertEquals(Actions.ActionType.REJECTED, attributeRecord.getActionType());
+ assertEquals(AttributeOperationType.REMOVE, attributeRecord.getOperationType());
+ assertEquals(7, attributeRecord.getActionLocation().getPosition().getLine());
+ }
+
+ public void testNamespaceAwareAttributeRemoval()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " tools:remove=\"android:theme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" \n"
+ + " android:theme=\"@oldtheme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ XmlDocument resultDocument = result.get();
+
+ Optional<XmlElement> activityOne = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ List<XmlAttribute> attributes = activityOne.get().getAttributes();
+ assertEquals(2, attributes.size());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:name")).isPresent());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove")).isPresent());
+ }
+
+ public void testMultipleAttributesRemoval()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " tools:remove=\"theme, exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" \n"
+ + " android:exported=\"true\"\n"
+ + " android:theme=\"@oldtheme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ XmlDocument resultDocument = result.get();
+
+ Optional<XmlElement> activityOne = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ List<XmlAttribute> attributes = activityOne.get().getAttributes();
+ assertEquals(2, attributes.size());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:name")).isPresent());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove")).isPresent());
+ }
+
+ public void testDeepAttributeRemoval()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " tools:remove=\"theme, exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" \n"
+ + " android:screenOrientation=\"landscape\"\n"
+ + " android:theme=\"@oldtheme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String evenLowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" \n"
+ + " android:theme=\"@oldtheme\"\n"
+ + " android:exported=\"true\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument highPriority = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "highPriority"), higherPriority);
+ XmlDocument lowPriority = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowPriority"), lowerPriority);
+ XmlDocument lowestPriority = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowestPriority"), evenLowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = highPriority.merge(lowPriority, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ XmlDocument resultDocument = result.get();
+ result = resultDocument.merge(lowestPriority, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ resultDocument = result.get();
+
+ Optional<XmlElement> activityOne = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ List<XmlAttribute> attributes = activityOne.get().getAttributes();
+ assertEquals(3, attributes.size());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:name")).isPresent());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:screenOrientation")).isPresent());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove")).isPresent());
+
+ Actions actions = mergingReportBuilder.getActionRecorder().build();
+ // check the recorded actions.
+ List<Actions.AttributeRecord> attributeRecords =
+ actions.getAttributeRecords(activityOne.get().getId(),
+ XmlNode.fromXmlName("android:theme"));
+ assertNotNull(attributeRecords);
+
+ // theme was removed twice...
+ assertEquals(2, attributeRecords.size());
+ Actions.AttributeRecord attributeRecord = attributeRecords.get(0);
+ assertEquals(Actions.ActionType.REJECTED, attributeRecord.getActionType());
+ assertEquals(AttributeOperationType.REMOVE, attributeRecord.getOperationType());
+ assertEquals("XmlAttributeTest#lowPriority",
+ attributeRecord.getActionLocation().getSourceLocation().print(true));
+ assertEquals(8, attributeRecord.getActionLocation().getPosition().getLine());
+
+ attributeRecord = attributeRecords.get(1);
+ assertEquals(Actions.ActionType.REJECTED, attributeRecord.getActionType());
+ assertEquals(AttributeOperationType.REMOVE, attributeRecord.getOperationType());
+ assertEquals("XmlAttributeTest#lowestPriority",
+ attributeRecord.getActionLocation().getSourceLocation().print(true));
+ assertEquals(7, attributeRecord.getActionLocation().getPosition().getLine());
+ }
+
+ public void testDefaultValueIllegalOverriding()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ // implicit required=true attribute present.
+ + " <uses-library android:name=\"libraryOne\"/>\n"
+ + " <permission android:name=\"permissionOne\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-library android:name=\"libraryOne\" android:required=\"false\"/>\n"
+ + " <permission android:name=\"permissionOne\" "
+ + " android:protectionLevel=\"dangerous\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ }
+
+ public void testToolsAttributeMerging()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " tools:remove=\"theme,exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" \n"
+ +" tools:remove=\"bar\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ Optional<XmlElement> activityOne = result.get().getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ // verify that only android:name and tools:remove remains in the result.
+ List<XmlAttribute> attributes = activityOne.get().getAttributes();
+ assertEquals(2, attributes.size());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:name")).isPresent());
+ Optional<XmlAttribute> toolsRemove = activityOne.get().getAttribute(
+ XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove"));
+ assertTrue(toolsRemove.isPresent());
+ assertEquals("theme,exported,bar", toolsRemove.get().getValue());
+
+ }
+
+ public void testToolsNodeAttributeNotMerging()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " tools:node=\"replace\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" \n"
+ +" tools:node=\"remove\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ Optional<XmlElement> activityOne = result.get().getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ // verify that only android:name and tools:remove remains in the result.
+ List<XmlAttribute> attributes = activityOne.get().getAttributes();
+ assertEquals(2, attributes.size());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:name")).isPresent());
+ Optional<XmlAttribute> toolsRemove = activityOne.get().getAttribute(
+ XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "node"));
+ assertTrue(toolsRemove.isPresent());
+ assertEquals("replace", toolsRemove.get().getValue());
+
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlDocumentTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlDocumentTest.java
new file mode 100644
index 0000000..fca9ff0
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlDocumentTest.java
@@ -0,0 +1,1689 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.MergingReport.Record.Severity.ERROR;
+
+import com.android.SdkConstants;
+import com.android.ide.common.sdk.SdkVersionInfo;
+import com.android.sdklib.mock.MockLog;
+import com.android.utils.ILogger;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for {@link com.android.manifmerger.XmlDocument}
+ */
+public class XmlDocumentTest extends TestCase {
+
+ @Mock ILogger mLogger;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testMergeableElementsIdentification()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<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"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testMergeableElementsIdentification()"), input);
+ ImmutableList<XmlElement> mergeableElements = xmlDocument.getRootNode().getMergeableElements();
+ assertEquals(2, mergeableElements.size());
+ }
+
+ public void testNamespaceEnabledElements()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <android:application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <android:activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testMergeableElementsIdentification()"), input);
+ ImmutableList<XmlElement> mergeableElements = xmlDocument.getRootNode().getMergeableElements();
+ assertEquals(2, mergeableElements.size());
+ assertEquals(ManifestModel.NodeTypes.APPLICATION, mergeableElements.get(0).getType());
+ assertEquals(ManifestModel.NodeTypes.ACTIVITY, mergeableElements.get(1).getType());
+ }
+
+ public void testMultipleNamespaceEnabledElements()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<android:manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <android:application android:label=\"@string/lib_name\" \n"
+ + " tools:node=\"replace\" />\n"
+ + " <acme:custom-tag android:label=\"@string/lib_name\" />\n"
+ + " <acme:application acme:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + "</android:manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testMergeableElementsIdentification()"), input);
+ ImmutableList<XmlElement> mergeableElements = xmlDocument.getRootNode().getMergeableElements();
+ assertEquals(3, mergeableElements.size());
+ assertEquals(ManifestModel.NodeTypes.APPLICATION, mergeableElements.get(0).getType());
+ assertEquals(ManifestModel.NodeTypes.CUSTOM, mergeableElements.get(1).getType());
+ assertEquals(ManifestModel.NodeTypes.CUSTOM, mergeableElements.get(2).getType());
+
+ }
+
+ public void testGetXmlNodeByTypeAndKey()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<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"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testGetXmlNodeByTypeAndKey()"), input);
+ assertTrue(xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").isPresent());
+ assertFalse(xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "noName").isPresent());
+ }
+
+ public void testSimpleMerge()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testSimpleMerge()"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testSimpleMerge()"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ Logger.getAnonymousLogger().info(mergedDocument.get().prettyPrint());
+ assertTrue(mergedDocument.get().getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.APPLICATION, null).isPresent());
+ Optional<XmlElement> activityOne = mergedDocument.get()
+ .getRootNode().getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+ }
+
+ public void testDiff1()
+ throws Exception {
+ String main = ""
+ + "<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"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff1()"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff1()"), library);
+ assertTrue(mainDocument.compareTo(libraryDocument).isPresent());
+ }
+
+ public void testDiff2()
+ throws Exception {
+ String main = ""
+ + "<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 android:name=\"activityOne\" />\n"
+ + "\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff2()"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff2()"), library);
+ assertFalse(mainDocument.compareTo(libraryDocument).isPresent());
+ }
+
+ public void testDiff3()
+ throws Exception {
+ String main = ""
+ + "<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"
+ + " <!-- some comment that should be ignored -->\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <!-- some comment that should also be ignored -->\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff3()"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff3()"), library);
+ assertFalse(mainDocument.compareTo(libraryDocument).isPresent());
+ }
+
+ public void testWriting() throws ParserConfigurationException, SAXException, IOException {
+ String input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:x=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:y=\"http://schemas.android.com/apk/res/android/tools\"\n"
+ + " package=\"com.example.lib3\" >\n"
+ + "\n"
+ + " <application\n"
+ + " x:label=\"@string/lib_name\"\n"
+ + " y:node=\"replace\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testWriting()"), input);
+ assertEquals(input, xmlDocument.prettyPrint());
+ }
+
+ public void testCustomElements()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <fantasy android:name=\"fantasyOne\" \n"
+ + " no-ns-attribute=\"no-ns\" >\n"
+ + " </fantasy>\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <fantasy android:name=\"fantasyTwo\" \n"
+ + " no-ns-attribute=\"no-ns\" >\n"
+ + " </fantasy>\n"
+ + " <acme:another acme:name=\"anotherOne\" \n"
+ + " acme:ns-attribute=\"ns-value\" >\n"
+ + " <some-child acme:child-attr=\"foo\" /> \n"
+ + " </acme:another>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlElement rootNode = mergedDocument.get().getRootNode();
+ assertTrue(rootNode.getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.APPLICATION, null).isPresent());
+ Optional<XmlElement> activityOne = rootNode
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ boolean foundFantasyOne = false;
+ boolean foundFantasyTwo = false;
+ boolean foundAnother = false;
+ NodeList childNodes = rootNode.getXml().getChildNodes();
+ for (int i =0; i < childNodes.getLength(); i++) {
+ Node item = childNodes.item(i);
+ if (item.getNodeName().equals("fantasy")) {
+ String name = ((Element) item).getAttributeNS(SdkConstants.ANDROID_URI, "name");
+ if (name.equals("fantasyOne"))
+ foundFantasyOne = true;
+ if (name.equals("fantasyTwo"))
+ foundFantasyTwo = true;
+ }
+ if (item.getNodeName().equals("acme:another")) {
+ foundAnother = true;
+ }
+ }
+ assertTrue(foundAnother);
+ assertTrue(foundFantasyOne);
+ assertTrue(foundFantasyTwo);
+
+ Element validated = validate(mergedDocument.get().prettyPrint());
+
+ // make sure acme: namespace is defined.
+ Node namedItem = validated.getAttributes().getNamedItem("xmlns:acme");
+ assertEquals(namedItem.getNodeValue(), "http://acme.org/schemas");
+ }
+
+ public void testMultipleCustomElements()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " xmlns:acme2=\"http://acme2.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <acme:another acme:name=\"anotherOne\" \n"
+ + " acme:ns-attribute=\"ns-value\" >\n"
+ + " <acme:some-child acme:child-attr=\"foo\" /> \n"
+ + " </acme:another>\n"
+ + " <acme:another2 acme:name=\"anotherOne\" \n"
+ + " acme:ns-attribute=\"ns-value\" >\n"
+ + " <acme:some-child acme:child-attr=\"foo\" /> \n"
+ + " </acme:another2>\n"
+ + " <acme2:another acme2:name=\"anotherOne\" \n"
+ + " acme2:ns-attribute=\"ns-value\" >\n"
+ + " <acme2:some-child acme2:child-attr=\"foo\" /> \n"
+ + " </acme2:another>\n"
+ + " <acme2:another2 acme2:name=\"anotherOne\" \n"
+ + " acme2:ns-attribute=\"ns-value\" >\n"
+ + " <acme2:some-child acme2:child-attr=\"foo\" /> \n"
+ + " </acme2:another2>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlElement rootNode = mergedDocument.get().getRootNode();
+ assertTrue(rootNode.getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.APPLICATION, null).isPresent());
+ Optional<XmlElement> activityOne = rootNode
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ Element validated = validate(mergedDocument.get().prettyPrint());
+
+ // make sure acme: and acme2: namespaces are defined.
+ Node namedItem = validated.getAttributes().getNamedItem("xmlns:acme");
+ assertEquals(namedItem.getNodeValue(), "http://acme.org/schemas");
+ namedItem = validated.getAttributes().getNamedItem("xmlns:acme2");
+ assertEquals(namedItem.getNodeValue(), "http://acme2.org/schemas");
+
+ // check we have all the children we expected.
+ int elementsNumber = 0;
+ for (int i = 0; i < validated.getChildNodes().getLength(); i++) {
+ Node item = validated.getChildNodes().item(i);
+ if (item instanceof Element) {
+ elementsNumber++;
+ }
+ }
+ assertEquals(6, elementsNumber);
+ }
+
+ public void testIllegalLibraryVersionMerge()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"4\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"19\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertFalse(mergedDocument.isPresent());
+ MergingReport mergingReport = mergingReportBuilder.build();
+ assertEquals(1, mergingReport.getLoggingRecords().size());
+ assertTrue(mergingReport.getLoggingRecords().get(0).getSeverity() == ERROR);
+ assertTrue(mergingReport.getLoggingRecords().get(0).toString().contains(
+ "uses-sdk:minSdkVersion 4"));
+ }
+
+ public void testNoMergingNecessary()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"19\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"19\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_PHONE_STATE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_CALL_LOG").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_CALL_LOG").isPresent());
+ }
+
+ public void testNoUsesSdkPresence()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_PHONE_STATE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_CALL_LOG").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_CALL_LOG").isPresent());
+ }
+
+ public void testGlEsVersionFromFlavor()
+ throws ParserConfigurationException, SAXException, IOException {
+ String flavor = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-feature\n"
+ + " android:name=\"android.hardware.camera\"\n"
+ + " android:glEsVersion=\"0x00020000\"\n"
+ + " android:required=\"true\" />"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + "</manifest>";
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument flavorDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "flavor"), flavor);
+ XmlDocument mainDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ flavorDocument.merge(mainDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ System.out.println(mergedDocument.get().prettyPrint());
+ XmlDocument xmlDocument = mergedDocument.get();
+ Optional<XmlElement> usesFeature = xmlDocument
+ .getByTypeAndKey(ManifestModel.NodeTypes.USES_FEATURE,
+ "android.hardware.camera");
+ assertTrue(usesFeature.isPresent());
+ Optional<XmlAttribute> glEsVersion = usesFeature.get()
+ .getAttribute(XmlNode.fromXmlName("android:glEsVersion"));
+ assertTrue(glEsVersion.isPresent());
+ assertEquals("0x00020000", glEsVersion.get().getValue());
+ }
+
+
+ public void testNoUsesSdkPresenceInMain()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertFalse(mergedDocument.isPresent());
+ }
+
+ public void testNoUsesSdkPresenceInLibrary()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_PHONE_STATE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_CALL_LOG").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_CALL_LOG").isPresent());
+ }
+
+ public void testLibraryVersion3Merge()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"19\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_EXTERNAL_STORAGE").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_PHONE_STATE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_CALL_LOG").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_CALL_LOG").isPresent());
+
+ // check records.
+ Actions actions = mergingReportBuilder.getActionRecorder().build();
+ XmlElement xmlElement = xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").get();
+ ImmutableList<Actions.NodeRecord> nodeRecords = actions
+ .getNodeRecords(XmlNode.NodeKey.fromXml(xmlElement.getXml()));
+ assertEquals(1, nodeRecords.size());
+ assertEquals(nodeRecords.iterator().next().mReason, "targetSdkVersion < 4");
+ }
+
+ public void testLibraryVersion10Merge()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"19\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"10\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_PHONE_STATE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_CALL_LOG").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_CALL_LOG").isPresent());
+ }
+
+ public void testLibraryVersion3MergeWithContacts()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"19\"/>\n"
+ + " <uses-permission android:name=\"android.permission.READ_CONTACTS\"/>\n"
+ + " <uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_EXTERNAL_STORAGE").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_PHONE_STATE").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_CALL_LOG").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_CALL_LOG").isPresent());
+
+ // check records.
+ Actions actions = mergingReportBuilder.getActionRecorder().build();
+ XmlElement xmlElement = xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").get();
+ ImmutableList<Actions.NodeRecord> nodeRecords = actions
+ .getNodeRecords(XmlNode.NodeKey.fromXml(xmlElement.getXml()));
+ assertEquals(1, nodeRecords.size());
+ assertEquals(nodeRecords.iterator().next().mReason, "targetSdkVersion < 4");
+
+ xmlElement = xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_CALL_LOG").get();
+ nodeRecords = actions.getNodeRecords(XmlNode.NodeKey.fromXml(xmlElement.getXml()));
+ assertEquals(1, nodeRecords.size());
+ assertEquals(nodeRecords.iterator().next().mReason,
+ "targetSdkVersion < 16 and requested READ_CONTACTS");
+ }
+
+ public void testLibraryVersion10MergeWithContacts()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"19\"/>\n"
+ + " <uses-permission android:name=\"android.permission.READ_CONTACTS\"/>\n"
+ + " <uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"10\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_EXTERNAL_STORAGE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_PHONE_STATE").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_CALL_LOG").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_CALL_LOG").isPresent());
+ }
+
+ public void testUsesSdkAbsenceInOverlay()
+ throws ParserConfigurationException, SAXException, IOException {
+ String overlay = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-permission android:name=\"android.permission.READ_CONTACTS\"/>\n"
+ + " <uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"15\"/>\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "overlay"),
+ overlay,
+ XmlDocument.Type.OVERLAY,
+ Optional.of("com.example.lib3"));
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ MockLog mockLog = new MockLog();
+ mergingReportBuilder.build().log(mockLog);
+ System.out.println(mockLog.toString());
+ assertTrue(mergedDocument.isPresent());
+ }
+
+
+ /**
+ * test illegal importation of a preview library (using the minSdk attribute) in a released
+ * application.
+ */
+ public void testLibraryAtPreviewInOldApp_usingMinSdk()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"19\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"XYZ\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertFalse(mergedDocument.isPresent());
+ MergingReport mergingReport = mergingReportBuilder.build();
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReport.getLoggingRecords();
+ assertTrue(mergingReport.getResult().isError());
+ assertEquals(1, loggingRecords.size());
+ assertTrue(loggingRecords.get(0).getMessage().contains("XYZ"));
+ }
+
+ /**
+ * test illegal importation of a preview library (using the minSdk attribute) in a released
+ * application.
+ */
+ public void testLibraryAtPreviewInNewerApp_usingMinSdk()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\""
+ + (SdkVersionInfo.HIGHEST_KNOWN_API + 2) // fantasy version in the far future.
+ + "\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"XYZ\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertFalse(mergedDocument.isPresent());
+ MergingReport mergingReport = mergingReportBuilder.build();
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReport.getLoggingRecords();
+ assertTrue(mergingReport.getResult().isError());
+ assertEquals(1, loggingRecords.size());
+ assertTrue(loggingRecords.get(0).getMessage().contains("XYZ"));
+ }
+
+
+ /**
+ * test illegal importation of a preview library (using the targetSdk attribute) in a released
+ * application.
+ */
+ public void testLibraryAtPreviewInOldApp_usingTargetSdk()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"19\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"XYZ\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertFalse(mergedDocument.isPresent());
+ MergingReport mergingReport = mergingReportBuilder.build();
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReport.getLoggingRecords();
+ assertTrue(mergingReport.getResult().isError());
+ assertEquals(1, loggingRecords.size());
+ assertTrue(loggingRecords.get(0).getMessage().contains("XYZ"));
+ }
+
+ /**
+ * test legal importation of a released library (using the minSdk attribute) into a preview
+ * application.
+ */
+ public void testLibraryAtReleaseAgainstAppInPreview_usingMinSdk()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"XYZ\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"19\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ // make sure the resulting min version is "XYZ".
+ Optional<XmlElement> usesSdk = mergedDocument.get()
+ .getByTypeAndKey(ManifestModel.NodeTypes.USES_SDK, null);
+ Optional<XmlAttribute> attribute = usesSdk.get()
+ .getAttribute(XmlNode.fromXmlName("android:minSdkVersion"));
+ assertTrue(attribute.isPresent());
+ assertEquals("XYZ", attribute.get().getValue());
+ }
+
+ /**
+ * test legal importation of a released library (using the minSdk attribute) into a preview
+ * application.
+ */
+ public void testLibraryAtReleaseAgainstAppInPreview_usingTargetSdk()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"XYZ\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"14\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ // make sure the resulting target version is "XYZ".
+ Optional<XmlElement> usesSdk = mergedDocument.get()
+ .getByTypeAndKey(ManifestModel.NodeTypes.USES_SDK, null);
+ Optional<XmlAttribute> attribute = usesSdk.get()
+ .getAttribute(XmlNode.fromXmlName("android:targetSdkVersion"));
+ assertTrue(attribute.isPresent());
+ assertEquals("XYZ", attribute.get().getValue());
+ }
+
+ /**
+ * test illegal importation of a more recent released library (using the minSdk attribute) into
+ * an older preview application.
+ */
+ public void testLibraryMoreRecentThanCodeName()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:minSdkVersion=\"XYZ\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:minSdkVersion=\""
+ + (SdkVersionInfo.HIGHEST_KNOWN_API + 2) // fantasy version in the far future.
+ + "\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertFalse(mergedDocument.isPresent());
+ }
+
+ /**
+ * Test that implicit elements are added correctly when importing an old library into a preview
+ * application.
+ */
+ public void testLibraryVersion3MergeInPreviewApp()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<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"
+ + " <uses-sdk android:targetSdkVersion=\"XYZ\"/>\n"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:targetSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_EXTERNAL_STORAGE").isPresent());
+ assertTrue(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_PHONE_STATE").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.READ_CALL_LOG").isPresent());
+ assertFalse(xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_CALL_LOG").isPresent());
+
+ // check records.
+ Actions actions = mergingReportBuilder.getActionRecorder().build();
+ XmlElement xmlElement = xmlDocument.getByTypeAndKey(ManifestModel.NodeTypes.USES_PERMISSION,
+ "android.permission.WRITE_EXTERNAL_STORAGE").get();
+ ImmutableList<Actions.NodeRecord> nodeRecords = actions
+ .getNodeRecords(XmlNode.NodeKey.fromXml(xmlElement.getXml()));
+ assertEquals(1, nodeRecords.size());
+ assertEquals(nodeRecords.iterator().next().mReason, "targetSdkVersion < 4");
+ }
+
+ /**
+ * Test that multiple intent-filters with the same key and no override are not merged or
+ * discarded.
+ */
+ public void testMultipleIntentFilter_sameKey_noLibraryDeclaration()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"myspecialdeeplinkscheme\"/>\n"
+ + " <data android:host=\"home\"/>\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.foo.com\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" />\n"
+ + " <uses-sdk android:targetSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ List<XmlElement> allIntentFilters = getAllElementsOfType(xmlDocument,
+ ManifestModel.NodeTypes.INTENT_FILTER);
+ assertEquals(2, allIntentFilters.size());
+ assertEquals(allIntentFilters.get(0).getId(), allIntentFilters.get(1).getId());
+ }
+
+ /**
+ * Test that multiple intent-filters with the same key and no override are not merged or
+ * discarded.
+ */
+ public void testMultipleIntentFilter_sameKey_noOverride()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"myspecialdeeplinkscheme\"/>\n"
+ + " <data android:host=\"home\"/>\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.foo.com\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.SEARCH\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>"
+ + " </application>"
+ + " <uses-sdk android:targetSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ List<XmlElement> allIntentFilters = getAllElementsOfType(xmlDocument,
+ ManifestModel.NodeTypes.INTENT_FILTER);
+ assertEquals(3, allIntentFilters.size());
+ }
+
+ /**
+ * Test that multiple intent-filters with the same key and no override are not merged or
+ * discarded.
+ */
+ public void testMultipleIntentFilter_sameKey_sameOverride()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"myspecialdeeplinkscheme\"/>\n"
+ + " <data android:host=\"home\"/>\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.foo.com\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.SEARCH\" />\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.foo.com\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>"
+ + " </application>"
+ + " <uses-sdk android:targetSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ List<XmlElement> allIntentFilters = getAllElementsOfType(xmlDocument,
+ ManifestModel.NodeTypes.INTENT_FILTER);
+
+ // the second intent-filter of the library should not have been merged in.
+ assertEquals(3, allIntentFilters.size());
+ }
+
+ /**
+ * Test that multiple intent-filters with the same key and no override are not merged or
+ * discarded.
+ */
+ public void testMultipleIntentFilter_sameKey_differentOverride()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"myspecialdeeplinkscheme\"/>\n"
+ + " <data android:host=\"home\"/>\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.foo.com\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.SEARCH\" />\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.bar.com\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>"
+ + " </application>"
+ + " <uses-sdk android:targetSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ List<XmlElement> allIntentFilters = getAllElementsOfType(xmlDocument,
+ ManifestModel.NodeTypes.INTENT_FILTER);
+ // all intent-filters should have been merged.
+ assertEquals(4, allIntentFilters.size());
+ }
+
+ /**
+ * Test that multiple intent-filters with the same key and no override are not merged or
+ * discarded.
+ */
+ public void testMultipleIntentFilter_sameKey_removal()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter tools:node=\"remove\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.SEARCH\" />\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.bar.com\"/>\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.foo.com\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>"
+ + " </application>"
+ + " <uses-sdk android:targetSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ List<XmlElement> allIntentFilters = getAllElementsOfType(xmlDocument,
+ ManifestModel.NodeTypes.INTENT_FILTER);
+ // the cleaner is not removed the "remove" node so we should have 2.
+ assertEquals(2, allIntentFilters.size());
+ }
+
+ public void testMultipleIntentFilter_sameKey_removalAll()
+ throws ParserConfigurationException, SAXException, IOException {
+ String main = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter tools:node=\"removeAll\"/>\n"
+ + " </activity>\n"
+ + " </application>"
+ + "\n"
+ + "</manifest>";
+ String library = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:acme=\"http://acme.org/schemas\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application>\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.SEARCH\" />\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.bar.com\"/>\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\"/>\n"
+ + " <data android:scheme=\"https\"/>\n"
+ + " <data android:host=\"www.foo.com\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>"
+ + " </application>"
+ + " <uses-sdk android:targetSdkVersion=\"3\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument mainDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "main"), main);
+ XmlDocument libraryDocument = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "library"), library);
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+ Optional<XmlDocument> mergedDocument =
+ mainDocument.merge(libraryDocument, mergingReportBuilder);
+
+ assertTrue(mergedDocument.isPresent());
+ XmlDocument xmlDocument = mergedDocument.get();
+ List<XmlElement> allIntentFilters = getAllElementsOfType(xmlDocument,
+ ManifestModel.NodeTypes.INTENT_FILTER);
+ // since the cleaner has not run, there is one intent-filter with the removeAll annotation.
+ assertEquals(1, allIntentFilters.size());
+ }
+
+ private static List<XmlElement> getAllElementsOfType(
+ XmlDocument xmlDocument,
+ ManifestModel.NodeTypes nodeType) {
+ ImmutableList.Builder<XmlElement> listBuilder = ImmutableList.builder();
+ getAllElementsOfType(xmlDocument.getRootNode(), nodeType, listBuilder);
+ return listBuilder.build();
+ }
+
+ private static void getAllElementsOfType(XmlElement element,
+ ManifestModel.NodeTypes nodeType,
+ ImmutableList.Builder<XmlElement> allElementsBuilder) {
+
+ for (XmlElement xmlElement : element.getMergeableElements()) {
+ if (xmlElement.isA(nodeType)) {
+ allElementsBuilder.add(xmlElement);
+ } else {
+ getAllElementsOfType(xmlElement, nodeType, allElementsBuilder);
+ }
+ }
+ }
+
+ private static Element validate(String xml)
+ throws ParserConfigurationException, IOException, SAXException {
+
+ DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+ dbFactory.setNamespaceAware(true);
+ DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+ dBuilder.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(SAXParseException e) throws SAXException {
+
+ }
+
+ @Override
+ public void error(SAXParseException e) throws SAXException {
+ fail(e.getMessage());
+ }
+
+ @Override
+ public void fatalError(SAXParseException e) throws SAXException {
+ fail(e.getMessage());
+ }
+ });
+ Document validated = dBuilder.parse(
+ new InputSource(new StringReader(xml)));
+ return (Element) validated.getChildNodes().item(0);
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlElementTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlElementTest.java
new file mode 100644
index 0000000..b17f79a
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlElementTest.java
@@ -0,0 +1,1779 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.manifmerger.Actions.NodeRecord;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
+import com.android.SdkConstants;
+import com.android.utils.StdLogger;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link XmlElement}
+ */
+public class XmlElementTest extends TestCase {
+
+ @Mock
+ MergingReport.Builder mergingReport;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testToolsNodeInstructions()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:node=\"remove\"/>\n"
+ + "\n"
+ + " <activity android:name=\"activityTwo\" "
+ + " tools:node=\"removeAll\"/>\n"
+ + "\n"
+ + " <activity android:name=\"activityThree\" "
+ + " tools:node=\"mergeOnlyAttributes\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testToolsNodeInstructions()"), input);
+ Optional<XmlElement> activity = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne");
+ assertTrue(activity.isPresent());
+ assertEquals(NodeOperationType.REMOVE,
+ activity.get().getOperationType());
+ activity = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityTwo");
+ assertTrue(activity.isPresent());
+ assertEquals(NodeOperationType.REMOVE_ALL,
+ activity.get().getOperationType());
+ activity = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityThree");
+ assertTrue(activity.isPresent());
+ assertEquals(NodeOperationType.MERGE_ONLY_ATTRIBUTES,
+ activity.get().getOperationType());
+ }
+
+ public void testInvalidNodeInstruction()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:node=\"funkyValue\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ try {
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(
+ getClass(), "testInvalidNodeInstruction()"), input);
+ xmlDocument.getRootNode();
+ fail("Exception not thrown");
+ } catch (IllegalArgumentException expected) {
+ // expected.
+ }
+ }
+
+ public void testAttributeInstructions()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:remove=\"android:theme\"/>\n"
+ + "\n"
+ + " <activity android:name=\"activityTwo\" "
+ + " android:theme=\"@theme1\"\n"
+ + " tools:replace=\"android:theme\"/>\n"
+ + "\n"
+ + " <activity android:name=\"activityThree\" "
+ + " tools:strict=\"android:exported\"/>\n"
+ + "\n"
+ + " <activity android:name=\"activityFour\" "
+ + " android:theme=\"@theme1\"\n"
+ + " android:exported=\"true\"\n"
+ + " android:windowSoftInputMode=\"stateUnchanged\"\n"
+ + " tools:replace="
+ + "\"android:theme, android:exported,android:windowSoftInputMode\"/>\n"
+ + "\n"
+ + " <activity android:name=\"activityFive\" "
+ + " android:theme=\"@theme1\"\n"
+ + " android:exported=\"true\"\n"
+ + " android:windowSoftInputMode=\"stateUnchanged\"\n"
+ + " tools:remove=\"android:exported\"\n"
+ + " tools:replace=\"android:theme\"\n"
+ + " tools:strict=\"android:windowSoftInputMode\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ // ActivityOne, remove operation.
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testAttributeInstructions()"), input);
+ Optional<XmlElement> activityOptional = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne");
+ assertTrue(activityOptional.isPresent());
+ XmlElement activity = activityOptional.get();
+ assertEquals(1, activity.getAttributeOperations().size());
+ AttributeOperationType attributeOperationType =
+ activity.getAttributeOperationType(XmlNode.fromXmlName("android:theme"));
+ assertEquals(AttributeOperationType.REMOVE, attributeOperationType);
+
+ // ActivityTwo, replace operation.
+ activityOptional = xmlDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityTwo");
+ assertTrue(activityOptional.isPresent());
+ activity = activityOptional.get();
+ assertEquals(1, activity.getAttributeOperations().size());
+ attributeOperationType = activity.getAttributeOperationType(XmlNode.fromXmlName("android:theme"));
+ assertEquals(AttributeOperationType.REPLACE, attributeOperationType);
+
+ // ActivityThree, strict operation.
+ activityOptional = xmlDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityThree");
+ assertTrue(activityOptional.isPresent());
+ activity = activityOptional.get();
+ assertEquals(1, activity.getAttributeOperations().size());
+ attributeOperationType = activity.getAttributeOperationType(XmlNode.fromXmlName("android:theme"));
+ assertEquals(AttributeOperationType.STRICT, attributeOperationType);
+
+ // ActivityFour, multiple target fields.
+ activityOptional = xmlDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityFour");
+ assertTrue(activityOptional.isPresent());
+ activity = activityOptional.get();
+ assertEquals(3, activity.getAttributeOperations().size());
+ assertEquals(AttributeOperationType.REPLACE,
+ activity.getAttributeOperationType(XmlNode.fromXmlName("android:theme")));
+ assertEquals(AttributeOperationType.REPLACE,
+ activity.getAttributeOperationType(XmlNode.fromXmlName("android:theme")));
+ assertEquals(AttributeOperationType.REPLACE,
+ activity.getAttributeOperationType(XmlNode.fromXmlName("android:theme")));
+
+ // ActivityFive, multiple operations.
+ activityOptional = xmlDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityFive");
+ assertTrue(activityOptional.isPresent());
+ activity = activityOptional.get();
+ assertEquals(3, activity.getAttributeOperations().size());
+
+ assertEquals(AttributeOperationType.REMOVE,
+ activity.getAttributeOperationType(XmlNode.fromXmlName("android:exported")));
+
+ assertEquals(AttributeOperationType.REPLACE,
+ activity.getAttributeOperationType(XmlNode.fromXmlName("android:theme")));
+
+ assertEquals(AttributeOperationType.STRICT,
+ activity.getAttributeOperationType(XmlNode.fromXmlName("android:windowSoftInputMode")));
+ }
+
+ public void testNoNamespaceAwareAttributeInstructions()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:remove=\"theme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ // ActivityOne, remove operation.
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testAttributeInstructions()"), input);
+ Optional<XmlElement> activityOptional = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne");
+ assertTrue(activityOptional.isPresent());
+ XmlElement activity = activityOptional.get();
+ assertEquals(1, activity.getAttributeOperations().size());
+ AttributeOperationType attributeOperationType =
+ activity.getAttributeOperationType(XmlNode.fromXmlName("android:theme"));
+ assertEquals(AttributeOperationType.REMOVE, attributeOperationType);
+ }
+
+ public void testUnusualNamespacePrefixAttributeInstructions()
+ throws ParserConfigurationException, SAXException, IOException {
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:z=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity z:name=\"activityOne\" tools:remove=\"theme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ // ActivityOne, remove operation.
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testAttributeInstructions()"), input);
+ Optional<XmlElement> activityOptional = xmlDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne");
+ assertTrue(activityOptional.isPresent());
+ XmlElement activity = activityOptional.get();
+
+ assertEquals(1, activity.getAttributeOperations().size());
+ AttributeOperationType attributeOperationType =
+ activity.getAttributeOperationType(XmlNode.fromXmlName("z:theme"));
+ assertEquals(AttributeOperationType.REMOVE, attributeOperationType);
+ }
+
+ public void testInvalidAttributeInstruction()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:bad-name=\"android:theme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ try {
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff6()"), input);
+ xmlDocument.getRootNode();
+ fail("Exception not thrown");
+ } catch (RuntimeException expected) {
+ // expected.
+ }
+ }
+
+ public void testOtherToolsInstruction()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:ignore=\"android:theme\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testOtherToolsInstruction"), input);
+ xmlDocument.getRootNode();
+ }
+
+
+ public void testDiff1()
+ throws Exception {
+
+ String reference = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String other = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/apk/res/android/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff1()"), reference);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff1()"), other);
+
+ assertFalse(refDocument.getRootNode().getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne").get()
+ .compareTo(otherDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne")
+ .get()).isPresent());
+ }
+
+ public void testDiff2()
+ throws Exception {
+
+ String reference = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String other = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/apk/res/android/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"mcc\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff2()"), reference);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff2()"), other);
+
+ assertTrue(refDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get()
+ .compareTo(
+ otherDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne")
+ .get()
+ ).isPresent());
+ }
+
+ public void testDiff3()
+ throws Exception {
+
+ String reference = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String other = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/apk/res/android/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\" android:exported=\"true\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff3()"), reference);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff3()"), other);
+
+ assertTrue(refDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get()
+ .compareTo(
+ otherDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne")
+ .get()
+ ).isPresent());
+ }
+
+ public void testDiff4()
+ throws Exception {
+
+ String reference = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\" android:exported=\"false\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String other = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff4()"), reference);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff4()"), other);
+ assertTrue(refDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get()
+ .compareTo(
+ otherDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne")
+ .get()
+ ).isPresent());
+ }
+
+ public void testDiff5()
+ throws Exception {
+
+ String reference = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\">\n"
+ + "\n"
+ + " </activity>\n"
+ + "</manifest>";
+
+ String other = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff5()"), reference);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff5()"), other);
+
+ assertFalse(refDocument.compareTo(otherDocument).isPresent());
+ }
+
+ public void testDiff6()
+ throws Exception {
+
+ String reference = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\">\n"
+ + "\n"
+ + " <intent-filter android:label=\"@string/foo\"/>\n"
+ + "\n"
+ + " </activity>\n"
+ + "</manifest>";
+
+ String other = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff6()"), reference);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testDiff6()"), other);
+ assertTrue(
+ refDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne").get()
+ .compareTo(
+ otherDocument.getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne")
+ .get()
+ ).isPresent()
+ );
+ }
+
+ /**
+ * test merging of same element types with no collision.
+ */
+ public void testMerge_NoCollision() throws ParserConfigurationException, SAXException, IOException {
+ String reference = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String other = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityTwo\" "
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testMerge()"), reference);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testMerge()"), other);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ XmlDocument resultDocument = result.get();
+
+ Optional<XmlElement> activityOne = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ Optional<XmlElement> activityTwo = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityTwo");
+ assertTrue(activityTwo.isPresent());
+ }
+
+ /**
+ * test merging of same element with no attribute collision.
+ */
+ public void testAttributeMerging()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " <intent-filter android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" \n"
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ XmlDocument resultDocument = result.get();
+
+ Optional<XmlElement> activityOne = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ // verify that both attributes are in the resulting merged element.
+ List<XmlAttribute> attributes = activityOne.get().getAttributes();
+ assertEquals(2, attributes.size());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:configChanges")).isPresent());
+ assertTrue(activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:name")).isPresent());
+ }
+
+ /**
+ * test merging of same element with no attribute collision.
+ */
+ public void testElementRemoval()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" tools:node=\"remove\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" \n"
+ + " android:configChanges=\"locale\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ // run the instruction cleaner to get rid of all unwanted attributes, nodes.
+ XmlDocument resultDocument = ToolsInstructionsCleaner.cleanToolsReferences(
+ result.get(), mergingReportBuilder.getLogger());
+
+ Optional<XmlElement> activityOne = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertFalse(activityOne.isPresent());
+ }
+
+ /**
+ * test merging of same element with no attribute collision.
+ */
+ public void testElementReplacement()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" tools:node=\"replace\""
+ + " android:exported=\"true\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\">\n"
+ + " android:screenOrientation=\"landscape\">\n"
+ + " <action android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ XmlDocument resultDocument = result.get();
+
+ Optional<XmlElement> activityOne = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ Logger.getAnonymousLogger().info(resultDocument.prettyPrint());
+
+
+ assertFalse(refDocument.getRootNode().getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne").get().compareTo(activityOne.get()).isPresent());
+ }
+
+ /**
+ * test merging of same element type with STRICT enforcing and no difference between the high
+ * and low priority elements.
+ */
+ public void testStrictElement_noDifference()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" tools:node=\"strict\""
+ + " android:exported=\"true\">\n"
+ + " <action android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"true\">\n"
+ + " <action android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ XmlDocument resultDocument = result.get();
+
+ Optional<XmlElement> activityOne = resultDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ Logger.getAnonymousLogger().info(resultDocument.prettyPrint());
+
+
+ assertFalse(refDocument.getRootNode().getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne").get().compareTo(activityOne.get()).isPresent());
+ }
+
+ /**
+ * test merging of same element type with STRICT enforcing and no difference between the high
+ * and low priority elements.
+ */
+ public void testStrictElement_withDifference()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" tools:node=\"strict\""
+ + " android:exported=\"true\">\n"
+ + " <action android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:screenOrientation=\"landscape\">\n"
+ + " <action android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ refDocument.merge(otherDocument, mergingReportBuilder);
+ assertEquals(MergingReport.Result.ERROR, mergingReportBuilder.build().getResult());
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReportBuilder.build()
+ .getLoggingRecords();
+ for (MergingReport.Record record : loggingRecords) {
+ Logger.getAnonymousLogger().info(record.toString());
+ }
+ assertEquals(1, loggingRecords.size());
+
+ }
+
+ /**
+ * test attributes merging of elements with no conflict between attributes.
+ */
+ public void testMerging_noConflict()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"true\">\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:screenOrientation=\"landscape\">\n"
+ + " <meta-data android:name=\"zoo\" android:value=\"@string/kangaroo\" />\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ Optional<XmlElement> activityOptional = result.get().getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOptional.isPresent());
+ XmlElement activityOne = activityOptional.get();
+ assertEquals(3, activityOne.getAttributes().size());
+ assertEquals(1, activityOne.getMergeableElements().size());
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReportBuilder.build()
+ .getLoggingRecords();
+ for (MergingReport.Record record : loggingRecords) {
+ Logger.getAnonymousLogger().info(record.toString());
+ }
+ }
+
+ /**
+ * test attributes merging of elements with no conflict between attributes.
+ */
+ public void testMerging_withConflict()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"true\">\n"
+ + " <action android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:exported=\"false\">\n"
+ + " <action android:label=\"@string/foo\"/>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ refDocument.merge(otherDocument, mergingReportBuilder);
+ assertEquals(MergingReport.Result.ERROR, mergingReportBuilder.build().getResult());
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReportBuilder.build()
+ .getLoggingRecords();
+ for (MergingReport.Record record : loggingRecords) {
+ Logger.getAnonymousLogger().info(record.toString());
+ }
+ }
+
+ /**
+ * test attributes merging of elements with no conflict between children.
+ */
+ public void testMerging_childrenDoNotConflict()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"true\">\n"
+ + " <meta-data android:name=\"fish\" android:value=\"@string/cod\" />\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:screenOrientation=\"landscape\">\n"
+ + " <meta-data android:name=\"bird\" android:value=\"@string/eagle\" />\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ Optional<XmlElement> activityOptional = result.get().getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOptional.isPresent());
+ XmlElement activityOne = activityOptional.get();
+ assertEquals(3, activityOne.getAttributes().size());
+ // both metat-data should be present.
+ assertEquals(2, activityOne.getMergeableElements().size());
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReportBuilder.build()
+ .getLoggingRecords();
+ assertTrue(loggingRecords.isEmpty());
+ }
+
+ /**
+ * test attributes merging of elements with some conflicts between children.
+ */
+ public void testMerging_childrenConflict()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"true\">\n"
+ + " <meta-data android:name=\"bird\" android:value=\"@string/hawk\" />\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:screenOrientation=\"landscape\">\n"
+ + " <meta-data android:name=\"bird\" android:value=\"@string/eagle\" />\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertFalse(result.isPresent());
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReportBuilder.build()
+ .getLoggingRecords();
+ assertEquals(1, loggingRecords.size());
+ // check the error message complains about the right attribute.
+ assertTrue(loggingRecords.get(0).toString().contains("meta-data#bird@value"));
+ }
+
+ /**
+ * test attributes merging of elements with some conflicts between children.
+ */
+ public void testMerging_differentChildrenTypes()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"true\">\n"
+ + " <meta-data android:name=\"bird\" android:value=\"@string/hawk\" />\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:screenOrientation=\"landscape\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.appwidget.action.APPWIDGET_CONFIGURE\"/>"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ Optional<XmlElement> activityOptional = result.get().getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY,
+ "com.example.lib3.activityOne");
+ assertTrue(activityOptional.isPresent());
+ XmlElement activityOne = activityOptional.get();
+ assertEquals(3, activityOne.getAttributes().size());
+ // both metat-data and intent-filter should be present.
+ assertEquals(2, activityOne.getMergeableElements().size());
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReportBuilder.build()
+ .getLoggingRecords();
+ assertTrue(loggingRecords.isEmpty());
+ }
+
+ /**
+ * test attributes merging of elements with some conflicts between children.
+ */
+ public void testHigherPriorityDefaultValue_NoOverride()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " android:protectionLevel=\"dangerous\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ Optional<XmlElement> activityOptional = result.get().getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.PERMISSION, "permissionOne");
+ assertTrue(activityOptional.get()
+ .getAttribute(XmlNode.fromXmlName("android:protectionLevel")).isPresent());
+ }
+
+ /**
+ * test attributes merging of elements with some conflicts between children.
+ */
+ public void testLowerPriorityDefaultValue_NoOverride()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\"\n"
+ + " android:protectionLevel=\"dangerous\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\">"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ Optional<XmlElement> activityOptional = result.get().getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.PERMISSION, "permissionOne");
+ assertTrue(activityOptional.get()
+ .getAttribute(XmlNode.fromXmlName("android:protectionLevel")).isPresent());
+ }
+
+ /**
+ * test attributes merging of elements with some conflicts between children.
+ */
+ public void testHigherPriorityDefaultValue_SameOverride()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ Optional<XmlElement> activityOptional = result.get().getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.PERMISSION, "permissionOne");
+ assertTrue(activityOptional.get()
+ .getAttribute(XmlNode.fromXmlName("android:protectionLevel")).isPresent());
+ }
+
+ /**
+ * test tools:node="removeAll" with several target elements to be removed.
+ */
+ public void testRemoveAll_severalTargets()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission tools:node=\"removeAll\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + " <permission android:name=\"permissionTwo\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + " <permission android:name=\"permissionThree\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ ImmutableList<XmlElement> mergeableElements = result.get().getRootNode()
+ .getMergeableElements();
+ assertEquals(1, mergeableElements.size());
+ }
+
+ /**
+ * test tools:node="removeAll" with several target elements to be removed.
+ */
+ public void testRemoveAll_oneTarget()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission tools:node=\"removeAll\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ ImmutableList<XmlElement> mergeableElements = result.get().getRootNode()
+ .getMergeableElements();
+ assertEquals(1, mergeableElements.size());
+ }
+
+ /**
+ * test tools:node="removeAll" with several target elements to be removed.
+ */
+ public void testRemoveAll_noTarget()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission tools:node=\"removeAll\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ ImmutableList<XmlElement> mergeableElements = result.get().getRootNode()
+ .getMergeableElements();
+ assertEquals(1, mergeableElements.size());
+ }
+
+ /**
+ * test tools:node="removeAll" with several target elements to be removed.
+ */
+ public void testMergeOnlyAttributes()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " android:exported=\"true\""
+ + " tools:node=\"mergeOnlyAttributes\">\n"
+ + " <meta-data android:name=\"bird\" android:value=\"@string/hawk\" />\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:screenOrientation=\"landscape\">\n"
+ + " <meta-data android:name=\"dog\" android:value=\"@string/dog\" />\n"
+ + " </activity>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument otherDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriority"), lowerPriority);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ Optional<XmlElement> activityOne = result.get().getRootNode().getNodeByTypeAndKey(
+ ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne");
+ assertTrue(activityOne.isPresent());
+
+ assertEquals(1, activityOne.get().getMergeableElements().size());
+ assertEquals(4, activityOne.get().getAttributes().size());
+
+ // check that we kept the right child from the higher priority node.
+ XmlNode.NodeName nodeName = XmlNode.fromXmlName(
+ SdkConstants.ANDROID_NS_NAME_PREFIX + SdkConstants.ATTR_NAME);
+ assertEquals("bird", activityOne.get().getMergeableElements().get(0)
+ .getAttribute(nodeName).get().getValue());
+
+ // check the records.
+ Actions actionRecorder = mergingReportBuilder.getActionRecorder().build();
+ assertEquals(3, actionRecorder.getNodeKeys().size());
+ ImmutableList<NodeRecord> nodeRecords = actionRecorder.getNodeRecords(
+ new XmlNode.NodeKey("activity#com.example.lib3.activityOne"));
+ for (NodeRecord nodeRecord : nodeRecords) {
+ if ("meta-data#dog".equals(nodeRecord.getTargetId().toString())) {
+ assertEquals(Actions.ActionType.REJECTED,
+ nodeRecord.getActionType());
+ return;
+ }
+ }
+ fail("Did not find the meta-data rejection record");
+ }
+
+ /**
+ * test tools:node="removeAll" with several target elements to be removed.
+ */
+ public void testRemove_withSelector()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " tools:node=\"remove\""
+ + " tools:selector=\"com.example.lib1\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriorityOne = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib1\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " android:protectionLevel=\"signature\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriorityTwo = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib2\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + " <permission android:name=\"permissionTwo\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ KeyResolver<String> keyResolver = (KeyResolver<String>) Mockito.mock(KeyResolver.class);
+ when(keyResolver.resolve(any(String.class))).thenReturn("valid");
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(keyResolver,
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument firstLibrary = TestUtils.xmlDocumentFromString(keyResolver,
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityOne"), lowerPriorityOne);
+ XmlDocument secondLibrary = TestUtils.xmlDocumentFromString(keyResolver,
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityTwo"), lowerPriorityTwo);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(firstLibrary, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ result = result.get().merge(secondLibrary, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ ImmutableList<XmlElement> mergeableElements = result.get().getRootNode()
+ .getMergeableElements();
+ assertEquals(2, mergeableElements.size());
+ }
+
+ /**
+ * test tools:node="removeAll" with several target elements to be removed.
+ */
+ public void testRemoveAll_withSelector()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission"
+ + " tools:node=\"removeAll\"\n"
+ + " tools:selector=\"com.example.lib1\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriorityOne = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib1\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " android:protectionLevel=\"signature\">\n"
+ + " </permission>\n"
+ + " <permission android:name=\"permissionTwo\""
+ + " android:protectionLevel=\"signature\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriorityTwo = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib2\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionThree\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + " <permission android:name=\"permissionFour\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument firstLibrary = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityOne"), lowerPriorityOne);
+ XmlDocument secondLibrary = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityTwo"), lowerPriorityTwo);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(firstLibrary, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ result = result.get().merge(secondLibrary, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ ImmutableList<XmlElement> mergeableElements = result.get().getRootNode()
+ .getMergeableElements();
+ assertEquals(3, mergeableElements.size());
+ XmlNode.NodeName nodeName = XmlNode.fromXmlName("android:name");
+ assertEquals("permissionThree",
+ mergeableElements.get(1).getAttribute(nodeName).get().getValue());
+ assertEquals("permissionFour",
+ mergeableElements.get(2).getAttribute(nodeName).get().getValue());
+ }
+
+ /**
+ * test tools:node="removeAll" with several target elements to be removed.
+ */
+ public void testInvalidSelector()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " tools:node=\"remove\""
+ + " tools:selector=\"com.example.libXYZ\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriorityOne = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib1\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " android:protectionLevel=\"signature\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument firstLibrary = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityOne"), lowerPriorityOne);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(firstLibrary, mergingReportBuilder);
+ assertFalse(result.isPresent());
+
+ ImmutableList<MergingReport.Record> loggingRecords = mergingReportBuilder.build()
+ .getLoggingRecords();
+ assertEquals(1, loggingRecords.size());
+ assertEquals(MergingReport.Record.Severity.ERROR, loggingRecords.get(0).getSeverity());
+ assertTrue(loggingRecords.get(0).getMessage().contains("tools:selector"));
+ }
+
+ /**
+ * test tools:node="removeAll" with several target elements to be removed.
+ */
+ public void testRemoveAndRemoveAll_withReplace()
+ throws ParserConfigurationException, SAXException, IOException {
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <permission"
+ + " android:name=\"permissionOne\"\n"
+ + " tools:node=\"remove\"\n"
+ + " tools:selector=\"com.example.lib1\">\n"
+ + " </permission>\n"
+ + " <permission"
+ + " tools:node=\"removeAll\"\n"
+ + " tools:selector=\"com.example.lib3\">\n"
+ + " </permission>\n"
+ + " <permission android:name=\"permissionThree\""
+ + " android:protectionLevel=\"signature\""
+ + " tools:node=\"replace\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriorityOne = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib1\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionOne\""
+ + " android:protectionLevel=\"signature\">\n"
+ + " </permission>\n"
+ + " <permission android:name=\"permissionTwo\""
+ + " android:protectionLevel=\"signature\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriorityTwo = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib2\">\n"
+ + "\n"
+ + " <permission android:name=\"permissionThree\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + " <permission android:name=\"permissionFour\""
+ + " android:protectionLevel=\"normal\">\n"
+ + " </permission>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+ XmlDocument firstLibrary = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityOne"), lowerPriorityOne);
+ XmlDocument secondLibrary = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityTwo"), lowerPriorityTwo);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(firstLibrary, mergingReportBuilder);
+ assertTrue(result.isPresent());
+ result = result.get().merge(secondLibrary, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ ImmutableList<XmlElement> mergeableElements = result.get().getRootNode()
+ .getMergeableElements();
+ // I should have permissionTwo, permissionThree (replaced), permissionFour
+ // + remove and removeAll not cleaned
+ assertEquals(5, mergeableElements.size());
+ XmlNode.NodeName nodeName = XmlNode.fromXmlName("android:name");
+ for (int i = 0; i < 5; i++) {
+ XmlElement xmlElement = mergeableElements.get(i);
+ Optional<XmlAttribute> optionalName = xmlElement.getAttribute(nodeName);
+ if (!optionalName.isPresent()) {
+ continue;
+ }
+ String elementName = optionalName.get().getValue();
+ if (elementName.equals("permissionThree")) {
+ assertEquals("signature", xmlElement.getAttribute(
+ XmlNode.fromXmlName("android:protectionLevel")).get().getValue());
+ } else {
+ if (!elementName.equals("permissionOne") && !elementName.equals("permissionTwo")
+ && !elementName.equals("permissionFour")) {
+ fail("Unexepected permission " + elementName);
+ }
+ }
+
+ }
+ }
+
+ public void testCompatibleScreens()
+ throws ParserConfigurationException, SAXException, IOException {
+
+ String higherPriority = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <compatible-screens>\n"
+ + " <!-- all small size screens -->\n"
+ + " <screen android:screenSize=\"small\" android:screenDensity=\"ldpi\" />\n"
+ + " <screen android:screenSize=\"small\" android:screenDensity=\"mdpi\" />\n"
+ + " <screen android:screenSize=\"small\" android:screenDensity=\"xhdpi\" />\n"
+ + " <!-- all normal size screens -->\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"ldpi\" />\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"hdpi\" />\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"xhdpi\" />\n"
+ + " </compatible-screens>"
+ + "\n"
+ + "</manifest>";
+
+ String lowerPriorityOne = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib1\">\n"
+ + "\n"
+ + " <compatible-screens>\n"
+ + " <!-- all small size screens -->\n"
+ + " <screen android:screenSize=\"small\" android:screenDensity=\"ldpi\" />\n"
+ + " <screen android:screenSize=\"small\" android:screenDensity=\"mdpi\" />\n"
+ + " <screen android:screenSize=\"small\" android:screenDensity=\"hdpi\" />\n"
+ + " <!-- all normal size screens -->\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"mdpi\" />\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"hdpi\" />\n"
+ + " <screen android:screenSize=\"normal\" android:screenDensity=\"xhdpi\" />\n"
+ + " </compatible-screens>"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "higherPriority"), higherPriority);
+
+ XmlDocument firstLibrary = TestUtils.xmlLibraryFromString(
+ new TestUtils.TestSourceLocation(getClass(), "lowerPriorityOne"), lowerPriorityOne);
+
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(
+ new StdLogger(StdLogger.Level.VERBOSE));
+ Optional<XmlDocument> result = refDocument.merge(firstLibrary, mergingReportBuilder);
+ assertTrue(result.isPresent());
+
+ ImmutableList<XmlElement> mergeableElements = result.get().getRootNode()
+ .getMergeableElements();
+
+ assertEquals(1, mergeableElements.size());
+ ImmutableList<XmlElement> mergedScreens = mergeableElements.get(0)
+ .getMergeableElements();
+
+ // we should have merged screens with no duplicated elements.
+ assertEquals(8, mergedScreens.size());
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlLoaderTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlLoaderTest.java
new file mode 100644
index 0000000..65032a2
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlLoaderTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ide.common.xml.XmlFormatPreferences;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.utils.PositionXmlParser;
+import com.google.common.base.Optional;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for {@link XmlLoader}
+ */
+public class XmlLoaderTest extends TestCase {
+
+ public void testAndroidPrefix() throws IOException, SAXException, ParserConfigurationException {
+
+ String input = ""
+ + "<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"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),"testToolsPrefix()"), input);
+ Optional<XmlElement> applicationOptional = xmlDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
+ assertTrue(applicationOptional.isPresent());
+ Node label = applicationOptional.get().getXml().getAttributes().item(0);
+ assertEquals("label", label.getLocalName());
+ assertEquals(SdkConstants.ANDROID_URI, label.getNamespaceURI());
+ assertEquals("android:label", label.getNodeName());
+ }
+
+ public void testPrettyPrint() throws IOException, SAXException, ParserConfigurationException {
+
+ String input = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\" >\n"
+ + "\n"
+ + " <!-- this is a comment -->\n"
+ + " <application android:label=\"@string/lib_name\" >\n"
+ + "\n"
+ + " <!-- The activity name will be expanded to its full FQCN by default. -->\n"
+ + " <activity\n"
+ + " android:name=\"com.example.lib3.MainActivity\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <intent-filter>\n\n"
+ + " <!-- some other comment -->\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + "\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),"testPrettyPrint()"), input);
+ Optional<XmlElement> applicationOptional = xmlDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
+ assertTrue(applicationOptional.isPresent());
+ String prettyPrinted = XmlPrettyPrinter
+ .prettyPrint(xmlDocument.getXml(), XmlFormatPreferences.defaults(),
+ XmlFormatStyle.get(xmlDocument.getRootNode().getXml()), null, false);
+ assertEquals(input, prettyPrinted);
+ }
+
+ public void testToolsPrefix() throws IOException, SAXException, ParserConfigurationException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" tools:node=\"replace\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(),"testToolsPrefix()"),input);
+ Optional<XmlElement> applicationOptional = xmlDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
+ assertTrue(applicationOptional.isPresent());
+ Element application = applicationOptional.get().getXml();
+ assertEquals(2, application.getAttributes().getLength());
+ Attr label = application.getAttributeNodeNS(SdkConstants.ANDROID_URI, "label");
+ assertEquals("android:label", label.getNodeName());
+ Attr tools = application.getAttributeNodeNS(SdkConstants.TOOLS_URI,
+ NodeOperationType.NODE_LOCAL_NAME);
+ assertEquals("replace", tools.getNodeValue());
+
+ // check positions.
+ PositionXmlParser.Position applicationPosition = applicationOptional.get().getPosition();
+ assertNotNull(applicationPosition);
+ assertEquals(6, applicationPosition.getLine());
+ assertEquals(5, applicationPosition.getColumn());
+
+ XmlAttribute xmlAttribute =
+ new XmlAttribute(applicationOptional.get(), tools, null /* AttributeModel */);
+ PositionXmlParser.Position toolsPosition = xmlAttribute.getPosition();
+ assertNotNull(toolsPosition);
+ assertEquals(6, toolsPosition.getLine());
+ assertEquals(51, toolsPosition.getColumn());
+ }
+
+ public void testUnusualPrefixes()
+ throws IOException, SAXException, ParserConfigurationException {
+
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:x=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:y=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application x:label=\"@string/lib_name\" y:node=\"replace\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ XmlDocument xmlDocument = TestUtils.xmlDocumentFromString(
+ new TestUtils.TestSourceLocation(getClass(), "testUnusualPrefixes()"), input);
+ Optional<XmlElement> applicationOptional = xmlDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
+ assertTrue(applicationOptional.isPresent());
+ Element application = applicationOptional.get().getXml();
+ assertEquals(2, application.getAttributes().getLength());
+ Node label = application.getAttributeNodeNS(SdkConstants.ANDROID_URI, "label");
+ assertEquals("x:label", label.getNodeName());
+ Node tools = application.getAttributeNodeNS(SdkConstants.TOOLS_URI,
+ NodeOperationType.NODE_LOCAL_NAME);
+ assertEquals("y:node", tools.getNodeName());
+ assertEquals("replace", tools.getNodeValue());
+ }
+
+
+
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlNodeTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlNodeTest.java
new file mode 100644
index 0000000..0ee376e
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlNodeTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static org.mockito.Mockito.when;
+
+import com.android.SdkConstants;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * Tests for the {@link XmlNode} class
+ */
+public class XmlNodeTest extends TestCase {
+
+ @Mock Node mNodeOne;
+ @Mock Node mNodeTwo;
+ @Mock Element mElement;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testNoNamespace() {
+ when(mNodeOne.getNodeName()).thenReturn("my-name");
+
+ XmlNode.NodeName nodeName = XmlNode.unwrapName(mNodeOne);
+ assertFalse(nodeName.isInNamespace(SdkConstants.ANDROID_URI));
+ assertEquals("my-name", nodeName.toString());
+ }
+
+ public void testNoNamespaceEqualityAndHashCoding() {
+ when(mNodeOne.getNodeName()).thenReturn("my-name");
+ when(mNodeTwo.getNodeName()).thenReturn("my-name");
+
+ assertEquals(XmlNode.unwrapName(mNodeOne), XmlNode.unwrapName(mNodeTwo));
+ assertEquals(XmlNode.unwrapName(mNodeOne).hashCode(),
+ XmlNode.unwrapName(mNodeTwo).hashCode());
+ }
+
+ public void testNoNamespaceInequalityAndHashCoding() {
+ when(mNodeOne.getNodeName()).thenReturn("my-name");
+ when(mNodeTwo.getNodeName()).thenReturn("my-other-name");
+
+ assertNotSame(XmlNode.unwrapName(mNodeOne), XmlNode.unwrapName(mNodeTwo));
+ assertNotSame(XmlNode.unwrapName(mNodeOne).hashCode(),
+ XmlNode.unwrapName(mNodeTwo).hashCode());
+ }
+
+ public void testNamespace() {
+ when(mNodeOne.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mNodeOne.getLocalName()).thenReturn("my-name");
+ when(mNodeOne.getPrefix()).thenReturn("android");
+
+ XmlNode.NodeName nodeName = XmlNode.unwrapName(mNodeOne);
+ assertTrue(nodeName.isInNamespace(SdkConstants.ANDROID_URI));
+ assertEquals("android:my-name", nodeName.toString());
+ }
+
+ public void testNamespaceEqualityAndHashCoding() {
+ // different local name.
+ when(mNodeOne.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mNodeOne.getLocalName()).thenReturn("my-name");
+ when(mNodeOne.getPrefix()).thenReturn("android");
+
+ when(mNodeTwo.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mNodeTwo.getLocalName()).thenReturn("my-name");
+ when(mNodeTwo.getPrefix()).thenReturn("android");
+
+ assertEquals(XmlNode.unwrapName(mNodeOne), XmlNode.unwrapName(mNodeTwo));
+ assertEquals(XmlNode.unwrapName(mNodeOne).hashCode(),
+ XmlNode.unwrapName(mNodeTwo).hashCode());
+
+ // different namespace.
+ when(mNodeOne.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mNodeOne.getLocalName()).thenReturn("my-name");
+ when(mNodeOne.getPrefix()).thenReturn("android");
+
+ when(mNodeTwo.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mNodeTwo.getLocalName()).thenReturn("my-name");
+ // another prefix should not matter, they are still the same xml names.
+ when(mNodeTwo.getPrefix()).thenReturn("y");
+
+ assertEquals(XmlNode.unwrapName(mNodeOne), XmlNode.unwrapName(mNodeTwo));
+ assertEquals(XmlNode.unwrapName(mNodeOne).hashCode(),
+ XmlNode.unwrapName(mNodeTwo).hashCode());
+ }
+
+ public void testNamespaceInequality() {
+ when(mNodeOne.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mNodeOne.getLocalName()).thenReturn("my-name");
+ when(mNodeOne.getPrefix()).thenReturn("android");
+
+ when(mNodeTwo.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mNodeTwo.getLocalName()).thenReturn("my-other-name");
+ when(mNodeTwo.getPrefix()).thenReturn("android");
+
+ assertNotSame(XmlNode.unwrapName(mNodeOne), XmlNode.unwrapName(mNodeTwo));
+ assertNotSame(XmlNode.unwrapName(mNodeOne).hashCode(),
+ XmlNode.unwrapName(mNodeTwo).hashCode());
+
+
+ when(mNodeOne.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mNodeOne.getLocalName()).thenReturn("my-name");
+ when(mNodeOne.getPrefix()).thenReturn("android");
+
+ when(mNodeTwo.getNamespaceURI()).thenReturn(SdkConstants.TOOLS_URI);
+ when(mNodeTwo.getLocalName()).thenReturn("my-name");
+ when(mNodeTwo.getPrefix()).thenReturn("android");
+
+ assertNotSame(XmlNode.unwrapName(mNodeOne), XmlNode.unwrapName(mNodeTwo));
+ assertNotSame(XmlNode.unwrapName(mNodeOne).hashCode(),
+ XmlNode.unwrapName(mNodeTwo).hashCode());
+ }
+
+ public void testAddAttributeToNode() {
+ when(mNodeOne.getNodeName()).thenReturn("my-name");
+
+ XmlNode.NodeName nodeName = XmlNode.unwrapName(mNodeOne);
+ nodeName.addToNode(mElement, "my-value");
+ Mockito.verify(mElement).setAttribute("my-name", "my-value");
+ Mockito.verifyNoMoreInteractions(mElement);
+ }
+
+ public void testAddNamespaceAwareAttributeToNode() {
+ when(mNodeOne.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI);
+ when(mNodeOne.getLocalName()).thenReturn("my-name");
+ when(mNodeOne.getPrefix()).thenReturn("android");
+
+ XmlNode.NodeName nodeName = XmlNode.unwrapName(mNodeOne);
+ nodeName.addToNode(mElement, "my-value");
+ Mockito.verify(mElement).setAttributeNS(
+ SdkConstants.ANDROID_URI, "android:my-name", "my-value");
+ Mockito.verifyNoMoreInteractions(mElement);
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/00_noop.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/00_noop.xml
new file mode 100755
index 0000000..27af1ca
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/00_noop.xml
@@ -0,0 +1,216 @@
+#
+# Syntax:
+# - Lines starting with # are ignored (anywhere, as long as # is the first char).
+# - Lines before the first @delimiter are ignored.
+# - Empty lines just after the @delimiter and before the first < XML line are ignored.
+# - Valid delimiters are @main for the XML of the main app manifest.
+# - Following delimiters are @libXYZ, read in the order of definition. The name can be
+# anything as long as it starts with "@lib".
+# - Last delimiter should be @result.
+#
+
+@main
+
+<!--
+ This is a canonical manifest that has some uses-permissions,
+ the usual uses-sdk and supports-screens, an app with an activity,
+ customer receiver & service and a widget.
+-->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+</manifest>
+
+
+@result
+
+<!--
+ This is a canonical manifest that has some uses-permissions,
+ the usual uses-sdk and supports-screens, an app with an activity,
+ customer receiver & service and a widget.
+-->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider
+ android:name="com.example.app1.app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ </application>
+
+</manifest>
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/01_ignore_app_attr.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/01_ignore_app_attr.xml
new file mode 100755
index 0000000..b939dd7
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/01_ignore_app_attr.xml
@@ -0,0 +1,69 @@
+#
+# Test:
+# - Attributes from the application element in a library are ignored (except name)
+# - Comments from nodes ignored in libraries are not merged either.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- Source comments are preserved as-is. -->
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- The attributes from <application> in a library are not merged nor checked
+ except for 'name' and 'backupAgent' which must match.
+ This comment is ignored. -->
+ <application
+ android:label="@string/lib_name"
+ android:icon="@drawable/lib_icon"
+ android:name="com.example.TheApp" >
+ </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">
+
+ <!-- Source comments are preserved as-is. -->
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/02_ignore_instrumentation.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/02_ignore_instrumentation.xml
new file mode 100755
index 0000000..ed0dbbc
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/02_ignore_instrumentation.xml
@@ -0,0 +1,62 @@
+#
+# Test:
+# - Instrumentation element from libraries are not merged in main manifest.
+#
+
+@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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- Instrumentation is not merged from libraries. -->
+ <instrumentation
+ android:targetPackage="com.example.app1"
+ android:name="android.test.InstrumentationTestRunner" />
+
+</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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/03_inject_attributes.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/03_inject_attributes.xml
new file mode 100755
index 0000000..0c9bcdd
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/03_inject_attributes.xml
@@ -0,0 +1,57 @@
+#
+# Test:
+# - Inject attributes in a main manifest.
+#
+
+@inject
+versionCode=101
+versionName=1.0.1
+minSdkVersion=10
+targetSdkVersion=14
+
+@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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+ </application>
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="101"
+ android:versionName="1.0.1">
+ <uses-sdk
+ android:minSdkVersion="10"
+ android:targetSdkVersion="14">
+ </uses-sdk>
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/04_inject_attributes.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/04_inject_attributes.xml
new file mode 100755
index 0000000..57f4e84
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/04_inject_attributes.xml
@@ -0,0 +1,63 @@
+#
+# Test:
+# - Inject attributes in a main manifest.
+# The attributes are injected and then the merge is done. In this case the app
+# starts with a minSdkVersion of 20, which is higher than the lib1's 15 value.
+# However the injection replaces it by 10, which is now lower than the lib's
+# version and thus a warning will be generated.
+#
+
+@fails
+
+@inject
+/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion=10
+/manifest/uses-sdk|http://schemas.android.com/apk/res/android targetSdkVersion=14
+/manifest/application|http://schemas.android.com/apk/res/android label=null
+/manifest/application|http://schemas.android.com/apk/res/android icon=null
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="20" android:targetSdkVersion="21"/>
+
+ <application android:name="com.example.TheApp" />
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="16"/>
+
+ <application android:name="com.example.TheApp" />
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="14"/>
+
+ <application android:name="com.example.TheApp" />
+
+</manifest>
+
+@errors
+
+E [ManifestMergerTest0_main.xml:3, ManifestMergerTest1_lib1.xml:3] Main manifest has <uses-sdk android:minSdkVersion='10'> but library uses minSdkVersion='15'
+W [ManifestMergerTest0_main.xml:3, ManifestMergerTest1_lib1.xml:3] Main manifest has <uses-sdk android:targetSdkVersion='14'> but library uses targetSdkVersion='16'
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package.xml
new file mode 100755
index 0000000..4fa4442
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package.xml
@@ -0,0 +1,44 @@
+#
+# Test:
+# - Replace the package in the manifest.
+#
+
+@package
+com.example.app1.injected
+
+@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:label="@string/app_name"
+ android:name="com.example.app1.TheApp" >
+ <activity
+ android:name=".Main" />
+ </application>
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1.injected"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:label="@string/app_name"
+ android:name="com.example.app1.TheApp" >
+ <activity
+ android:name="com.example.app1.Main" />
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_placeholder.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_placeholder.xml
new file mode 100755
index 0000000..08e49d8
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_placeholder.xml
@@ -0,0 +1,71 @@
+#
+# Test:
+# - Replace the package in the manifest.
+#
+
+@package
+com.example.app1.injected
+
+@main
+
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:label="@string/app_name"
+ android:name="com.example.app1.TheApp" >
+ <activity
+ android:name=".Main">
+ <intent-filter>
+ <action android:name="${packageName}.foo">
+ </action>
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1.injected"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:label="@string/app_name"
+ android:name="com.example.app1.TheApp" >
+ <activity
+ android:name="com.example.app1.Main" >
+ <intent-filter>
+ <action android:name="com.example.app1.injected.foo">
+ </action>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/06_inject_attributes_with_specific_prefix.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/06_inject_attributes_with_specific_prefix.xml
new file mode 100755
index 0000000..d811f8b
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/06_inject_attributes_with_specific_prefix.xml
@@ -0,0 +1,57 @@
+#
+# Test:
+# - Inject attributes in a main manifest.
+#
+
+@inject
+versionCode=101
+versionName=1.0.1
+minSdkVersion=10
+targetSdkVersion=14
+
+@main
+
+<manifest
+ xmlns:t="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ t:versionCode="100"
+ t:versionName="1.0.0">
+
+ <application
+ t:label="@string/app_name"
+ t:icon="@drawable/app_icon"
+ t:backupAgent="com.example.app.BackupAgentClass"
+ t:restoreAnyVersion="true"
+ t:allowBackup="true"
+ t:killAfterRestore="true"
+ t:name="com.example.TheApp" >
+ </application>
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:t="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ t:versionCode="101"
+ t:versionName="1.0.1">
+ <uses-sdk
+ t:minSdkVersion="10"
+ t:targetSdkVersion="14">
+ </uses-sdk>
+
+ <application
+ t:label="@string/app_name"
+ t:icon="@drawable/app_icon"
+ t:backupAgent="com.example.app.BackupAgentClass"
+ t:restoreAnyVersion="true"
+ t:allowBackup="true"
+ t:killAfterRestore="true"
+ t:name="com.example.TheApp" >
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/07_no_package_provided.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/07_no_package_provided.xml
new file mode 100755
index 0000000..8c233e5
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/07_no_package_provided.xml
@@ -0,0 +1,117 @@
+#
+# Syntax:
+# - Lines starting with # are ignored (anywhere, as long as # is the first char).
+# - Lines before the first @delimiter are ignored.
+# - Empty lines just after the @delimiter and before the first < XML line are ignored.
+# - Valid delimiters are @main for the XML of the main app manifest.
+# - Following delimiters are @libXYZ, read in the order of definition. The name can be
+# anything as long as it starts with "@lib".
+# - Last delimiter should be @result.
+#
+
+@main
+
+<!--
+ This is a canonical manifest that has some uses-permissions,
+ the usual uses-sdk and supports-screens, an app with an activity,
+ customer receiver & service and a widget.
+-->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+# An empty library is not supported. It must be a valid XML file.
+<manifest/>
+
+
+@errors
+ERROR:Main AndroidManifest.xml at ManifestMerger2Test0_main.xml manifest:package attribute is not declared
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08_no_library_package_provided.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08_no_library_package_provided.xml
new file mode 100755
index 0000000..4d2b87a
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08_no_library_package_provided.xml
@@ -0,0 +1,208 @@
+#
+# Syntax:
+# - Lines starting with # are ignored (anywhere, as long as # is the first char).
+# - Lines before the first @delimiter are ignored.
+# - Empty lines just after the @delimiter and before the first < XML line are ignored.
+# - Valid delimiters are @main for the XML of the main app manifest.
+# - Following delimiters are @libXYZ, read in the order of definition. The name can be
+# anything as long as it starts with "@lib".
+# - Last delimiter should be @result.
+#
+
+@main
+
+<!--
+ This is a canonical manifest that has some uses-permissions,
+ the usual uses-sdk and supports-screens, an app with an activity,
+ customer receiver & service and a widget.
+-->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+# An empty library is not supported. It must be a valid XML file.
+<manifest/>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ </application>
+</manifest>
+
+@errors
+WARNING:Missing 'package' declaration in manifest at ManifestMerger2Test1_lib1.xml:1:1
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/10_activity_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/10_activity_merge.xml
new file mode 100755
index 0000000..7826532
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/10_activity_merge.xml
@@ -0,0 +1,386 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that a normal merge is done as expected.
+# There's a warning because one of the activities from lib2 is already defined
+# in the main but it's purely identical so it's not an error.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="19"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp"
+ tools:replace="label">
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- This is exactly the same as in lib2_activity -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+
+ <!-- When comparing duplicate elements, whitespace and comments are ignored. -->
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ </activity>
+
+ <!-- end of the main manifest's application element. Note that the
+ merger will insert at the end of this comment, in the specific
+ order activity, activity-alias, service, receiver and provider. -->
+
+ </application>
+
+</manifest>
+
+
+@lib1_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="19"/>
+
+ <application android:label="@string/lib_name" >
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+
+@lib2_activity
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="19"/>
+
+ <application android:label="@string/lib_name" >
+
+ <!-- This won't be merged because there's already an identical definition in the main. -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ <!-- This one does not conflict with the main -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib3_alias
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3">
+
+ <!-- This comment is ignored. -->
+
+ <application android:label="@string/lib_name" >
+
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- This is a dup of the 2nd activity in lib2 -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="19"/>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="19"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- This is exactly the same as in lib2_activity -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+
+ <!-- When comparing duplicate elements, whitespace and comments are ignored. -->
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ </activity>
+
+ <!-- end of the main manifest's application element. Note that the
+ merger will insert at the end of this comment, in the specific
+ order activity, activity-alias, service, receiver and provider. -->
+
+# from @lib1_widget
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+# from @lib2_activity
+ <!-- This one does not conflict with the main -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name="com.example.lib2.app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+# from @lib3_alias
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+@errors
+
+P [ManifestMergerTest0_main.xml:31, ManifestMergerTest2_lib2_activity.xml:6] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element.
+P [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:19] Skipping identical /manifest/application/activity[@name=com.example.LibActivity2] element.
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11_activity_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11_activity_dup.xml
new file mode 100755
index 0000000..20d50fe
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11_activity_dup.xml
@@ -0,0 +1,378 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that an error is generated because the libraries define
+# activities which are already in the main, with slightly different XML content:
+# number and *order* of elements must match, attributes must match.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp"
+ tools:replace="label">
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+
+ <!-- Conflict with lib1 -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI">
+ <!-- intent-filter child should be merged. -->
+ </activity>
+
+
+ <!-- Conflict with lib2, no conflict, should be merged -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon">
+ <!-- missing attribute android:theme="@style/Lib.Theme" -->
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <application android:label="@string/lib_name" >
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+
+@lib2_activity
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application android:label="@string/lib_name" >
+
+ <!-- This won't be merged because there's already an identical definition in the main. -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ <!-- This one does not conflict with the main -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib3_alias
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3">
+
+ <!-- This comment is ignored. -->
+
+ <application android:label="@string/lib_name" >
+
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- This will conflict with the 2nd one from lib2 -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.MOARCATZPLZ" />
+ </intent-filter>
+ </activity>
+
+ </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">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+
+ <!-- Conflict with lib1 -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <!-- intent-filter child should be merged. -->
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+
+ <!-- Conflict with lib2, no conflict, should be merged -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+# from @lib1_widget
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+# from @lib2_activity
+ <!-- This one does not conflict with the main -->
+ <activity android:icon="@drawable/lib_activity_icon" android:label="@string/lib_activity_name" android:name="com.example.LibActivity2" android:theme="@style/Lib.Theme">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.MOARCATZPLZ"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name="com.example.lib2.app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+# from @lib3_alias
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11b_activity_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11b_activity_dup.xml
new file mode 100755
index 0000000..e037279
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11b_activity_dup.xml
@@ -0,0 +1,376 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that an error is generated because the libraries define
+# activities which are already in the main, with slightly different XML content:
+# number and *order* of elements must match, attributes must match.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp"
+ tools:replace="label">
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+
+ <!-- Conflict with lib1 -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI"
+ tools:node="strict">
+ <!-- missing the intent-filter -->
+ </activity>
+
+
+ <!-- Conflict with lib2 -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ tools:node="strict">
+ <!-- missing attribute android:theme="@style/Lib.Theme" -->
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <application android:label="@string/lib_name" >
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+
+@lib2_activity
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application android:label="@string/lib_name" >
+
+ <!-- This won't be merged because there's already an identical definition in the main. -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ <!-- This one does not conflict with the main -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib3_alias
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3">
+
+ <!-- This comment is ignored. -->
+
+ <application android:label="@string/lib_name" >
+
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- This will conflict with the 2nd one from lib2 -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.MOARCATZPLZ" />
+ </intent-filter>
+ </activity>
+
+ </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">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+
+ <!-- Conflict with lib1 -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <!-- missing the intent-filter -->
+ </activity>
+
+
+ <!-- Conflict with lib2 -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon">
+ <!-- missing attribute android:theme="@style/Lib.Theme" -->
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+# from @lib1_widget
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+# from @lib2_activity
+ <!-- This one does not conflict with the main -->
+ <activity android:icon="@drawable/lib_activity_icon" android:label="@string/lib_activity_name" android:name="com.example.LibActivity2" android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name="com.example.lib2.app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+# from @lib3_alias
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+
+@errors
+
+ERROR:Node activity#com.example.WidgetConfigurationUI at (59,9) file:ManifestMerger2Test0_main.xml:59 is tagged with tools:node="strict", yet activity#com.example.WidgetConfigurationUI at (26,9) file:ManifestMerger2Test1_lib1_widget.xml:26 is different : activity#com.example.WidgetConfigurationUI: Number of children do not match up: expected 0 versus 1 at (26,9) file:ManifestMerger2Test1_lib1_widget.xml:26
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/12_alias_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/12_alias_dup.xml
new file mode 100755
index 0000000..7d84b3e
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/12_alias_dup.xml
@@ -0,0 +1,195 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+# - To no generate an error, a tools:replace instruction can be set to
+# explicitely override lower priority nodes.
+#
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp"
+ tools:replace="label">
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity1"
+ android:targetActivity="com.example.MainActivity1"
+ android:label="@string/alias_name1"
+ android:icon="@drawable/alias_icon1"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity2"
+ android:targetActivity="com.example.MainActivity2"
+ android:label="@string/alias_name2"
+ android:icon="@drawable/alias_icon2"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <application android:label="@string/lib_name1" >
+
+ <!-- Same as 1 in main -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity1"
+ android:targetActivity="com.example.MainActivity1"
+ android:label="@string/alias_name1"
+ android:icon="@drawable/alias_icon1"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- Differs from 2 in main -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity2"
+ android:targetActivity="com.example.MainActivity2">
+ </activity-alias>
+
+ <!-- A new one defined by lib1 -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity3"
+ android:targetActivity="com.example.MainActivity3"
+ android:label="@string/alias_name3"
+ android:icon="@drawable/alias_icon3"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application android:label="@string/lib_name2" >
+
+ <!-- Conflicts with 3 from lib1 -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity3"
+ android:label="@string/alias_name3"
+ android:icon="@drawable/alias_icon3">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER2" />
+ </intent-filter>
+ </activity-alias>
+ </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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity1"
+ android:targetActivity="com.example.MainActivity1"
+ android:label="@string/alias_name1"
+ android:icon="@drawable/alias_icon1"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity2"
+ android:targetActivity="com.example.MainActivity2"
+ android:label="@string/alias_name2"
+ android:icon="@drawable/alias_icon2"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- A new one defined by lib1 -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity3"
+ android:targetActivity="com.example.MainActivity3"
+ android:label="@string/alias_name3"
+ android:icon="@drawable/alias_icon3"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER2"/>
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/13_service_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/13_service_dup.xml
new file mode 100755
index 0000000..b594110
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/13_service_dup.xml
@@ -0,0 +1,155 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+# - To no generate an error, a tools:replace instruction can be set to
+# explicitely override lower priority nodes.
+#
+# The services definition although duplicated in their definition do not have
+# conflicting attributes or sub elements and therefore should be merged
+# successfully.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp"
+ tools:replace="label">
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService1" />
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService2" />
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <application android:label="@string/lib_name1" >
+
+ <!-- Same as 1 in main -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService1" />
+
+ <!-- Differs from 2 in main -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService2" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </service>
+
+ <!-- A new one defined by lib1 -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService3" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </service>
+
+ </application>
+
+</manifest>
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application android:label="@string/lib_name2" >
+
+ <!-- Conflicts with 3 from lib1, but with same values -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService3" />
+
+ </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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService1">
+ </service>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService2" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </service>
+
+ <!-- A new one defined by lib1 -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService3" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </service>
+
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/14_receiver_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/14_receiver_dup.xml
new file mode 100755
index 0000000..337155e
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/14_receiver_dup.xml
@@ -0,0 +1,160 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# This tests passes since all conflicting attributes are explicitely replaced.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp"
+ tools:replace="label">
+
+ <receiver
+ android:name="com.example.AppReceiver1"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <receiver
+ android:name="com.example.AppReceiver2"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <application android:label="@string/lib_name1" >
+
+ <!-- Same as 1 in main -->
+ <receiver
+ android:name="com.example.AppReceiver1"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Differs from 2 in main but no conflicting attribute -->
+ <receiver
+ android:name="com.example.AppReceiver2" />
+
+ <!-- A new one defined by lib1 -->
+ <receiver
+ android:name="com.example.AppReceiver3"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM1" />
+ <action android:name="com.example.action.ACTION_CUSTOM2" />
+ <action android:name="com.example.action.ACTION_CUSTOM3" />
+ </intent-filter>
+ </receiver>
+
+ </application>
+
+</manifest>
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application android:label="@string/lib_name2" >
+
+ <!-- also defined in lib1, but no conflicting actions.-->
+ <receiver
+ android:name="com.example.AppReceiver3"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+ </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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <receiver
+ android:name="com.example.AppReceiver1"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <receiver
+ android:name="com.example.AppReceiver2"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- A new one defined by lib1 -->
+ <receiver
+ android:name="com.example.AppReceiver3"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM1" />
+ <action android:name="com.example.action.ACTION_CUSTOM2" />
+ <action android:name="com.example.action.ACTION_CUSTOM3" />
+ </intent-filter>
+ </receiver>
+
+ </application>
+
+</manifest>
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/15_provider_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/15_provider_dup.xml
new file mode 100755
index 0000000..dbd265e
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/15_provider_dup.xml
@@ -0,0 +1,136 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that an error is generated because the libraries define
+# providers which are already defined differently.
+#
+
+@fails
+
+@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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <provider
+ android:name="com.example.Provider1"
+ android:authorities="com.example.android.apis.app.thingy1"
+ android:enabled="@bool/someConditionalValue" />
+
+ <provider
+ android:name="com.example.Provider2" />
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <application>
+
+ <!-- Same as MyActivity1 in main -->
+ <provider
+ android:name="com.example.Provider1"
+ android:authorities="com.example.android.apis.app.thingy1"
+ android:enabled="@bool/someConditionalValue" />
+
+ <!-- Differs from MyActivity2 in main -->
+ <provider
+ android:name="com.example.Provider2"
+ android:authorities="com.example.android.apis.app.thingy2"
+ android:enabled="@bool/someConditionalValue2" />
+
+ <!-- A new one defined by lib1 -->
+ <provider
+ android:name="com.example.Provider3"
+ android:authorities="com.example.android.apis.app.thingy3"
+ android:enabled="@bool/someConditionalValue" />
+
+ </application>
+
+</manifest>
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application>
+
+ <!-- Conflicts with 3 from lib1 -->
+ <provider
+ android:name="com.example.Provider3"
+ android:authorities="com.example.android.apis.app.thingy3" />
+ </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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <provider
+ android:name="com.example.Provider1"
+ android:authorities="com.example.android.apis.app.thingy1"
+ android:enabled="@bool/someConditionalValue" />
+
+ <provider
+ android:name="com.example.Provider2"
+ android:authorities="com.example.android.apis.app.thingy2"
+ android:enabled="@bool/someConditionalValue2" />
+
+ <!-- A new one defined by lib1 -->
+ <provider
+ android:name="com.example.Provider3"
+ android:authorities="com.example.android.apis.app.thingy3"
+ android:enabled="@bool/someConditionalValue" />
+
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/16_fqcn_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/16_fqcn_merge.xml
new file mode 100755
index 0000000..2127ee9
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/16_fqcn_merge.xml
@@ -0,0 +1,134 @@
+#
+# 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
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/17_fqcn_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/17_fqcn_conflict.xml
new file mode 100755
index 0000000..0ed4978
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/17_fqcn_conflict.xml
@@ -0,0 +1,118 @@
+#
+# 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.
+#
+# Test fail since com.example.lib1 has a different application name than the main
+# manifest.
+#
+
+@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">
+
+ <!-- App name is different, will not merge. -->
+ <application android:name="TheApp" >
+ <activity android:name=".WidgetLibrary1" />
+ </application>
+</manifest>
+
+
+@lib2_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- App name is good, but backupAgent is mentioned and is different, will not merge. -->
+ <application
+ android:name="com.example.app1.TheApp"
+ android:backupAgent=".MyBackupAgent" >
+ <activity android:name=".WidgetLibrary2" />
+ <activity android:name=".LibActivity" />
+ </application>
+</manifest>
+
+
+@lib3_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3">
+
+ <application android:name="com.example.app1.TheApp">
+ <activity android:name=".WidgetLibrary3" />
+ </application>
+
+</manifest>
+
+
+@lib4_not_package
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- It's an error for the manifest to lack a 'package' attribute.
+ We just emit a warning in this case.
+ -->
+
+ <application>
+ <!-- These class name can't be expanded due to the lack of 'package' attribute. -->
+ <activity android:name=".LibActivity4" />
+ <service android:name=".LibService4" />
+ <receiver android:name=".LibReceiver4" />
+ <provider android:name=".LibProvider4" />
+
+ </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" />
+ <activity android:name="com.example.lib1.WidgetLibrary1" />
+ <activity android:name="com.example.lib2.WidgetLibrary2" />
+ <activity android:name="com.example.lib3.WidgetLibrary3" />
+# from @lib4_alias
+ <!-- These class name can't be expanded due to the lack of 'package' attribute. -->
+ <activity android:name=".LibActivity4" />
+ <service android:name=".LibService4" />
+ <receiver android:name=".LibReceiver4" />
+ <provider android:name=".LibProvider4" />
+ </application>
+</manifest>
+
+@errors
+ERROR:Attribute application@name value=(com.example.app1.TheApp) from ManifestMerger2Test0_main.xml:8:13
+ is also present at ManifestMerger2Test1_lib1_widget.xml:6:18 value=(com.example.lib1.TheApp)
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/18_fqcn_success.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/18_fqcn_success.xml
new file mode 100755
index 0000000..bf25232
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/18_fqcn_success.xml
@@ -0,0 +1,118 @@
+#
+# 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.
+#
+# All tests fail with just warnings, no solid errors.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:name="TheApp"
+ android:backupAgent=".MyBackupAgent"
+ tools:replace="name, backupAgent">
+ <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">
+
+ <!-- App name is different, will not merge. -->
+ <application>
+ <activity android:name=".WidgetLibrary1" />
+ </application>
+</manifest>
+
+
+@lib2_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- App name is good, but backupAgent is mentioned and is different, will not merge. -->
+ <application
+ android:name="com.example.app1.TheApp"
+ android:backupAgent=".MyBackupAgent" >
+ <activity android:name=".WidgetLibrary2" />
+ <activity android:name=".LibActivity" />
+ </application>
+</manifest>
+
+
+@lib3_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3">
+
+ <application android:name="com.example.app1.TheApp">
+ <activity android:name=".WidgetLibrary3" />
+ </application>
+
+</manifest>
+
+
+@lib4_not_package
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- It's an error for the manifest to lack a 'package' attribute.
+ We just emit a warning in this case.
+ -->
+
+ <application>
+ <!-- These class name can't be expanded due to the lack of 'package' attribute. -->
+ <activity android:name=".LibActivity4" />
+ <service android:name=".LibService4" />
+ <receiver android:name=".LibReceiver4" />
+ <provider android:name=".LibProvider4" />
+
+ </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" />
+ <activity android:name="com.example.lib1.WidgetLibrary1" />
+ <activity android:name="com.example.lib2.WidgetLibrary2" />
+ <activity android:name="com.example.lib3.WidgetLibrary3" />
+# from @lib4_alias
+ <!-- These class name can't be expanded due to the lack of 'package' attribute. -->
+ <activity android:name=".LibActivity4" />
+ <service android:name=".LibService4" />
+ <receiver android:name=".LibReceiver4" />
+ <provider android:name=".LibProvider4" />
+ </application>
+</manifest>
+
+@errors
+WARNING:Missing 'package' declaration in manifest at ManifestMerger2Test4_lib4_not_package.xml:1:1
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/20_uses_lib_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/20_uses_lib_merge.xml
new file mode 100755
index 0000000..5159d6d
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/20_uses_lib_merge.xml
@@ -0,0 +1,178 @@
+#
+# Test merge of uses-library:
+# - Merge is OK if destination already has one with the same @name.
+# - required defaults to "true"
+# - when merging, a required=true (explicit or implicit) overwrites a required=false.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp"
+ tools:replace="label">
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary0_DefaultTrue" />
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue" />
+
+ <!-- A library that is explicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <application android:label="@string/lib_name1" >
+
+ <!-- Same as 1 from main, marking it as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue"
+ android:required="false" />
+
+ <!-- Same as 3 from main -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- Same as 4 from main -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Add a new lib that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary5_RequiredTrue"
+ android:required="true" />
+
+ <!-- Add a new lib that is implicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse"
+ android:required="false" />
+
+ </application>
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application android:label="@string/lib_name1" >
+
+ <!-- Overrides 3, changing it from required=false to true -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="true" />
+
+ <!-- Same as 4 from main -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Overrides 6, but implicitly declaring required=True -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse" />
+
+ </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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary0_DefaultTrue" />
+
+ <!-- A library that is implicitly marked as required=true -->
+# required=false from lib1 is ignored, it stays at the default
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue"/>
+
+ <!-- A library that is explicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false -->
+# lib1 keeps it required=false but lib2 makes it switch to required=true
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+# new from lib1
+ <!-- Add a new lib that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary5_RequiredTrue"
+ android:required="true" />
+
+# new from lib1, but lib2 makes it switch to required=true
+ <!-- Add a new lib that is implicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse"
+ android:required="true" />
+
+ </application>
+
+</manifest>
+
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/21_uses_main_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/21_uses_main_errors.xml
new file mode 100755
index 0000000..f9af32b
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/21_uses_main_errors.xml
@@ -0,0 +1,63 @@
+#
+# Test merge of uses-library:
+# - Merge is OK if destination already has one with the same @name.
+# - required defaults to "true"
+# - when merging, a required=true (explicit or implicit) overwrites a required=false.
+#
+
+@fails
+
+@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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary0_DefaultTrue" />
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue" />
+
+ <!-- A library that is explicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="booh!" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- Duplicated with different attribute values -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ </application>
+
+</manifest>
+
+@errors
+
+ERROR:Attribute uses-library#com.example.SomeLibrary2_RequiredTrue@required at ManifestMerger2Test0_main.xml:27:13 has an illegal value=(booh!), expected 'true' or 'false'
+ERROR:Element uses-library#com.example.SomeLibrary3_RequiredFalse at ManifestMerger2Test0_main.xml:35:9 duplicated with element declared at ManifestMerger2Test0_main.xml:30:9
+ERROR:Validation failed, exiting
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/22_uses_lib_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/22_uses_lib_errors.xml
new file mode 100755
index 0000000..6ec79a4
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/22_uses_lib_errors.xml
@@ -0,0 +1,108 @@
+#
+# Test merge of uses-library:
+# - Merge is OK if destination already has one with the same @name.
+# - required defaults to "true"
+# - when merging, a required=true (explicit or implicit) overwrites a required=false.
+#
+
+@fails
+
+@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:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary0_DefaultTrue" />
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue" />
+
+ <!-- A library that is explicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A library that is explicitly marked as required=false. Duplicated. -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <application android:label="@string/lib_name1" >
+
+ <!-- Error: android:name attribute is missing. -->
+ <uses-library />
+ <uses-library android:required="false" />
+ <uses-library android:required="true" />
+
+ <!-- Same as 2 from main. Warning/ignore because dest required isn't true/false. -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="true" />
+
+ <!-- Same as 3 from main. Warning because destination has a duplicate. -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- Same as 4 from main. Warning because required isn't true or false. -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="foo" />
+
+ <!-- Add a new lib that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary5_RequiredTrue"
+ android:required="true" />
+
+ <!-- Add a new lib that is implicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse"
+ android:required="false" />
+
+ </application>
+</manifest>
+
+@errors
+
+ERROR:Missing 'name' key attribute on element uses-library at ManifestMerger2Test1_lib1.xml:10:9
+ERROR:Missing 'name' key attribute on element uses-library at ManifestMerger2Test1_lib1.xml:9:9
+ERROR:Missing 'name' key attribute on element uses-library at ManifestMerger2Test1_lib1.xml:8:9
+WARNING:Element uses-library#com.example.SomeLibrary3_RequiredFalse at ManifestMerger2Test0_main.xml:35:9 duplicated with element declared at ManifestMerger2Test0_main.xml:30:9
+ERROR:Attribute uses-library#com.example.SomeLibrary4_RequiredFalse@required at ManifestMerger2Test1_lib1.xml:25:13 has an illegal value=(foo), expected 'true' or 'false'
+ERROR:Validation failed, exiting
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/25_permission_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/25_permission_merge.xml
new file mode 100755
index 0000000..4943e13
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/25_permission_merge.xml
@@ -0,0 +1,255 @@
+#
+# Text permission, permission-group and permission-tree:
+# - Libraries can add any of these elements as long as they don't conflict
+# with the destination: either the element must not be at all in the destination
+# (as identified by the name) or it must match exactly.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Same permissions as main manifest -->
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <!-- Added by lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.Permission1"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:description="This is getting"
+ android:label="too silly"
+ android:name="com.example.EnoughWithTheQuotes" />
+
+ <permission-tree
+ android:name="com.example.PermTree1" />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- Redefine one permission from main manifest -->
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+ <!-- And one from lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.Permission1"
+ android:protectionLevel="normal" />
+ <permission-tree
+ android:name="com.example.PermTree1" />
+
+ <!-- Added by lib2. -->
+ <permission
+ android:name="com.example.SensiblePermission2"
+ android:permissionGroup="com.example.SensibleGroup2"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:name="com.example.SensibleGroup2" />
+
+ <permission-tree
+ android:name="com.example.PermTree2" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+# Added by lib1
+ <!-- Added by lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.Permission1"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:description="This is getting"
+ android:label="too silly"
+ android:name="com.example.EnoughWithTheQuotes" />
+
+ <permission-tree
+ android:name="com.example.PermTree1" />
+
+# Added by lib2
+ <!-- Added by lib2. -->
+ <permission
+ android:name="com.example.SensiblePermission2"
+ android:permissionGroup="com.example.SensibleGroup2"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:name="com.example.SensibleGroup2" />
+
+ <permission-tree
+ android:name="com.example.PermTree2" />
+
+</manifest>
+
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/26_permission_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/26_permission_dup.xml
new file mode 100755
index 0000000..ac3886e
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/26_permission_dup.xml
@@ -0,0 +1,270 @@
+#
+# Text permission, permission-group and permission-tree:
+# - Libraries can add any of these elements as long as they don't conflict
+# with the destination: either the element must not be at all in the destination
+# (as identified by the name) or it must match exactly.
+#
+# This one tests that duplicate definitions that are strictly equal generate errors
+# with some (hopefully useful) diff.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous"
+ tools:replace="icon, description, permissionGroup"/>
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem"
+ tools:replace="protectionLevel, permissionGroup"/>
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree"
+ tools:replace="label"/>
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.lib1">
+
+ <!-- forward reference to a group added by a lower library -->
+ <permission
+ android:description="Different description here"
+ android:icon="@drawable/not_the_same_icon"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+ <!-- missing icon: --><permission-group
+ android:description="Nobody expects..."
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+ <permission-tree
+ android:label="This is not the same label"
+ android:name="com.example.PermTree" />
+
+ <!-- different protectionLevel --><permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="normal" />
+
+ <!-- Added by lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.EnoughWithTheQuotes"
+ android:protectionLevel="normal"
+ tools:replace="protectionLevel"/>
+
+ <permission-group
+ android:description="This is getting"
+ android:label="too silly"
+ android:name="com.example.EnoughWithTheQuotes" />
+
+ <permission-tree
+ android:name="com.example.PermTree1" />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- Redefine one permission from main manifest -->
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.SensibleGroup2"
+ android:protectionLevel="dangerous" />
+ <!-- And one from lib1, with a slight variation. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.EnoughWithTheQuotes"
+ android:protectionLevel="signature" />
+ <permission-tree
+ android:description="Extra description"
+ android:name="com.example.PermTree1" />
+
+ <!-- Added by lib2. -->
+ <permission
+ android:name="com.example.SensiblePermission2"
+ android:permissionGroup="com.example.SensibleGroup2"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:name="com.example.SensibleGroup2" />
+
+ <permission-tree
+ android:name="com.example.PermTree2" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+# Added by lib1
+ <!-- Added by lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.EnoughWithTheQuotes"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:description="This is getting"
+ android:label="too silly"
+ android:name="com.example.EnoughWithTheQuotes" />
+
+ <permission-tree
+ android:description="Extra description"
+ android:name="com.example.PermTree1" />
+
+# Added by lib2
+ <!-- Added by lib2. -->
+ <permission
+ android:name="com.example.SensiblePermission2"
+ android:permissionGroup="com.example.SensibleGroup2"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:name="com.example.SensibleGroup2" />
+
+ <permission-tree
+ android:name="com.example.PermTree2" />
+
+</manifest>
+
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/28_uses_perm_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/28_uses_perm_merge.xml
new file mode 100755
index 0000000..c368e36
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/28_uses_perm_merge.xml
@@ -0,0 +1,152 @@
+#
+# Text uses-permission:
+# - Libraries can add any of these elements as long as they don't conflict
+# with the destination: either the element must not be at all in the destination
+# (as identified by the name) or it must match exactly.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Same permissions as main manifest -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Library 1 wants to know what you're running. -->
+ <uses-permission android:name="android.permission.GET_TASKS" />
+
+ <!-- Are you calling me? -->
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- Redefine one permission from main manifest -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <!-- And one from lib1. -->
+ <uses-permission android:name="android.permission.GET_TASKS" />
+
+ <!-- Lib2 wants to know it all. -->
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+# Added by lib1
+ <!-- Library 1 wants to know what you're running. -->
+ <uses-permission android:name="android.permission.GET_TASKS" />
+
+ <!-- Are you calling me? -->
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+# Added by lib2
+ <!-- Lib2 wants to know it all. -->
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+
+</manifest>
+
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29_uses_perm_selector.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29_uses_perm_selector.xml
new file mode 100755
index 0000000..229673c
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29_uses_perm_selector.xml
@@ -0,0 +1,76 @@
+#
+# Text uses-permission:
+# - Libraries can add any of these elements as long as they don't conflict
+# with the destination: either the element must not be at all in the destination
+# (as identified by the name) or it must match exactly.
+#
+
+@main
+
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ xmlns:tools="http://schemas.android.com/tools"
+
+ package="com.example.app1">
+
+
+ <uses-permission android:name="android.permission.INTERNET"
+ tools:node="remove" tools:selector="com.example.lib1"/>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Same permissions as main manifest -->
+ <uses-permission android:name="android.permission.INTERNET" android:maxSdkVersion="18"/>
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- this should be merged -->
+ <uses-permission android:name="android.permission.INTERNET"
+ android:maxSdkVersion="20"/>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <!-- this should be merged -->
+ <uses-permission android:name="android.permission.INTERNET" android:maxSdkVersion="20"/>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29b_uses_perm_invalidSelector.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29b_uses_perm_invalidSelector.xml
new file mode 100755
index 0000000..0687798
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29b_uses_perm_invalidSelector.xml
@@ -0,0 +1,68 @@
+#
+# Text uses-permission:
+# - Libraries can add any of these elements as long as they don't conflict
+# with the destination: either the element must not be at all in the destination
+# (as identified by the name) or it must match exactly.
+#
+
+@main
+
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ xmlns:tools="http://schemas.android.com/tools"
+
+ package="com.example.app1">
+
+
+ <uses-permission android:name="android.permission.INTERNET"
+ tools:node="remove" tools:selector="com.example.libXYZ"/>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Same permissions as main manifest -->
+ <uses-permission android:name="android.permission.INTERNET" android:maxSdkVersion="18"/>
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- this should be merged -->
+ <uses-permission android:name="android.permission.INTERNET"
+ android:maxSdkVersion="20"/>
+
+</manifest>
+
+
+@result
+
+@errors
+
+ERROR:'tools:selector="com.example.libXYZ"' is not a valid library identifier, valid identifiers are : com.example.lib1,com.example.lib2
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/30_uses_sdk_ok.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/30_uses_sdk_ok.xml
new file mode 100755
index 0000000..3109de4
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/30_uses_sdk_ok.xml
@@ -0,0 +1,109 @@
+#
+# Test uses-sdk: add a uses-sdk from an app that doesn't define one.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ />
+
+ <!-- I should be moved first -->
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="14"
+ tools:replace="minSdkVersion, targetSdkVersion"
+ tools:remove="maxSdkVersion"
+ />
+
+ <!-- I should remain last -->
+ <application android:name="com.example.TheApp" />
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- This app requires cupcake. -->
+ <uses-sdk android:minSdkVersion="3" />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- This only defines a max-sdk, and we purposely ignore this attribute.
+ It doesn't get merged and doesn't generate a conflict either.
+ -->
+ <uses-sdk
+ android:maxSdkVersion="5"
+ />
+
+</manifest>
+
+
+@lib3
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3">
+
+ <!-- Lib3 redefines the same requirements as lib1.
+ -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="11"
+ />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- I should be moved first -->
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="14"
+ />
+
+ <supports-screens
+ android:largeScreens="true"
+ />
+
+ <android:uses-permission
+ android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:maxSdkVersion="18" />
+ <android:uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <android:uses-permission
+ android:name="android.permission.READ_EXTERNAL_STORAGE"
+ android:maxSdkVersion="18" />
+
+ <!-- I should remain last -->
+ <application android:name="com.example.TheApp" />
+
+</manifest>
+
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/32_uses_sdk_minsdk_ok.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/32_uses_sdk_minsdk_ok.xml
new file mode 100755
index 0000000..177a352
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/32_uses_sdk_minsdk_ok.xml
@@ -0,0 +1,71 @@
+#
+# Test uses-sdk: it's ok for a library to have a smaller minSdkVersion than the main manifest.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="14" tools:replace="minSdkVersion"/>
+
+ <application />
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- it's ok for a library to have a smaller minSdkVersion than the main manifest. -->
+ <uses-sdk android:minSdkVersion="4" />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <uses-sdk android:minSdkVersion="10" />
+
+</manifest>
+
+
+@lib3
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3">
+
+ <uses-sdk android:minSdkVersion="11" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application />
+
+</manifest>
+
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33_uses_sdk_minsdk_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33_uses_sdk_minsdk_conflict.xml
new file mode 100755
index 0000000..437d4f1
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33_uses_sdk_minsdk_conflict.xml
@@ -0,0 +1,137 @@
+#
+# Test uses-sdk: it's an error for a library to require a minSdkVersion higher than the
+# one defined in the main manifest.
+#
+# Also a uses-sdk with a lack of minSdkVersion is equivalent to using version=1.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This is the same as writing android:minSdkVersion="1" -->
+ <uses-sdk android:targetSdkVersion="14" />
+
+ <application />
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- The app can cope with API 1 but this library can only cope with API 4. -->
+ <uses-sdk android:minSdkVersion="4" />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <uses-sdk android:minSdkVersion="10" />
+
+</manifest>
+
+
+@lib3
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3">
+
+ <uses-sdk android:minSdkVersion="11" />
+
+</manifest>
+
+
+@lib4_parsingError
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib4">
+
+ <!-- Parsing errors -->
+ <uses-sdk android:minSdkVersion="abcd" />
+
+</manifest>
+
+
+@lib5_parsingError
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib5">
+
+ <!-- Parsing errors -->
+ <uses-sdk android:minSdkVersion="123456789123456789" />
+
+</manifest>
+
+
+@lib6_parsingError
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib6">
+
+ <!-- Parsing errors -->
+ <uses-sdk android:minSdkVersion="0xFFFFFFFFFFFFFFFF" />
+
+</manifest>
+
+
+@lib7_parsingError
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib7">
+
+ <!-- Parsing errors -->
+ <uses-sdk android:minSdkVersion="InvalidMinSdk" android:targetSdkVersion="InvalidTargetSdk" />
+
+</manifest>
+
+
+@lib8_parsingCodename
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib8">
+
+ <!-- Test code names -->
+ <uses-sdk android:minSdkVersion="ApiCodename1" android:targetSdkVersion="ApiCodename10" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This is the same as writing android:minSdkVersion="1" -->
+ <uses-sdk android:targetSdkVersion="14" />
+
+ <application />
+
+</manifest>
+
+
+@errors
+ERROR:uses-sdk:minSdkVersion 1 cannot be smaller than version 4 declared in library ManifestMerger2Test1_lib1.xml
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/36_uses_sdk_targetsdk_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/36_uses_sdk_targetsdk_warning.xml
new file mode 100755
index 0000000..1770cc8
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/36_uses_sdk_targetsdk_warning.xml
@@ -0,0 +1,75 @@
+#
+# Test uses-sdk: there's a warning if the main manifest defines a targetSdkVersion that
+# is smaller than what the libraries target.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This app requires cupcake and targets at least 10. -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="GingerbreadMR1"
+ />
+
+ <application />
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- This lib requires cupcake and targets 11 which is > 10 so it's a warning. -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="11"
+ />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- This is not an error nor a warning. -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="4"
+ />
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This app requires cupcake and targets at least 10. -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="GingerbreadMR1"
+ />
+
+ <application />
+
+</manifest>
+
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/40_uses_feat_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/40_uses_feat_merge.xml
new file mode 100755
index 0000000..e7d2447
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/40_uses_feat_merge.xml
@@ -0,0 +1,180 @@
+#
+# Test merge of uses-feature:
+# - Merge is OK if destination already has one with the same @name.
+# - required defaults to "true"
+# - when merging, a required=true (explicit or implicit) overwrites a required=false.
+#
+# Note: uses-feature with android:glEsVersion is dealt with in another test case.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature0_DefaultTrue" />
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue" />
+
+ <!-- A feature that is explicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp"
+ tools:replace="label">
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Same as 1 from main, marking it as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue"
+ android:required="false" />
+
+ <!-- Same as 3 from main -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="false" />
+
+ <!-- Same as 4 from main -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Add a new feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature5_RequiredTrue"
+ android:required="true" />
+
+ <!-- Add a new feature that is implicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse"
+ android:required="false" />
+
+ <application android:label="@string/lib_name1" >
+
+ </application>
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- Overrides 3, changing it from required=false to true -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- Same as 4 from main -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Overrides 6, but implicitly declaring required=True -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse" />
+
+ <application android:label="@string/lib_name2" >
+
+ </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">
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature0_DefaultTrue" />
+
+ <!-- A feature that is implicitly marked as required=true -->
+# required=false from lib1 is ignored, it stays at the default
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue"/>
+
+ <!-- A feature that is explicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+# lib1 keeps it required=false but lib2 makes it switch to required=true
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ </application>
+
+# new from lib1
+ <!-- Add a new feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature5_RequiredTrue"
+ android:required="true" />
+
+# new from lib1, but lib2 makes it switch to required=true
+ <!-- Add a new feature that is implicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse"
+ android:required="true" />
+
+</manifest>
+
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/41_uses_feat_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/41_uses_feat_errors.xml
new file mode 100755
index 0000000..98d17f1
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/41_uses_feat_errors.xml
@@ -0,0 +1,203 @@
+#
+# Test merge of uses-feature:
+# - Merge is OK if destination already has one with the same @name.
+# - required defaults to "true"
+# - when merging, a required=true (explicit or implicit) overwrites a required=false.
+#
+# Note: uses-feature with android:glEsVersion is dealt with in another test case.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature0_DefaultTrue" />
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue" />
+
+ <!-- A feature that is explicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A feature that is explicitly marked as required=false. Duplicated. -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Error: android:name attribute is missing. -->
+ <uses-feature />
+ <uses-feature android:required="false" />
+ <uses-feature android:required="true" />
+
+ <!-- Same as 2 from main. Warning/ignore because dest required isn't true/false. -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="true" />
+
+ <!-- Same as 3 from main. Warning because destination as a duplicate. -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="booh!" />
+
+ <!-- Same as 4 from main. Warning because required isn't true or false. -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="foo" />
+
+ <!-- Add a new feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature5_RequiredTrue"
+ android:required="true" />
+
+ <!-- Add a new feature that is implicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse"
+ android:required="false" />
+
+ <application android:label="@string/lib_name1" >
+
+ </application>
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- Overrides 3, changing it from required=false to true -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- Same as 4 from main -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Overrides 6, but implicitly declaring required=True -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse" />
+
+ <application android:label="@string/lib_name2" >
+
+ </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">
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature0_DefaultTrue" />
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue" />
+
+ <!-- A feature that is explicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="booh!" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+# lib1 keeps it required=false but lib2 makes it switch to required=true
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false. Duplicated. -->
+# in case of duplicated name, they are all modified.
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ </application>
+
+# new from lib1
+ <!-- Add a new feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature5_RequiredTrue"
+ android:required="true" />
+
+# new from lib1, but lib2 makes it switch to required=true
+ <!-- Add a new feature that is implicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse"
+ android:required="true" />
+
+</manifest>
+
+
+@errors
+
+ERROR:Missing one of the key attributes 'name,glEsVersion' on element uses-feature at ManifestMerger2Test1_lib1.xml:6:5
+ERROR:Missing one of the key attributes 'name,glEsVersion' on element uses-feature at ManifestMerger2Test1_lib1.xml:7:5
+ERROR:Missing one of the key attributes 'name,glEsVersion' on element uses-feature at ManifestMerger2Test1_lib1.xml:8:5
+ERROR:Attribute uses-feature#com.example.SomeFeature3_RequiredFalse@required at ManifestMerger2Test1_lib1.xml:18:9 has an illegal value=(booh!), expected 'true' or 'false'
+ERROR:Attribute uses-feature#com.example.SomeFeature4_RequiredFalse@required at ManifestMerger2Test1_lib1.xml:23:9 has an illegal value=(foo), expected 'true' or 'false'
+ERROR:Validation failed, exiting
+WARNING:Element uses-feature#com.example.SomeFeature3_RequiredFalse at ManifestMerger2Test0_main.xml:26:5 duplicated with element declared at ManifestMerger2Test0_main.xml:21:5
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/45_uses_feat_gles_once.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/45_uses_feat_gles_once.xml
new file mode 100755
index 0000000..0512911
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/45_uses_feat_gles_once.xml
@@ -0,0 +1,131 @@
+#
+# Test merge of uses-feature with android:glEsVersion:
+# - Error if defined in lib+dest with dest < lib.
+# - Never automatically change dest.
+# - Default implied value is 1.0 (0x00010000).
+#
+# This tests a case that works. Also checks that glEsVersion attributes are stripped
+# when merging uses-feature with the name attribute.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-feature
+ android:name="com.example.SomeFeature0" />
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+ <uses-feature android:glEsVersion="0x00020001" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+ <uses-feature
+ android:name="com.example.SomeFeature5"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+
+ <!-- Add a glEsVersion of 2.0, which will not be ignored, since it is
+ required -->
+ <uses-feature
+ android:glEsVersion="0x00020000"
+ />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+ <uses-feature
+ android:name="com.example.SomeFeature6"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+
+ <!-- Add a glEsVersion of 1.0, should not be ignored since 2.x is not backward compatible -->
+ <uses-feature android:glEsVersion="0x00010000" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-feature
+ android:name="com.example.SomeFeature0" />
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+ <uses-feature android:glEsVersion="0x00020001" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ </application>
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+ <uses-feature
+ android:name="com.example.SomeFeature5"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+ <!-- Add a glEsVersion of 2.0, which will not be ignored, since it is
+ required -->
+ <uses-feature android:glEsVersion="0x00020000"/>
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+ <uses-feature
+ android:name="com.example.SomeFeature6"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+
+ <!-- Add a glEsVersion of 1.0, should not be ignored since 2.x is not backward compatible -->
+ <uses-feature android:glEsVersion="0x00010000" />
+
+</manifest>
+
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/47_uses_feat_gles_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/47_uses_feat_gles_conflict.xml
new file mode 100755
index 0000000..8b99039
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/47_uses_feat_gles_conflict.xml
@@ -0,0 +1,158 @@
+#
+# Test merge of uses-feature with android:glEsVersion:
+# - Error if defined in lib+dest with dest < lib.
+# - Never automatically change dest.
+# - Default implied value is 1.0 (0x00010000).
+#
+# This tests a case that doesn't works because the main manifest doesn't declare
+# the value and thus defaults to 1.0, so libraries with higher requirements will
+# conflict.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-feature
+ android:name="com.example.SomeFeature0" />
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+ <uses-feature
+ android:name="com.example.SomeFeature5"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+
+ <!-- Add a glEsVersion of 2.0, which will be ignored -->
+ <uses-feature
+ android:glEsVersion="0x00020000"
+ />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- Add a new feature with a glEsVersion of 1.1 which will be ignored -->
+ <uses-feature
+ android:name="com.example.SomeFeature6"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+
+ <!-- Add a glEsVersion of 1.0, which will be ignored -->
+ <uses-feature
+ android:glEsVersion="0x00010000"
+ />
+
+ <!-- Test some invalid values. -->
+
+ <!-- 0 isn't a valid value and generates a warning stating it's ignored. -->
+ <uses-feature
+ android:glEsVersion="0"
+ />
+
+ <!-- 0.0xFFFF is 0.99... and generates a warning stating it's ignored.
+ The real minimal value is 1.0, not 0.99... -->
+ <uses-feature
+ android:glEsVersion="0x0000FFFF"
+ />
+
+ <!-- 0xFFFF.xFFFF is not invalid. It does correspond to 65535.9999847412109375
+ which is unlikely to be valid anyway. It's not ignored and should parse just fine.
+ -->
+ <uses-feature
+ android:glEsVersion="0xFFFFFFFF"
+ />
+
+ <!-- This value shouldn't parse correctly with a Long and will generate a parsing error.
+ -->
+ <uses-feature
+ android:glEsVersion="0xFFFFFFFFFFFFFFFF"
+ />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-feature
+ android:name="com.example.SomeFeature0" />
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ </application>
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+# lib1 adds this new node. Note how the glEsVersion=2.1 is stripped out.
+ <uses-feature
+ android:name="com.example.SomeFeature5"
+ android:required="false"
+ />
+
+ <!-- Add a new feature with a glEsVersion of 1.1 which will be ignored -->
+# lib2 adds this new node. Note how the glEsVersion=2.0 is stripped out.
+ <uses-feature
+ android:name="com.example.SomeFeature6"
+ android:required="false"
+ />
+
+</manifest>
+
+
+@errors
+
+ERROR:Attribute uses-feature#0@glEsVersion at ManifestMerger2Test2_lib2.xml:21:9 is not a valid hexadecimal 32 bit value, found 0
+ERROR:Attribute uses-feature#0x0000FFFF@glEsVersion at ManifestMerger2Test2_lib2.xml:27:9 is not a valid hexadecimal value, minimum is 0x00010000, maximum is 0x7FFFFFFF, found 0x0000FFFF
+ERROR:Attribute uses-feature#0xFFFFFFFF@glEsVersion at ManifestMerger2Test2_lib2.xml:34:9 is not a valid hexadecimal value, minimum is 0x00010000, maximum is 0x7FFFFFFF, found 0xFFFFFFFF
+ERROR:Attribute uses-feature#0xFFFFFFFFFFFFFFFF@glEsVersion at ManifestMerger2Test2_lib2.xml:40:9 is not a valid hexadecimal 32 bit value, found 0xFFFFFFFFFFFFFFFF
+ERROR:Validation failed, exiting
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/50_uses_conf_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/50_uses_conf_warning.xml
new file mode 100755
index 0000000..7e1392d
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/50_uses_conf_warning.xml
@@ -0,0 +1,163 @@
+#
+# Test uses-configuration:
+# - it's OK if a library defines one or multiple times an element already in the application.
+# - it's a warning if the library defines an element not in the application.
+# - this does not actually merge anything. The XML is not changed at all.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- this is the same uses-conf than in the main. -->
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- this is the not same uses-conf than in the main. -->
+ <uses-configuration
+ android:reqFiveWayNav="false"
+ android:reqNavigation="trackball"
+ android:reqTouchScreen="finger" />
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@errors
+
+ERROR:Attribute uses-configuration@reqFiveWayNav value=(true) from ManifestMerger2Test0_main.xml:18:9
+ is also present at ManifestMerger2Test2_lib2.xml:7:9 value=(false)
+ERROR:Attribute uses-configuration@reqNavigation value=(nonav) from ManifestMerger2Test0_main.xml:21:9
+ is also present at ManifestMerger2Test2_lib2.xml:8:9 value=(trackball)
+ERROR:Attribute uses-configuration@reqTouchScreen value=(stylus) from ManifestMerger2Test0_main.xml:22:9
+ is also present at ManifestMerger2Test2_lib2.xml:9:9 value=(finger)
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/52_support_screens_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/52_support_screens_warning.xml
new file mode 100755
index 0000000..1da6644
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/52_support_screens_warning.xml
@@ -0,0 +1,155 @@
+#
+# Test supports-screens:
+# - it's OK if a library defines one or multiple times an element already in the application.
+# - it's a warning if the library defines an element not in the application.
+# - this does not actually merge anything. The XML is not changed at all.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ tools:replace="resizeable, smallScreens"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- this is the same supports-screens than in the main. -->
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- this is the not same supports-screens than in the main. -->
+ <supports-screens
+ android:smallScreens="false"
+ android:resizeable="false"
+ />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/54_compat_screens_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/54_compat_screens_warning.xml
new file mode 100755
index 0000000..d1b1244
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/54_compat_screens_warning.xml
@@ -0,0 +1,166 @@
+#
+# Test compatible-screens:
+# - it's OK if a library defines one or multiple times an element already in the application.
+# - it's a warning if the library defines an element not in the application.
+# - this does not actually merge anything. The XML is not changed at all.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <compatible-screens>
+ <screen android:screenSize="small" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="xhdpi" />
+ </compatible-screens>
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- this is the same compatible-screens than in the main. -->
+ <compatible-screens>
+ <screen android:screenSize="small" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="xhdpi" />
+ </compatible-screens>
+
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- this is the not same compatible-screens than in the main. -->
+ <compatible-screens>
+ <screen android:screenSize="small" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="mdpi" />
+ <screen android:screenSize="normal" android:screenDensity="hdpi" />
+ <screen android:screenSize="normal" android:screenDensity="xhdpi" />
+ </compatible-screens>
+
+</manifest>
+
+@result
+
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <compatible-screens>
+ <screen android:screenSize="small" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="mdpi" />
+ <screen android:screenSize="normal" android:screenDensity="hdpi" />
+ <screen android:screenSize="normal" android:screenDensity="xhdpi" />
+ </compatible-screens>
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/56_support_gltext_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/56_support_gltext_warning.xml
new file mode 100755
index 0000000..4e45514
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/56_support_gltext_warning.xml
@@ -0,0 +1,144 @@
+#
+# Test supports-gl-texture:
+# - it's a warning if the library defines a supports-gl-texture not in the application.
+# - this does not actually merge anything. The XML is not changed at all.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- this is the same supports-gl-texture than in the main. -->
+ <supports-gl-texture android:name="some.gl.texture1" />
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <!-- this is the not same supports-gl-texture than in the main. -->
+ <supports-gl-texture android:name="some.gl.texture3" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+ <supports-gl-texture android:name="some.gl.texture3" />
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/60_merge_order.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/60_merge_order.xml
new file mode 100755
index 0000000..63f490b
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/60_merge_order.xml
@@ -0,0 +1,315 @@
+#
+# Test merge order:
+# - When activity / activity-alias / service / receiver / provider are merged,
+# we do a comparison to check whether the elements are already present in the
+# main manifest. The order of the elements must NOT matter in the comparison,
+# nor does the whitespace between them.
+# - What this checks is that the order of the elements or attributes within
+# the elements should not matter.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.Activity1"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data
+ android:name="metaName"
+ android:value="metaValue"
+ android:resource="@color/someColor" />
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THIS" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THAT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.TIMEZONE_CHANGED" />
+ <action android:name="android.intent.action.TIME_SET" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PHONE_STATE"/>
+ </intent-filter>
+ </receiver>
+
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+
+ <!-- When comparing duplicate elements, whitespace and comments are ignored. -->
+
+ <intent-filter>
+ <action android:name="com.example.IN_APP_NOTIFY" />
+ <action android:name="com.example.RESPONSE_CODE" />
+ <action android:name="com.example.PURCHASE_STATE_CHANGED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data
+ android:name="metaName2"
+ android:value="metaValue2"
+ android:resource="@color/someColor2"
+ />
+ </activity>
+ </application>
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Redefine the same elements as in the main manifest except it changes
+ the attribute order and the the inner elements order. -->
+ <application
+ android:name="com.example.TheApp"
+ android:icon="@drawable/app_icon"
+ android:label="@string/app_name"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:restoreAnyVersion="true"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ >
+
+ <!-- Receiver -->
+ <receiver
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppReceiver"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.TIME_SET" />
+ <action android:name="android.intent.action.TIMEZONE_CHANGED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THIS" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PHONE_STATE"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THAT" />
+ </intent-filter>
+ </receiver>
+
+ <activity
+ android:theme="@style/Lib.Theme"
+ android:name="com.example.LibActivity"
+ android:icon="@drawable/lib_activity_icon"
+ android:label="@string/lib_activity_name"
+ >
+ <!-- When comparing duplicate elements, whitespace and comments are ignored. -->
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ <meta-data
+ android:resource="@color/someColor2"
+ android:value="metaValue2"
+ android:name="metaName2">
+ </meta-data>
+ <intent-filter>
+ <action android:name="com.example.IN_APP_NOTIFY" />
+ <action android:name="com.example.PURCHASE_STATE_CHANGED" />
+ <action android:name="com.example.RESPONSE_CODE" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:icon="@drawable/activity_icon"
+ android:label="@string/activity_name"
+ android:name="com.example.Activity1"
+ android:theme="@style/Some.Theme">
+ <meta-data
+ android:value="metaValue"
+ android:name="metaName"
+ android:resource="@color/someColor" />
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <!-- The whitespace and alignment is also drastically different here and has
+ no impact whatsoever on the content's comparison.
+ Some empty elements have been 'uncollapsed' with their closing element separated. -->
+ <activity android:label="@string/activity_name" android:icon="@drawable/activity_icon" android:theme="@style/Some.Theme" android:name="com.example.Activity1">
+ <intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
+ <intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
+ <meta-data android:value="metaValue" android:resource="@color/someColor" android:name="metaName" />
+ </activity>
+ <activity android:label="@string/lib_activity_name" android:icon="@drawable/lib_activity_icon" android:name="com.example.LibActivity" android:theme="@style/Lib.Theme"><intent-filter>
+ <action android:name="com.example.IN_APP_NOTIFY" /> <action android:name="com.example.RESPONSE_CODE" /> <action android:name="com.example.PURCHASE_STATE_CHANGED" />
+ </intent-filter>
+ <intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="metaName2" android:value="metaValue2" android:resource="@color/someColor2"
+ />
+ </activity>
+
+ <!-- Receiver -->
+ <receiver android:icon="@drawable/app_icon" android:name="com.example.AppReceiver" >
+ <intent-filter><action android:name="android.intent.action.TIME_SET"></action>
+ <action android:name="android.intent.action.TIMEZONE_CHANGED" /></intent-filter>
+ <intent-filter><action android:name="android.intent.action.PHONE_STATE" >
+ </action></intent-filter>
+ <intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ <intent-filter><action android:name="com.example.intent.action.DO_THIS" /></intent-filter>
+ <intent-filter><action android:name="com.example.intent.action.DO_THAT" /></intent-filter>
+ </receiver>
+ </application>
+</manifest>
+
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.Activity1"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data
+ android:name="metaName"
+ android:value="metaValue"
+ android:resource="@color/someColor" />
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THIS" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THAT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.TIMEZONE_CHANGED" />
+ <action android:name="android.intent.action.TIME_SET" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PHONE_STATE"/>
+ </intent-filter>
+ </receiver>
+
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+
+ <!-- When comparing duplicate elements, whitespace and comments are ignored. -->
+
+ <intent-filter>
+ <action android:name="com.example.IN_APP_NOTIFY" />
+ <action android:name="com.example.RESPONSE_CODE" />
+ <action android:name="com.example.PURCHASE_STATE_CHANGED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data
+ android:name="metaName2"
+ android:value="metaValue2"
+ android:resource="@color/someColor2"
+ />
+ </activity>
+ </application>
+</manifest>
+
+@errors
+
+WARNING:Element intent-filter#android.intent.action.MAIN+android.intent.category.LAUNCHER at ManifestMerger2Test0_main.xml:23:13 duplicated with element declared at ManifestMerger2Test0_main.xml:19:13
+WARNING:Element intent-filter#android.intent.action.MAIN+android.intent.category.LAUNCHER at ManifestMerger2Test1_lib1.xml:76:13 duplicated with element declared at ManifestMerger2Test1_lib1.xml:72:13
+WARNING:Element intent-filter#android.intent.action.MAIN+android.intent.category.LAUNCHER at ManifestMerger2Test2_lib2.xml:19:13 duplicated with element declared at ManifestMerger2Test2_lib2.xml:18:13
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/65_override_app.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/65_override_app.xml
new file mode 100755
index 0000000..2ffafe8
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/65_override_app.xml
@@ -0,0 +1,193 @@
+#
+# Test the tools:merge="override" tag on an application.
+# It essentially ignores _any_ application tag from libraries.
+# All other non-application elements are merged as usual.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application
+ tools:node="replace"
+ 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">
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <!-- This is overridden by main manifest and never merged, INCLUDING all activities. -->
+ <application android:name="TheApp" >
+ <activity android:name=".WidgetLibrary1" />
+ </application>
+
+</manifest>
+
+
+@lib2_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+
+ <!-- This is overridden by main manifest and never merged, INCLUDING all activities. -->
+ <application
+ android:name="com.example.app1.TheApp"
+ android:backupAgent=".MyBackupAgent" >
+ <activity android:name=".WidgetLibrary2" />
+ <activity android:name=".LibActivity" />
+ </application>
+
+</manifest>
+
+
+@lib3_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3">
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <!-- This is overridden by main manifest and never merged, INCLUDING all activities. -->
+ <application android:name="com.example.app1.TheApp">
+ <activity android:name=".WidgetLibrary3" />
+ </application>
+
+</manifest>
+
+
+@lib4_not_package
+
+<!-- It's an error for the manifest to lack a 'package' attribute.
+ We just emit a warning in this case.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Permission tree for lib4 -->
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <!-- This is overridden by main manifest and never merged, INCLUDING all activities. -->
+ <application>
+ <!-- These class name can't be expanded due to the lack of 'package' attribute. -->
+ <activity android:name=".LibActivity4" />
+ <service android:name=".LibService4" />
+ <receiver android:name=".LibReceiver4" />
+ <provider android:name=".LibProvider4" />
+
+ </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">
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <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" />
+ </application>
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <!-- Permission tree for lib4 -->
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+</manifest>
+
+@errors
+
+WARNING:Missing 'package' declaration in manifest at ManifestMerger2Test4_lib4_not_package.xml:4:1
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/66_remove_app.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/66_remove_app.xml
new file mode 100755
index 0000000..363d166
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/66_remove_app.xml
@@ -0,0 +1,53 @@
+#
+# Test how elements are removed by tools:merge="remove".
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- The "remove" tag will eradicate this element from the output. -->
+ <application
+ tools:node="remove"
+ 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">
+
+ <!-- This application and all its activities or content is ignored because the
+ main manifest requested to remove the application element. -->
+ <application android:name="TheApp" >
+ <activity android:name=".WidgetLibrary1" />
+ </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">
+
+ <!-- The "remove" tag will eradicate this element from the output. -->
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/67_override_activities.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/67_override_activities.xml
new file mode 100755
index 0000000..35375e0
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/67_override_activities.xml
@@ -0,0 +1,159 @@
+#
+# Test how elements are overriden by tools:merge="override".
+# The override only blocks elements that would be merged.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:name="TheApp"
+ android:backupAgent=".MyBackupAgent" >
+ <activity android:name=".MainActivity" tools:node="replace" />
+ <receiver android:name="AppReceiver" tools:node="replace"/>
+ <activity android:name="com.example.lib2.LibActivity" />
+ <service android:name="com.example.AppService1" tools:node="replace" />
+ <provider android:name="com.example.Provider1" tools:node="replace" />
+ <activity-alias android:name="AliasActivity1" tools:node="replace" />
+
+ </application>
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example">
+
+ <application>
+ <!-- Activity merged -->
+ <activity android:name=".WidgetLibrary1" />
+
+ <!-- Conflicting activity ignored by override -->
+ <activity
+ android:name="com.example.MainActivity"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Conflicting receiver ignored by override -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Receiver merged -->
+ <receiver android:name="LibReceiver" />
+
+ <!-- Conflicting alias activity ignored by override -->
+ <activity-alias
+ android:name="com.example.AliasActivity1"
+ android:targetActivity="com.example.MainActivity1"
+ android:label="@string/alias_name1"
+ android:icon="@drawable/alias_icon1">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- Alias activity merged -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity2"
+ android:targetActivity="com.example.MainActivity2"
+ android:label="@string/alias_name2"
+ android:icon="@drawable/alias_icon2">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- Conflicting service ignored by override -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService1" />
+
+ <!-- Service merged -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService2" />
+
+ <!-- Conflicting provider ignored by override -->
+ <provider
+ android:name="com.example.Provider1"
+ android:authorities="com.example.android.apis.app.thingy1"
+ android:enabled="@bool/someConditionalValue" />
+
+ <!-- Provider merged -->
+ <provider
+ android:name="com.example.Provider2" />
+
+ </application>
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:name="com.example.TheApp"
+ android:backupAgent="com.example.MyBackupAgent" >
+ <activity android:name="com.example.MainActivity" />
+ <receiver android:name="com.example.AppReceiver" />
+ <activity android:name="com.example.lib2.LibActivity" />
+ <service android:name="com.example.AppService1" />
+ <provider android:name="com.example.Provider1" />
+ <activity-alias android:name="com.example.AliasActivity1" />
+ <!-- Activity merged -->
+ <activity android:name="com.example.WidgetLibrary1" />
+
+ <!-- Alias activity merged -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity2"
+ android:targetActivity="com.example.MainActivity2"
+ android:label="@string/alias_name2"
+ android:icon="@drawable/alias_icon2">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- Service merged -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService2" />
+
+ <!-- Receiver merged -->
+ <receiver android:name="com.example.LibReceiver" />
+
+ <!-- Provider merged -->
+ <provider
+ android:name="com.example.Provider2" />
+
+ </application>
+</manifest>
+
+@errors
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/68_override_uses.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/68_override_uses.xml
new file mode 100755
index 0000000..691e546
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/68_override_uses.xml
@@ -0,0 +1,204 @@
+#
+# Test how elements are overriden by tools:node="replace".
+# The removal prevents the actual merge operation.
+# However items are still checked out for consistency (just not merged anymore)
+# and thus still produce warnings.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This is the same as writing android:minSdkVersion="1" -->
+ <uses-sdk android:targetSdkVersion="14" tools:node="replace" />
+
+ <!-- Ignore permissions elements from lib that would conflict because
+ their definition is different. -->
+ <permission
+ tools:node="replace"
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ tools:node="replace"
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ tools:node="replace"
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <!-- uses-feature is never merged, only checked, so tools:merge=override does nothing. -->
+ <uses-feature
+ android:name="com.example.SomeFeature0"
+ android:glEsVersion="0x00020001"
+ tools:replace="glEsVersion"/>
+ <!-- Ignore uses-feature from library, which would change required to
+ true if it were merged. -->
+ <uses-feature
+ tools:node="replace"
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+
+ <!-- supports-screens is never merged, only checked, so tools:merge=override does nothing. -->
+ <supports-screens
+ tools:node="replace"
+ android:smallScreens="true"
+ android:resizeable="false"
+ />
+
+ <!-- supports-gl-texture-screens-feature is never merged, only checked, so tools:merge=override does nothing. -->
+ <supports-gl-texture android:name="some.gl.texture1" tools:node="replace" />
+
+ <application android:name="com.example.TheApp" >
+ <!-- Ignore uses-library from library, which would change required to
+ true if it were merged. -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false"
+ tools:node="replace" />
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example">
+
+ <!-- The app can cope with API 1 but this library can only cope with API 4. -->
+ <uses-sdk android:minSdkVersion="4" />
+
+ <!-- Ignored permissions -->
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.AnotherGroup"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:label="Nobody expects the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:description="This is not the same label"
+ android:name="com.example.PermTree" />
+
+ <!-- GL 0.0 is a warning which is not prevented by tools:merge=override. -->
+ <uses-feature
+ android:name="com.example.SomeFeature0"
+ android:glEsVersion="0x00020000"/>
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="true" />
+
+ <!-- supports-screens isn't really merged, just checked, so tools:merge=override does nothing. -->
+ <!-- this is the not same supports-screens than in the main, will till make a warning. -->
+ <supports-screens
+ android:smallScreens="false"
+ android:resizeable="false"
+ />
+
+ <!-- supports-gl-texture isn't really merged, just checked, so tools:merge=override does nothing. -->
+ <!-- this is the not same supports-gl-texture than in the main. -->
+ <supports-gl-texture android:name="some.gl.texture3" />
+
+ <application android:name="com.example.TheApp" >
+ <!-- Ignored uses-library -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="true" />
+
+ </application>
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example">
+
+ <!-- The app targets API 14 but this library targets 42. -->
+ <uses-sdk android:targetSdkVersion="42" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This is the same as writing android:minSdkVersion="1" -->
+ <uses-sdk android:targetSdkVersion="14" />
+
+ <!-- Ignore permissions elements from lib that would conflict because
+ their definition is different. -->
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <!-- uses-feature is never merged, only checked, so tools:merge=override does nothing. -->
+ <uses-feature
+ android:name="com.example.SomeFeature0"
+ android:glEsVersion="0x00020001" />
+ <!-- Ignore uses-feature from library, which would change required to
+ true if it were merged. -->
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+
+ <!-- supports-screens is never merged, only checked, so tools:merge=override does nothing. -->
+ <supports-screens
+ android:smallScreens="true"
+ android:resizeable="false"
+ />
+
+ <!-- supports-gl-texture-screens-feature is never merged, only checked, so tools:merge=override does nothing. -->
+ <supports-gl-texture android:name="some.gl.texture1" />
+
+ <application android:name="com.example.TheApp" >
+ <!-- Ignore uses-library from library, which would change required to
+ true if it were merged. -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ </application>
+ <!-- supports-gl-texture isn't really merged, just checked, so tools:merge=override does nothing. -->
+ <!-- this is the not same supports-gl-texture than in the main. -->
+ <supports-gl-texture android:name="some.gl.texture3" />
+
+</manifest>
+
+@errors
+
+WARNING:supports-gl-texture#some.gl.texture1 was tagged at ManifestMerger2Test0_main.xml:51 to replace another declaration but no other declaration present
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/69_remove_uses.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/69_remove_uses.xml
new file mode 100755
index 0000000..1ab0e67
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/69_remove_uses.xml
@@ -0,0 +1,169 @@
+#
+# Test how elements are removed by tools:node="remove".
+# The removal prevents the actual merge operation.
+# However items are still checked out for consistency (just not merged anymore)
+# and thus still produce warnings.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This is the same as writing android:minSdkVersion="1" -->
+ <uses-sdk android:targetSdkVersion="14" tools:node="remove" />
+
+ <!-- Ignore permissions elements from lib that would conflict because
+ their definition is different. -->
+ <permission
+ tools:node="remove"
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ tools:node="remove"
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ tools:node="remove"
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <!-- uses-feature is never merged, only checked, so tools:merge=remove does nothing. -->
+ <uses-feature
+ tools:node="remove"
+ android:name="com.example.SomeFeature0"
+ android:glEsVersion="0x00020001" />
+ <!-- Ignore uses-feature from library, which would change required to
+ true if it were merged. -->
+ <uses-feature
+ tools:node="remove"
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+
+ <!-- supports-screens is never merged, only checked, so tools:merge=remove does nothing. -->
+ <supports-screens
+ tools:node="remove"
+ android:smallScreens="true"
+ android:resizeable="false"
+ />
+
+ <!-- supports-gl-texture-screens-feature is never merged, only checked, so tools:merge=remove does nothing. -->
+ <supports-gl-texture android:name="some.gl.texture3" tools:node="remove" />
+
+ <application android:name="com.example.TheApp" >
+ <!-- Ignore uses-library from library, which would change required to
+ true if it were merged. -->
+ <uses-library
+ tools:node="remove"
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example">
+
+ <!-- The app can cope with API 1 but this library can only cope with API 4. -->
+ <uses-sdk android:minSdkVersion="4" />
+
+ <!-- Ignored permissions -->
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.AnotherGroup"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:label="Nobody expects the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:description="This is not the same label"
+ android:name="com.example.PermTree" />
+
+ <!-- GL 0.0 is a warning which is not prevented by tools:merge=remove. -->
+ <uses-feature
+ android:name="com.example.SomeFeature0"
+ android:glEsVersion="0x00020000" />
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="true" />
+
+ <!-- supports-screens isn't really merged, just checked, so tools:merge=remove does nothing. -->
+ <!-- this is the not same supports-screens than in the main, will till make a warning. -->
+ <supports-screens
+ android:smallScreens="false"
+ android:resizeable="false"
+ />
+
+ <!-- supports-gl-texture isn't really merged, just checked, so tools:merge=remove does nothing. -->
+ <!-- this is the not same supports-gl-texture than in the main. -->
+ <supports-gl-texture android:name="some.gl.texture3" />
+
+ <application android:name="com.example.TheApp" >
+ <!-- Ignored uses-library -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="true" />
+
+ </application>
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example">
+
+ <!-- The app targets API 14 but this library targets 42. -->
+ <uses-sdk android:targetSdkVersion="42" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This is the same as writing android:minSdkVersion="1" -->
+
+ <!-- Ignore permissions elements from lib that would conflict because
+ their definition is different. -->
+
+ <!-- uses-feature is never merged, only checked, so tools:merge=remove does nothing. -->
+ <!-- Ignore uses-feature from library, which would change required to
+ true if it were merged. -->
+
+ <!-- supports-screens is never merged, only checked, so tools:merge=remove does nothing. -->
+
+ <!-- supports-gl-texture-screens-feature is never merged, only checked, so tools:merge=remove does nothing. -->
+
+ <application android:name="com.example.TheApp" >
+ <!-- Ignore uses-library from library, which would change required to
+ true if it were merged. -->
+
+ </application>
+
+</manifest>
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/70_expand_fqcns.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/70_expand_fqcns.xml
new file mode 100755
index 0000000..db5ac81
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/70_expand_fqcns.xml
@@ -0,0 +1,100 @@
+#
+# Tests the option to extract prefixes.
+# The default is for the manifest merger to expand all the class names
+# it finds from their short form (e.g. when the package name is implied)
+# to their FQCN.
+#
+
+@main
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.blankactivity5"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="16" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <!-- The activity name will be expanded to its full FQCN by default. -->
+ <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>
+
+
+@lib1
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.blankactivity5" >
+
+ <application>
+ <!-- The activity name will be expanded to its full FQCN by default. -->
+ <activity
+ android:name=".FooActivity"
+ android:label="@string/title_activity_foo" >
+ </activity>
+ </application>
+
+</manifest>
+
+
+@result
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.blankactivity5"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="16" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <!-- The activity name will be expanded to its full FQCN by default. -->
+ <activity
+ android:name="com.example.blankactivity5.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>
+ <!-- The activity name will be expanded to its full FQCN by default. -->
+ <activity
+ android:name="com.example.blankactivity5.FooActivity"
+ android:label="@string/title_activity_foo" >
+ </activity>
+ </application>
+ <android:uses-permission
+ android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:maxSdkVersion="18" />
+ <android:uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <android:uses-permission
+ android:name="android.permission.READ_EXTERNAL_STORAGE"
+ android:maxSdkVersion="18" />
+
+</manifest>
+
+@errors
+
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/71_extract_package_prefix.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/71_extract_package_prefix.xml
new file mode 100755
index 0000000..58c090a
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/71_extract_package_prefix.xml
@@ -0,0 +1,91 @@
+#
+# Tests the option to extract prefixes.
+# The default is for the manifest merger to expand all the class names
+# it finds from their short form (e.g. when the package name is implied)
+# to their FQCN. Setting ManifMerger.setExtractPackagePrefix(true) prevents
+# this expansion and keeps the short class names in the merged result.
+#
+
+@features
+setExtractPackagePrefix=true
+
+@main
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.blankactivity5"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <!-- The activity name will NOT be expanded to its full FQCN because
+ ManifMerger.setExtractPackagePrefix is set to true. -->
+ <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>
+
+
+@lib1
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.blankactivity5" >
+
+ <application>
+ <!-- The activity name will NOT be expanded to its full FQCN. -->
+ <activity
+ android:name=".FooActivity"
+ android:label="@string/title_activity_foo" >
+ </activity>
+ </application>
+
+</manifest>
+
+
+@result
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.blankactivity5"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <!-- The activity name will NOT be expanded to its full FQCN because
+ ManifMerger.setExtractPackagePrefix is set to true. -->
+ <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>
+ <!-- The activity name will NOT be expanded to its full FQCN. -->
+ <activity
+ android:name=".FooActivity"
+ android:label="@string/title_activity_foo" >
+ </activity>
+ </application>
+
+</manifest>
+
+@errors
+
+
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/75_app_metadata_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/75_app_metadata_merge.xml
new file mode 100755
index 0000000..545d828
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/75_app_metadata_merge.xml
@@ -0,0 +1,96 @@
+#
+# Tests merging application/meta-data.
+# Several APIs provide app-specific keys (e.g. Android Backup API, Google Maps API.)
+# and the key needs to be placed in the <application> element as meta-data.
+#
+# This tests the default behavior which is to merge the elements from the library which are new.
+#
+
+@main
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ 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" />
+
+ <!-- This key is defined in the main application. -->
+ <meta-data
+ android:name="name.for.yet.another.api.key"
+ android:value="your_yet_another_api_key"/>
+
+ <!-- Merged elements will be appended here at the end. -->
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <application android:name="TheApp" >
+ <activity android:name=".Library1" />
+
+ <!-- The library maps API key gets merged in the main application. -->
+ <meta-data
+ android:name="name.for.maps.api.key"
+ android:value="your_maps_api_key"/>
+
+ <!-- The library backup key gets merged in the main application. -->
+ <meta-data
+ android:name="name.for.backup.api.key"
+ android:value="your_backup_api_key" />
+ </application>
+
+</manifest>
+
+
+@result
+
+<?xml version="1.0" encoding="utf-8"?>
+<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" />
+
+ <!-- This key is defined in the main application. -->
+ <meta-data
+ android:name="name.for.yet.another.api.key"
+ android:value="your_yet_another_api_key"/>
+
+ <!-- Merged elements will be appended here at the end. -->
+ <activity android:name="com.example.app1.Library1" />
+
+ <!-- The library maps API key gets merged in the main application. -->
+ <meta-data
+ android:name="name.for.maps.api.key"
+ android:value="your_maps_api_key"/>
+
+ <!-- The library backup key gets merged in the main application. -->
+ <meta-data
+ android:name="name.for.backup.api.key"
+ android:value="your_backup_api_key" />
+ </application>
+
+</manifest>
+
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/76_app_metadata_ignore.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/76_app_metadata_ignore.xml
new file mode 100755
index 0000000..cc8f061
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/76_app_metadata_ignore.xml
@@ -0,0 +1,99 @@
+#
+# Tests merging application/meta-data.
+# Several APIs provide app-specific keys (e.g. Android Backup API, Google Maps API.)
+# and the key needs to be placed in the <application> element as meta-data.
+#
+# This tests that an application can selectively prevent specific meta-data from
+# being merged by using the tools:merge="remove" attribute.
+#
+
+@main
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ 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" />
+
+ <!-- This key is defined in the main application. -->
+ <meta-data
+ android:name="name.for.yet.another.api.key"
+ android:value="your_yet_another_api_key"/>
+
+ <!-- We do not want any maps API key to be merged here. -->
+ <meta-data
+ android:name="name.for.maps.api.key"
+ tools:node="remove" />
+
+ <!-- Merged elements will be appended here at the end. -->
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <application android:name="TheApp" >
+ <activity android:name=".Library1" />
+
+ <!-- The library maps API key gets merged in the main application. -->
+ <meta-data
+ android:name="name.for.maps.api.key"
+ android:value="your_maps_api_key"/>
+
+ <!-- The library backup key gets merged in the main application. -->
+ <meta-data
+ android:name="name.for.backup.api.key"
+ android:value="your_backup_api_key" />
+ </application>
+
+</manifest>
+
+
+@result
+
+<?xml version="1.0" encoding="utf-8"?>
+<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" />
+
+ <!-- This key is defined in the main application. -->
+ <meta-data
+ android:name="name.for.yet.another.api.key"
+ android:value="your_yet_another_api_key"/>
+
+ <!-- We do not want any maps API key to be merged here. -->
+
+ <!-- Merged elements will be appended here at the end. -->
+ <activity android:name="com.example.app1.Library1" />
+
+ <!-- The library backup key gets merged in the main application. -->
+ <meta-data
+ android:name="name.for.backup.api.key"
+ android:value="your_backup_api_key" />
+ </application>
+
+</manifest>
+
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/77_app_metadata_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/77_app_metadata_conflict.xml
new file mode 100755
index 0000000..ee419eb
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/77_app_metadata_conflict.xml
@@ -0,0 +1,126 @@
+#
+# Tests merging application/meta-data.
+# Several APIs provide app-specific keys (e.g. Android Backup API, Google Maps API.)
+# and the key needs to be placed in the <application> element as meta-data.
+#
+# This tests the default behavior which is to conflict when a library tries to
+# add a meta-data which is has the same name but not the same value as one already
+# defined in the application.
+#
+# The application can also override a meta-data using the tools:merge="override" attribute.
+# This lets the main application define a meta-data and prevent any library ones from being
+# merged and potentially conflict.
+#
+
+@fails
+
+@main
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <application
+ android:name="TheApp"
+ android:backupAgent=".MyBackupAgent" >
+ <activity android:name=".MainActivity" />
+ <receiver android:name="AppReceiver" />
+ <activity android:name="com.example.lib2.LibActivity" />
+
+ <!-- This key is defined in the main application. -->
+ <meta-data
+ android:name="name.for.yet.another.api.key"
+ android:value="your_yet_another_api_key"/>
+
+ <!-- The library has a maps API key that would conflict but it will
+ actually be ignored since the merge-override flag is set. -->
+ <meta-data
+ tools:node="replace"
+ android:name="name.for.maps.api.key"
+ android:value="the_apps_maps_api_key"/>
+
+ <!-- The library has a backup API key will conflict since it has a
+ different value and the merge operation isn't overridden. -->
+ <meta-data
+ android:name="name.for.backup.api.key"
+ android:value="the_apps_backup_api_key" />
+
+ <!-- Merged elements will be appended here at the end. -->
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <application android:name="TheApp" >
+ <activity android:name=".Library1" />
+
+ <!-- The library maps API key doesn't get merged in the main application. -->
+ <meta-data
+ android:name="name.for.maps.api.key"
+ android:value="the_library1_maps_api_key"/>
+
+ <!-- The library backup key doesn't get merged in the main application. -->
+ <meta-data
+ android:name="name.for.backup.api.key"
+ android:value="the_library1_backup_api_key" />
+ </application>
+
+</manifest>
+
+
+@result
+
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <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" />
+
+ <!-- This key is defined in the main application. -->
+ <meta-data
+ android:name="name.for.yet.another.api.key"
+ android:value="your_yet_another_api_key"/>
+
+ <!-- The library has a maps API key that would conflict but it will
+ actually be ignored since the merge-override flag is set. -->
+ <meta-data
+ android:name="name.for.maps.api.key"
+ android:value="the_apps_maps_api_key"/>
+
+ <!-- The library has a backup API key will conflict since it has a
+ different value and the merge operation isn't overridden. -->
+ <meta-data
+ android:name="name.for.backup.api.key"
+ android:value="the_apps_backup_api_key" />
+
+ <!-- Merged elements will be appended here at the end. -->
+ <activity android:name="com.example.app1.Library1" />
+ </application>
+
+</manifest>
+
+
+@errors
+
+ERROR:Attribute meta-data#name.for.backup.api.key@value value=(the_apps_backup_api_key) from ManifestMerger2Test0_main.xml:33:13
+ is also present at ManifestMerger2Test1_lib1.xml:16:13 value=(the_library1_backup_api_key)
\ No newline at end of file
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/78_removeAll.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/78_removeAll.xml
new file mode 100644
index 0000000..199a178
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/78_removeAll.xml
@@ -0,0 +1,93 @@
+#
+# Tests for removeAll node operation type. all permission nodes
+# should be deleted.
+#
+
+@fails
+
+@main
+
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:name="TheApp"
+ android:backupAgent=".MyBackupAgent" >
+ <permission tools:node="removeAll" />
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <application android:name="TheApp" >
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:protectionLevel="dangerous" />
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:protectionLevel="signatureOrSystem" />
+ </application>
+
+</manifest>
+
+@lib2
+
+ <manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <application android:name="TheApp" >
+ <permission
+ android:name="com.example.AnotherPermission"
+ android:protectionLevel="signatureOrSystem" />
+ </application>
+
+ </manifest>
+
+@result
+
+<?xml version="1.0" encoding="utf-8"?>
+ <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" >
+ </application>
+
+ </manifest>
+
+
+@errors
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/79_custom_node.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/79_custom_node.xml
new file mode 100755
index 0000000..76c24fd
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/79_custom_node.xml
@@ -0,0 +1,61 @@
+#
+# Test:
+# - Attributes from the application element in a library are ignored (except name)
+# - Comments from nodes ignored in libraries are not merged either.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:acme="http://schemas.acme.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application>
+ <!-- custom tag -->
+ <acme:enable-feature
+ android:name="com.amazon.geo.maps"
+ android:required="false" />
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:label="@string/app_name"
+ android:name="com.example.TheApp" >
+ </application>
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:acme="http://schemas.acme.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:label="@string/app_name"
+ android:name="com.example.TheApp" >
+ <!-- custom tag -->
+ <acme:enable-feature
+ android:name="com.amazon.geo.maps"
+ android:required="false" />
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/build-system/tests/3rdPartyTests/app/build.gradle b/build-system/tests/3rdPartyTests/app/build.gradle
index 9d89e954..6cb8d59 100644
--- a/build-system/tests/3rdPartyTests/app/build.gradle
+++ b/build-system/tests/3rdPartyTests/app/build.gradle
@@ -1,18 +1,28 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
project.ext.fakeProvider = new com.android.tests.basic.buildscript.FakeProvider()
project.ext.fakeServer = new com.android.tests.basic.buildscript.FakeServer()
+repositories {
+ mavenCentral()
+}
+
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
deviceProvider project.ext.fakeProvider
testServer project.ext.fakeServer
+
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+ }
}
project.afterEvaluate {
- configure(fakeInstrumentTest) {
+ configure(fakeAndroidTest) {
doLast {
String error = project.ext.fakeProvider.isValid()
if (error != null) {
diff --git a/build-system/tests/3rdPartyTests/app/src/instrumentTest/AndroidManifest.xml b/build-system/tests/3rdPartyTests/app/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from build-system/tests/3rdPartyTests/app/src/instrumentTest/AndroidManifest.xml
rename to build-system/tests/3rdPartyTests/app/src/androidTest/AndroidManifest.xml
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/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
similarity index 100%
rename from build-system/tests/3rdPartyTests/app/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
rename to build-system/tests/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
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/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
similarity index 100%
rename from build-system/tests/3rdPartyTests/app/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
rename to build-system/tests/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
diff --git a/build-system/tests/3rdPartyTests/build.gradle b/build-system/tests/3rdPartyTests/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/3rdPartyTests/build.gradle
+++ b/build-system/tests/3rdPartyTests/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/3rdPartyTests/buildSrc/build.gradle b/build-system/tests/3rdPartyTests/buildSrc/build.gradle
index d0d1b1d..9d896a1 100644
--- a/build-system/tests/3rdPartyTests/buildSrc/build.gradle
+++ b/build-system/tests/3rdPartyTests/buildSrc/build.gradle
@@ -2,9 +2,9 @@
apply plugin: 'idea'
repositories {
- maven { url '../../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../../out/repo' }
}
dependencies {
- compile 'com.android.tools.build:builder-test-api:0.7.0-SNAPSHOT'
+ compile 'com.android.tools.build:builder-test-api:0.12.2'
}
\ 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
index df810fe..5f082b7 100644
--- 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
@@ -4,6 +4,7 @@
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;
@@ -25,10 +26,10 @@
private boolean installCalled = false;
private boolean uninstallCalled = false;
private boolean execShellCalled = false;
+ private boolean pullFileCalled = false;
private final List<File> installedApks = Lists.newArrayList();
-
FakeDevice(String name) {
this.name = name;
}
@@ -92,6 +93,12 @@
execShellCalled = true;
}
+ @Override
+ public void pullFile(String remote, String local) throws IOException {
+ System.out.println(String.format("PULL_FILE(%S) CALLED", name));
+ pullFileCalled = true;
+ }
+
public String isValid() {
if (!connectCalled) {
return "connect not called on " + name;
@@ -113,13 +120,26 @@
return "executeShellCommand not called on " + name;
}
+ if (!pullFileCalled) {
+ return "pullFile not called on " + name;
+ }
+
return null;
}
+
+ public IDevice.DeviceState getState() {
+ return IDevice.DeviceState.ONLINE;
+ }
+
public int getApiLevel() {
return 99;
}
+ public String getApiCodeName() {
+ return null;
+ }
+
@NonNull
public List<String> getAbis() {
return Collections.singletonList("fake");
diff --git a/build-system/tests/3rdPartyTests/lib/build.gradle b/build-system/tests/3rdPartyTests/lib/build.gradle
index 75ef7d6..8666245 100644
--- a/build-system/tests/3rdPartyTests/lib/build.gradle
+++ b/build-system/tests/3rdPartyTests/lib/build.gradle
@@ -1,18 +1,28 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
project.ext.fakeProvider = new com.android.tests.basic.buildscript.FakeProvider()
project.ext.fakeServer = new com.android.tests.basic.buildscript.FakeServer()
+repositories {
+ mavenCentral()
+}
+
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
deviceProvider project.ext.fakeProvider
testServer project.ext.fakeServer
+
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+ }
}
project.afterEvaluate {
- configure(fakeInstrumentTest) {
+ configure(fakeAndroidTest) {
doLast {
String error = project.ext.fakeProvider.isValid()
if (error != null) {
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/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
similarity index 100%
rename from build-system/tests/3rdPartyTests/lib/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
rename to build-system/tests/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
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/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
similarity index 100%
rename from build-system/tests/3rdPartyTests/lib/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
rename to build-system/tests/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
diff --git a/build-system/tests/3rdPartyTests/lib/src/instrumentTest/res/values/strings.xml b/build-system/tests/3rdPartyTests/lib/src/androidTest/res/values/strings.xml
similarity index 100%
rename from build-system/tests/3rdPartyTests/lib/src/instrumentTest/res/values/strings.xml
rename to build-system/tests/3rdPartyTests/lib/src/androidTest/res/values/strings.xml
diff --git a/build-system/tests/aidl/build.gradle b/build-system/tests/aidl/build.gradle
index 337d40fd..9c310b8 100644
--- a/build-system/tests/aidl/build.gradle
+++ b/build-system/tests/aidl/build.gradle
@@ -1,15 +1,14 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
-
-}
\ No newline at end of file
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/aidl/src/main/AndroidManifest.xml b/build-system/tests/aidl/src/main/AndroidManifest.xml
index 1d6740d..0aeb232 100644
--- a/build-system/tests/aidl/src/main/AndroidManifest.xml
+++ b/build-system/tests/aidl/src/main/AndroidManifest.xml
@@ -3,7 +3,7 @@
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"
+ <activity android:name="com.android.tests.basicprojectwithaidl.Main"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/build-system/tests/api/app/build.gradle b/build-system/tests/api/app/build.gradle
index c307fc8..28117f1 100644
--- a/build-system/tests/api/app/build.gradle
+++ b/build-system/tests/api/app/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
// query for all (non-test) variants and inject a new step in the builds
@@ -21,7 +21,7 @@
// now make the dex task depend on it and use its output
variant.dex.dependsOn jarTask
- variant.dex.sourceFiles = files(jarTask.archivePath).files
+ variant.dex.inputFiles = files(jarTask.archivePath).files
}
project.afterEvaluate {
diff --git a/build-system/tests/api/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from build-system/tests/api/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
rename to build-system/tests/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/api/app/src/main/AndroidManifest.xml b/build-system/tests/api/app/src/main/AndroidManifest.xml
index 4f8d570..7734165 100644
--- a/build-system/tests/api/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/api/app/src/main/AndroidManifest.xml
@@ -22,7 +22,7 @@
android:label="@string/app_name"
android:description="@string/app_name" />
- <permission android:name="foo.blah.SEND_SMS"
+ <permission android:name="foo.blah.SEND_SMS_AGAIN"
android:permissionGroup="foo.permission-group.COST_MONEY"
android:label="@string/app_name"
android:description="@string/app_name" />
diff --git a/build-system/tests/api/build.gradle b/build-system/tests/api/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/api/build.gradle
+++ b/build-system/tests/api/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/api/lib/build.gradle b/build-system/tests/api/lib/build.gradle
index b2cbda8..f374bb8 100644
--- a/build-system/tests/api/lib/build.gradle
+++ b/build-system/tests/api/lib/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
// query for all (non-test) variants and inject a new step in the builds
diff --git a/build-system/tests/api/lib/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java b/build-system/tests/api/lib/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
new file mode 100644
index 0000000..e4b7190
--- /dev/null
+++ b/build-system/tests/api/lib/src/androidTest/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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 6ac4a5c..0000000
--- a/build-system/tests/api/lib/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
+++ /dev/null
@@ -1,86 +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.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/applibtest/app/build.gradle b/build-system/tests/applibtest/app/build.gradle
index 39e5199..5df514f 100644
--- a/build-system/tests/applibtest/app/build.gradle
+++ b/build-system/tests/applibtest/app/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
//
diff --git a/build-system/tests/applibtest/app/src/instrumentTest/AndroidManifest.xml b/build-system/tests/applibtest/app/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from build-system/tests/applibtest/app/src/instrumentTest/AndroidManifest.xml
rename to build-system/tests/applibtest/app/src/androidTest/AndroidManifest.xml
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/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
similarity index 100%
rename from build-system/tests/applibtest/app/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
rename to build-system/tests/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
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/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
similarity index 100%
rename from build-system/tests/applibtest/app/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
rename to build-system/tests/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
diff --git a/build-system/tests/applibtest/build.gradle b/build-system/tests/applibtest/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/applibtest/build.gradle
+++ b/build-system/tests/applibtest/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/applibtest/lib/build.gradle b/build-system/tests/applibtest/lib/build.gradle
index c02b29c..8225550 100644
--- a/build-system/tests/applibtest/lib/build.gradle
+++ b/build-system/tests/applibtest/lib/build.gradle
@@ -1,10 +1,20 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
+
+repositories {
+ mavenCentral()
+}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
- testPackageName = "com.android.tests.testprojecttest.testlib"
+ testApplicationId = "com.android.tests.testprojecttest.testlib"
}
-}
\ No newline at end of file
+
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+ }
+}
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/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
similarity index 100%
rename from build-system/tests/applibtest/lib/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
rename to build-system/tests/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
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/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
similarity index 100%
rename from build-system/tests/applibtest/lib/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
rename to build-system/tests/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
diff --git a/build-system/tests/applibtest/lib/src/instrumentTest/res/values/strings.xml b/build-system/tests/applibtest/lib/src/androidTest/res/values/strings.xml
similarity index 100%
rename from build-system/tests/applibtest/lib/src/instrumentTest/res/values/strings.xml
rename to build-system/tests/applibtest/lib/src/androidTest/res/values/strings.xml
diff --git a/build-system/tests/applibtest/lib/src/main/AndroidManifest.xml b/build-system/tests/applibtest/lib/src/main/AndroidManifest.xml
index 26598f0..c291cb9 100644
--- a/build-system/tests/applibtest/lib/src/main/AndroidManifest.xml
+++ b/build-system/tests/applibtest/lib/src/main/AndroidManifest.xml
@@ -21,7 +21,7 @@
android:label="@string/app_name"
android:description="@string/app_name" />
- <permission android:name="foo.blah.SEND_SMS"
+ <permission android:name="foo.blah.SEND_SMS_AGAIN"
android:permissionGroup="foo.permission-group.COST_MONEY"
android:label="@string/app_name"
android:description="@string/app_name" />
diff --git a/build-system/tests/artifactApi/build.gradle b/build-system/tests/artifactApi/build.gradle
index 4e26af5..fec49b6 100644
--- a/build-system/tests/artifactApi/build.gradle
+++ b/build-system/tests/artifactApi/build.gradle
@@ -1,17 +1,22 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
import com.android.builder.model.ArtifactMetaData
import com.android.builder.model.SourceProvider
+// create a configuration to allow dependencies specific to the Java artifact
+configurations {
+ foo
+}
+
// 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)
@@ -41,32 +46,40 @@
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
+ configurations.foo,
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"
+// after the artifact is created, add a dependency
+dependencies {
+ foo files('libs/util-1.0.jar')
+}
+
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ flavorDimensions "pricing", "releaseType"
productFlavors {
beta {
- flavorGroup "releaseType"
+ flavorDimension "releaseType"
}
normal {
- flavorGroup "releaseType"
+ flavorDimension "releaseType"
}
free {
- flavorGroup "pricing"
+ flavorDimension "pricing"
}
paid {
- flavorGroup "pricing"
+ flavorDimension "pricing"
}
}
}
@@ -79,7 +92,11 @@
SourceProviderImpl(String name) {
this.name = name
}
-
+
+ String getName() {
+ return name
+ }
+
File getManifestFile() {
return new File(name)
}
@@ -104,15 +121,19 @@
return Collections.emptyList()
}
+ Collection<File> getJniLibsDirectories() {
+ 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/libs/util-1.0.jar b/build-system/tests/artifactApi/libs/util-1.0.jar
new file mode 100644
index 0000000..88dcf46
--- /dev/null
+++ b/build-system/tests/artifactApi/libs/util-1.0.jar
Binary files differ
diff --git a/build-system/tests/assets/app/build.gradle b/build-system/tests/assets/app/build.gradle
index 5a77673..25ec236 100644
--- a/build-system/tests/assets/app/build.gradle
+++ b/build-system/tests/assets/app/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
dependencies {
diff --git a/build-system/tests/assets/app/src/androidTest/java/com/android/tests/assets/app/MainActivityTest.java b/build-system/tests/assets/app/src/androidTest/java/com/android/tests/assets/app/MainActivityTest.java
new file mode 100644
index 0000000..57387a7
--- /dev/null
+++ b/build-system/tests/assets/app/src/androidTest/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().toString());
+ assertEquals("SUCCESS-LIB", mLibTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView2.getText().toString());
+ assertEquals("SUCCESS-LIB", mLibTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 77ee434..0000000
--- a/build-system/tests/assets/app/src/instrumentTest/java/com/android/tests/assets/app/MainActivityTest.java
+++ /dev/null
@@ -1,92 +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.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
index 404f93f..10a0d74 100644
--- a/build-system/tests/assets/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/assets/app/src/main/AndroidManifest.xml
@@ -5,12 +5,12 @@
android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
<uses-sdk
- android:minSdkVersion="15"
- tools:ignore="UsesMinSdkAttributes" />
+ android:minSdkVersion="15" />
<application
android:icon="@drawable/icon"
- android:label="@string/app_name" >
+ android:label="@string/app_name"
+ tools:replace="icon, label">
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
diff --git a/build-system/tests/assets/build.gradle b/build-system/tests/assets/build.gradle
index a8fdb64..4324232 100644
--- a/build-system/tests/assets/build.gradle
+++ b/build-system/tests/assets/build.gradle
@@ -1,9 +1,9 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/assets/lib/build.gradle b/build-system/tests/assets/lib/build.gradle
index 4b2a733..ba65495 100644
--- a/build-system/tests/assets/lib/build.gradle
+++ b/build-system/tests/assets/lib/build.gradle
@@ -1,6 +1,6 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
\ No newline at end of file
diff --git a/build-system/tests/assets/lib/src/androidTest/java/com/android/tests/assets/lib/MainActivityTest.java b/build-system/tests/assets/lib/src/androidTest/java/com/android/tests/assets/lib/MainActivityTest.java
new file mode 100644
index 0000000..eaa1bd8
--- /dev/null
+++ b/build-system/tests/assets/lib/src/androidTest/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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 40ebe35..0000000
--- a/build-system/tests/assets/lib/src/instrumentTest/java/com/android/tests/assets/lib/MainActivityTest.java
+++ /dev/null
@@ -1,86 +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.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/attrOrder/app/build.gradle b/build-system/tests/attrOrder/app/build.gradle
index 98e423b..4328b87 100644
--- a/build-system/tests/attrOrder/app/build.gradle
+++ b/build-system/tests/attrOrder/app/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
dependencies{
diff --git a/build-system/tests/attrOrder/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/attrOrder/app/src/androidTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..0dca339
--- /dev/null
+++ b/build-system/tests/attrOrder/app/src/androidTest/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;
+
+ /**
+ * 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() {
+ assertEquals("Hello, world!", mTextView.getText().toString());
+ }
+
+}
+
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
deleted file mode 100644
index 41272ac..0000000
--- a/build-system/tests/attrOrder/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-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
index 4f8d570..43fe215 100644
--- a/build-system/tests/attrOrder/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/attrOrder/app/src/main/AndroidManifest.xml
@@ -1,7 +1,9 @@
<?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">
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon"
+ tools:replace="label, icon">
<activity android:name=".Main"
android:label="@string/app_name">
<intent-filter>
@@ -10,21 +12,4 @@
</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/build.gradle b/build-system/tests/attrOrder/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/attrOrder/build.gradle
+++ b/build-system/tests/attrOrder/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/attrOrder/lib/build.gradle b/build-system/tests/attrOrder/lib/build.gradle
index db660d9..24196f4 100644
--- a/build-system/tests/attrOrder/lib/build.gradle
+++ b/build-system/tests/attrOrder/lib/build.gradle
@@ -1,7 +1,7 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
diff --git a/build-system/tests/autorepo/build.gradle b/build-system/tests/autorepo/build.gradle
index b1e5a6d..68ca50d 100644
--- a/build-system/tests/autorepo/build.gradle
+++ b/build-system/tests/autorepo/build.gradle
@@ -1,17 +1,17 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
+ compileSdkVersion 19
}
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/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from build-system/tests/autorepo/src/instrumentTest/java/com/android/tests/basic/MainTest.java
rename to build-system/tests/autorepo/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/basic/build.gradle b/build-system/tests/basic/build.gradle
index 8f0cf2a..ece5247 100644
--- a/build-system/tests/basic/build.gradle
+++ b/build-system/tests/basic/build.gradle
@@ -1,12 +1,12 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
repositories {
mavenCentral()
@@ -20,8 +20,8 @@
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
testBuildType "debug"
@@ -45,6 +45,9 @@
buildConfigField "boolean", "DEFAULT", "true"
buildConfigField "String", "FOO", "\"foo\""
+ buildConfigField "String", "FOO", "\"foo2\""
+
+ resValue "string", "foo", "foo"
resConfig "en"
resConfigs "nodpi", "hdpi"
@@ -52,10 +55,15 @@
buildTypes {
debug {
- packageNameSuffix ".debug"
+ applicationIdSuffix ".debug"
signingConfig signingConfigs.myConfig
+ testCoverageEnabled true
+
+ buildConfigField "String", "FOO", "\"bar1\""
buildConfigField "String", "FOO", "\"bar\""
+
+ resValue "string", "foo", "foo2"
}
}
@@ -63,4 +71,43 @@
noCompress 'txt'
ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
-}
\ No newline at end of file
+
+ 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'
+ // if true, generate an XML report for use by for example Jenkins
+ xmlReport false
+ // 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")
+ }
+}
diff --git a/build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/basic/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java
rename to build-system/tests/basic/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/basicMultiFlavors/build.gradle b/build-system/tests/basicMultiFlavors/build.gradle
index 877a268..fcb876c 100644
--- a/build-system/tests/basicMultiFlavors/build.gradle
+++ b/build-system/tests/basicMultiFlavors/build.gradle
@@ -1,35 +1,35 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
- flavorGroups "pricing", "releaseType"
+ flavorDimensions "pricing", "releaseType"
productFlavors {
beta {
- flavorGroup "releaseType"
+ flavorDimension "releaseType"
}
normal {
- flavorGroup "releaseType"
+ flavorDimension "releaseType"
}
free {
- flavorGroup "pricing"
+ flavorDimension "pricing"
}
paid {
- flavorGroup "pricing"
+ flavorDimension "pricing"
}
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/customArtifactDep/app/build.gradle b/build-system/tests/customArtifactDep/app/build.gradle
new file mode 100644
index 0000000..760514c
--- /dev/null
+++ b/build-system/tests/customArtifactDep/app/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
+
+dependencies {
+ compile project(path: ':util', configuration: 'custom')
+}
diff --git a/build-system/tests/customArtifactDep/app/src/main/AndroidManifest.xml b/build-system/tests/customArtifactDep/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..71e7a47
--- /dev/null
+++ b/build-system/tests/customArtifactDep/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/customArtifactDep/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/tests/customArtifactDep/app/src/main/java/com/example/android/multiproject/MainActivity.java
new file mode 100644
index 0000000..4c827fa
--- /dev/null
+++ b/build-system/tests/customArtifactDep/app/src/main/java/com/example/android/multiproject/MainActivity.java
@@ -0,0 +1,20 @@
+package com.example.android.multiproject;
+
+import android.app.Activity;
+import android.view.View;
+import android.content.Intent;
+import android.os.Bundle;
+
+import android.widget.TextView;
+import com.example.android.custom.Foo;
+
+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);
+ tv.setText(Foo.getString());
+ }
+}
diff --git a/build-system/tests/customArtifactDep/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/customArtifactDep/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/build-system/tests/customArtifactDep/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/customArtifactDep/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/customArtifactDep/app/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/build-system/tests/customArtifactDep/app/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/customArtifactDep/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/customArtifactDep/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/build-system/tests/customArtifactDep/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/customArtifactDep/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/tests/customArtifactDep/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/build-system/tests/customArtifactDep/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/customArtifactDep/app/src/main/res/layout/main.xml b/build-system/tests/customArtifactDep/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..e7e77c9
--- /dev/null
+++ b/build-system/tests/customArtifactDep/app/src/main/res/layout/main.xml
@@ -0,0 +1,11 @@
+<?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"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/text" />
+</LinearLayout>
diff --git a/build-system/tests/customArtifactDep/app/src/main/res/values/strings.xml b/build-system/tests/customArtifactDep/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e1f49b6
--- /dev/null
+++ b/build-system/tests/customArtifactDep/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/customArtifactDep/build.gradle b/build-system/tests/customArtifactDep/build.gradle
new file mode 100644
index 0000000..d50884c
--- /dev/null
+++ b/build-system/tests/customArtifactDep/build.gradle
@@ -0,0 +1,16 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+allprojects {
+ version = '1.0'
+
+ repositories {
+ mavenCentral()
+ }
+}
diff --git a/build-system/tests/customArtifactDep/settings.gradle b/build-system/tests/customArtifactDep/settings.gradle
new file mode 100644
index 0000000..4fba024
--- /dev/null
+++ b/build-system/tests/customArtifactDep/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'util'
diff --git a/build-system/tests/customArtifactDep/util/build.gradle b/build-system/tests/customArtifactDep/util/build.gradle
new file mode 100644
index 0000000..a8976c2
--- /dev/null
+++ b/build-system/tests/customArtifactDep/util/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'java'
+
+configurations {
+ custom
+}
+
+sourceSets {
+ custom {
+ java {
+ srcDirs = ['src/custom/java']
+ }
+ }
+}
+
+task customJar(type: Jar) {
+ from sourceSets.custom.output
+ classifier "custom"
+}
+
+// declare the new artifact
+artifacts {
+ custom customJar
+}
diff --git a/build-system/tests/customArtifactDep/util/src/custom/java/com/example/android/custom/Foo.java b/build-system/tests/customArtifactDep/util/src/custom/java/com/example/android/custom/Foo.java
new file mode 100644
index 0000000..354040d
--- /dev/null
+++ b/build-system/tests/customArtifactDep/util/src/custom/java/com/example/android/custom/Foo.java
@@ -0,0 +1,7 @@
+package com.example.android.custom;
+
+public class Foo {
+ public static String getString() {
+ return Integer.toString(new java.util.Random(System.currentTimeMillis()).nextInt());
+ }
+}
diff --git a/build-system/tests/dependencies/build.gradle b/build-system/tests/dependencies/build.gradle
index cf56a16..eab479c 100644
--- a/build-system/tests/dependencies/build.gradle
+++ b/build-system/tests/dependencies/build.gradle
@@ -1,12 +1,12 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
version='1.0'
@@ -15,23 +15,11 @@
}
dependencies {
- compile 'com.google.guava:guava:11.0.2'
- apk files('libs/jarProject.jar')
+ apk project(':jarProject')
+ provided project(':jarProject2')
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
-
- testBuildType "blah"
-
- defaultConfig {
- }
-
- buildTypes {
- blah {
- packageNameSuffix ".blah"
- signingConfig signingConfigs.debug
- }
- }
-}
\ No newline at end of file
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/dependencies/debug.keystore b/build-system/tests/dependencies/debug.keystore
deleted file mode 100644
index 389278e..0000000
--- a/build-system/tests/dependencies/debug.keystore
+++ /dev/null
Binary files differ
diff --git a/build-system/tests/dependencies/jarProject2/build.gradle b/build-system/tests/dependencies/jarProject2/build.gradle
new file mode 100644
index 0000000..f3eca85
--- /dev/null
+++ b/build-system/tests/dependencies/jarProject2/build.gradle
@@ -0,0 +1 @@
+apply plugin: 'java'
\ No newline at end of file
diff --git a/build-system/tests/dependencies/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java b/build-system/tests/dependencies/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java
new file mode 100644
index 0000000..a928639
--- /dev/null
+++ b/build-system/tests/dependencies/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java
@@ -0,0 +1,8 @@
+package com.android.tests.dependencies.jar;
+
+public class StringHelper2 {
+
+ public static String getString2(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
deleted file mode 100644
index 6b03f1d..0000000
--- a/build-system/tests/dependencies/libs/jarProject.jar
+++ /dev/null
Binary files differ
diff --git a/build-system/tests/dependencies/settings.gradle b/build-system/tests/dependencies/settings.gradle
new file mode 100644
index 0000000..fedde4c
--- /dev/null
+++ b/build-system/tests/dependencies/settings.gradle
@@ -0,0 +1,2 @@
+include ':jarProject'
+include ':jarProject2'
\ No newline at end of file
diff --git a/build-system/tests/dependencies/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java b/build-system/tests/dependencies/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java
new file mode 100644
index 0000000..b1dde12
--- /dev/null
+++ b/build-system/tests/dependencies/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java
@@ -0,0 +1,54 @@
+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 testPackageOnly() {
+ assertEquals("Foo-helper", mTextView.getText().toString());
+ }
+
+ public void testProvided() {
+ boolean exception = false;
+ try {
+ getActivity().getString2("foo");
+ } catch (Throwable t) {
+ exception = true;
+ }
+ assertTrue(exception);
+ }
+}
+
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
deleted file mode 100644
index 034b8f6..0000000
--- a/build-system/tests/dependencies/src/instrumentTest/java/com/android/tests/dependencies/MainActivityTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-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
index 5b71e12..3fc2e18 100644
--- a/build-system/tests/dependencies/src/main/AndroidManifest.xml
+++ b/build-system/tests/dependencies/src/main/AndroidManifest.xml
@@ -11,12 +11,5 @@
<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
deleted file mode 100644
index 40fdaa2..0000000
--- a/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/BuildType.java
+++ /dev/null
@@ -1,5 +0,0 @@
-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
index 3d617f6..dfec087 100644
--- 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
@@ -4,9 +4,8 @@
import android.view.View;
import android.content.Intent;
import android.os.Bundle;
-
-import android.annotation.TargetApi;
import android.widget.TextView;
+import com.android.tests.dependencies.jar.StringHelper2;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -37,9 +36,7 @@
}
}
- @TargetApi(10)
- public void sendMessage(View view) {
- Intent intent = new Intent(this, ShowPeopleActivity.class);
- startActivity(intent);
+ public String getString2(String foo) {
+ return StringHelper2.getString2(foo);
}
}
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
deleted file mode 100644
index fcd6f21..0000000
--- a/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/Person.java
+++ /dev/null
@@ -1,13 +0,0 @@
-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
deleted file mode 100644
index 3a1ff01..0000000
--- a/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/ShowPeopleActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-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/build-system/tests/dependencies/src/main/res/layout/main.xml b/build-system/tests/dependencies/src/main/res/layout/main.xml
index 1884ac9..81207f4 100644
--- a/build-system/tests/dependencies/src/main/res/layout/main.xml
+++ b/build-system/tests/dependencies/src/main/res/layout/main.xml
@@ -7,8 +7,7 @@
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/button_send"
- android:onClick="sendMessage" />
+ android:text="@string/button_send" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/build-system/tests/dependencyChecker/build.gradle b/build-system/tests/dependencyChecker/build.gradle
index e1f83c7..6e52fb8 100644
--- a/build-system/tests/dependencyChecker/build.gradle
+++ b/build-system/tests/dependencyChecker/build.gradle
@@ -1,12 +1,12 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
repositories {
mavenCentral()
@@ -17,6 +17,6 @@
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
-}
\ No newline at end of file
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/dependencyChecker/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/dependencyChecker/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from build-system/tests/dependencyChecker/src/instrumentTest/java/com/android/tests/basic/MainTest.java
rename to build-system/tests/dependencyChecker/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/dependencyChecker/src/main/AndroidManifest.xml b/build-system/tests/dependencyChecker/src/main/AndroidManifest.xml
index 4f8d570..a34d937 100644
--- a/build-system/tests/dependencyChecker/src/main/AndroidManifest.xml
+++ b/build-system/tests/dependencyChecker/src/main/AndroidManifest.xml
@@ -10,21 +10,4 @@
</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/embedded/build.gradle b/build-system/tests/embedded/build.gradle
new file mode 100644
index 0000000..84fb877
--- /dev/null
+++ b/build-system/tests/embedded/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
diff --git a/build-system/tests/embedded/embedded/build.gradle b/build-system/tests/embedded/embedded/build.gradle
new file mode 100644
index 0000000..6cef734
--- /dev/null
+++ b/build-system/tests/embedded/embedded/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ defaultConfig {
+ versionCode 42
+ versionName "foo"
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:13.0.0'
+}
diff --git a/build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/embedded/embedded/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java
copy to build-system/tests/embedded/embedded/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/embedded/embedded/src/main/AndroidManifest.xml b/build-system/tests/embedded/embedded/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/embedded/embedded/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/embedded/embedded/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/embedded/embedded/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/embedded/embedded/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/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/embedded/embedded/src/main/res/drawable/icon.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/embedded/embedded/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/embedded/embedded/src/main/res/layout/main.xml b/build-system/tests/embedded/embedded/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/embedded/embedded/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/embedded/embedded/src/main/res/values/strings.xml b/build-system/tests/embedded/embedded/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/embedded/embedded/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/embedded/main/build.gradle b/build-system/tests/embedded/main/build.gradle
new file mode 100644
index 0000000..0e0247b
--- /dev/null
+++ b/build-system/tests/embedded/main/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'com.android.application'
+
+dependencies {
+ wearApp project(':embedded')
+}
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/embedded/main/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java
copy to build-system/tests/embedded/main/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/embedded/main/src/main/AndroidManifest.xml b/build-system/tests/embedded/main/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/embedded/main/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/embedded/main/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/embedded/main/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/embedded/main/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/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/embedded/main/src/main/res/drawable/icon.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/embedded/main/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/embedded/main/src/main/res/layout/main.xml b/build-system/tests/embedded/main/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/embedded/main/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/embedded/main/src/main/res/values/strings.xml b/build-system/tests/embedded/main/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/embedded/main/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/embedded/settings.gradle b/build-system/tests/embedded/settings.gradle
new file mode 100644
index 0000000..ca1ceba
--- /dev/null
+++ b/build-system/tests/embedded/settings.gradle
@@ -0,0 +1,2 @@
+include ':main'
+include ':embedded'
diff --git a/build-system/tests/extractAnnotations/build.gradle b/build-system/tests/extractAnnotations/build.gradle
new file mode 100644
index 0000000..16f8b79
--- /dev/null
+++ b/build-system/tests/extractAnnotations/build.gradle
@@ -0,0 +1,26 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'com.android.library'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.android.support:support-annotations:+'
+ compile 'com.android.support:support-v4:+'
+}
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
+
+defaultTasks 'extractDebugAnnotations'
diff --git a/build-system/tests/extractAnnotations/src/main/AndroidManifest.xml b/build-system/tests/extractAnnotations/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0bbc8c4
--- /dev/null
+++ b/build-system/tests/extractAnnotations/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.extractannotations"
+ android:versionCode="1"
+ android:versionName="1.0" >
+</manifest>
diff --git a/build-system/tests/extractAnnotations/src/main/java/com/android/tests/extractannotations/Constants.java b/build-system/tests/extractAnnotations/src/main/java/com/android/tests/extractannotations/Constants.java
new file mode 100644
index 0000000..cddfd36
--- /dev/null
+++ b/build-system/tests/extractAnnotations/src/main/java/com/android/tests/extractannotations/Constants.java
@@ -0,0 +1,9 @@
+package com.android.tests.extractannotations;
+
+public class Constants {
+ public static final int CONSTANT_1 = 1;
+ public static final int CONSTANT_2 = CONSTANT_1 + 1;
+ public static final int CONSTANT_3 = CONSTANT_2 + 1;
+ public static final int FLAG_VALUE_1 = 0x1;
+ public static final int FLAG_VALUE_2 = 0x2;
+}
diff --git a/build-system/tests/extractAnnotations/src/main/java/com/android/tests/extractannotations/ExtractTest.java b/build-system/tests/extractAnnotations/src/main/java/com/android/tests/extractannotations/ExtractTest.java
new file mode 100644
index 0000000..1678385
--- /dev/null
+++ b/build-system/tests/extractAnnotations/src/main/java/com/android/tests/extractannotations/ExtractTest.java
@@ -0,0 +1,114 @@
+package com.android.tests.extractannotations;
+
+import android.support.annotation.ColorRes;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringDef;
+import android.support.annotation.StringRes;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+import static com.android.tests.extractannotations.Constants.FLAG_VALUE_1;
+import static com.android.tests.extractannotations.Constants.FLAG_VALUE_2;
+
+@SuppressWarnings({"JavaDoc", "UnusedDeclaration", "SpellCheckingInspection"})
+public class ExtractTest {
+ public ExtractTest(@IdRes int param1, @NonNull @StringRes String param2) {
+ }
+
+ // Nullness annotations
+
+ @Nullable
+ public Object getNullableReturn() { return null; }
+
+ @NonNull
+ protected Object getNonNullableReturn() { return ""; }
+
+ public static void setNullableNonNullable(@Nullable Number param1, @NonNull String param2) {
+ }
+
+ // Resource type annotations
+ @StringRes @IdRes
+ public int resourceTypeMethod(@DrawableRes int arg1, @IdRes @ColorRes int arg2) {
+ return 0;
+ }
+
+ // Complicated signature: check that annotation signature extracted in XML is correct
+ public <T> void resourceTypeMethodWithTypeArgs(@StringRes Map<String,? extends Number> map,
+ @DrawableRes T myArg,
+ @IdRes int arg2) {
+ }
+
+ // Typedefs
+ public void checkForeignTypeDef(@TopLevelTypeDef int topLevel) {
+ }
+
+ /** @hide */
+ @IntDef(flag=true, value={0, FLAG_VALUE_1, FLAG_VALUE_2})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Mask {}
+
+ public void testMask(@Mask int mask) {
+ }
+
+ @IntDef(flag=false, value={0, Constants.CONSTANT_1, Constants.CONSTANT_3})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface NonMask {}
+
+ public void testNonMask(@NonMask int mask) {
+ }
+
+ /** @hide */
+ @IntDef({VISIBLE, INVISIBLE, GONE, 5, 7 + 10, Constants.CONSTANT_1})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Visibility {}
+
+ public static final int VISIBLE = 0x00000000;
+ public static final int INVISIBLE = 0x00000004;
+ public static final int GONE = 0x00000008;
+
+ @Visibility
+ public int getVisibility() {
+ return VISIBLE;
+ }
+
+ /** @hide */
+ @StringDef({STRING_1, STRING_2, "literalValue", "conc" + "atenated"})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StringMode {}
+
+ public static final String STRING_1 = "String1";
+ public static final String STRING_2 = "String1";
+
+ @StringMode
+ public String getStringMode(@Visibility int visibility) {
+ return STRING_1;
+ }
+
+ // Hidden annotations: not extracted for various reasons
+
+ // This method should not be included: it's private
+ @IdRes
+ private int getPrivate() { return 0; }
+
+ // This method should not be included: it's package private
+ @IdRes
+ private Object getPackagePrivate() { return 0; }
+
+ // This method should not be included: method is hidden
+ /** @hide */
+ @IdRes
+ public int getHiddenMethod() { return 0; }
+
+ // This method should not be included: method is hidden
+ /** @hide */
+ private static class HiddenClass {
+ @IdRes
+ public int getHiddenMember() { return 0; }
+ }
+}
diff --git a/build-system/tests/extractAnnotations/src/main/java/com/android/tests/extractannotations/TopLevelTypeDef.java b/build-system/tests/extractAnnotations/src/main/java/com/android/tests/extractannotations/TopLevelTypeDef.java
new file mode 100644
index 0000000..f7a17ea
--- /dev/null
+++ b/build-system/tests/extractAnnotations/src/main/java/com/android/tests/extractannotations/TopLevelTypeDef.java
@@ -0,0 +1,11 @@
+package com.android.tests.extractannotations;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@IntDef(flag=true, value={Constants.CONSTANT_1, Constants.CONSTANT_2})
+@Retention(RetentionPolicy.SOURCE)
+public @interface TopLevelTypeDef {
+}
diff --git a/build-system/tests/filteredOutBuildType/build.gradle b/build-system/tests/filteredOutBuildType/build.gradle
new file mode 100644
index 0000000..a10ca4c
--- /dev/null
+++ b/build-system/tests/filteredOutBuildType/build.gradle
@@ -0,0 +1,21 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ variantFilter {
+ if (it.buildType.name.equals("debug")) {
+ it.ignore = true
+ }
+ }
+}
diff --git a/build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/filteredOutBuildType/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java
copy to build-system/tests/filteredOutBuildType/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/filteredOutBuildType/src/main/AndroidManifest.xml b/build-system/tests/filteredOutBuildType/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/filteredOutBuildType/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/filteredOutBuildType/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/filteredOutBuildType/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/filteredOutBuildType/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/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/filteredOutBuildType/src/main/res/drawable/icon.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/filteredOutBuildType/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/filteredOutBuildType/src/main/res/layout/main.xml b/build-system/tests/filteredOutBuildType/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/filteredOutBuildType/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/filteredOutBuildType/src/main/res/values/strings.xml b/build-system/tests/filteredOutBuildType/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/filteredOutBuildType/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/filteredOutVariants/build.gradle b/build-system/tests/filteredOutVariants/build.gradle
new file mode 100644
index 0000000..b3fe8b2
--- /dev/null
+++ b/build-system/tests/filteredOutVariants/build.gradle
@@ -0,0 +1,42 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ variantFilter {
+ String abi = it.flavors.get(0).name
+ if ("cupcake".equals(it.flavors.get(1).name) && ("x86".equals(abi) || "mips".equals(abi))) {
+ it.ignore = true
+ }
+ }
+
+ flavorDimensions "abi", "api"
+
+ productFlavors {
+ x86 {
+ flavorDimension "abi"
+ }
+ mips {
+ flavorDimension "abi"
+ }
+ arm {
+ flavorDimension "abi"
+ }
+ cupcake {
+ flavorDimension "api"
+ }
+ gingerbread {
+ flavorDimension "api"
+ }
+ }
+}
diff --git a/build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/filteredOutVariants/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java
copy to build-system/tests/filteredOutVariants/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/filteredOutVariants/src/main/AndroidManifest.xml b/build-system/tests/filteredOutVariants/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/filteredOutVariants/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/filteredOutVariants/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/filteredOutVariants/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/filteredOutVariants/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/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/filteredOutVariants/src/main/res/drawable/icon.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/filteredOutVariants/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/filteredOutVariants/src/main/res/layout/main.xml b/build-system/tests/filteredOutVariants/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/filteredOutVariants/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/filteredOutVariants/src/main/res/values/strings.xml b/build-system/tests/filteredOutVariants/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/filteredOutVariants/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/flavored/build.gradle b/build-system/tests/flavored/build.gradle
index bb26a71..17f886a 100644
--- a/build-system/tests/flavored/build.gradle
+++ b/build-system/tests/flavored/build.gradle
@@ -1,16 +1,16 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
testBuildType = "staging"
@@ -19,24 +19,24 @@
productFlavors {
f1 {
- packageName = "com.android.tests.flavored.f1"
+ applicationId = "com.android.tests.flavored.f1"
versionName = "1.0.0-f1"
}
f2 {
- packageName = "com.android.tests.flavored.f2"
+ applicationId = "com.android.tests.flavored.f2"
versionName = "1.0.0-f2"
}
}
buildTypes {
debug {
- packageNameSuffix = ".debug"
+ applicationIdSuffix = ".debug"
versionNameSuffix = ".D"
}
staging {
- packageNameSuffix = ".staging"
+ applicationIdSuffix = ".staging"
versionNameSuffix = ".S"
signingConfig signingConfigs.debug
}
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/flavored/src/androidTest/java/com/android/tests/flavored/MainTest.java b/build-system/tests/flavored/src/androidTest/java/com/android/tests/flavored/MainTest.java
new file mode 100644
index 0000000..dffb167
--- /dev/null
+++ b/build-system/tests/flavored/src/androidTest/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().toString());
+ } else {
+ assertEquals("default text", mTextView.getText().toString());
+ }
+ }
+}
+
diff --git a/build-system/tests/flavored/src/instrumentTestF2/java/com/android/tests/flavored/OtherActivityTest.java b/build-system/tests/flavored/src/androidTestF2/java/com/android/tests/flavored/OtherActivityTest.java
similarity index 100%
rename from build-system/tests/flavored/src/instrumentTestF2/java/com/android/tests/flavored/OtherActivityTest.java
rename to build-system/tests/flavored/src/androidTestF2/java/com/android/tests/flavored/OtherActivityTest.java
diff --git a/build-system/tests/flavored/src/f2/AndroidManifest.xml b/build-system/tests/flavored/src/f2/AndroidManifest.xml
index ce0bb8d..a8a578b 100644
--- a/build-system/tests/flavored/src/f2/AndroidManifest.xml
+++ b/build-system/tests/flavored/src/f2/AndroidManifest.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="com.android.tests.flavored.OtherActivity"
android:label="@string/other_activity">
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
deleted file mode 100644
index b22c53d..0000000
--- a/build-system/tests/flavored/src/instrumentTest/java/com/android/tests/flavored/MainTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-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/flavoredlib/app/build.gradle b/build-system/tests/flavoredlib/app/build.gradle
new file mode 100644
index 0000000..0dedc08
--- /dev/null
+++ b/build-system/tests/flavoredlib/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ productFlavors {
+ flavor1 {
+ applicationId = "com.android.tests.flavorlib.app.flavor1"
+ }
+ flavor2 {
+ applicationId = "com.android.tests.flavorlib.app.flavor2"
+ }
+ }
+
+ testOptions {
+ resultsDir = "$project.buildDir/foo/results"
+ reportDir = "$project.buildDir/foo/report"
+ }
+}
+
+dependencies {
+ flavor1Compile project(path: ':lib', configuration: 'flavor1Release')
+ flavor2Compile project(path: ':lib', configuration: 'flavor2Release')
+}
diff --git a/build-system/tests/flavoredlib/app/proguard-project.txt b/build-system/tests/flavoredlib/app/proguard-project.txt
new file mode 100644
index 0000000..349f80f
--- /dev/null
+++ b/build-system/tests/flavoredlib/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/flavoredlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java b/build-system/tests/flavoredlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
new file mode 100644
index 0000000..1f30ceb
--- /dev/null
+++ b/build-system/tests/flavoredlib/app/src/androidTest/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("SUCCESS-APP", mAppTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavoredlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavoredlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..75521ea
--- /dev/null
+++ b/build-system/tests/flavoredlib/app/src/androidTestFlavor1/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("SUCCESS-LIB1", mLibTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mLibTextView2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavoredlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavoredlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..9ecc66c
--- /dev/null
+++ b/build-system/tests/flavoredlib/app/src/androidTestFlavor2/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("SUCCESS-LIB2", mLibTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mLibTextView2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavoredlib/app/src/flavor1/java/com/android/tests/flavorlib/app/LibWrapper.java b/build-system/tests/flavoredlib/app/src/flavor1/java/com/android/tests/flavorlib/app/LibWrapper.java
new file mode 100644
index 0000000..56cb6e3
--- /dev/null
+++ b/build-system/tests/flavoredlib/app/src/flavor1/java/com/android/tests/flavorlib/app/LibWrapper.java
@@ -0,0 +1,14 @@
+package com.android.tests.flavorlib.app;
+
+import android.app.Activity;
+
+import com.android.tests.flavorlib.lib.flavor1.Lib;
+
+/**
+ */
+public class LibWrapper {
+
+ public static void handleTextView(Activity a) {
+ Lib.handleTextView(a);
+ }
+}
diff --git a/build-system/tests/flavoredlib/app/src/flavor1/res/values/strings.xml b/build-system/tests/flavoredlib/app/src/flavor1/res/values/strings.xml
new file mode 100644
index 0000000..b402efb
--- /dev/null
+++ b/build-system/tests/flavoredlib/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/flavoredlib/app/src/flavor2/java/com/android/tests/flavorlib/app/LibWrapper.java b/build-system/tests/flavoredlib/app/src/flavor2/java/com/android/tests/flavorlib/app/LibWrapper.java
new file mode 100644
index 0000000..b87b668
--- /dev/null
+++ b/build-system/tests/flavoredlib/app/src/flavor2/java/com/android/tests/flavorlib/app/LibWrapper.java
@@ -0,0 +1,13 @@
+package com.android.tests.flavorlib.app;
+
+import android.app.Activity;
+import com.android.tests.flavorlib.lib.flavor2.Lib;
+
+/**
+ */
+public class LibWrapper {
+
+ public static void handleTextView(Activity a) {
+ Lib.handleTextView(a);
+ }
+}
diff --git a/build-system/tests/flavoredlib/app/src/flavor2/res/values/strings.xml b/build-system/tests/flavoredlib/app/src/flavor2/res/values/strings.xml
new file mode 100644
index 0000000..af1b43c
--- /dev/null
+++ b/build-system/tests/flavoredlib/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/flavoredlib/app/src/main/AndroidManifest.xml b/build-system/tests/flavoredlib/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..cb1e0cb
--- /dev/null
+++ b/build-system/tests/flavoredlib/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" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name"
+ tools:replace="icon, label">
+ <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/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/App.java b/build-system/tests/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/App.java
new file mode 100644
index 0000000..83e8373
--- /dev/null
+++ b/build-system/tests/flavoredlib/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/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java b/build-system/tests/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
new file mode 100644
index 0000000..1930c7a
--- /dev/null
+++ b/build-system/tests/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
@@ -0,0 +1,16 @@
+package com.android.tests.flavorlib.app;
+
+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.main);
+
+ App.handleTextView(this);
+ LibWrapper.handleTextView(this);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/libsTest/libapp/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/flavoredlib/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
copy from build-system/tests/libsTest/libapp/src/main/res/drawable-hdpi/ic_launcher.png
copy to build-system/tests/flavoredlib/app/src/main/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/build-system/tests/libsTest/libapp/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/flavoredlib/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
copy from build-system/tests/libsTest/libapp/src/main/res/drawable-ldpi/ic_launcher.png
copy to build-system/tests/flavoredlib/app/src/main/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/build-system/tests/libsTest/libapp/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/flavoredlib/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
copy from build-system/tests/libsTest/libapp/src/main/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/flavoredlib/app/src/main/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavoredlib/app/src/main/res/layout/main.xml b/build-system/tests/flavoredlib/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..6761bef
--- /dev/null
+++ b/build-system/tests/flavoredlib/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/flavoredlib/app/src/main/res/values/strings.xml b/build-system/tests/flavoredlib/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..190a400
--- /dev/null
+++ b/build-system/tests/flavoredlib/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/flavoredlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt b/build-system/tests/flavoredlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
new file mode 100644
index 0000000..084e7d0
--- /dev/null
+++ b/build-system/tests/flavoredlib/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/flavoredlib/build.gradle b/build-system/tests/flavoredlib/build.gradle
new file mode 100644
index 0000000..4324232
--- /dev/null
+++ b/build-system/tests/flavoredlib/build.gradle
@@ -0,0 +1,10 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/build-system/tests/flavoredlib/lib/build.gradle b/build-system/tests/flavoredlib/lib/build.gradle
new file mode 100644
index 0000000..334a679
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ defaultPublishConfig "flavor1Release"
+ publishNonDefault true
+
+ productFlavors {
+ flavor1 { }
+ flavor2 { }
+ }
+
+ libraryVariants.all { variant ->
+ assert variant.productFlavors.size() != 0
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavoredlib/lib/proguard-project.txt b/build-system/tests/flavoredlib/lib/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/flavoredlib/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/flavoredlib/lib/src/androidTestFlavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivityTest.java b/build-system/tests/flavoredlib/lib/src/androidTestFlavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivityTest.java
new file mode 100644
index 0000000..ebde14a
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/androidTestFlavor1/java/com/android/tests/flavorlib/lib/flavor1/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.flavor1;
+
+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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mTextView2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavoredlib/lib/src/androidTestFlavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivityTest.java b/build-system/tests/flavoredlib/lib/src/androidTestFlavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivityTest.java
new file mode 100644
index 0000000..94bf086
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/androidTestFlavor2/java/com/android/tests/flavorlib/lib/flavor2/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.flavor2;
+
+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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavoredlib/lib/src/flavor1/AndroidManifest.xml b/build-system/tests/flavoredlib/lib/src/flavor1/AndroidManifest.xml
new file mode 100644
index 0000000..f6544e6
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor1/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application >
+ <activity
+ android:name=".flavor1.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/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/Lib.java b/build-system/tests/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/Lib.java
new file mode 100644
index 0000000..77ea9ea
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/Lib.java
@@ -0,0 +1,45 @@
+package com.android.tests.flavorlib.lib.flavor1;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import com.android.tests.flavorlib.lib.R;
+
+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/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivity.java b/build-system/tests/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivity.java
new file mode 100644
index 0000000..142a1cf
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivity.java
@@ -0,0 +1,17 @@
+package com.android.tests.flavorlib.lib.flavor1;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.flavorlib.lib.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.lib_main);
+
+ Lib.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/flavoredlib/lib/src/flavor1/res/layout/lib_main.xml b/build-system/tests/flavoredlib/lib/src/flavor1/res/layout/lib_main.xml
new file mode 100644
index 0000000..47e792a
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor1/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/flavoredlib/lib/src/flavor1/res/values/strings.xml b/build-system/tests/flavoredlib/lib/src/flavor1/res/values/strings.xml
new file mode 100644
index 0000000..ca7dcdb
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor1/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/flavoredlib/lib/src/flavor1/resources/com/android/tests/flavorlib/lib/flavor1/Lib.txt b/build-system/tests/flavoredlib/lib/src/flavor1/resources/com/android/tests/flavorlib/lib/flavor1/Lib.txt
new file mode 100644
index 0000000..452e397
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor1/resources/com/android/tests/flavorlib/lib/flavor1/Lib.txt
@@ -0,0 +1 @@
+SUCCESS-LIB1
\ No newline at end of file
diff --git a/build-system/tests/flavoredlib/lib/src/flavor2/AndroidManifest.xml b/build-system/tests/flavoredlib/lib/src/flavor2/AndroidManifest.xml
new file mode 100644
index 0000000..b7a5a25
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor2/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application >
+ <activity
+ android:name=".flavor2.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/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/Lib.java b/build-system/tests/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/Lib.java
new file mode 100644
index 0000000..4f1cc56
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/Lib.java
@@ -0,0 +1,45 @@
+package com.android.tests.flavorlib.lib.flavor2;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import com.android.tests.flavorlib.lib.R;
+
+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/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivity.java b/build-system/tests/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivity.java
new file mode 100644
index 0000000..4ef32c6
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivity.java
@@ -0,0 +1,17 @@
+package com.android.tests.flavorlib.lib.flavor2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.flavorlib.lib.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.lib_main);
+
+ Lib.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/flavoredlib/lib/src/flavor2/res/layout/lib_main.xml b/build-system/tests/flavoredlib/lib/src/flavor2/res/layout/lib_main.xml
new file mode 100644
index 0000000..47e792a
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor2/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/flavoredlib/lib/src/flavor2/res/values/strings.xml b/build-system/tests/flavoredlib/lib/src/flavor2/res/values/strings.xml
new file mode 100644
index 0000000..e27cb40
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor2/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/flavoredlib/lib/src/flavor2/resources/com/android/tests/flavorlib/lib/flavor2/Lib.txt b/build-system/tests/flavoredlib/lib/src/flavor2/resources/com/android/tests/flavorlib/lib/flavor2/Lib.txt
new file mode 100644
index 0000000..94cabe4
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/flavor2/resources/com/android/tests/flavorlib/lib/flavor2/Lib.txt
@@ -0,0 +1 @@
+SUCCESS-LIB2
\ No newline at end of file
diff --git a/build-system/tests/flavoredlib/lib/src/main/AndroidManifest.xml b/build-system/tests/flavoredlib/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e968cb2
--- /dev/null
+++ b/build-system/tests/flavoredlib/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.lib">
+
+ <application/>
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/flavoredlib/settings.gradle b/build-system/tests/flavoredlib/settings.gradle
new file mode 100644
index 0000000..eedb2a1
--- /dev/null
+++ b/build-system/tests/flavoredlib/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
diff --git a/build-system/tests/flavorlib/app/build.gradle b/build-system/tests/flavorlib/app/build.gradle
index ce9946c..6d66c40 100644
--- a/build-system/tests/flavorlib/app/build.gradle
+++ b/build-system/tests/flavorlib/app/build.gradle
@@ -1,15 +1,15 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
productFlavors {
flavor1 {
- packageName = "com.android.tests.flavorlib.app.flavor1"
+ applicationId = "com.android.tests.flavorlib.app.flavor1"
}
flavor2 {
- packageName = "com.android.tests.flavorlib.app.flavor2"
+ applicationId = "com.android.tests.flavorlib.app.flavor2"
}
}
diff --git a/build-system/tests/flavorlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java b/build-system/tests/flavorlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
new file mode 100644
index 0000000..f301190
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/androidTest/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("SUCCESS-APP", mAppTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavorlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavorlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..275e3ed
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/androidTestFlavor1/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("SUCCESS-LIB1", mLibTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mLibTextView2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavorlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavorlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..601f914
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/androidTestFlavor2/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("SUCCESS-LIB2", mLibTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mLibTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 2788b27..0000000
--- a/build-system/tests/flavorlib/app/src/instrumentTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
+++ /dev/null
@@ -1,84 +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.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
deleted file mode 100644
index 6dd5088..0000000
--- a/build-system/tests/flavorlib/app/src/instrumentTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
+++ /dev/null
@@ -1,84 +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.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
deleted file mode 100644
index 56988c0..0000000
--- a/build-system/tests/flavorlib/app/src/instrumentTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
+++ /dev/null
@@ -1,84 +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.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
index 45758cc..ad5007f 100644
--- a/build-system/tests/flavorlib/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/flavorlib/app/src/main/AndroidManifest.xml
@@ -5,15 +5,16 @@
android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
<uses-sdk
- android:minSdkVersion="15"
- tools:ignore="UsesMinSdkAttributes" />
+ android:minSdkVersion="15" />
<application
android:icon="@drawable/icon"
- android:label="@string/app_name" >
+ android:label="@string/app_name"
+ tools:replace="icon, label">
<activity
android:name=".MainActivity"
- android:label="@string/app_name" >
+ android:label="@string/app_name"
+ >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/build-system/tests/flavorlib/build.gradle b/build-system/tests/flavorlib/build.gradle
index a8fdb64..4324232 100644
--- a/build-system/tests/flavorlib/build.gradle
+++ b/build-system/tests/flavorlib/build.gradle
@@ -1,9 +1,9 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/flavorlib/lib1/build.gradle b/build-system/tests/flavorlib/lib1/build.gradle
index 4b2a733..ba65495 100644
--- a/build-system/tests/flavorlib/lib1/build.gradle
+++ b/build-system/tests/flavorlib/lib1/build.gradle
@@ -1,6 +1,6 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/tests/flavorlib/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
new file mode 100644
index 0000000..7561e43
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/androidTest/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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 970fcbe..0000000
--- a/build-system/tests/flavorlib/lib1/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
+++ /dev/null
@@ -1,86 +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.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/lib2/build.gradle b/build-system/tests/flavorlib/lib2/build.gradle
index 4b2a733..ba65495 100644
--- a/build-system/tests/flavorlib/lib2/build.gradle
+++ b/build-system/tests/flavorlib/lib2/build.gradle
@@ -1,6 +1,6 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/tests/flavorlib/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
new file mode 100644
index 0000000..688cea6
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/androidTest/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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 05a12e5..0000000
--- a/build-system/tests/flavorlib/lib2/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
+++ /dev/null
@@ -1,86 +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.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/app/build.gradle b/build-system/tests/flavorlibWithFailedTests/app/build.gradle
index 87e7230..e004807 100644
--- a/build-system/tests/flavorlibWithFailedTests/app/build.gradle
+++ b/build-system/tests/flavorlibWithFailedTests/app/build.gradle
@@ -1,15 +1,15 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
productFlavors {
flavor1 {
- packageName = "com.android.tests.flavorlib.app.flavor1"
+ applicationId = "com.android.tests.flavorlib.app.flavor1"
}
flavor2 {
- packageName = "com.android.tests.flavorlib.app.flavor2"
+ applicationId = "com.android.tests.flavorlib.app.flavor2"
}
}
}
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java b/build-system/tests/flavorlibWithFailedTests/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
new file mode 100644
index 0000000..f301190
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/androidTest/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("SUCCESS-APP", mAppTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavorlibWithFailedTests/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..5e73747
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/androidTestFlavor1/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("SUCCESS-LIB1", mLibTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mLibTextView2.getText().toString());
+ }
+
+ @SmallTest
+ public void testFailureOk() {
+ assertTrue("Testing failing test", false);
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavorlibWithFailedTests/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..b2c4d6a
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/androidTestFlavor2/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("SUCCESS-LIB2", mLibTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mLibTextView2.getText().toString());
+ }
+
+ @SmallTest
+ public void testIsApi17() {
+ assertEquals(17, android.os.Build.VERSION.SDK_INT);
+ }
+}
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
deleted file mode 100644
index 2788b27..0000000
--- a/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
+++ /dev/null
@@ -1,84 +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.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
deleted file mode 100644
index d4b3ded..0000000
--- a/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
+++ /dev/null
@@ -1,90 +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.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
deleted file mode 100644
index 8a4b8f7..0000000
--- a/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
+++ /dev/null
@@ -1,90 +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.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
index 45758cc..f7266e4 100644
--- a/build-system/tests/flavorlibWithFailedTests/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/AndroidManifest.xml
@@ -5,8 +5,7 @@
android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
<uses-sdk
- android:minSdkVersion="15"
- tools:ignore="UsesMinSdkAttributes" />
+ android:minSdkVersion="15" />
<application
android:icon="@drawable/icon"
diff --git a/build-system/tests/flavorlibWithFailedTests/build.gradle b/build-system/tests/flavorlibWithFailedTests/build.gradle
index a8fdb64..4324232 100644
--- a/build-system/tests/flavorlibWithFailedTests/build.gradle
+++ b/build-system/tests/flavorlibWithFailedTests/build.gradle
@@ -1,9 +1,9 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/build.gradle b/build-system/tests/flavorlibWithFailedTests/lib1/build.gradle
index 4b2a733..ba65495 100644
--- a/build-system/tests/flavorlibWithFailedTests/lib1/build.gradle
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/build.gradle
@@ -1,6 +1,6 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/tests/flavorlibWithFailedTests/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
new file mode 100644
index 0000000..077f3d6
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/androidTest/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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mTextView2.getText().toString());
+ }
+
+ @SmallTest
+ public void testFailureOk() {
+ assertTrue("Testing failing test", false);
+ }
+
+}
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
deleted file mode 100644
index 26e9518..0000000
--- a/build-system/tests/flavorlibWithFailedTests/lib1/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
+++ /dev/null
@@ -1,93 +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.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/lib2/build.gradle b/build-system/tests/flavorlibWithFailedTests/lib2/build.gradle
index 4b2a733..ba65495 100644
--- a/build-system/tests/flavorlibWithFailedTests/lib2/build.gradle
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/build.gradle
@@ -1,6 +1,6 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/tests/flavorlibWithFailedTests/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
new file mode 100644
index 0000000..688cea6
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/androidTest/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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 05a12e5..0000000
--- a/build-system/tests/flavorlibWithFailedTests/lib2/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
+++ /dev/null
@@ -1,86 +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.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/flavors/build.gradle b/build-system/tests/flavors/build.gradle
index 8eb7407..113934c 100644
--- a/build-system/tests/flavors/build.gradle
+++ b/build-system/tests/flavors/build.gradle
@@ -1,32 +1,32 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
- flavorGroups "group1", "group2"
+ flavorDimensions "group1", "group2"
productFlavors {
f1 {
- flavorGroup "group1"
+ flavorDimension "group1"
}
f2 {
- flavorGroup "group1"
+ flavorDimension "group1"
}
fa {
- flavorGroup "group2"
+ flavorDimension "group2"
}
fb {
- flavorGroup "group2"
+ flavorDimension "group2"
}
}
}
diff --git a/build-system/tests/flavors/src/androidTest/java/com/android/tests/flavors/MainActivityCustomizedTest.java b/build-system/tests/flavors/src/androidTest/java/com/android/tests/flavors/MainActivityCustomizedTest.java
new file mode 100644
index 0000000..d395bf4
--- /dev/null
+++ b/build-system/tests/flavors/src/androidTest/java/com/android/tests/flavors/MainActivityCustomizedTest.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.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 MainActivityCustomizedTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mCodeOverlay3;
+ private String testString;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityCustomizedTest() {
+ 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);
+
+ mCodeOverlay3 = (TextView) a.findViewById(R.id.codeoverlay3);
+
+ // special case of F2-fb where the customization is per full variants.
+ if ("f2".equals(BuildConfig.FLAVOR_group1) && "fb".equals(BuildConfig.FLAVOR_group2)) {
+ testString = BuildConfig.FLAVOR_group1 + "-" + BuildConfig.FLAVOR_group2 + "-" + BuildConfig.BUILD_TYPE;
+ } else {
+ testString = BuildConfig.FLAVOR_group1 + "-" + BuildConfig.FLAVOR_group2;
+ }
+ }
+
+ /**
+ * 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(mCodeOverlay3);
+ }
+
+ @MediumTest
+ public void testCodeOverlay() {
+ assertEquals(testString, mCodeOverlay3.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavors/src/androidTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java b/build-system/tests/flavors/src/androidTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java
new file mode 100644
index 0000000..e40241f
--- /dev/null
+++ b/build-system/tests/flavors/src/androidTestF1/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().toString());
+ assertEquals("f1", mResOverLay1.getText().toString());
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("f1", mBuildConfig1.getText().toString());
+ }
+
+ @MediumTest
+ public void testCodeOverlay() {
+ assertEquals("f1", mCodeOverlay1.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavors/src/androidTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java b/build-system/tests/flavors/src/androidTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java
new file mode 100644
index 0000000..cdb6a3f
--- /dev/null
+++ b/build-system/tests/flavors/src/androidTestF2/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().toString());
+ assertEquals("f2", mResOverLay1.getText().toString());
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("f2", mBuildConfig1.getText().toString());
+ }
+
+ @MediumTest
+ public void testCodeOverlay() {
+ assertEquals("f2", mCodeOverlay1.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavors/src/androidTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java b/build-system/tests/flavors/src/androidTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java
new file mode 100644
index 0000000..0ab6482
--- /dev/null
+++ b/build-system/tests/flavors/src/androidTestFa/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().toString()));
+ assertEquals("fa", mResOverLay2.getText().toString());
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("fa", mBuildConfig2.getText().toString());
+ }
+
+ @MediumTest
+ public void testCodeOverlay() {
+ assertEquals("fa", mCodeOverlay2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavors/src/androidTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java b/build-system/tests/flavors/src/androidTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java
new file mode 100644
index 0000000..29773c2
--- /dev/null
+++ b/build-system/tests/flavors/src/androidTestFb/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().toString()));
+ assertEquals("fb", mResOverLay2.getText().toString());
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("fb", mBuildConfig2.getText().toString());
+ }
+
+ @MediumTest
+ public void testCodeOverlay() {
+ assertEquals("fb", mCodeOverlay2.getText().toString());
+ }
+}
diff --git a/build-system/tests/flavors/src/f1Fa/java/com/android/tests/flavors/CustomizedClass.java b/build-system/tests/flavors/src/f1Fa/java/com/android/tests/flavors/CustomizedClass.java
new file mode 100644
index 0000000..43647c2
--- /dev/null
+++ b/build-system/tests/flavors/src/f1Fa/java/com/android/tests/flavors/CustomizedClass.java
@@ -0,0 +1,7 @@
+package com.android.tests.flavors;
+
+public class CustomizedClass {
+ public static String getString() {
+ return "f1-fa";
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/f1Fb/java/com/android/tests/flavors/CustomizedClass.java b/build-system/tests/flavors/src/f1Fb/java/com/android/tests/flavors/CustomizedClass.java
new file mode 100644
index 0000000..88e4f71
--- /dev/null
+++ b/build-system/tests/flavors/src/f1Fb/java/com/android/tests/flavors/CustomizedClass.java
@@ -0,0 +1,7 @@
+package com.android.tests.flavors;
+
+public class CustomizedClass {
+ public static String getString() {
+ return "f1-fb";
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/f2Fa/java/com/android/tests/flavors/CustomizedClass.java b/build-system/tests/flavors/src/f2Fa/java/com/android/tests/flavors/CustomizedClass.java
new file mode 100644
index 0000000..4a743bc
--- /dev/null
+++ b/build-system/tests/flavors/src/f2Fa/java/com/android/tests/flavors/CustomizedClass.java
@@ -0,0 +1,7 @@
+package com.android.tests.flavors;
+
+public class CustomizedClass {
+ public static String getString() {
+ return "f2-fa";
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/f2FbDebug/java/com/android/tests/flavors/CustomizedClass.java b/build-system/tests/flavors/src/f2FbDebug/java/com/android/tests/flavors/CustomizedClass.java
new file mode 100644
index 0000000..8f617b7
--- /dev/null
+++ b/build-system/tests/flavors/src/f2FbDebug/java/com/android/tests/flavors/CustomizedClass.java
@@ -0,0 +1,7 @@
+package com.android.tests.flavors;
+
+public class CustomizedClass {
+ public static String getString() {
+ return "f2-fb-debug";
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/f2FbRelease/java/com/android/tests/flavors/CustomizedClass.java b/build-system/tests/flavors/src/f2FbRelease/java/com/android/tests/flavors/CustomizedClass.java
new file mode 100644
index 0000000..1c7bc2d
--- /dev/null
+++ b/build-system/tests/flavors/src/f2FbRelease/java/com/android/tests/flavors/CustomizedClass.java
@@ -0,0 +1,7 @@
+package com.android.tests.flavors;
+
+public class CustomizedClass {
+ public static String getString() {
+ return "f2-fb-release";
+ }
+}
\ 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
deleted file mode 100644
index 9379d96..0000000
--- a/build-system/tests/flavors/src/instrumentTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java
+++ /dev/null
@@ -1,96 +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.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
deleted file mode 100644
index 8ecd057..0000000
--- a/build-system/tests/flavors/src/instrumentTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java
+++ /dev/null
@@ -1,96 +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.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
deleted file mode 100644
index 3e51225..0000000
--- a/build-system/tests/flavors/src/instrumentTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java
+++ /dev/null
@@ -1,98 +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.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
deleted file mode 100644
index f11b5ce..0000000
--- a/build-system/tests/flavors/src/instrumentTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java
+++ /dev/null
@@ -1,98 +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.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/java/com/android/tests/flavors/MainActivity.java b/build-system/tests/flavors/src/main/java/com/android/tests/flavors/MainActivity.java
index ff2b47c..d54dbae 100644
--- 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
@@ -24,5 +24,8 @@
tv = (TextView) findViewById(R.id.codeoverlay2);
tv.setText(com.android.tests.flavors.group2.SomeClass.getString());
+
+ tv = (TextView) findViewById(R.id.codeoverlay3);
+ tv.setText(com.android.tests.flavors.CustomizedClass.getString());
}
}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/main/res/layout/main.xml b/build-system/tests/flavors/src/main/res/layout/main.xml
index c9814d1..fb0edca 100644
--- a/build-system/tests/flavors/src/main/res/layout/main.xml
+++ b/build-system/tests/flavors/src/main/res/layout/main.xml
@@ -39,4 +39,9 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/codeoverlay3"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/genFolderApi/build.gradle b/build-system/tests/genFolderApi/build.gradle
index 229645b..7d89304 100644
--- a/build-system/tests/genFolderApi/build.gradle
+++ b/build-system/tests/genFolderApi/build.gradle
@@ -1,16 +1,16 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
@@ -42,4 +42,4 @@
}
variant.registerJavaGeneratingTask(javaGenerationTask, sourceFolder)
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/genFolderApi2/build.gradle b/build-system/tests/genFolderApi2/build.gradle
new file mode 100644
index 0000000..04107e1
--- /dev/null
+++ b/build-system/tests/genFolderApi2/build.gradle
@@ -0,0 +1,38 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
+
+
+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 ->
+ File sourceFolder = file("${buildDir}/customCode/${variant.dirName}")
+ variant.addJavaSourceFoldersToModel(sourceFolder)
+}
diff --git a/build-system/tests/genFolderApi2/src/main/AndroidManifest.xml b/build-system/tests/genFolderApi2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/genFolderApi2/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/genFolderApi2/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/genFolderApi2/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..df05828e
--- /dev/null
+++ b/build-system/tests/genFolderApi2/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/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/genFolderApi2/src/main/res/drawable/icon.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/genFolderApi2/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/genFolderApi2/src/main/res/layout/main.xml b/build-system/tests/genFolderApi2/src/main/res/layout/main.xml
new file mode 100644
index 0000000..ee817cf
--- /dev/null
+++ b/build-system/tests/genFolderApi2/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/genFolderApi2/src/main/res/values/strings.xml b/build-system/tests/genFolderApi2/src/main/res/values/strings.xml
new file mode 100644
index 0000000..64ee88e
--- /dev/null
+++ b/build-system/tests/genFolderApi2/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
index 75904f5..0a8b8a6 100644
--- a/build-system/tests/libProguard/build.gradle
+++ b/build-system/tests/libProguard/build.gradle
@@ -1,17 +1,17 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
versionCode 12
@@ -20,7 +20,9 @@
targetSdkVersion 16
proguardFile 'config.pro'
}
- release {
- runProguard true
+ buildTypes {
+ release {
+ runProguard true
+ }
}
}
diff --git a/build-system/tests/libProguardConsumerFiles/build.gradle b/build-system/tests/libProguardConsumerFiles/build.gradle
index 59ed5ea..91cbee8 100644
--- a/build-system/tests/libProguardConsumerFiles/build.gradle
+++ b/build-system/tests/libProguardConsumerFiles/build.gradle
@@ -1,17 +1,17 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
versionCode 12
@@ -22,11 +22,13 @@
consumerProguardFiles 'A.txt'
}
- debug {
- }
+ buildTypes {
+ debug {
+ }
- release {
- runProguard true
- consumerProguardFiles 'B.txt', 'C.txt'
+ release {
+ runProguard true
+ consumerProguardFiles 'B.txt', 'C.txt'
+ }
}
}
diff --git a/build-system/tests/libProguardJarDep/app/build.gradle b/build-system/tests/libProguardJarDep/app/build.gradle
index 8166bf8..4609838 100644
--- a/build-system/tests/libProguardJarDep/app/build.gradle
+++ b/build-system/tests/libProguardJarDep/app/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
repositories {
mavenCentral()
@@ -9,8 +9,8 @@
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
testBuildType "proguard"
diff --git a/build-system/tests/libProguardJarDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/libProguardJarDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..727b789
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/app/src/androidTest/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().toString());
+ }
+}
+
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
deleted file mode 100644
index 6b3ff36..0000000
--- a/build-system/tests/libProguardJarDep/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-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
index 4f8d570..f2066d2 100644
--- a/build-system/tests/libProguardJarDep/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/libProguardJarDep/app/src/main/AndroidManifest.xml
@@ -12,19 +12,4 @@
</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/build.gradle b/build-system/tests/libProguardJarDep/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/libProguardJarDep/build.gradle
+++ b/build-system/tests/libProguardJarDep/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/libProguardJarDep/lib/build.gradle b/build-system/tests/libProguardJarDep/lib/build.gradle
index 40cb840..e2b2893 100644
--- a/build-system/tests/libProguardJarDep/lib/build.gradle
+++ b/build-system/tests/libProguardJarDep/lib/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
repositories {
mavenCentral()
@@ -10,8 +10,8 @@
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
versionCode 12
@@ -21,10 +21,12 @@
proguardFile 'config.pro'
consumerProguardFiles 'config.pro'
}
- debug {
- runProguard true
- }
- release {
- runProguard true
+ buildTypes {
+ debug {
+ runProguard true
+ }
+ release {
+ runProguard true
+ }
}
}
diff --git a/build-system/tests/libProguardJarDep/lib/src/main/AndroidManifest.xml b/build-system/tests/libProguardJarDep/lib/src/main/AndroidManifest.xml
index 950a35a..8c2efe0 100644
--- a/build-system/tests/libProguardJarDep/lib/src/main/AndroidManifest.xml
+++ b/build-system/tests/libProguardJarDep/lib/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.basic">
+ package="com.android.tests.basic.lib">
<application />
</manifest>
diff --git a/build-system/tests/libProguardLibDep/app/build.gradle b/build-system/tests/libProguardLibDep/app/build.gradle
index c52309a..51bc0a5 100644
--- a/build-system/tests/libProguardLibDep/app/build.gradle
+++ b/build-system/tests/libProguardLibDep/app/build.gradle
@@ -1,12 +1,12 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
dependencies {
compile project(':lib')
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
testBuildType "proguard"
diff --git a/build-system/tests/libProguardLibDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/libProguardLibDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..81cf859
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/app/src/androidTest/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().toString());
+ }
+
+ 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/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/libProguardLibDep/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index f7289d1..0000000
--- a/build-system/tests/libProguardLibDep/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-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
index 4f8d570..a34d937 100644
--- a/build-system/tests/libProguardLibDep/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/libProguardLibDep/app/src/main/AndroidManifest.xml
@@ -10,21 +10,4 @@
</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/build.gradle b/build-system/tests/libProguardLibDep/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/libProguardLibDep/build.gradle
+++ b/build-system/tests/libProguardLibDep/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/libProguardLibDep/lib/build.gradle b/build-system/tests/libProguardLibDep/lib/build.gradle
index ce3ccf6..90e753c 100644
--- a/build-system/tests/libProguardLibDep/lib/build.gradle
+++ b/build-system/tests/libProguardLibDep/lib/build.gradle
@@ -1,12 +1,12 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
dependencies {
compile project(':lib2')
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
versionCode 12
@@ -16,7 +16,10 @@
proguardFile 'config.pro'
consumerProguardFiles 'consumerRules.pro'
}
- release {
- runProguard true
+
+ buildTypes {
+ release {
+ runProguard true
+ }
}
}
diff --git a/build-system/tests/libProguardLibDep/lib/src/main/AndroidManifest.xml b/build-system/tests/libProguardLibDep/lib/src/main/AndroidManifest.xml
index 950a35a..8c2efe0 100644
--- a/build-system/tests/libProguardLibDep/lib/src/main/AndroidManifest.xml
+++ b/build-system/tests/libProguardLibDep/lib/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.basic">
+ package="com.android.tests.basic.lib">
<application />
</manifest>
diff --git a/build-system/tests/libProguardLibDep/lib2/build.gradle b/build-system/tests/libProguardLibDep/lib2/build.gradle
index 78da4d4..2595b4d 100644
--- a/build-system/tests/libProguardLibDep/lib2/build.gradle
+++ b/build-system/tests/libProguardLibDep/lib2/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
versionCode 12
@@ -10,9 +10,12 @@
minSdkVersion 16
targetSdkVersion 16
proguardFile 'config.pro'
- consumerProguardFiles 'config.pro'
+ consumerProguardFiles 'config.pro'
}
- release {
- runProguard true
+
+ buildTypes {
+ release {
+ runProguard true
+ }
}
}
diff --git a/build-system/tests/libProguardLibDep/lib2/src/main/AndroidManifest.xml b/build-system/tests/libProguardLibDep/lib2/src/main/AndroidManifest.xml
index 593a287..ecd885c 100644
--- a/build-system/tests/libProguardLibDep/lib2/src/main/AndroidManifest.xml
+++ b/build-system/tests/libProguardLibDep/lib2/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.basic">
+ package="com.android.tests.basic.lib2">
</manifest>
diff --git a/build-system/tests/libTestDep/build.gradle b/build-system/tests/libTestDep/build.gradle
index 6d866c5..aa9fedf 100644
--- a/build-system/tests/libTestDep/build.gradle
+++ b/build-system/tests/libTestDep/build.gradle
@@ -1,13 +1,13 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
repositories {
mavenCentral()
@@ -18,6 +18,6 @@
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
-}
\ No newline at end of file
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/libTestDep/src/instrumentTest/java/com/android/tests/libdeps/MainActivityTest.java b/build-system/tests/libTestDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java
similarity index 100%
rename from build-system/tests/libTestDep/src/instrumentTest/java/com/android/tests/libdeps/MainActivityTest.java
rename to build-system/tests/libTestDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java
diff --git a/build-system/tests/libsTest/app/build.gradle b/build-system/tests/libsTest/app/build.gradle
index 7ca58b2..7cc170f 100644
--- a/build-system/tests/libsTest/app/build.gradle
+++ b/build-system/tests/libsTest/app/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
//
diff --git a/build-system/tests/libsTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java b/build-system/tests/libsTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
new file mode 100644
index 0000000..273c8d1
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/androidTest/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("SUCCESS-APP", mAppTextView1.getText().toString());
+ assertEquals("SUCCESS-LIB1", mLib1TextView1.getText().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView1.getText().toString());
+ assertEquals("SUCCESS-LIB2b", mLib2bTextView1.getText().toString());
+ assertEquals("SUCCESS-LIBAPP", mLibappTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView2.getText().toString());
+ assertEquals("SUCCESS-LIB1", mLib1TextView2.getText().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView2.getText().toString());
+ assertEquals("SUCCESS-LIB2b", mLib2bTextView2.getText().toString());
+ assertEquals("SUCCESS-LIBAPP", mLibappTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 61a0a31..0000000
--- a/build-system/tests/libsTest/app/src/instrumentTest/java/com/android/tests/libstest/app/MainActivityTest.java
+++ /dev/null
@@ -1,116 +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.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
index 74f0ff2..87d883c 100644
--- a/build-system/tests/libsTest/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/libsTest/app/src/main/AndroidManifest.xml
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.tests.libstest.app"
android:versionCode="1"
- android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
+ android:versionName="1.0">
<uses-sdk
- android:minSdkVersion="15"
- tools:ignore="UsesMinSdkAttributes" />
+ android:minSdkVersion="15" />
<application
android:icon="@drawable/icon"
- android:label="@string/app_name" >
+ android:label="@string/app_name" tools:replace="label, icon">
<activity
android:name="com.android.tests.libstest.app.MainActivity"
android:label="@string/app_name" >
diff --git a/build-system/tests/libsTest/build.gradle b/build-system/tests/libsTest/build.gradle
index a8fdb64..4324232 100644
--- a/build-system/tests/libsTest/build.gradle
+++ b/build-system/tests/libsTest/build.gradle
@@ -1,9 +1,9 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/libsTest/lib1/build.gradle b/build-system/tests/libsTest/lib1/build.gradle
index 8975f4b..49c5492 100644
--- a/build-system/tests/libsTest/lib1/build.gradle
+++ b/build-system/tests/libsTest/lib1/build.gradle
@@ -1,15 +1,16 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
dependencies {
compile project(':lib2')
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ resourcePrefix 'lib1_'
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
minSdkVersion 14
targetSdkVersion 15
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/libsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java b/build-system/tests/libsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
new file mode 100644
index 0000000..6136a06
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/androidTest/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().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mLib1TextView2.getText().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 4ed7ae6..0000000
--- a/build-system/tests/libsTest/lib1/src/instrumentTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
+++ /dev/null
@@ -1,92 +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.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
index 7739b4a..9646885 100644
--- a/build-system/tests/libsTest/lib1/src/main/AndroidManifest.xml
+++ b/build-system/tests/libsTest/lib1/src/main/AndroidManifest.xml
@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.tests.libstest.lib1"
android:versionCode="1"
android:versionName="1.0" >
<application
- android:icon="@drawable/ic_launcher"
- android:label="@string/lib1_name" >
+ android:icon="@drawable/lib1_ic_launcher"
+ android:label="@string/lib1_name" tools:replace="label, icon" >
<activity
android:name="MainActivity"
android:label="@string/lib1_name" >
@@ -18,4 +19,4 @@
</activity>
</application>
-</manifest>
\ No newline at end of file
+</manifest>
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/lib1_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/tests/libsTest/lib1/src/main/res/drawable-hdpi/lib1_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/lib1_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/tests/libsTest/lib1/src/main/res/drawable-ldpi/lib1_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/lib1_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/lib1_ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib2/build.gradle b/build-system/tests/libsTest/lib2/build.gradle
index 4b2a733..b1c9361 100644
--- a/build-system/tests/libsTest/lib2/build.gradle
+++ b/build-system/tests/libsTest/lib2/build.gradle
@@ -1,6 +1,15 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
-}
\ No newline at end of file
+ resourcePrefix 'lib2_'
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+}
+
+dependencies {
+ compile 'com.google.android.gms:play-services:3.1.36'
+}
diff --git a/build-system/tests/libsTest/lib2/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java b/build-system/tests/libsTest/lib2/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
new file mode 100644
index 0000000..e4b7190
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/androidTest/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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 6ac4a5c..0000000
--- a/build-system/tests/libsTest/lib2/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
+++ /dev/null
@@ -1,86 +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.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
index 9374b53..3908662 100644
--- a/build-system/tests/libsTest/lib2/src/main/AndroidManifest.xml
+++ b/build-system/tests/libsTest/lib2/src/main/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionName="1.0" >
<application
- android:icon="@drawable/ic_launcher"
+ android:icon="@drawable/lib2_ic_launcher"
android:label="@string/lib2_name" >
<activity
android:name="MainActivity"
@@ -18,4 +18,4 @@
</activity>
</application>
-</manifest>
\ No newline at end of file
+</manifest>
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/lib2_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/lib2/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/tests/libsTest/lib2/src/main/res/drawable-hdpi/lib2_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/lib2_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/lib2/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/tests/libsTest/lib2/src/main/res/drawable-ldpi/lib2_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/lib2_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/lib2/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/tests/libsTest/lib2/src/main/res/drawable-mdpi/lib2_ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib2b/build.gradle b/build-system/tests/libsTest/lib2b/build.gradle
index 4b2a733..f86228b 100644
--- a/build-system/tests/libsTest/lib2b/build.gradle
+++ b/build-system/tests/libsTest/lib2b/build.gradle
@@ -1,6 +1,7 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
-}
\ No newline at end of file
+ resourcePrefix 'lib2b_'
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/libsTest/lib2b/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java b/build-system/tests/libsTest/lib2b/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
new file mode 100644
index 0000000..71cb731
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/androidTest/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.lib2b.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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2b", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 35c56e3..0000000
--- a/build-system/tests/libsTest/lib2b/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
+++ /dev/null
@@ -1,86 +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.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
index 814af46..2f90fe8 100644
--- a/build-system/tests/libsTest/lib2b/src/main/AndroidManifest.xml
+++ b/build-system/tests/libsTest/lib2b/src/main/AndroidManifest.xml
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.libstest.lib2"
+ package="com.android.tests.libstest.lib2b"
android:versionCode="1"
android:versionName="1.0" >
<application
- android:icon="@drawable/ic_launcher"
+ android:icon="@drawable/lib2b_ic_launcher"
android:label="@string/lib2b_name" >
<activity
- android:name="MainActivity2b"
+ android:name="com.android.tests.libstest.lib2.MainActivity2b"
android:label="@string/lib2b_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -18,4 +18,4 @@
</activity>
</application>
-</manifest>
\ No newline at end of file
+</manifest>
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
index e4329e5..42ad441 100644
--- 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
@@ -8,6 +8,8 @@
import java.io.InputStream;
import java.io.InputStreamReader;
+import com.android.tests.libstest.lib2b.R;
+
public class Lib2b {
public static void handleTextView(Activity a) {
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
index 2e09018..f6ec10b 100644
--- 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
@@ -3,6 +3,8 @@
import android.app.Activity;
import android.os.Bundle;
+import com.android.tests.libstest.lib2b.R;
+
public class MainActivity2b extends Activity {
/** Called when the activity is first created. */
@Override
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/lib2b_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/lib2b/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/tests/libsTest/lib2b/src/main/res/drawable-hdpi/lib2b_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/lib2b_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/lib2b/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/tests/libsTest/lib2b/src/main/res/drawable-ldpi/lib2b_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/lib2b_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/lib2b/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/tests/libsTest/lib2b/src/main/res/drawable-mdpi/lib2b_ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/libapp/build.gradle b/build-system/tests/libsTest/libapp/build.gradle
index 4b2a733..4ea8731 100644
--- a/build-system/tests/libsTest/libapp/build.gradle
+++ b/build-system/tests/libsTest/libapp/build.gradle
@@ -1,6 +1,7 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
-}
\ No newline at end of file
+ resourcePrefix 'libapp_'
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/libsTest/libapp/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java b/build-system/tests/libsTest/libapp/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
new file mode 100644
index 0000000..541ac60
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/androidTest/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.libapp.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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIBAPP", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index e6087a7..0000000
--- a/build-system/tests/libsTest/libapp/src/instrumentTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
+++ /dev/null
@@ -1,86 +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.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
index 0592d2d..a3552ea 100644
--- a/build-system/tests/libsTest/libapp/src/main/AndroidManifest.xml
+++ b/build-system/tests/libsTest/libapp/src/main/AndroidManifest.xml
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.libstest.app"
+ package="com.android.tests.libstest.libapp"
android:versionCode="1"
android:versionName="1.0" >
<application
- android:icon="@drawable/ic_launcher"
+ android:icon="@drawable/libapp_ic_launcher"
android:label="@string/libapp_name" >
<activity
- android:name="MainActivityLibApp"
+ android:name="com.android.tests.libstest.app.MainActivityLibApp"
android:label="@string/libapp_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -18,4 +18,4 @@
</activity>
</application>
-</manifest>
\ No newline at end of file
+</manifest>
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
index 9a25e9e..22b6b1c 100644
--- 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
@@ -8,6 +8,8 @@
import java.io.InputStream;
import java.io.InputStreamReader;
+import com.android.tests.libstest.libapp.R;
+
public class LibApp {
public static void handleTextView(Activity a) {
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
index 65460f7..a6f1bac 100644
--- 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
@@ -3,6 +3,8 @@
import android.app.Activity;
import android.os.Bundle;
+import com.android.tests.libstest.libapp.R;
+
public class MainActivityLibApp extends Activity {
/** Called when the activity is first created. */
@Override
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/libapp_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/libapp/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/tests/libsTest/libapp/src/main/res/drawable-hdpi/libapp_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/libapp_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/libapp/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/tests/libsTest/libapp/src/main/res/drawable-ldpi/libapp_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/libapp_ic_launcher.png
similarity index 100%
rename from build-system/tests/libsTest/libapp/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/tests/libsTest/libapp/src/main/res/drawable-mdpi/libapp_ic_launcher.png
Binary files differ
diff --git a/build-system/tests/localAarTest/app/build.gradle b/build-system/tests/localAarTest/app/build.gradle
new file mode 100644
index 0000000..331badb
--- /dev/null
+++ b/build-system/tests/localAarTest/app/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':lib1')
+}
diff --git a/build-system/tests/localAarTest/app/proguard-project.txt b/build-system/tests/localAarTest/app/proguard-project.txt
new file mode 100644
index 0000000..349f80f
--- /dev/null
+++ b/build-system/tests/localAarTest/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/localAarTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java b/build-system/tests/localAarTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
new file mode 100644
index 0000000..273c8d1
--- /dev/null
+++ b/build-system/tests/localAarTest/app/src/androidTest/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("SUCCESS-APP", mAppTextView1.getText().toString());
+ assertEquals("SUCCESS-LIB1", mLib1TextView1.getText().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView1.getText().toString());
+ assertEquals("SUCCESS-LIB2b", mLib2bTextView1.getText().toString());
+ assertEquals("SUCCESS-LIBAPP", mLibappTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView2.getText().toString());
+ assertEquals("SUCCESS-LIB1", mLib1TextView2.getText().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView2.getText().toString());
+ assertEquals("SUCCESS-LIB2b", mLib2bTextView2.getText().toString());
+ assertEquals("SUCCESS-LIBAPP", mLibappTextView2.getText().toString());
+ }
+}
diff --git a/build-system/tests/localAarTest/app/src/main/AndroidManifest.xml b/build-system/tests/localAarTest/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..06c3ee2
--- /dev/null
+++ b/build-system/tests/localAarTest/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?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"/>
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ 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/localAarTest/app/src/main/java/com/android/tests/libstest/app/App.java b/build-system/tests/localAarTest/app/src/main/java/com/android/tests/libstest/app/App.java
new file mode 100644
index 0000000..54e2a09
--- /dev/null
+++ b/build-system/tests/localAarTest/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/localAarTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java b/build-system/tests/localAarTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
new file mode 100644
index 0000000..c07ca38
--- /dev/null
+++ b/build-system/tests/localAarTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.libstest.app;
+
+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.main);
+
+ App.handleTextView(this);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/localAarTest/app/src/main/res/layout/main.xml b/build-system/tests/localAarTest/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..42a2bd6
--- /dev/null
+++ b/build-system/tests/localAarTest/app/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"
+ 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" />
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/localAarTest/app/src/main/res/values/strings.xml b/build-system/tests/localAarTest/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..2a2e006
--- /dev/null
+++ b/build-system/tests/localAarTest/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/localAarTest/app/src/main/resources/com/android/tests/libstest/app/App.txt b/build-system/tests/localAarTest/app/src/main/resources/com/android/tests/libstest/app/App.txt
new file mode 100644
index 0000000..084e7d0
--- /dev/null
+++ b/build-system/tests/localAarTest/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/localAarTest/build.gradle b/build-system/tests/localAarTest/build.gradle
new file mode 100644
index 0000000..4324232
--- /dev/null
+++ b/build-system/tests/localAarTest/build.gradle
@@ -0,0 +1,10 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/build-system/tests/localAarTest/lib1/build.gradle b/build-system/tests/localAarTest/lib1/build.gradle
new file mode 100644
index 0000000..a6ce841
--- /dev/null
+++ b/build-system/tests/localAarTest/lib1/build.gradle
@@ -0,0 +1,17 @@
+configurations.create("default")
+artifacts.add("default", file('lib1.aar'))
+
+/* uncomment to generate lib1.aar
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 15
+ }
+}
+*/
\ No newline at end of file
diff --git a/build-system/tests/localAarTest/lib1/lib1.aar b/build-system/tests/localAarTest/lib1/lib1.aar
new file mode 100644
index 0000000..f09dd6a
--- /dev/null
+++ b/build-system/tests/localAarTest/lib1/lib1.aar
Binary files differ
diff --git a/build-system/tests/localAarTest/lib1/src/main/AndroidManifest.xml b/build-system/tests/localAarTest/lib1/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a64391f
--- /dev/null
+++ b/build-system/tests/localAarTest/lib1/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.lib1" >
+
+ <application />
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/localAarTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png
copy to build-system/tests/localAarTest/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/localAarTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png
copy to build-system/tests/localAarTest/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/localAarTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/localAarTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/localAarTest/settings.gradle b/build-system/tests/localAarTest/settings.gradle
new file mode 100644
index 0000000..ceacfd5
--- /dev/null
+++ b/build-system/tests/localAarTest/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib1'
diff --git a/build-system/tests/localJars/app/build.gradle b/build-system/tests/localJars/app/build.gradle
index 286d360..9791a09 100644
--- a/build-system/tests/localJars/app/build.gradle
+++ b/build-system/tests/localJars/app/build.gradle
@@ -1,8 +1,13 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ packagingOptions {
+ exclude 'META-INF/exclude.txt'
+ exclude 'META-INF/LICENSE'
+ }
}
dependencies {
diff --git a/build-system/tests/localJars/baseLibrary/build.gradle b/build-system/tests/localJars/baseLibrary/build.gradle
index e8609b5..7fa7c44 100644
--- a/build-system/tests/localJars/baseLibrary/build.gradle
+++ b/build-system/tests/localJars/baseLibrary/build.gradle
@@ -1,8 +1,12 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ packagingOptions {
+ exclude 'META-INF/exclude.txt'
+ }
}
dependencies {
diff --git a/build-system/tests/localJars/baseLibrary/libs/util-1.0.jar b/build-system/tests/localJars/baseLibrary/libs/util-1.0.jar
index 58972e7..88dcf46 100644
--- a/build-system/tests/localJars/baseLibrary/libs/util-1.0.jar
+++ b/build-system/tests/localJars/baseLibrary/libs/util-1.0.jar
Binary files differ
diff --git a/build-system/tests/localJars/build.gradle b/build-system/tests/localJars/build.gradle
index 0916ba9..699a9a4 100644
--- a/build-system/tests/localJars/build.gradle
+++ b/build-system/tests/localJars/build.gradle
@@ -1,9 +1,9 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/localJars/library/build.gradle b/build-system/tests/localJars/library/build.gradle
index 979a308..2757df5 100644
--- a/build-system/tests/localJars/library/build.gradle
+++ b/build-system/tests/localJars/library/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
dependencies {
diff --git a/build-system/tests/localJars/util/src/main/resources/META-INF/exclude.txt b/build-system/tests/localJars/util/src/main/resources/META-INF/exclude.txt
new file mode 100644
index 0000000..192ed2d
--- /dev/null
+++ b/build-system/tests/localJars/util/src/main/resources/META-INF/exclude.txt
@@ -0,0 +1 @@
+please exclude!
\ No newline at end of file
diff --git a/build-system/tests/mavenLocal/.gitignore b/build-system/tests/mavenLocal/.gitignore
new file mode 100644
index 0000000..6971bfa
--- /dev/null
+++ b/build-system/tests/mavenLocal/.gitignore
@@ -0,0 +1 @@
+testrepo
\ No newline at end of file
diff --git a/build-system/tests/mavenLocal/app/build.gradle b/build-system/tests/mavenLocal/app/build.gradle
new file mode 100644
index 0000000..5f81d76
--- /dev/null
+++ b/build-system/tests/mavenLocal/app/build.gradle
@@ -0,0 +1,26 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'maven'
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.example.android.multiproject:lib:1.0'
+}
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
+
diff --git a/build-system/tests/mavenLocal/app/src/main/AndroidManifest.xml b/build-system/tests/mavenLocal/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..71e7a47
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/mavenLocal/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/tests/mavenLocal/app/src/main/java/com/example/android/multiproject/MainActivity.java
new file mode 100644
index 0000000..6a8b95b
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/build-system/tests/mavenLocal/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/mavenLocal/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/build-system/tests/mavenLocal/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/mavenLocal/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/mavenLocal/app/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/build-system/tests/mavenLocal/app/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/mavenLocal/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/mavenLocal/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/build-system/tests/mavenLocal/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/mavenLocal/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/tests/mavenLocal/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/build-system/tests/mavenLocal/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/mavenLocal/app/src/main/res/layout/main.xml b/build-system/tests/mavenLocal/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..ccc59fb
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/mavenLocal/app/src/main/res/values/strings.xml b/build-system/tests/mavenLocal/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e1f49b6
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/mavenLocal/baseLibrary/build.gradle b/build-system/tests/mavenLocal/baseLibrary/build.gradle
new file mode 100644
index 0000000..4ebef5b
--- /dev/null
+++ b/build-system/tests/mavenLocal/baseLibrary/build.gradle
@@ -0,0 +1,36 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.example.android.multiproject:util:1.0'
+ releaseCompile 'com.google.guava:guava:11.0.2'
+}
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'baseLib'
+version = '1.0'
+
+uploadArchives {
+ repositories {
+ mavenInstaller()
+ }
+}
diff --git a/build-system/tests/mavenLocal/baseLibrary/src/main/AndroidManifest.xml b/build-system/tests/mavenLocal/baseLibrary/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..54d079c
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/mavenLocal/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java b/build-system/tests/mavenLocal/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
new file mode 100644
index 0000000..b218532
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/mavenLocal/library/build.gradle b/build-system/tests/mavenLocal/library/build.gradle
new file mode 100644
index 0000000..9795a68
--- /dev/null
+++ b/build-system/tests/mavenLocal/library/build.gradle
@@ -0,0 +1,35 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.example.android.multiproject:baseLib:1.0'
+}
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'lib'
+version = '2.0'
+
+uploadArchives {
+ repositories {
+ mavenInstaller()
+ }
+}
diff --git a/build-system/tests/mavenLocal/library/src/main/AndroidManifest.xml b/build-system/tests/mavenLocal/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2bc9331
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/mavenLocal/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java b/build-system/tests/mavenLocal/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
new file mode 100644
index 0000000..a3f2195
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/mavenLocal/library/src/main/res/values/strings.xml b/build-system/tests/mavenLocal/library/src/main/res/values/strings.xml
new file mode 100644
index 0000000..45e9dbb
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/mavenLocal/util/build.gradle b/build-system/tests/mavenLocal/util/build.gradle
new file mode 100644
index 0000000..d182fb6
--- /dev/null
+++ b/build-system/tests/mavenLocal/util/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'util'
+version = '1.0'
+
+sourceCompatibility = "1.6"
+targetCompatibility = "1.6"
+
+uploadArchives {
+ repositories {
+ mavenInstaller()
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/mavenLocal/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/tests/mavenLocal/util/src/main/java/com/example/android/multiproject/person/People.java
new file mode 100644
index 0000000..2dbc9b5
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/mavenLocal/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/tests/mavenLocal/util/src/main/java/com/example/android/multiproject/person/Person.java
new file mode 100644
index 0000000..2f4aa9f
--- /dev/null
+++ b/build-system/tests/mavenLocal/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/migrated/AndroidManifest.xml b/build-system/tests/migrated/AndroidManifest.xml
index 4f8d570..a34d937 100644
--- a/build-system/tests/migrated/AndroidManifest.xml
+++ b/build-system/tests/migrated/AndroidManifest.xml
@@ -10,21 +10,4 @@
</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/build.gradle b/build-system/tests/migrated/build.gradle
index 6c2470f..0c76867 100644
--- a/build-system/tests/migrated/build.gradle
+++ b/build-system/tests/migrated/build.gradle
@@ -1,16 +1,16 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
sourceSets {
main {
@@ -23,7 +23,7 @@
// srcDir 'src'
// would *add* to the default folder so we use a different syntax
srcDirs = ['src']
- exclude 'some/unwanted/package/**'
+ exclude 'some/unwanted/packageName/**'
}
res {
srcDirs = ['res']
@@ -42,10 +42,10 @@
}
}
- // this moves src/instrumentTest to tests so all folders follow:
+ // this moves src/androidTest 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')
+ androidTest.setRoot('tests')
// Could also be done with:
//main.manifest.srcFile 'AndroidManifest.xml'
@@ -53,6 +53,6 @@
//main.res.srcDir 'res'
//main.assets.srcDir 'assets'
//main.resources.srcDir 'src'
- //instrumentTest.java.srcDir 'tests/src'
+ //androidTest.java.srcDir 'tests/src'
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/migrated/src/com/android/tests/basic/Foo.aidl b/build-system/tests/migrated/src/com/android/tests/basic/Foo.aidl
new file mode 100644
index 0000000..8cc8e1a
--- /dev/null
+++ b/build-system/tests/migrated/src/com/android/tests/basic/Foo.aidl
@@ -0,0 +1,3 @@
+package com.android.tests.basic;
+
+parcelable Foo;
\ No newline at end of file
diff --git a/build-system/tests/migrated/src/com/android/tests/basic/Foo.java b/build-system/tests/migrated/src/com/android/tests/basic/Foo.java
new file mode 100644
index 0000000..3cbcf14
--- /dev/null
+++ b/build-system/tests/migrated/src/com/android/tests/basic/Foo.java
@@ -0,0 +1,43 @@
+package com.android.tests.basic;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class Foo implements Parcelable {
+
+ private final int foo;
+
+ public static final Parcelable.Creator<Foo> CREATOR = new
+ Parcelable.Creator<Foo>() {
+ public Foo createFromParcel(Parcel in) {
+ return new Foo(in);
+ }
+
+ public Foo[] newArray(int size) {
+ return new Foo[size];
+ }
+ };
+
+
+ public Foo(int foo) {
+ this.foo = foo;
+ }
+
+ private Foo(Parcel in) {
+ foo = in.readInt();
+ }
+
+ int getFoo() {
+ return foo;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(foo);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/migrated/src/com/android/tests/basic/IService.aidl b/build-system/tests/migrated/src/com/android/tests/basic/IService.aidl
new file mode 100644
index 0000000..9de0889
--- /dev/null
+++ b/build-system/tests/migrated/src/com/android/tests/basic/IService.aidl
@@ -0,0 +1,20 @@
+package com.android.tests.basic;
+
+import com.android.tests.basic.Foo;
+
+// Declare any non-default types here with import statements
+
+/** Example service interface */
+interface IService {
+ /** Request the process ID of this service, to do evil things with it. */
+ int getPid();
+
+ /** Demonstrates some basic types that you can use as parameters
+ * and return values in AIDL.
+ */
+ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
+ double aDouble, String aString);
+
+
+ Foo getFoo();
+}
\ No newline at end of file
diff --git a/build-system/tests/migrated/src/some/unwanted/packageName/Useless.java b/build-system/tests/migrated/src/some/unwanted/packageName/Useless.java
new file mode 100644
index 0000000..821cfeb
--- /dev/null
+++ b/build-system/tests/migrated/src/some/unwanted/packageName/Useless.java
@@ -0,0 +1,6 @@
+package some.unwanted.packageName;
+
+public class Useless {
+ public static final void doNothing() {
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/multiproject/app/build.gradle b/build-system/tests/multiproject/app/build.gradle
index 286d360..243b2bb 100644
--- a/build-system/tests/multiproject/app/build.gradle
+++ b/build-system/tests/multiproject/app/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
dependencies {
diff --git a/build-system/tests/multiproject/baseLibrary/build.gradle b/build-system/tests/multiproject/baseLibrary/build.gradle
index b746cd3..0ad73b9 100644
--- a/build-system/tests/multiproject/baseLibrary/build.gradle
+++ b/build-system/tests/multiproject/baseLibrary/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
dependencies {
diff --git a/build-system/tests/multiproject/build.gradle b/build-system/tests/multiproject/build.gradle
index 0916ba9..699a9a4 100644
--- a/build-system/tests/multiproject/build.gradle
+++ b/build-system/tests/multiproject/build.gradle
@@ -1,9 +1,9 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/multiproject/library/build.gradle b/build-system/tests/multiproject/library/build.gradle
index 4c62fe2..f43f578 100644
--- a/build-system/tests/multiproject/library/build.gradle
+++ b/build-system/tests/multiproject/library/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
dependencies {
diff --git a/build-system/tests/multires/build.gradle b/build-system/tests/multires/build.gradle
index dafdfae..aefbdc0 100644
--- a/build-system/tests/multires/build.gradle
+++ b/build-system/tests/multires/build.gradle
@@ -1,16 +1,16 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
sourceSets {
main {
@@ -19,4 +19,4 @@
}
}
}
-}
\ 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/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from build-system/tests/multires/src/instrumentTest/java/com/android/tests/basic/MainTest.java
rename to build-system/tests/multires/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/ndkJniLib/app/build.gradle b/build-system/tests/ndkJniLib/app/build.gradle
index 0c26b2a..2fba2ab 100644
--- a/build-system/tests/ndkJniLib/app/build.gradle
+++ b/build-system/tests/ndkJniLib/app/build.gradle
@@ -1,28 +1,69 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
dependencies {
compile project(':lib')
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ // This actual the app version code. Giving ourselves 100,000 values [0, 99999]
+ defaultConfig.versionCode = 123
+
+ flavorDimensions "api", "abi"
productFlavors {
+ gingerbread {
+ flavorDimension "api"
+ minSdkVersion 10
+ versionCode = 1
+ }
+ icecreamSandwich {
+ flavorDimension "api"
+ minSdkVersion 14
+ versionCode = 2
+ }
x86 {
+ flavorDimension "abi"
ndk {
abiFilter "x86"
}
+ // this is the flavor part of the version code.
+ // It must be higher than the arm one for devices supporting
+ // both, as x86 is preferred.
+ versionCode = 3
}
arm {
+ flavorDimension "abi"
ndk {
abiFilter "armeabi-v7a"
}
+ versionCode = 2
}
mips {
+ flavorDimension "abi"
ndk {
abiFilter "mips"
}
+ versionCode = 1
+ }
+ fat {
+ flavorDimension "abi"
+ // fat binary, lowest version code to be
+ // the last option
+ versionCode = 0
}
}
-}
\ No newline at end of file
+
+ // make per-variant version code
+ applicationVariants.all { variant ->
+ // get the version code of each flavor
+ def apiVersion = variant.productFlavors.get(0).versionCode
+ def abiVersion = variant.productFlavors.get(1).versionCode
+
+ // set the composite code
+ variant.mergedFlavor.versionCode = apiVersion * 1000000 + abiVersion * 100000 + defaultConfig.versionCode
+ }
+
+}
diff --git a/build-system/tests/ndkJniLib/app/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/tests/ndkJniLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
rename from build-system/tests/ndkJniLib/app/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java
rename to build-system/tests/ndkJniLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/build-system/tests/ndkJniLib/build.gradle b/build-system/tests/ndkJniLib/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/ndkJniLib/build.gradle
+++ b/build-system/tests/ndkJniLib/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/ndkJniLib/lib/build.gradle b/build-system/tests/ndkJniLib/lib/build.gradle
index 5d18344..0a26cbb 100644
--- a/build-system/tests/ndkJniLib/lib/build.gradle
+++ b/build-system/tests/ndkJniLib/lib/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
ndk {
diff --git a/build-system/tests/ndkJniLib/lib/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/tests/ndkJniLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
rename from build-system/tests/ndkJniLib/lib/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java
rename to build-system/tests/ndkJniLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/build-system/tests/ndkLibPrebuilts/build.gradle b/build-system/tests/ndkLibPrebuilts/build.gradle
new file mode 100644
index 0000000..69194a9
--- /dev/null
+++ b/build-system/tests/ndkLibPrebuilts/build.gradle
@@ -0,0 +1,15 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/ndkJniLib/app/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/tests/ndkLibPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
copy from build-system/tests/ndkJniLib/app/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java
copy to build-system/tests/ndkLibPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/build-system/tests/ndkLibPrebuilts/src/main/AndroidManifest.xml b/build-system/tests/ndkLibPrebuilts/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..bb11409
--- /dev/null
+++ b/build-system/tests/ndkLibPrebuilts/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?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">
+ <activity android:name="com.example.hellojni.lib.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/ndkLibPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java b/build-system/tests/ndkLibPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java
new file mode 100644
index 0000000..c97a0eb
--- /dev/null
+++ b/build-system/tests/ndkLibPrebuilts/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/ndkLibPrebuilts/src/main/jniLibs/armeabi-v7a/libhello-jni.so b/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/armeabi-v7a/libhello-jni.so
new file mode 100755
index 0000000..deb9cae
--- /dev/null
+++ b/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/armeabi-v7a/libhello-jni.so
Binary files differ
diff --git a/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/armeabi/libhello-jni.so b/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/armeabi/libhello-jni.so
new file mode 100755
index 0000000..ea63ce4
--- /dev/null
+++ b/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/armeabi/libhello-jni.so
Binary files differ
diff --git a/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/mips/libhello-jni.so b/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/mips/libhello-jni.so
new file mode 100755
index 0000000..07b79cb
--- /dev/null
+++ b/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/mips/libhello-jni.so
Binary files differ
diff --git a/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/x86/libhello-jni.so b/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/x86/libhello-jni.so
new file mode 100755
index 0000000..46fef16
--- /dev/null
+++ b/build-system/tests/ndkLibPrebuilts/src/main/jniLibs/x86/libhello-jni.so
Binary files differ
diff --git a/build-system/tests/ndkLibPrebuilts/src/main/res/values/strings.xml b/build-system/tests/ndkLibPrebuilts/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c526073
--- /dev/null
+++ b/build-system/tests/ndkLibPrebuilts/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/ndkPrebuilts/build.gradle b/build-system/tests/ndkPrebuilts/build.gradle
new file mode 100644
index 0000000..3f10fed
--- /dev/null
+++ b/build-system/tests/ndkPrebuilts/build.gradle
@@ -0,0 +1,34 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilters "armeabi-v7a", "armeabi"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+
+}
diff --git a/build-system/tests/ndkJniLib/app/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/tests/ndkPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
copy from build-system/tests/ndkJniLib/app/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java
copy to build-system/tests/ndkPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/build-system/tests/ndkPrebuilts/src/main/AndroidManifest.xml b/build-system/tests/ndkPrebuilts/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..bb11409
--- /dev/null
+++ b/build-system/tests/ndkPrebuilts/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?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">
+ <activity android:name="com.example.hellojni.lib.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/ndkPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java b/build-system/tests/ndkPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java
new file mode 100644
index 0000000..c97a0eb
--- /dev/null
+++ b/build-system/tests/ndkPrebuilts/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/ndkPrebuilts/src/main/jniLibs/armeabi-v7a/libhello-jni.so b/build-system/tests/ndkPrebuilts/src/main/jniLibs/armeabi-v7a/libhello-jni.so
new file mode 100755
index 0000000..deb9cae
--- /dev/null
+++ b/build-system/tests/ndkPrebuilts/src/main/jniLibs/armeabi-v7a/libhello-jni.so
Binary files differ
diff --git a/build-system/tests/ndkPrebuilts/src/main/jniLibs/armeabi/libhello-jni.so b/build-system/tests/ndkPrebuilts/src/main/jniLibs/armeabi/libhello-jni.so
new file mode 100755
index 0000000..ea63ce4
--- /dev/null
+++ b/build-system/tests/ndkPrebuilts/src/main/jniLibs/armeabi/libhello-jni.so
Binary files differ
diff --git a/build-system/tests/ndkPrebuilts/src/main/jniLibs/mips/libhello-jni.so b/build-system/tests/ndkPrebuilts/src/main/jniLibs/mips/libhello-jni.so
new file mode 100755
index 0000000..07b79cb
--- /dev/null
+++ b/build-system/tests/ndkPrebuilts/src/main/jniLibs/mips/libhello-jni.so
Binary files differ
diff --git a/build-system/tests/ndkPrebuilts/src/main/jniLibs/x86/libhello-jni.so b/build-system/tests/ndkPrebuilts/src/main/jniLibs/x86/libhello-jni.so
new file mode 100755
index 0000000..46fef16
--- /dev/null
+++ b/build-system/tests/ndkPrebuilts/src/main/jniLibs/x86/libhello-jni.so
Binary files differ
diff --git a/build-system/tests/ndkPrebuilts/src/main/res/values/strings.xml b/build-system/tests/ndkPrebuilts/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c526073
--- /dev/null
+++ b/build-system/tests/ndkPrebuilts/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/ndkRsHelloCompute/build.gradle b/build-system/tests/ndkRsHelloCompute/build.gradle
index 4c0f284..db44f17 100644
--- a/build-system/tests/ndkRsHelloCompute/build.gradle
+++ b/build-system/tests/ndkRsHelloCompute/build.gradle
@@ -1,16 +1,16 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
compileSdkVersion 19
- buildToolsVersion "19.0.1"
+ buildToolsVersion '19.1.0'
defaultConfig {
renderscriptNdkMode true
@@ -40,4 +40,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/ndkSanAngeles/build.gradle b/build-system/tests/ndkSanAngeles/build.gradle
index a0bd5d4..6b48d79 100644
--- a/build-system/tests/ndkSanAngeles/build.gradle
+++ b/build-system/tests/ndkSanAngeles/build.gradle
@@ -1,16 +1,16 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
ndk {
@@ -19,6 +19,10 @@
ldLibs "GLESv1_CM", "dl", "log"
stl "stlport_static"
}
+
+ // This actual the app version code. Giving ourselves 1,000,000 values
+ versionCode = 123
+
}
buildTypes.debug.jniDebugBuild true
@@ -28,16 +32,37 @@
ndk {
abiFilter "x86"
}
+
+ // this is the flavor part of the version code.
+ // It must be higher than the arm one for devices supporting
+ // both, as x86 is preferred.
+ versionCode = 3
}
arm {
ndk {
abiFilter "armeabi-v7a"
}
+ versionCode = 2
}
mips {
ndk {
abiFilter "mips"
}
+ versionCode = 1
+ }
+ fat {
+ // fat binary, lowest version code to be
+ // the last option
+ versionCode = 0
}
}
-}
\ No newline at end of file
+
+ // make per-variant version code
+ applicationVariants.all { variant ->
+ // get the single flavor
+ def flavorVersion = variant.productFlavors.get(0).versionCode
+
+ // set the composite code
+ variant.mergedFlavor.versionCode = flavorVersion * 1000000 + defaultConfig.versionCode
+ }
+}
diff --git a/build-system/tests/noPreDex/build.gradle b/build-system/tests/noPreDex/build.gradle
new file mode 100644
index 0000000..103cbe8
--- /dev/null
+++ b/build-system/tests/noPreDex/build.gradle
@@ -0,0 +1,20 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+apply plugin: 'com.android.application'
+
+dependencies {
+ compile 'com.android.support:support-v4:13.0.0'
+}
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ dexOptions.preDexLibraries = false
+}
diff --git a/build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/noPreDex/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java
copy to build-system/tests/noPreDex/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/noPreDex/src/main/AndroidManifest.xml b/build-system/tests/noPreDex/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/noPreDex/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/noPreDex/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/noPreDex/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/noPreDex/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/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/noPreDex/src/main/res/drawable/icon.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/noPreDex/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/noPreDex/src/main/res/layout/main.xml b/build-system/tests/noPreDex/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/noPreDex/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/noPreDex/src/main/res/values/strings.xml b/build-system/tests/noPreDex/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/noPreDex/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/noPreDex/src/release/res/values/strings.xml b/build-system/tests/noPreDex/src/release/res/values/strings.xml
new file mode 100644
index 0000000..532909c
--- /dev/null
+++ b/build-system/tests/noPreDex/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/overlay1/build.gradle b/build-system/tests/overlay1/build.gradle
index 026e777..70394f5 100644
--- a/build-system/tests/overlay1/build.gradle
+++ b/build-system/tests/overlay1/build.gradle
@@ -1,15 +1,15 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
-}
\ No newline at end of file
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/overlay1/src/instrumentTest/java/com/android/tests/overlay1/MainTest.java b/build-system/tests/overlay1/src/androidTest/java/com/android/tests/overlay1/MainTest.java
similarity index 100%
rename from build-system/tests/overlay1/src/instrumentTest/java/com/android/tests/overlay1/MainTest.java
rename to build-system/tests/overlay1/src/androidTest/java/com/android/tests/overlay1/MainTest.java
diff --git a/build-system/tests/overlay2/build.gradle b/build-system/tests/overlay2/build.gradle
index e785ff5..c038004 100644
--- a/build-system/tests/overlay2/build.gradle
+++ b/build-system/tests/overlay2/build.gradle
@@ -1,19 +1,19 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
productFlavors {
one {}
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/overlay2/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java b/build-system/tests/overlay2/src/androidTest/java/com/android/tests/overlay2/MainTest.java
similarity index 100%
rename from build-system/tests/overlay2/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java
rename to build-system/tests/overlay2/src/androidTest/java/com/android/tests/overlay2/MainTest.java
diff --git a/build-system/tests/overlay3/build.gradle b/build-system/tests/overlay3/build.gradle
index 5132796..bdc458a 100644
--- a/build-system/tests/overlay3/build.gradle
+++ b/build-system/tests/overlay3/build.gradle
@@ -1,19 +1,19 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
- flavorGroups "pricing", "releaseType"
+ flavorDimensions "pricing", "releaseType"
sourceSets {
beta.setRoot('movedSrc/beta')
@@ -27,19 +27,19 @@
productFlavors {
beta {
- flavorGroup "releaseType"
+ flavorDimension "releaseType"
}
normal {
- flavorGroup "releaseType"
+ flavorDimension "releaseType"
}
free {
- flavorGroup "pricing"
+ flavorDimension "pricing"
}
paid {
- flavorGroup "pricing"
+ flavorDimension "pricing"
}
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/overlay3/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java b/build-system/tests/overlay3/src/androidTest/java/com/android/tests/overlay2/MainTest.java
similarity index 100%
rename from build-system/tests/overlay3/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java
rename to build-system/tests/overlay3/src/androidTest/java/com/android/tests/overlay2/MainTest.java
diff --git a/build-system/tests/packagingOptions/build.gradle b/build-system/tests/packagingOptions/build.gradle
new file mode 100644
index 0000000..00aa7d8
--- /dev/null
+++ b/build-system/tests/packagingOptions/build.gradle
@@ -0,0 +1,24 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.12.2'
+ }
+}
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ packagingOptions {
+ exclude 'excluded.txt'
+ pickFirst 'first_pick.txt'
+ }
+}
+
+dependencies {
+ compile files('jar1.jar')
+ compile files('jar2.jar')
+}
diff --git a/build-system/tests/packagingOptions/jar1.jar b/build-system/tests/packagingOptions/jar1.jar
new file mode 100644
index 0000000..41e058b
--- /dev/null
+++ b/build-system/tests/packagingOptions/jar1.jar
Binary files differ
diff --git a/build-system/tests/packagingOptions/jar2.jar b/build-system/tests/packagingOptions/jar2.jar
new file mode 100644
index 0000000..7ff93d1
--- /dev/null
+++ b/build-system/tests/packagingOptions/jar2.jar
Binary files differ
diff --git a/build-system/tests/packagingOptions/jars/jar1/build.gradle b/build-system/tests/packagingOptions/jars/jar1/build.gradle
new file mode 100644
index 0000000..f3eca85
--- /dev/null
+++ b/build-system/tests/packagingOptions/jars/jar1/build.gradle
@@ -0,0 +1 @@
+apply plugin: 'java'
\ No newline at end of file
diff --git a/build-system/tests/packagingOptions/jars/jar1/src/main/resources/excluded.txt b/build-system/tests/packagingOptions/jars/jar1/src/main/resources/excluded.txt
new file mode 100644
index 0000000..cef102d
--- /dev/null
+++ b/build-system/tests/packagingOptions/jars/jar1/src/main/resources/excluded.txt
@@ -0,0 +1 @@
+foo.
diff --git a/build-system/tests/packagingOptions/jars/jar1/src/main/resources/first_pick.txt b/build-system/tests/packagingOptions/jars/jar1/src/main/resources/first_pick.txt
new file mode 100644
index 0000000..ba0e162
--- /dev/null
+++ b/build-system/tests/packagingOptions/jars/jar1/src/main/resources/first_pick.txt
@@ -0,0 +1 @@
+bar
\ No newline at end of file
diff --git a/build-system/tests/packagingOptions/jars/jar2/build.gradle b/build-system/tests/packagingOptions/jars/jar2/build.gradle
new file mode 100644
index 0000000..f3eca85
--- /dev/null
+++ b/build-system/tests/packagingOptions/jars/jar2/build.gradle
@@ -0,0 +1 @@
+apply plugin: 'java'
\ No newline at end of file
diff --git a/build-system/tests/packagingOptions/jars/jar2/src/main/resources/excluded.txt b/build-system/tests/packagingOptions/jars/jar2/src/main/resources/excluded.txt
new file mode 100644
index 0000000..cef102d
--- /dev/null
+++ b/build-system/tests/packagingOptions/jars/jar2/src/main/resources/excluded.txt
@@ -0,0 +1 @@
+foo.
diff --git a/build-system/tests/packagingOptions/jars/jar2/src/main/resources/first_pick.txt b/build-system/tests/packagingOptions/jars/jar2/src/main/resources/first_pick.txt
new file mode 100644
index 0000000..ba0e162
--- /dev/null
+++ b/build-system/tests/packagingOptions/jars/jar2/src/main/resources/first_pick.txt
@@ -0,0 +1 @@
+bar
\ No newline at end of file
diff --git a/build-system/tests/packagingOptions/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/packagingOptions/src/androidTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..109b5d1
--- /dev/null
+++ b/build-system/tests/packagingOptions/src/androidTest/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 testFirstPick() {
+ assertEquals("bar", mTextView.getText().toString());
+ }
+}
+
diff --git a/build-system/tests/packagingOptions/src/main/AndroidManifest.xml b/build-system/tests/packagingOptions/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/packagingOptions/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/packagingOptions/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/packagingOptions/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..05ddf93
--- /dev/null
+++ b/build-system/tests/packagingOptions/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,48 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+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(getContent());
+ }
+
+ public static String getContent() {
+ InputStream input = Main.class.getResourceAsStream("/first_pick.txt");
+ if (input == null) {
+ return "FAILED TO FIND first_pick.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/res/drawable-mdpi/ic_launcher.png b/build-system/tests/packagingOptions/src/main/res/drawable/icon.png
similarity index 100%
copy from build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/packagingOptions/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/packagingOptions/src/main/res/layout/main.xml b/build-system/tests/packagingOptions/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/packagingOptions/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/packagingOptions/src/main/res/values/strings.xml b/build-system/tests/packagingOptions/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/packagingOptions/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/pkgOverride/build.gradle b/build-system/tests/pkgOverride/build.gradle
index 96774ee..9233876 100644
--- a/build-system/tests/pkgOverride/build.gradle
+++ b/build-system/tests/pkgOverride/build.gradle
@@ -1,18 +1,18 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
- packageName "com.android.tests.basic.foo"
+ applicationId "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/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from build-system/tests/pkgOverride/src/instrumentTest/java/com/android/tests/basic/MainTest.java
rename to build-system/tests/pkgOverride/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/proguard/build.gradle b/build-system/tests/proguard/build.gradle
index 3a9d3e7..b607826 100644
--- a/build-system/tests/proguard/build.gradle
+++ b/build-system/tests/proguard/build.gradle
@@ -1,16 +1,16 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
testBuildType "proguard"
@@ -32,4 +32,4 @@
dexOptions {
incremental false
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/proguard/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/proguard/src/androidTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..3c00c79
--- /dev/null
+++ b/build-system/tests/proguard/src/androidTest/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().toString());
+ }
+
+ /** 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/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/proguard/src/instrumentTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 44bc9f4..0000000
--- a/build-system/tests/proguard/src/instrumentTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-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
index 4f8d570..a34d937 100644
--- a/build-system/tests/proguard/src/main/AndroidManifest.xml
+++ b/build-system/tests/proguard/src/main/AndroidManifest.xml
@@ -10,21 +10,4 @@
</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/build.gradle b/build-system/tests/proguardLib/app/build.gradle
index 2653d36..d9f090d 100644
--- a/build-system/tests/proguardLib/app/build.gradle
+++ b/build-system/tests/proguardLib/app/build.gradle
@@ -1,12 +1,12 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
dependencies {
compile project(':lib')
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
testBuildType "proguard"
@@ -28,4 +28,4 @@
dexOptions {
incremental false
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/proguardLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/proguardLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..6426ca8
--- /dev/null
+++ b/build-system/tests/proguardLib/app/src/androidTest/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().toString());
+ }
+}
+
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
deleted file mode 100644
index cbbc52b..0000000
--- a/build-system/tests/proguardLib/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-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
index 4f8d570..a34d937 100644
--- a/build-system/tests/proguardLib/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/proguardLib/app/src/main/AndroidManifest.xml
@@ -10,21 +10,4 @@
</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/build.gradle b/build-system/tests/proguardLib/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/proguardLib/build.gradle
+++ b/build-system/tests/proguardLib/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/proguardLib/lib/build.gradle b/build-system/tests/proguardLib/lib/build.gradle
index efbffe6..57e116d 100644
--- a/build-system/tests/proguardLib/lib/build.gradle
+++ b/build-system/tests/proguardLib/lib/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
versionCode 12
diff --git a/build-system/tests/proguardLib/lib/src/main/AndroidManifest.xml b/build-system/tests/proguardLib/lib/src/main/AndroidManifest.xml
index 593a287..45f6ffd 100644
--- a/build-system/tests/proguardLib/lib/src/main/AndroidManifest.xml
+++ b/build-system/tests/proguardLib/lib/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.basic">
+ package="com.android.tests.basic.lib">
</manifest>
diff --git a/build-system/tests/renamedApk/build.gradle b/build-system/tests/renamedApk/build.gradle
index 9600a82..9889fd4 100644
--- a/build-system/tests/renamedApk/build.gradle
+++ b/build-system/tests/renamedApk/build.gradle
@@ -1,20 +1,20 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
repositories {
mavenCentral()
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
buildTypes.debug {
zipAlign true
@@ -23,4 +23,4 @@
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/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from build-system/tests/renamedApk/src/instrumentTest/java/com/android/tests/basic/MainTest.java
rename to build-system/tests/renamedApk/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/renderscript/build.gradle b/build-system/tests/renderscript/build.gradle
index 8a15397..6fdb3a4 100644
--- a/build-system/tests/renderscript/build.gradle
+++ b/build-system/tests/renderscript/build.gradle
@@ -1,18 +1,19 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 17
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
+ minSdkVersion 17
renderscriptTargetApi = 17
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/renderscriptInLib/app/build.gradle b/build-system/tests/renderscriptInLib/app/build.gradle
index c028ad5..5372a18 100644
--- a/build-system/tests/renderscriptInLib/app/build.gradle
+++ b/build-system/tests/renderscriptInLib/app/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 17
- buildToolsVersion "18.0.1"
+ compileSdkVersion 15
+ buildToolsVersion '19.1.0'
defaultConfig {
renderscriptTargetApi = 11
diff --git a/build-system/tests/renderscriptInLib/build.gradle b/build-system/tests/renderscriptInLib/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/renderscriptInLib/build.gradle
+++ b/build-system/tests/renderscriptInLib/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/renderscriptInLib/lib/build.gradle b/build-system/tests/renderscriptInLib/lib/build.gradle
index 1c875e4..eacf7cd 100644
--- a/build-system/tests/renderscriptInLib/lib/build.gradle
+++ b/build-system/tests/renderscriptInLib/lib/build.gradle
@@ -1,6 +1,6 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ buildToolsVersion '19.1.0'
}
diff --git a/build-system/tests/renderscriptMultiSrc/build.gradle b/build-system/tests/renderscriptMultiSrc/build.gradle
index 6f15777..c2f4823 100644
--- a/build-system/tests/renderscriptMultiSrc/build.gradle
+++ b/build-system/tests/renderscriptMultiSrc/build.gradle
@@ -1,18 +1,18 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 17
- buildToolsVersion "18.0.1"
+ compileSdkVersion 15
+ buildToolsVersion '19.1.0'
defaultConfig {
renderscriptTargetApi = 11
}
-}
\ No newline at end of file
+}
diff --git a/build-system/tests/repo/app/build.gradle b/build-system/tests/repo/app/build.gradle
index f2131fe..40fbedc 100644
--- a/build-system/tests/repo/app/build.gradle
+++ b/build-system/tests/repo/app/build.gradle
@@ -1,13 +1,13 @@
buildscript {
repositories {
- maven { url '../../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
apply plugin: 'maven'
repositories {
@@ -20,7 +20,7 @@
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
diff --git a/build-system/tests/repo/baseLibrary/build.gradle b/build-system/tests/repo/baseLibrary/build.gradle
index 9e3bf41..3241168 100644
--- a/build-system/tests/repo/baseLibrary/build.gradle
+++ b/build-system/tests/repo/baseLibrary/build.gradle
@@ -1,13 +1,13 @@
buildscript {
repositories {
- maven { url '../../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
apply plugin: 'maven'
repositories {
@@ -21,8 +21,8 @@
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
group = 'com.example.android.multiproject'
diff --git a/build-system/tests/repo/library/build.gradle b/build-system/tests/repo/library/build.gradle
index 9e5c7c9..87180f1 100644
--- a/build-system/tests/repo/library/build.gradle
+++ b/build-system/tests/repo/library/build.gradle
@@ -1,13 +1,13 @@
buildscript {
repositories {
- maven { url '../../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
apply plugin: 'maven'
repositories {
@@ -20,8 +20,8 @@
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
group = 'com.example.android.multiproject'
@@ -32,6 +32,7 @@
repositories {
mavenDeployer {
repository(url: uri("../testrepo"))
+ pom.groupId = 'com.example.android.multiproject'
}
}
}
diff --git a/build-system/tests/rsSupportMode/build.gradle b/build-system/tests/rsSupportMode/build.gradle
index 3909321..6ac4c0a 100644
--- a/build-system/tests/rsSupportMode/build.gradle
+++ b/build-system/tests/rsSupportMode/build.gradle
@@ -1,16 +1,16 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 18
- buildToolsVersion "18.1.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
minSdkVersion 8
diff --git a/build-system/tests/rsSupportMode/src/main/res/layout/main.xml b/build-system/tests/rsSupportMode/src/main/res/layout/main.xml
index f0a2b92..55fbd37 100644
--- a/build-system/tests/rsSupportMode/src/main/res/layout/main.xml
+++ b/build-system/tests/rsSupportMode/src/main/res/layout/main.xml
@@ -26,7 +26,7 @@
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent">
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ <LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
@@ -34,7 +34,7 @@
android:id="@+id/display"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ <LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
diff --git a/build-system/tests/sameNamedLibs/app/build.gradle b/build-system/tests/sameNamedLibs/app/build.gradle
index ea8974b..58d6d37 100644
--- a/build-system/tests/sameNamedLibs/app/build.gradle
+++ b/build-system/tests/sameNamedLibs/app/build.gradle
@@ -1,8 +1,8 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
//
diff --git a/build-system/tests/sameNamedLibs/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java b/build-system/tests/sameNamedLibs/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
new file mode 100644
index 0000000..273c8d1
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/androidTest/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("SUCCESS-APP", mAppTextView1.getText().toString());
+ assertEquals("SUCCESS-LIB1", mLib1TextView1.getText().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView1.getText().toString());
+ assertEquals("SUCCESS-LIB2b", mLib2bTextView1.getText().toString());
+ assertEquals("SUCCESS-LIBAPP", mLibappTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView2.getText().toString());
+ assertEquals("SUCCESS-LIB1", mLib1TextView2.getText().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView2.getText().toString());
+ assertEquals("SUCCESS-LIB2b", mLib2bTextView2.getText().toString());
+ assertEquals("SUCCESS-LIBAPP", mLibappTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 61a0a31..0000000
--- a/build-system/tests/sameNamedLibs/app/src/instrumentTest/java/com/android/tests/libstest/app/MainActivityTest.java
+++ /dev/null
@@ -1,116 +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.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
index 74f0ff2..742f043 100644
--- a/build-system/tests/sameNamedLibs/app/src/main/AndroidManifest.xml
+++ b/build-system/tests/sameNamedLibs/app/src/main/AndroidManifest.xml
@@ -5,12 +5,12 @@
android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
<uses-sdk
- android:minSdkVersion="15"
- tools:ignore="UsesMinSdkAttributes" />
+ android:minSdkVersion="15" />
<application
android:icon="@drawable/icon"
- android:label="@string/app_name" >
+ android:label="@string/app_name"
+ tools:replace="label, icon">
<activity
android:name="com.android.tests.libstest.app.MainActivity"
android:label="@string/app_name" >
diff --git a/build-system/tests/sameNamedLibs/build.gradle b/build-system/tests/sameNamedLibs/build.gradle
index a8fdb64..4324232 100644
--- a/build-system/tests/sameNamedLibs/build.gradle
+++ b/build-system/tests/sameNamedLibs/build.gradle
@@ -1,9 +1,9 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/build.gradle b/build-system/tests/sameNamedLibs/lib1/libs/build.gradle
index 32e056b..4086632 100644
--- a/build-system/tests/sameNamedLibs/lib1/libs/build.gradle
+++ b/build-system/tests/sameNamedLibs/lib1/libs/build.gradle
@@ -1,12 +1,12 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
dependencies {
compile project(':lib2:libs')
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
defaultConfig {
minSdkVersion 14
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java b/build-system/tests/sameNamedLibs/lib1/libs/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
new file mode 100644
index 0000000..6136a06
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/androidTest/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().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mLib1TextView2.getText().toString());
+ assertEquals("SUCCESS-LIB2", mLib2TextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 4ed7ae6..0000000
--- a/build-system/tests/sameNamedLibs/lib1/libs/src/instrumentTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
+++ /dev/null
@@ -1,92 +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.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
index 7739b4a..c2f3731 100644
--- a/build-system/tests/sameNamedLibs/lib1/libs/src/main/AndroidManifest.xml
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/AndroidManifest.xml
@@ -2,11 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tests.libstest.lib1"
android:versionCode="1"
- android:versionName="1.0" >
+ android:versionName="1.0"
+ xmlns:tools="http://schemas.android.com/tools" >
<application
android:icon="@drawable/ic_launcher"
- android:label="@string/lib1_name" >
+ android:label="@string/lib1_name"
+ tools:replace="label">
<activity
android:name="MainActivity"
android:label="@string/lib1_name" >
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/build.gradle b/build-system/tests/sameNamedLibs/lib2/libs/build.gradle
index 4b2a733..ba65495 100644
--- a/build-system/tests/sameNamedLibs/lib2/libs/build.gradle
+++ b/build-system/tests/sameNamedLibs/lib2/libs/build.gradle
@@ -1,6 +1,6 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java b/build-system/tests/sameNamedLibs/lib2/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
new file mode 100644
index 0000000..e4b7190
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/androidTest/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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 6ac4a5c..0000000
--- a/build-system/tests/sameNamedLibs/lib2/libs/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
+++ /dev/null
@@ -1,86 +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.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/lib2b/libs/build.gradle b/build-system/tests/sameNamedLibs/lib2b/libs/build.gradle
index 4b2a733..ba65495 100644
--- a/build-system/tests/sameNamedLibs/lib2b/libs/build.gradle
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/build.gradle
@@ -1,6 +1,6 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java b/build-system/tests/sameNamedLibs/lib2b/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
new file mode 100644
index 0000000..71cb731
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/androidTest/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.lib2b.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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2b", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index 35c56e3..0000000
--- a/build-system/tests/sameNamedLibs/lib2b/libs/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
+++ /dev/null
@@ -1,86 +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.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
index 814af46..3ff3697 100644
--- a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/AndroidManifest.xml
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.libstest.lib2"
+ package="com.android.tests.libstest.lib2b"
android:versionCode="1"
android:versionName="1.0" >
@@ -8,7 +8,7 @@
android:icon="@drawable/ic_launcher"
android:label="@string/lib2b_name" >
<activity
- android:name="MainActivity2b"
+ android:name="com.android.tests.libstest.lib2.MainActivity2b"
android:label="@string/lib2b_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
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
index e4329e5..42ad441 100644
--- 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
@@ -8,6 +8,8 @@
import java.io.InputStream;
import java.io.InputStreamReader;
+import com.android.tests.libstest.lib2b.R;
+
public class Lib2b {
public static void handleTextView(Activity a) {
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
index 2e09018..f6ec10b 100644
--- 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
@@ -3,6 +3,8 @@
import android.app.Activity;
import android.os.Bundle;
+import com.android.tests.libstest.lib2b.R;
+
public class MainActivity2b extends Activity {
/** Called when the activity is first created. */
@Override
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/build.gradle b/build-system/tests/sameNamedLibs/libapp/libs/build.gradle
index 4b2a733..ba65495 100644
--- a/build-system/tests/sameNamedLibs/libapp/libs/build.gradle
+++ b/build-system/tests/sameNamedLibs/libapp/libs/build.gradle
@@ -1,6 +1,6 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java b/build-system/tests/sameNamedLibs/libapp/libs/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
new file mode 100644
index 0000000..541ac60
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/androidTest/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.libapp.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().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIBAPP", mTextView2.getText().toString());
+ }
+}
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
deleted file mode 100644
index e6087a7..0000000
--- a/build-system/tests/sameNamedLibs/libapp/libs/src/instrumentTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
+++ /dev/null
@@ -1,86 +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.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
index 0592d2d..a4a6ead 100644
--- a/build-system/tests/sameNamedLibs/libapp/libs/src/main/AndroidManifest.xml
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.libstest.app"
+ package="com.android.tests.libstest.libapp"
android:versionCode="1"
android:versionName="1.0" >
@@ -8,7 +8,7 @@
android:icon="@drawable/ic_launcher"
android:label="@string/libapp_name" >
<activity
- android:name="MainActivityLibApp"
+ android:name="com.android.tests.libstest.app.MainActivityLibApp"
android:label="@string/libapp_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
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
index 9a25e9e..22b6b1c 100644
--- 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
@@ -8,6 +8,8 @@
import java.io.InputStream;
import java.io.InputStreamReader;
+import com.android.tests.libstest.libapp.R;
+
public class LibApp {
public static void handleTextView(Activity a) {
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
index 65460f7..a6f1bac 100644
--- 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
@@ -3,6 +3,8 @@
import android.app.Activity;
import android.os.Bundle;
+import com.android.tests.libstest.libapp.R;
+
public class MainActivityLibApp extends Activity {
/** Called when the activity is first created. */
@Override
diff --git a/build-system/tests/testWithDep/build.gradle b/build-system/tests/testWithDep/build.gradle
index e7fb476..0dd47ab 100644
--- a/build-system/tests/testWithDep/build.gradle
+++ b/build-system/tests/testWithDep/build.gradle
@@ -1,22 +1,22 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
-apply plugin: 'android'
+apply plugin: 'com.android.application'
repositories {
mavenCentral()
}
dependencies {
- instrumentTestCompile 'com.jayway.android.robotium:robotium-solo:4.3.1'
+ androidTestCompile 'com.jayway.android.robotium:robotium-solo:4.3.1'
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
-}
\ No newline at end of file
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+}
diff --git a/build-system/tests/testWithDep/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/testWithDep/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from build-system/tests/testWithDep/src/instrumentTest/java/com/android/tests/basic/MainTest.java
rename to build-system/tests/testWithDep/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/tests/tictactoe/app/build.gradle b/build-system/tests/tictactoe/app/build.gradle
index 376c388..26c600c 100644
--- a/build-system/tests/tictactoe/app/build.gradle
+++ b/build-system/tests/tictactoe/app/build.gradle
@@ -1,10 +1,10 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
dependencies {
compile project(':lib')
}
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
}
diff --git a/build-system/tests/tictactoe/build.gradle b/build-system/tests/tictactoe/build.gradle
index 83b3e0b..84fb877 100644
--- a/build-system/tests/tictactoe/build.gradle
+++ b/build-system/tests/tictactoe/build.gradle
@@ -1,8 +1,8 @@
buildscript {
repositories {
- maven { url '../../../../../out/host/gradle/repo' }
+ maven { url '../../../../../out/repo' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/build-system/tests/tictactoe/lib/build.gradle b/build-system/tests/tictactoe/lib/build.gradle
index 4b2a733..4493783 100644
--- a/build-system/tests/tictactoe/lib/build.gradle
+++ b/build-system/tests/tictactoe/lib/build.gradle
@@ -1,6 +1,10 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
android {
- compileSdkVersion 15
- buildToolsVersion "18.0.1"
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+
+ defaultConfig {
+ minSdkVersion 3
+ }
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 57c79cc..b07f6c9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,68 +1,7 @@
-buildscript {
- repositories {
- maven { url "$rootDir/../../prebuilts/tools/common/gradle-plugins/repository" }
- }
- dependencies {
- classpath 'com.android.tools.internal:internal-plugins:1.0'
- }
-}
-
-apply plugin: 'clone-artifacts'
-apply plugin: 'distrib'
-
-// artifact cloning destinations
-cloneArtifacts {
- mainRepo = "$rootDir/../../prebuilts/tools/common/m2/repository"
- secondaryRepo = "$rootDir/../../prebuilts/tools/common/m2/internal"
-}
-
-// set up the distribution destination
-distribution {
- destinationPath = "$rootDir/../../prebuilts/devtools"
- dependenciesRepo = cloneArtifacts.mainRepo
-}
-
-// ext.androidHostOut is shared by all tools/{base,build,swt} gradle projects/
-ext.androidHostOut = file("$rootDir/../../out/host/gradle")
-// rootProject.buildDir is specific to this gradle build.
-buildDir = new File(file(ext.androidHostOut), "tools/base/build")
-
-ext.localRepo = project.hasProperty('localRepo') ? localRepo : "$ext.androidHostOut/repo"
-
-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")
+ // only configure leaf projects.
+ if (!project.getSubprojects().isEmpty()) return
- apply plugin: 'clone-artifacts'
-
- repositories {
- maven { url = uri(rootProject.cloneArtifacts.mainRepo) }
- maven { url = uri(rootProject.cloneArtifacts.secondaryRepo) }
- }
-
- apply from: "$project.rootDir/base.gradle"
+ apply from: "$rootDir/buildSrc/base/baseJava.gradle"
}
-// delay evaluation of this project before all subprojects have been evaluated.
-subprojects.each { subproject -> evaluationDependsOn(subproject.path) }
-
-def testTasks = subprojects.collect { it.tasks.withType(Test) }.flatten()
-
-task aggregateResults(type: Copy) {
- 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
deleted file mode 100644
index ae0918f..0000000
--- a/buildVersion.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-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 c98c4c7..ca3ebea 100644
--- a/common/.classpath
+++ b/common/.classpath
@@ -4,6 +4,6 @@
<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"/>
+ <classpathentry exported="true" kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/common/build.gradle b/common/build.gradle
index 11b520d..215345b 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -1,21 +1,19 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
+
dependencies {
- compile 'com.google.guava:guava:13.0.1'
+ compile 'com.google.guava:guava:15.0'
testCompile 'junit:junit:3.8.1'
}
group = 'com.android.tools'
archivesBaseName = 'common'
+version = rootProject.ext.baseVersion
+
project.ext.pomName = 'Android Tools common library'
project.ext.pomDesc = 'common library used by other Android tools libraries.'
-jar {
- from 'NOTICE'
-}
-
-apply from: '../baseVersion.gradle'
-apply from: '../publish.gradle'
-apply from: '../javadoc.gradle'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/common/src/main/java/com/android/SdkConstants.java b/common/src/main/java/com/android/SdkConstants.java
index 37510ce..0feef9e 100644
--- a/common/src/main/java/com/android/SdkConstants.java
+++ b/common/src/main/java/com/android/SdkConstants.java
@@ -53,10 +53,22 @@
/** Property in local.properties file that specifies the path of the Android SDK. */
public static final String SDK_DIR_PROPERTY = "sdk.dir";
+ /** Property in gradle-wrapper.properties file that specifies the URL to the correct Gradle distribution. */
+ public static final String GRADLE_DISTRIBUTION_URL_PROPERTY = "distributionUrl"; //$NON-NLS-1$
+
+ /**
+ * The encoding we strive to use for all files we write.
+ * <p>
+ * When possible, use the APIs which take a {@link java.nio.charset.Charset} and pass in
+ * {@link com.google.common.base.Charsets#UTF_8} instead of using the String encoding
+ * method.
+ */
+ public static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
+
/**
* Charset for the ini file handled by the SDK.
*/
- public static final String INI_CHARSET = "UTF-8"; //$NON-NLS-1$
+ public static final String INI_CHARSET = UTF_8;
/** Path separator used by Gradle */
public static final String GRADLE_PATH_SEPARATOR = ":"; //$NON-NLS-1$
@@ -74,7 +86,18 @@
public static final String FN_BUILD_GRADLE = "build.gradle"; //$NON-NLS-1$
/** An SDK Project's settings.gradle file */
public static final String FN_SETTINGS_GRADLE = "settings.gradle"; //$NON-NLS-1$
-
+ /** An SDK Project's gradle.properties file */
+ public static final String FN_GRADLE_PROPERTIES = "gradle.properties"; //$NON-NLS-1$
+ /** An SDK Project's gradle daemon executable */
+ public static final String FN_GRADLE_UNIX = "gradle"; //$NON-NLS-1$
+ /** An SDK Project's gradle.bat daemon executable (gradle for windows) */
+ public static final String FN_GRADLE_WIN = FN_GRADLE_UNIX + ".bat"; //$NON-NLS-1$
+ /** An SDK Project's gradlew file */
+ public static final String FN_GRADLE_WRAPPER_UNIX = "gradlew"; //$NON-NLS-1$
+ /** An SDK Project's gradlew.bat file (gradlew for windows) */
+ public static final String FN_GRADLE_WRAPPER_WIN = FN_GRADLE_WRAPPER_UNIX + ".bat"; //$NON-NLS-1$
+ /** An SDK Project's gradle wrapper library */
+ public static final String FN_GRADLE_WRAPPER_JAR = "gradle-wrapper.jar"; //$NON-NLS-1$
/** Name of the framework library, i.e. "android.jar" */
public static final String FN_FRAMEWORK_LIBRARY = "android.jar"; //$NON-NLS-1$
/** Name of the framework library, i.e. "uiautomator.jar" */
@@ -127,6 +150,9 @@
/** project ant property file */
public static final String FN_ANT_PROPERTIES = "ant.properties"; //$NON-NLS-1$
+ /** project local property file */
+ public static final String FN_GRADLE_WRAPPER_PROPERTIES = "gradle-wrapper.properties"; //$NON-NLS-1$
+
/** Skin layout file */
public static final String FN_SKIN_LAYOUT = "layout"; //$NON-NLS-1$
@@ -189,6 +215,10 @@
public static final String FN_FIND_LOCK =
"find_lock" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ /** hprof-conv executable (with extension for the current OS) */
+ public static final String FN_HPROF_CONV =
+ "hprof-conv" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
/** properties file for SDK Updater packages */
public static final String FN_SOURCE_PROP = "source.properties"; //$NON-NLS-1$
/** properties file for content hash of installed packages */
@@ -225,10 +255,14 @@
public static final String FD_SOURCES = "src"; //$NON-NLS-1$
/** Default main source set folder name, i.e. "main" */
public static final String FD_MAIN = "main"; //$NON-NLS-1$
- /** Default test source set folder name, i.e. "instrumentTest" */
- public static final String FD_TEST = "instrumentTest"; //$NON-NLS-1$
+ /** Default test source set folder name, i.e. "androidTest" */
+ public static final String FD_TEST = "androidTest"; //$NON-NLS-1$
/** Default java code folder name, i.e. "java" */
public static final String FD_JAVA = "java"; //$NON-NLS-1$
+ /** Default gradle folder name, i.e. "gradle" */
+ public static final String FD_GRADLE = "gradle"; //$NON-NLS-1$
+ /** Default gradle wrapper folder name, i.e. "gradle/wrapper" */
+ public static final String FD_GRADLE_WRAPPER = FD_GRADLE + File.separator + "wrapper"; //$NON-NLS-1$
/** Default generated source folder name, i.e. "gen" */
public static final String FD_GEN_SOURCES = "gen"; //$NON-NLS-1$
/** Default native library folder name inside the project, i.e. "libs"
@@ -282,14 +316,21 @@
/** Name of the SDK images folder. */
public static final String FD_IMAGES = "images"; //$NON-NLS-1$
/** Name of the ABI to support. */
- public static final String ABI_ARMEABI = "armeabi"; //$NON-NLS-1$
- public static final String ABI_ARMEABI_V7A = "armeabi-v7a"; //$NON-NLS-1$
- public static final String ABI_INTEL_ATOM = "x86"; //$NON-NLS-1$
- public static final String ABI_MIPS = "mips"; //$NON-NLS-1$
+ public static final String ABI_ARMEABI = "armeabi"; //$NON-NLS-1$
+ public static final String ABI_ARMEABI_V7A = "armeabi-v7a"; //$NON-NLS-1$
+ public static final String ABI_ARM64_V8A = "arm64-v8a"; //$NON-NLS-1$
+ public static final String ABI_INTEL_ATOM = "x86"; //$NON-NLS-1$
+ public static final String ABI_INTEL_ATOM64 = "x86_64"; //$NON-NLS-1$
+ public static final String ABI_MIPS = "mips"; //$NON-NLS-1$
+ public static final String ABI_MIPS64 = "mips64"; //$NON-NLS-1$
/** Name of the CPU arch to support. */
- public static final String CPU_ARCH_ARM = "arm"; //$NON-NLS-1$
- public static final String CPU_ARCH_INTEL_ATOM = "x86"; //$NON-NLS-1$
- public static final String CPU_ARCH_MIPS = "mips"; //$NON-NLS-1$
+ public static final String CPU_ARCH_ARM = "arm"; //$NON-NLS-1$
+ public static final String CPU_ARCH_ARM64 = "arm64"; //$NON-NLS-1$
+ public static final String CPU_ARCH_INTEL_ATOM = "x86"; //$NON-NLS-1$
+ public static final String CPU_ARCH_INTEL_ATOM64 = "x86_64"; //$NON-NLS-1$
+ public static final String CPU_ARCH_MIPS = "mips"; //$NON-NLS-1$
+ /** TODO double-check this is appropriate value for mips64 */
+ public static final String CPU_ARCH_MIPS64 = "mips64"; //$NON-NLS-1$
/** Name of the CPU model to support. */
public static final String CPU_MODEL_CORTEX_A8 = "cortex-a8"; //$NON-NLS-1$
@@ -299,6 +340,7 @@
public static final String FD_SAMPLES = "samples"; //$NON-NLS-1$
/** Name of the SDK extras folder. */
public static final String FD_EXTRAS = "extras"; //$NON-NLS-1$
+ public static final String FD_M2_REPOSITORY = "m2repository"; //$NON-NLS-1$
/**
* Name of an extra's sample folder.
* Ideally extras should have one {@link #FD_SAMPLES} folder containing
@@ -335,10 +377,6 @@
public static final String NS_RESOURCES =
"http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
- /** Namespace for the device schema, i.e. "http://schemas.android.com/sdk/devices/1" */
- public static final String NS_DEVICES_XSD =
- "http://schemas.android.com/sdk/devices/1"; //$NON-NLS-1$
-
/**
* Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s"
* <p/>
@@ -402,9 +440,7 @@
/** Path of the template gradle wrapper folder relative to the sdk folder.
* This is an OS path, ending with a separator. */
public static final String OS_SDK_TOOLS_TEMPLATES_GRADLE_WRAPPER_FOLDER =
- OS_SDK_TOOLS_FOLDER + FD_TEMPLATES + File.separator +
- "gradle" + File.separator + //$NON-NLS-1$
- "wrapper" + File.separator; //$NON-NLS-1$
+ OS_SDK_TOOLS_FOLDER + FD_TEMPLATES + File.separator + FD_GRADLE_WRAPPER + File.separator;
/* Folder paths relative to a platform or add-on folder */
@@ -500,6 +536,7 @@
"android.test.InstrumentationTestRunner"; //$NON-NLS-1$
public static final String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
public static final String CLASS_R = "android.R"; //$NON-NLS-1$
+ public static final String CLASS_R_PREFIX = CLASS_R + "."; //$NON-NLS-1$
public static final String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
public static final String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$
public static final String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$
@@ -632,7 +669,7 @@
public static final String ANDROID_NS_NAME_PREFIX = "android:"; //$NON-NLS-1$
public static final int ANDROID_NS_NAME_PREFIX_LEN = ANDROID_NS_NAME_PREFIX.length();
- /** The default prefix used for the app */
+ /** The default prefix used for the app */
public static final String APP_PREFIX = "app"; //$NON-NLS-1$
/** The entity for the ampersand character */
public static final String AMP_ENTITY = "&"; //$NON-NLS-1$
@@ -697,6 +734,7 @@
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$
+ public static final String TAG_SKIP = "skip"; //$NON-NLS-1$
// Tags: XML
public static final String TAG_HEADER = "header"; //$NON-NLS-1$
@@ -772,6 +810,8 @@
public static final String ATTR_DEBUGGABLE = "debuggable"; //$NON-NLS-1$
public static final String ATTR_READ_PERMISSION = "readPermission"; //$NON_NLS-1$
public static final String ATTR_WRITE_PERMISSION = "writePermission"; //$NON_NLS-1$
+ public static final String ATTR_VERSION_CODE = "versionCode"; //$NON_NLS-1$
+ public static final String ATTR_VERSION_NAME = "versionName"; //$NON_NLS-1$
// Attributes: Resources
public static final String ATTR_NAME = "name"; //$NON-NLS-1$
@@ -904,6 +944,9 @@
// Attributes: Drawables
public static final String ATTR_TILE_MODE = "tileMode"; //$NON-NLS-1$
+ // Values: Manifest
+ public static final String VALUE_SPLIT_ACTION_BAR_WHEN_NARROW = "splitActionBarWhenNarrow"; // NON-NLS-$1
+
// Values: Layouts
public static final String VALUE_FILL_PARENT = "fill_parent"; //$NON-NLS-1$
public static final String VALUE_MATCH_PARENT = "match_parent"; //$NON-NLS-1$
@@ -959,7 +1002,7 @@
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$
-
+ public static final String DOT_PROPERTIES = ".properties"; //$NON-NLS-1$
/** Extension of the Application package Files, i.e. "apk". */
public static final String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$
@@ -973,6 +1016,8 @@
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 ZIP files, i.e. "zip" */
+ public static final String EXT_ZIP = "zip"; //$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" */
@@ -995,6 +1040,8 @@
public static final String EXT_PNG = "png"; //$NON-NLS-1$
/** Extension for Android archive files */
public static final String EXT_AAR = "aar"; //$NON-NLS-1$
+ /** Extension for Java heap dumps. */
+ public static final String EXT_HPROF = "hprof"; //$NON-NLS-1$
private static final String DOT = "."; //$NON-NLS-1$
@@ -1046,6 +1093,7 @@
public static final String DRAWABLE_FOLDER = "drawable"; //$NON-NLS-1$
public static final String DRAWABLE_XHDPI = "drawable-xhdpi"; //$NON-NLS-1$
+ public static final String DRAWABLE_XXHDPI = "drawable-xxhdpi"; //$NON-NLS-1$
public static final String DRAWABLE_HDPI = "drawable-hdpi"; //$NON-NLS-1$
public static final String DRAWABLE_MDPI = "drawable-mdpi"; //$NON-NLS-1$
public static final String DRAWABLE_LDPI = "drawable-ldpi"; //$NON-NLS-1$
@@ -1057,10 +1105,12 @@
public static final String ANDROID_THEME_PREFIX = "?android:"; //$NON-NLS-1$
public static final String LAYOUT_RESOURCE_PREFIX = "@layout/"; //$NON-NLS-1$
public static final String STYLE_RESOURCE_PREFIX = "@style/"; //$NON-NLS-1$
+ public static final String COLOR_RESOURCE_PREFIX = "@color/"; //$NON-NLS-1$
public static final String NEW_ID_PREFIX = "@+id/"; //$NON-NLS-1$
public static final String ID_PREFIX = "@id/"; //$NON-NLS-1$
public static final String DRAWABLE_PREFIX = "@drawable/"; //$NON-NLS-1$
public static final String STRING_PREFIX = "@string/"; //$NON-NLS-1$
+ public static final String DIMEN_PREFIX = "@dimen/"; //$NON-NLS-1$
public static final String ANDROID_LAYOUT_RESOURCE_PREFIX = "@android:layout/"; //$NON-NLS-1$
public static final String ANDROID_STYLE_RESOURCE_PREFIX = "@android:style/"; //$NON-NLS-1$
@@ -1125,6 +1175,7 @@
"android/content/ContentProvider"; //$NON-NLS-1$
public static final String ANDROID_CONTENT_BROADCAST_RECEIVER =
"android/content/BroadcastReceiver"; //$NON-NLS-1$
+ public static final String ANDROID_VIEW_VIEW = "android/view/View"; //$NON-NLS-1$
// Method Names
public static final String FORMAT_METHOD = "format"; //$NON-NLS-1$
@@ -1284,4 +1335,22 @@
public static final String ATTR_EMS = "ems"; //$NON-NLS-1$
public static final String VALUE_HORIZONTAL = "horizontal"; //$NON-NLS-1$
+
+ public static final String GRADLE_PLUGIN_NAME = "com.android.tools.build:gradle:";
+ public static final String GRADLE_MINIMUM_VERSION = "1.10";
+ public static final String GRADLE_LATEST_VERSION = "1.12";
+ public static final String GRADLE_PLUGIN_MINIMUM_VERSION = "0.12.0";
+ public static final String GRADLE_PLUGIN_LATEST_VERSION = "0.12.+";
+ public static final String GRADLE_PLUGIN_RECOMMENDED_VERSION = "0.12.1";
+ public static final String MIN_BUILD_TOOLS_VERSION = "19.1.0";
+ public static final String SUPPORT_LIB_ARTIFACT = "com.android.support:support-v4";
+ public static final String APPCOMPAT_LIB_ARTIFACT = "com.android.support:appcompat-v7";
+
+ // Annotations
+ public static final String SUPPORT_ANNOTATIONS_PREFIX = "android.support.annotation.";
+ public static final String INT_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "IntDef";
+ public static final String STRING_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "StringDef";
+ public static final String TYPE_DEF_VALUE_ATTRIBUTE = "value";
+ public static final String TYPE_DEF_FLAG_ATTRIBUTE = "flag";
+ public static final String FN_ANNOTATIONS_ZIP = "annotations.zip";
}
diff --git a/common/src/main/java/com/android/io/IAbstractFile.java b/common/src/main/java/com/android/io/IAbstractFile.java
index 285df1f..b47f46e 100644
--- a/common/src/main/java/com/android/io/IAbstractFile.java
+++ b/common/src/main/java/com/android/io/IAbstractFile.java
@@ -29,6 +29,9 @@
/**
* Returns an {@link InputStream} object on the file content.
+ *
+ * The stream must be closed by the caller.
+ *
* @throws StreamException
*/
InputStream getContents() throws StreamException;
diff --git a/common/src/main/java/com/android/io/NonClosingInputStream.java b/common/src/main/java/com/android/io/NonClosingInputStream.java
new file mode 100755
index 0000000..2f8385d
--- /dev/null
+++ b/common/src/main/java/com/android/io/NonClosingInputStream.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.io;
+
+import com.android.annotations.NonNull;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Wraps an {@link InputStream} to change its closing behavior:
+ * this makes it possible to ignore close operations or have them perform a
+ * {@link InputStream#reset()} instead (if supported by the underlying stream)
+ * or plain ignored.
+ * <p/>
+ * This is useful to pass a stream to an XML parser or validator that automatically
+ * closes its input stream -- which makes it impossible to validate a stream and then
+ * parse it without reacquiring the stream.
+ * <p/>
+ * Example of usage:
+ * <pre>
+ * // Get a stream that supports mark/reset in case the original does not
+ * InputStream buffered = new BufferedInputStream(source);
+ * // Mark the beginning of the stream and indicate how much buffering is acceptable
+ * buffered.mark(500000);
+ * // Wrap the stream so that closing it actually just resets it to the mark
+ * NonClosingInputStream stream = new NonClosingInputStream(buffered);
+ * stream.setCloseBehavior(CloseBehavior.RESET)
+ * // pass it to a schema validator (pseudo code, real code is way more verbose.)
+ * SAXParserFactory...newSAXParser().parse(stream) ...
+ * // the validator parser closes the stream, which resets it to the beginning
+ * // so passing it another parser will actually parse the whole stream
+ * SAXParser...parse(stream) ...
+ * </pre>
+ */
+public class NonClosingInputStream extends FilterInputStream {
+
+ private final InputStream mInputStream;
+ private CloseBehavior mCloseBehavior = CloseBehavior.CLOSE;
+
+ public enum CloseBehavior {
+ /**
+ * The behavior of {@link NonClosingInputStream#close()} is to close the
+ * underlying input stream. This is the default.
+ */
+ CLOSE,
+ /**
+ * The behavior of {@link NonClosingInputStream#close()} is to ignore the
+ * close request and do nothing.
+ */
+ IGNORE,
+ /**
+ * The behavior of {@link NonClosingInputStream#close()} is to call
+ * {@link InputStream#reset()} on the underlying stream. This will
+ * only succeed if the underlying stream supports it, e.g. it must
+ * have {@link InputStream#markSupported()} return true <em>and</em>
+ * the caller should have called {@link InputStream#mark(int)} at some
+ * point before.
+ */
+ RESET
+ }
+
+ /**
+ * Wraps an existing stream into this filtering stream.
+ * @param in A non-null input stream.
+ */
+ public NonClosingInputStream(@NonNull InputStream in) {
+ super(in);
+ mInputStream = in;
+ }
+
+ /**
+ * Returns the current {@link CloseBehavior}.
+ * @return the current {@link CloseBehavior}. Never null.
+ */
+ @NonNull
+ public CloseBehavior getCloseBehavior() {
+ return mCloseBehavior;
+ }
+
+ /**
+ * Changes the current {@link CloseBehavior}.
+ *
+ * @param closeBehavior A new non-null {@link CloseBehavior}.
+ * @return Self for chaining.
+ */
+ public NonClosingInputStream setCloseBehavior(@NonNull CloseBehavior closeBehavior) {
+ mCloseBehavior = closeBehavior;
+ return this;
+ }
+
+ /**
+ * Performs the requested {@code close()} operation, depending on the current
+ * {@link CloseBehavior}.
+ */
+ @Override
+ public void close() throws IOException {
+ switch (mCloseBehavior) {
+ case IGNORE:
+ break;
+ case RESET:
+ mInputStream.reset();
+ break;
+ case CLOSE:
+ mInputStream.close();
+ break;
+ }
+ }
+}
diff --git a/common/src/main/java/com/android/prefs/AndroidLocation.java b/common/src/main/java/com/android/prefs/AndroidLocation.java
index 11c1540..1444cc8 100644
--- a/common/src/main/java/com/android/prefs/AndroidLocation.java
+++ b/common/src/main/java/com/android/prefs/AndroidLocation.java
@@ -49,6 +49,30 @@
private static String sPrefsLocation = null;
/**
+ * Enum describing which variables to check and whether they should
+ * be checked via {@link System#getProperty(String)} or {@link System#getenv()} or both.
+ */
+ public enum EnvVar {
+ ANDROID_SDK_HOME("ANDROID_SDK_HOME", true, true), // both sys prop and env var
+ USER_HOME ("user.home", true, false), // sys prop only
+ HOME ("HOME", false, true); // env var only
+
+ final String mName;
+ final boolean mIsSysProp;
+ final boolean mIsEnvVar;
+
+ private EnvVar(String name, boolean isSysProp, boolean isEnvVar) {
+ mName = name;
+ mIsSysProp = isSysProp;
+ mIsEnvVar = isEnvVar;
+ }
+
+ public String getName() {
+ return mName;
+ }
+ }
+
+ /**
* Returns the folder used to store android related files.
* @return an OS specific path, terminated by a separator.
* @throws AndroidLocationException
@@ -56,7 +80,9 @@
@NonNull
public static final String getFolder() throws AndroidLocationException {
if (sPrefsLocation == null) {
- String home = findValidPath("ANDROID_SDK_HOME", "user.home", "HOME");
+ String home = findValidPath(new EnvVar[] { EnvVar.ANDROID_SDK_HOME,
+ EnvVar.USER_HOME,
+ EnvVar.HOME });
// if the above failed, we throw an exception.
if (home == null) {
@@ -104,21 +130,22 @@
/**
* Checks a list of system properties and/or system environment variables for validity, and
* existing director, and returns the first one.
- * @param names
- * @return the content of the first property/variable.
+ * @param vars The variables to check. Order does matter.
+ * @return the content of the first property/variable that is a valid directory.
*/
- private static String findValidPath(String... names) {
- for (String name : names) {
+ private static String findValidPath(EnvVar... vars) {
+ for (EnvVar var : vars) {
String path;
- if (name.indexOf('.') != -1) {
- path = System.getProperty(name);
- } else {
- path = System.getenv(name);
+ if (var.mIsSysProp) {
+ path = checkPath(System.getProperty(var.mName));
+ if (path != null) {
+ return path;
+ }
}
- if (path != null) {
- File f = new File(path);
- if (f.isDirectory()) {
+ if (var.mIsEnvVar) {
+ path = checkPath(System.getenv(var.mName));
+ if (path != null) {
return path;
}
}
@@ -126,4 +153,14 @@
return null;
}
+
+ private static String checkPath(String path) {
+ if (path != null) {
+ File f = new File(path);
+ if (f.isDirectory()) {
+ return path;
+ }
+ }
+ return null;
+ }
}
diff --git a/common/src/main/java/com/android/utils/GrabProcessOutput.java b/common/src/main/java/com/android/utils/GrabProcessOutput.java
new file mode 100755
index 0000000..d5cd78b
--- /dev/null
+++ b/common/src/main/java/com/android/utils/GrabProcessOutput.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.utils;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.io.Closeables;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class GrabProcessOutput {
+
+ public enum Wait {
+ /**
+ * Doesn't wait for the exec to complete.
+ * This still monitors the output but does not wait for the process to finish.
+ * In this mode the process return code is unknown and always 0.
+ */
+ ASYNC,
+ /**
+ * This waits for the process to finish.
+ * In this mode, {@link GrabProcessOutput#grabProcessOutput} returns the
+ * error code from the process.
+ * In some rare cases and depending on the OS, the process might not have
+ * finished dumping data into stdout/stderr.
+ * <p/>
+ * Use this when you don't particularly care for the output but instead
+ * care for the return code of the executed process.
+ */
+ WAIT_FOR_PROCESS,
+ /**
+ * This waits for the process to finish <em>and</em> for the stdout/stderr
+ * threads to complete.
+ * In this mode, {@link GrabProcessOutput#grabProcessOutput} returns the
+ * error code from the process.
+ * <p/>
+ * Use this one when capturing all the output from the process is important.
+ */
+ WAIT_FOR_READERS,
+ }
+
+ public interface IProcessOutput {
+ /**
+ * Processes an stdout message line.
+ * @param line The stdout message line. Null when the reader reached the end of stdout.
+ */
+ public void out(@Nullable String line);
+ /**
+ * Processes an stderr message line.
+ * @param line The stderr message line. Null when the reader reached the end of stderr.
+ */
+ public void err(@Nullable String line);
+ }
+
+ /**
+ * Get the stderr/stdout outputs of a process and return when the process is done.
+ * Both <b>must</b> be read or the process will block on windows.
+ *
+ * @param process The process to get the output from.
+ * @param output Optional object to capture stdout/stderr.
+ * Note that on Windows capturing the output is not optional. If output is null
+ * the stdout/stderr will be captured and discarded.
+ * @param waitMode Whether to wait for the process and/or the readers to finish.
+ * @return the process return code.
+ * @throws InterruptedException if {@link Process#waitFor()} was interrupted.
+ */
+ public static int grabProcessOutput(
+ @NonNull final Process process,
+ Wait waitMode,
+ @Nullable final IProcessOutput output) throws InterruptedException {
+ // read the lines as they come. if null is returned, it's
+ // because the process finished
+ Thread threadErr = new Thread("stderr") {
+ @Override
+ public void run() {
+ // create a buffer to read the stderr output
+ InputStream is = process.getErrorStream();
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader errReader = new BufferedReader(isr);
+
+ try {
+ while (true) {
+ String line = errReader.readLine();
+ if (output != null) {
+ output.err(line);
+ }
+ if (line == null) {
+ break;
+ }
+ }
+ } catch (IOException e) {
+ // do nothing.
+ } finally {
+ try {
+ Closeables.close(is, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(isr, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(errReader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+ }
+ };
+
+ Thread threadOut = new Thread("stdout") {
+ @Override
+ public void run() {
+ InputStream is = process.getInputStream();
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader outReader = new BufferedReader(isr);
+
+ try {
+ while (true) {
+ String line = outReader.readLine();
+ if (output != null) {
+ output.out(line);
+ }
+ if (line == null) {
+ break;
+ }
+ }
+ } catch (IOException e) {
+ // do nothing.
+ } finally {
+ try {
+ Closeables.close(is, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(isr, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(outReader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+ }
+ };
+
+ threadErr.start();
+ threadOut.start();
+
+ if (waitMode == Wait.ASYNC) {
+ return 0;
+ }
+
+ // it looks like on windows process#waitFor() can return
+ // before the thread have filled the arrays, so we wait for both threads and the
+ // process itself.
+ if (waitMode == Wait.WAIT_FOR_READERS) {
+ try {
+ threadErr.join();
+ } catch (InterruptedException e) {
+ }
+ try {
+ threadOut.join();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // get the return code from the process
+ return process.waitFor();
+ }
+}
diff --git a/common/src/main/java/com/android/utils/HtmlBuilder.java b/common/src/main/java/com/android/utils/HtmlBuilder.java
index f2e0d20..3d80502 100644
--- a/common/src/main/java/com/android/utils/HtmlBuilder.java
+++ b/common/src/main/java/com/android/utils/HtmlBuilder.java
@@ -232,9 +232,10 @@
}
mStringBuilder.append("<img src='");
mStringBuilder.append(link);
+ mStringBuilder.append("'");
if (altText != null) {
- mStringBuilder.append("' alt=\"");
+ mStringBuilder.append(" alt=\"");
mStringBuilder.append(altText);
mStringBuilder.append("\"");
}
diff --git a/common/src/main/java/com/android/utils/PositionXmlParser.java b/common/src/main/java/com/android/utils/PositionXmlParser.java
index acbd86c..daee9ef 100644
--- a/common/src/main/java/com/android/utils/PositionXmlParser.java
+++ b/common/src/main/java/com/android/utils/PositionXmlParser.java
@@ -16,10 +16,14 @@
package com.android.utils;
+import static com.android.SdkConstants.UTF_8;
+
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import org.w3c.dom.Attr;
+import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -28,7 +32,8 @@
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.DefaultHandler2;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -51,7 +56,6 @@
* (and line and column numbers) for element nodes as well as attribute nodes.
*/
public class PositionXmlParser {
- private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
private static final String UTF_16 = "UTF_16"; //$NON-NLS-1$
private static final String UTF_16LE = "UTF_16LE"; //$NON-NLS-1$
private static final String CONTENT_KEY = "contents"; //$NON-NLS-1$
@@ -60,6 +64,8 @@
"http://xml.org/sax/features/namespace-prefixes"; //$NON-NLS-1$
private static final String NAMESPACE_FEATURE =
"http://xml.org/sax/features/namespaces"; //$NON-NLS-1$
+ private static final String PROVIDE_XMLNS_URIS =
+ "http://xml.org/sax/features/xmlns-uris"; //$NON-NLS-1$
/** See http://www.w3.org/TR/REC-xml/#NT-EncodingDecl */
private static final Pattern ENCODING_PATTERN =
Pattern.compile("encoding=['\"](\\S*)['\"]");//$NON-NLS-1$
@@ -107,6 +113,7 @@
public Document parse(@NonNull byte[] data)
throws ParserConfigurationException, SAXException, IOException {
String xml = getXmlString(data);
+ xml = XmlUtils.stripBom(xml);
return parse(xml, new InputSource(new StringReader(xml)), true);
}
@@ -125,6 +132,7 @@
@Nullable
public Document parse(@NonNull String xml)
throws ParserConfigurationException, SAXException, IOException {
+ xml = XmlUtils.stripBom(xml);
return parse(xml, new InputSource(new StringReader(xml)), true);
}
@@ -135,8 +143,14 @@
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setFeature(NAMESPACE_FEATURE, true);
factory.setFeature(NAMESPACE_PREFIX_FEATURE, true);
+ factory.setFeature(PROVIDE_XMLNS_URIS, true);
SAXParser parser = factory.newSAXParser();
DomBuilder handler = new DomBuilder(xml);
+ XMLReader xmlReader = parser.getXMLReader();
+ xmlReader.setProperty(
+ "http://xml.org/sax/properties/lexical-handler",
+ handler
+ );
parser.parse(input, handler);
return handler.getDocument();
} catch (SAXException e) {
@@ -466,9 +480,9 @@
* SAX parser handler which incrementally builds up a DOM document as we go
* along, and updates position information along the way. Position
* information is attached to the DOM nodes by setting user data with the
- * {@link POS_KEY} key.
+ * {@link #POS_KEY} key.
*/
- private final class DomBuilder extends DefaultHandler {
+ private final class DomBuilder extends DefaultHandler2 {
private final String mXml;
private final Document mDocument;
private Locator mLocator;
@@ -506,7 +520,7 @@
flushText();
Element element = mDocument.createElement(qName);
for (int i = 0; i < attributes.getLength(); i++) {
- if (attributes.getURI(i) != null && attributes.getURI(i).length() > 0) {
+ if (attributes.getURI(i) != null && !attributes.getURI(i).isEmpty()) {
Attr attr = mDocument.createAttributeNS(attributes.getURI(i),
attributes.getQName(i));
attr.setValue(attributes.getValue(i));
@@ -530,35 +544,7 @@
// the beginning since pos.offset will typically point to the first character
// AFTER the element open tag, which could be a closing tag or a child open
// tag
-
- for (int offset = pos.getOffset() - 1; offset >= 0; offset--) {
- char c = mXml.charAt(offset);
- // < cannot appear in attribute values or anywhere else within
- // an element open tag, so we know the first occurrence is the real
- // element start
- if (c == '<') {
- // Adjust line position
- int line = pos.getLine();
- for (int i = offset, n = pos.getOffset(); i < n; i++) {
- if (mXml.charAt(i) == '\n') {
- line--;
- }
- }
-
- // Compute new column position
- int column = 0;
- for (int i = offset - 1; i >= 0; i--, column++) {
- if (mXml.charAt(i) == '\n') {
- break;
- }
- }
-
- pos = createPosition(line, column, offset);
- break;
- }
- }
-
- element.setUserData(POS_KEY, pos, null);
+ element.setUserData(POS_KEY, findOpeningTag(pos), null);
mStack.add(element);
} catch (Exception t) {
throw new SAXException(t);
@@ -574,15 +560,78 @@
assert pos != null;
pos.setEnd(getCurrentPosition());
- if (mStack.isEmpty()) {
- mDocument.appendChild(element);
+ addNodeToParent(element);
+ }
+
+ @Override
+ public void comment(char[] chars, int start, int length) throws SAXException {
+
+ flushText();
+ String comment = new String(chars, start, length);
+ Comment domComment = mDocument.createComment(comment);
+
+ // current position is the closing comment tag.
+ Position currentPosition = getCurrentPosition();
+ Position startPosition = findOpeningTag(currentPosition);
+ startPosition.setEnd(currentPosition);
+
+ domComment.setUserData(POS_KEY, startPosition, null);
+ addNodeToParent(domComment);
+ }
+
+ /**
+ * Adds a node to the current parent element being visited, or to the document if there is
+ * no parent in context.
+ * @param nodeToAdd xml node to add.
+ */
+ private void addNodeToParent(Node nodeToAdd) {
+ if (mStack.isEmpty()){
+ mDocument.appendChild(nodeToAdd);
} else {
Element parent = mStack.get(mStack.size() - 1);
- parent.appendChild(element);
+ parent.appendChild(nodeToAdd);
}
}
/**
+ * Find opening tags from the current position.
+ * < cannot appear in attribute values or anywhere else within
+ * an element open tag, so we know the first occurrence is the real
+ * element start
+ * For comments, it is not legal to put < in a comment, however we are not
+ * validating so we will return an invalid column in that case.
+ * @param startingPosition the position to walk backwards until < is reached.
+ * @return the opening tag position or startPosition if cannot be found.
+ */
+ private Position findOpeningTag(Position startingPosition) {
+ for (int offset = startingPosition.getOffset() - 1; offset >= 0; offset--) {
+ char c = mXml.charAt(offset);
+
+ if (c == '<') {
+ // Adjust line position
+ int line = startingPosition.getLine();
+ for (int i = offset, n = startingPosition.getOffset(); i < n; i++) {
+ if (mXml.charAt(i) == '\n') {
+ line--;
+ }
+ }
+
+ // Compute new column position
+ int column = 0;
+ for (int i = offset - 1; i >= 0; i--, column++) {
+ if (mXml.charAt(i) == '\n') {
+ break;
+ }
+ }
+
+ return createPosition(line, column, offset);
+ }
+ }
+ // we did not find it, approximate.
+ return startingPosition;
+ }
+
+ /**
* Returns a position holder for the current position. The most
* important part of this function is to incrementally compute the
* offset as well, by counting forwards until it reaches the new line
@@ -653,7 +702,7 @@
return new DefaultPosition(line, column, offset);
}
- protected interface Position {
+ public interface Position {
/**
* Linked position: for a begin position this will point to the
* corresponding end position. For an end position this will be null.
diff --git a/common/src/main/java/com/android/utils/SdkUtils.java b/common/src/main/java/com/android/utils/SdkUtils.java
index b12caac..5454b70 100644
--- a/common/src/main/java/com/android/utils/SdkUtils.java
+++ b/common/src/main/java/com/android/utils/SdkUtils.java
@@ -22,6 +22,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
+import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
@@ -423,4 +424,40 @@
Closeables.close(in, successfulOps < 2);
}
}
+
+ /**
+ * Translates an XML name (e.g. xml-name) into a Java / C++ constant name (e.g. XML_NAME)
+ * @param xmlName the hyphen separated lower case xml name.
+ * @return the equivalent constant name.
+ */
+ public static String xmlNameToConstantName(String xmlName) {
+ return CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, xmlName);
+ }
+
+ /**
+ * Translates a camel case name (e.g. xmlName) into a Java / C++ constant name (e.g. XML_NAME)
+ * @param camelCaseName the camel case name.
+ * @return the equivalent constant name.
+ */
+ public static String camelCaseToConstantName(String camelCaseName) {
+ return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, camelCaseName);
+ }
+
+ /**
+ * Translates a Java / C++ constant name (e.g. XML_NAME) into camel case name (e.g. xmlName)
+ * @param constantName the constant name.
+ * @return the equivalent camel case name.
+ */
+ public static String constantNameToCamelCase(String constantName) {
+ return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, constantName);
+ }
+
+ /**
+ * Translates a Java / C++ constant name (e.g. XML_NAME) into a XML case name (e.g. xml-name)
+ * @param constantName the constant name.
+ * @return the equivalent XML name.
+ */
+ public static String constantNameToXmlName(String constantName) {
+ return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, constantName);
+ }
}
diff --git a/common/src/main/java/com/android/utils/XmlUtils.java b/common/src/main/java/com/android/utils/XmlUtils.java
index bd99287..831cc31 100644
--- a/common/src/main/java/com/android/utils/XmlUtils.java
+++ b/common/src/main/java/com/android/utils/XmlUtils.java
@@ -25,10 +25,16 @@
import static com.android.SdkConstants.XMLNS;
import static com.android.SdkConstants.XMLNS_PREFIX;
import static com.android.SdkConstants.XMLNS_URI;
+import static com.google.common.base.Charsets.UTF_16BE;
+import static com.google.common.base.Charsets.UTF_16LE;
+import static com.google.common.base.Charsets.UTF_8;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
@@ -37,13 +43,24 @@
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
import java.io.StringReader;
+import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Locale;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
/** XML Utilities */
public class XmlUtils {
@@ -53,6 +70,11 @@
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$
/**
+ * Separator for xml namespace and localname
+ */
+ public static final char NS_SEPARATOR = ':'; //$NON-NLS-1$
+
+ /**
* Returns the namespace prefix matching the requested namespace URI.
* If no such declaration is found, returns the default "android" prefix for
* the Android URI, and "app" for other URI's. By default the app namespace
@@ -220,6 +242,22 @@
}
/**
+ * Converts the given XML-attribute-safe value to a java string
+ *
+ * @param escapedAttrValue the escaped value
+ * @return the unescaped value
+ */
+ @NonNull
+ public static String fromXmlAttributeValue(@NonNull String escapedAttrValue) {
+ String workingString = escapedAttrValue.replace(QUOT_ENTITY, "\"");
+ workingString = workingString.replace(LT_ENTITY, "<");
+ workingString = workingString.replace(APOS_ENTITY, "'");
+ workingString = workingString.replace(AMP_ENTITY, "&");
+
+ return workingString;
+ }
+
+ /**
* Converts the given attribute value to an XML-text-safe value, meaning that
* less than and ampersand characters are escaped.
*
@@ -307,8 +345,130 @@
}
/**
+ * Returns a character reader for the given file, which must be a UTF encoded file.
+ * <p>
+ * The reader does not need to be closed by the caller (because the file is read in
+ * full in one shot and the resulting array is then wrapped in a byte array input stream,
+ * which does not need to be closed.)
+ */
+ public static Reader getUtfReader(@NonNull File file) throws IOException {
+ byte[] bytes = Files.toByteArray(file);
+ int length = bytes.length;
+ if (length == 0) {
+ return new StringReader("");
+ }
+
+ switch (bytes[0]) {
+ case (byte)0xEF: {
+ if (length >= 3
+ && bytes[1] == (byte)0xBB
+ && bytes[2] == (byte)0xBF) {
+ // UTF-8 BOM: EF BB BF: Skip it
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 3, length - 3),
+ UTF_8);
+ }
+ break;
+ }
+ case (byte)0xFE: {
+ if (length >= 2
+ && bytes[1] == (byte)0xFF) {
+ // UTF-16 Big Endian BOM: FE FF
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 2, length - 2),
+ UTF_16BE);
+ }
+ break;
+ }
+ case (byte)0xFF: {
+ if (length >= 2
+ && bytes[1] == (byte)0xFE) {
+ if (length >= 4
+ && bytes[2] == (byte)0x00
+ && bytes[3] == (byte)0x00) {
+ // UTF-32 Little Endian BOM: FF FE 00 00
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 4,
+ length - 4), "UTF-32LE");
+ }
+
+ // UTF-16 Little Endian BOM: FF FE
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 2, length - 2),
+ UTF_16LE);
+ }
+ break;
+ }
+ case (byte)0x00: {
+ if (length >= 4
+ && bytes[0] == (byte)0x00
+ && bytes[1] == (byte)0x00
+ && bytes[2] == (byte)0xFE
+ && bytes[3] == (byte)0xFF) {
+ // UTF-32 Big Endian BOM: 00 00 FE FF
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 4, length - 4),
+ "UTF-32BE");
+ }
+ break;
+ }
+ }
+
+ // No byte order mark: Assume UTF-8 (where the BOM is optional).
+ return new InputStreamReader(new ByteArrayInputStream(bytes), UTF_8);
+ }
+
+ /**
* Parses the given XML string as a DOM document, using the JDK parser. The parser does not
- * validate, and is namespace aware.
+ * validate, and is optionally namespace aware.
+ *
+ * @param xml the XML content to be parsed (must be well formed)
+ * @param namespaceAware whether the parser is namespace aware
+ * @return the DOM document
+ */
+ @NonNull
+ public static Document parseDocument(@NonNull String xml, boolean namespaceAware)
+ throws ParserConfigurationException, IOException, SAXException {
+ xml = stripBom(xml);
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ InputSource is = new InputSource(new StringReader(xml));
+ factory.setNamespaceAware(namespaceAware);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ return builder.parse(is);
+ }
+
+ /**
+ * Parses the given UTF file as a DOM document, using the JDK parser. The parser does not
+ * validate, and is optionally namespace aware.
+ *
+ * @param file the UTF encoded file to parse
+ * @param namespaceAware whether the parser is namespace aware
+ * @return the DOM document
+ */
+ @NonNull
+ public static Document parseUtfXmlFile(@NonNull File file, boolean namespaceAware)
+ throws ParserConfigurationException, IOException, SAXException {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Reader reader = getUtfReader(file);
+ try {
+ InputSource is = new InputSource(reader);
+ factory.setNamespaceAware(namespaceAware);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ return builder.parse(is);
+ } finally {
+ reader.close();
+ }
+ }
+
+ /** Strips out a leading UTF byte order mark, if present */
+ @NonNull
+ public static String stripBom(@NonNull String xml) {
+ if (!xml.isEmpty() && xml.charAt(0) == '\uFEFF') {
+ return xml.substring(1);
+ }
+ return xml;
+ }
+
+ /**
+ * Parses the given XML string as a DOM document, using the JDK parser. The parser does not
+ * validate, and is optionally namespace aware. Any parsing errors are silently ignored.
*
* @param xml the XML content to be parsed (must be well formed)
* @param namespaceAware whether the parser is namespace aware
@@ -316,13 +476,8 @@
*/
@Nullable
public static Document parseDocumentSilently(@NonNull String xml, boolean namespaceAware) {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- InputSource is = new InputSource(new StringReader(xml));
- factory.setNamespaceAware(namespaceAware);
- factory.setValidating(false);
try {
- DocumentBuilder builder = factory.newDocumentBuilder();
- return builder.parse(is);
+ return parseDocument(xml, namespaceAware);
} catch (Exception e) {
// pass
// This method is deliberately silent; will return null
diff --git a/common/src/main/java/com/android/xml/AndroidManifest.java b/common/src/main/java/com/android/xml/AndroidManifest.java
index 527a210..b53566b 100644
--- a/common/src/main/java/com/android/xml/AndroidManifest.java
+++ b/common/src/main/java/com/android/xml/AndroidManifest.java
@@ -17,13 +17,19 @@
package com.android.xml;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.io.IAbstractFile;
import com.android.io.IAbstractFolder;
import com.android.io.StreamException;
+import com.google.common.io.Closeables;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
+import java.io.IOException;
+import java.io.InputStream;
+
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
@@ -90,6 +96,10 @@
public static final String ATTRIBUTE_BACKUP_AGENT = "backupAgent";
public static final String ATTRIBUTE_PARENT_ACTIVITY_NAME = "parentActivityName";
public static final String ATTRIBUTE_SUPPORTS_RTL = "supportsRtl";
+ public static final String ATTRIBUTE_UI_OPTIONS = "uiOptions";
+ public static final String ATTRIBUTE_VALUE = "value";
+
+ public static final String VALUE_PARENT_ACTIVITY = SdkConstants.ANDROID_SUPPORT_PKG_PREFIX + "PARENT_ACTIVITY";
/**
* Returns an {@link IAbstractFile} object representing the manifest for the given project.
@@ -133,12 +143,9 @@
*/
public static String getPackage(IAbstractFile manifestFile)
throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- return xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/@" + ATTRIBUTE_PACKAGE,
- new InputSource(manifestFile.getContents()));
+ return getStringValue(manifestFile,
+ "/" + NODE_MANIFEST +
+ "/@" + ATTRIBUTE_PACKAGE);
}
/**
@@ -154,14 +161,11 @@
*/
public static boolean getDebuggable(IAbstractFile manifestFile)
throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- String value = xPath.evaluate(
+ String value = getStringValue(manifestFile,
"/" + NODE_MANIFEST +
"/" + NODE_APPLICATION +
"/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_DEBUGGABLE,
- new InputSource(manifestFile.getContents()));
+ ":" + ATTRIBUTE_DEBUGGABLE);
// default is not debuggable, which is the same behavior as parseBoolean
return Boolean.parseBoolean(value);
@@ -176,13 +180,10 @@
*/
public static int getVersionCode(IAbstractFile manifestFile)
throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- String result = xPath.evaluate(
- "/" + NODE_MANIFEST +
+ String result = getStringValue(manifestFile,
+ "/" + NODE_MANIFEST +
"/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_VERSIONCODE,
- new InputSource(manifestFile.getContents()));
+ ":" + ATTRIBUTE_VERSIONCODE);
try {
return Integer.parseInt(result);
@@ -202,17 +203,27 @@
throws XPathExpressionException, StreamException {
XPath xPath = AndroidXPathFactory.newXPath();
- Object result = xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_VERSIONCODE,
- new InputSource(manifestFile.getContents()),
- XPathConstants.NODE);
+ InputStream is = null;
+ try {
+ is = manifestFile.getContents();
+ Object result = xPath.evaluate(
+ "/" + NODE_MANIFEST +
+ "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
+ ":" + ATTRIBUTE_VERSIONCODE,
+ new InputSource(is),
+ XPathConstants.NODE);
- if (result != null) {
- Node node = (Node)result;
- if (node.getNodeValue().length() > 0) {
- return true;
+ if (result != null) {
+ Node node = (Node)result;
+ if (!node.getNodeValue().isEmpty()) {
+ return true;
+ }
+ }
+ } finally {
+ try {
+ Closeables.close(is, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
}
}
@@ -233,47 +244,48 @@
* @throws XPathExpressionException
* @throws StreamException If any error happens when reading the manifest.
*/
+ @Nullable
public static Object getMinSdkVersion(IAbstractFile manifestFile)
throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- String result = xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_USES_SDK +
+ String result = getStringValue(manifestFile,
+ "/" + NODE_MANIFEST +
+ "/" + NODE_USES_SDK +
"/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_MIN_SDK_VERSION,
- new InputSource(manifestFile.getContents()));
+ ":" + ATTRIBUTE_MIN_SDK_VERSION);
try {
return Integer.valueOf(result);
} catch (NumberFormatException e) {
- return result.length() > 0 ? result : null;
+ return !result.isEmpty() ? result : null;
}
}
/**
- * Returns the value of the targetSdkVersion attribute (defaults to 1 if the attribute is
- * not set), or -1 if the value is a codename.
+ * Returns the value of the targetSdkVersion attribute.
+ * <p/>
+ * If the attribute is set with an int value, the method returns an Integer object.
+ * <p/>
+ * If the attribute is set with a codename, it returns the codename as a String object.
+ * <p/>
+ * If the attribute is not set, it returns null.
+ *
* @param manifestFile the manifest file to read the attribute from.
* @return the integer value or -1 if not set.
* @throws XPathExpressionException
* @throws StreamException If any error happens when reading the manifest.
*/
- public static Integer getTargetSdkVersion(IAbstractFile manifestFile)
+ @Nullable
+ public static Object getTargetSdkVersion(IAbstractFile manifestFile)
throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- String result = xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_USES_SDK +
+ String result = getStringValue(manifestFile,
+ "/" + NODE_MANIFEST +
+ "/" + NODE_USES_SDK +
"/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_TARGET_SDK_VERSION,
- new InputSource(manifestFile.getContents()));
-
+ ":" + ATTRIBUTE_TARGET_SDK_VERSION);
try {
return Integer.valueOf(result);
} catch (NumberFormatException e) {
- return result.length() > 0 ? -1 : null;
+ return !result.isEmpty() ? result : null;
}
}
@@ -286,14 +298,11 @@
*/
public static String getApplicationIcon(IAbstractFile manifestFile)
throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- return xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_APPLICATION +
+ return getStringValue(manifestFile,
+ "/" + NODE_MANIFEST +
+ "/" + NODE_APPLICATION +
"/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_ICON,
- new InputSource(manifestFile.getContents()));
+ ":" + ATTRIBUTE_ICON);
}
/**
@@ -305,14 +314,11 @@
*/
public static String getApplicationLabel(IAbstractFile manifestFile)
throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- return xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_APPLICATION +
+ return getStringValue(manifestFile,
+ "/" + NODE_MANIFEST +
+ "/" + NODE_APPLICATION +
"/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_LABEL,
- new InputSource(manifestFile.getContents()));
+ ":" + ATTRIBUTE_LABEL);
}
/**
@@ -327,18 +333,16 @@
* @throws StreamException If any error happens when reading the manifest.
*/
public static boolean getSupportsRtl(IAbstractFile manifestFile)
- throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
+ throws XPathExpressionException, StreamException {
- String value = xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_APPLICATION +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_SUPPORTS_RTL,
- new InputSource(manifestFile.getContents()));
+ String value = getStringValue(manifestFile,
+ "/" + NODE_MANIFEST +
+ "/" + NODE_APPLICATION +
+ "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
+ ":" + ATTRIBUTE_SUPPORTS_RTL);
- // default is not debuggable, which is the same behavior as parseBoolean
- return Boolean.parseBoolean(value);
+ // default is not debuggable, which is the same behavior as parseBoolean
+ return Boolean.parseBoolean(value);
}
/**
@@ -349,10 +353,10 @@
* @return the fully qualified class name.
*/
public static String combinePackageAndClassName(String javaPackage, String className) {
- if (className == null || className.length() == 0) {
+ if (className == null || className.isEmpty()) {
return javaPackage;
}
- if (javaPackage == null || javaPackage.length() == 0) {
+ if (javaPackage == null || javaPackage.isEmpty()) {
return className;
}
@@ -360,7 +364,7 @@
// char), a simple class name (no dot), or a full java package
boolean startWithDot = (className.charAt(0) == '.');
boolean hasDot = (className.indexOf('.') != -1);
- if (startWithDot || hasDot == false) {
+ if (startWithDot || !hasDot) {
// add the concatenation of the package and class name
if (startWithDot) {
@@ -385,9 +389,9 @@
*/
public static String extractActivityName(String fullActivityName, String packageName) {
if (packageName != null && fullActivityName != null) {
- if (packageName.length() > 0 && fullActivityName.startsWith(packageName)) {
+ if (!packageName.isEmpty() && fullActivityName.startsWith(packageName)) {
String name = fullActivityName.substring(packageName.length());
- if (name.length() > 0 && name.charAt(0) == '.') {
+ if (!name.isEmpty() && name.charAt(0) == '.') {
return name;
}
}
@@ -395,4 +399,22 @@
return fullActivityName;
}
+
+
+ private static String getStringValue(@NonNull IAbstractFile file, @NonNull String xPath)
+ throws StreamException, XPathExpressionException {
+ XPath xpath = AndroidXPathFactory.newXPath();
+
+ InputStream is = null;
+ try {
+ is = file.getContents();
+ return xpath.evaluate(xPath, new InputSource(is));
+ } finally {
+ try {
+ Closeables.close(is, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+ }
}
diff --git a/common/src/test/.classpath b/common/src/test/.classpath
index 7564f2f..1fb6d89 100644
--- a/common/src/test/.classpath
+++ b/common/src/test/.classpath
@@ -4,6 +4,6 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/guava-tools/guava-13.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/guava-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/common/src/test/java/com/android/io/NonClosingInputStreamTest.java b/common/src/test/java/com/android/io/NonClosingInputStreamTest.java
new file mode 100755
index 0000000..9589985
--- /dev/null
+++ b/common/src/test/java/com/android/io/NonClosingInputStreamTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2014 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.io;
+
+import com.android.annotations.NonNull;
+import com.android.io.NonClosingInputStream.CloseBehavior;
+import com.google.common.base.Charsets;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ */
+public class NonClosingInputStreamTest extends TestCase {
+
+ private File mFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mFile = File.createTempFile("test", "txt");
+ FileWrapper fw = new FileWrapper(mFile);
+ fw.setContents(new ByteArrayInputStream("1234".getBytes(Charsets.UTF_8)));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ if (!mFile.delete()) {
+ mFile.deleteOnExit();
+ }
+ }
+
+ /**
+ * Tests the normal case where a method parses a stream closes it.
+ * Next parser/reset operation fails with an IOException since the stream is closed.
+ */
+ public void testFailure() throws IOException {
+ InputStream is = loadResource();
+ try {
+
+ assertEquals('1', parseAndClose(is));
+ try {
+ assertEquals('2', parse(is));
+ fail("Expected: IOException 'stream closed'; Actual: no error.");
+ } catch (IOException e) {
+ assertEquals("Stream closed", e.getMessage());
+ }
+
+ } finally {
+ // Stream should have been closed already.
+ // This is to prevent from keeping a stream open in case the test fails.
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+
+ /**
+ * Tests with a NonClosingInputStream using CLOSE behavior.
+ * This should do exactly like in the previous testFailure case.
+ */
+ public void testCloseBehavior() throws IOException {
+ InputStream is = loadResource();
+ try {
+ InputStream ncis = new NonClosingInputStream(is);
+
+ assertEquals('1', parseAndClose(ncis));
+ try {
+ assertEquals('2', parse(ncis));
+ fail("Expected: IOException 'stream closed'; Actual: no error.");
+ } catch (IOException e) {
+ assertEquals("stream closed", e.getMessage().toLowerCase(Locale.US));
+ }
+ } finally {
+ // Stream should have been closed already.
+ // This is to prevent from keeping a stream open in case the test fails.
+ //
+ // Note that to really close, we need to invoke the original stream
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+
+ /**
+ * Tests with a NonClosingInputStream using IGNORE behavior.
+ * In this case, when the parser tries to close the stream, nothing happens.
+ */
+ public void testIgnoreBehavior() throws IOException {
+ InputStream is = loadResource();
+ try {
+ InputStream ncis = new NonClosingInputStream(is);
+ ((NonClosingInputStream) ncis).setCloseBehavior(CloseBehavior.IGNORE);
+ assertTrue(ncis.markSupported());
+
+ assertEquals('1', parseAndClose(ncis));
+
+ // the first parser's close should have done nothing
+ assertEquals('2', parse(ncis));
+
+ // closing does nothing
+ ncis.close();
+ assertEquals('3', parse(ncis));
+ assertEquals('4', parse(ncis));
+
+ // we can change the closing behavior to really close the stream
+ ((NonClosingInputStream) ncis).setCloseBehavior(CloseBehavior.CLOSE);
+ try {
+ ncis.close();
+ assertEquals('5', parse(ncis));
+ fail("Expected: IOException 'stream closed'; Actual: no error.");
+ } catch (IOException e) {
+ assertEquals("stream closed", e.getMessage().toLowerCase(Locale.US));
+ }
+
+ } finally {
+ // Stream should have been closed already.
+ // This is to prevent from keeping a stream open in case the test fails.
+ //
+ // Note that to really close, we need to invoke the original stream
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+
+ /**
+ * Tests with a NonClosingInputStream using RESET behavior.
+ * In this case, when the parser tries to close the stream, it actually
+ * calls reset and reverts it the last marked position.
+ */
+ public void testResetBehavior() throws IOException {
+ InputStream is = loadResource();
+ try {
+ InputStream ncis = new NonClosingInputStream(is);
+ ((NonClosingInputStream) ncis).setCloseBehavior(CloseBehavior.RESET);
+ assertTrue(ncis.markSupported());
+ ncis.mark(10);
+
+ assertEquals('1', parseAndClose(ncis));
+
+ // the first parser's close should have reset to the beginning
+ assertEquals('1', parse(ncis));
+ assertEquals('2', parse(ncis));
+
+ // a reset takes us back to the beginning
+ is.reset();
+ assertEquals('1', parse(ncis));
+ assertEquals('2', parse(ncis));
+
+ // a direct close on the wrapper also resets
+ ncis.close();
+ assertEquals('1', parse(ncis));
+ assertEquals('2', parse(ncis));
+
+ // we can change the closing behavior to really close the stream
+ ((NonClosingInputStream) ncis).setCloseBehavior(CloseBehavior.CLOSE);
+ try {
+ ncis.close();
+ assertEquals('3', parse(ncis));
+ fail("Expected: IOException 'stream closed'; Actual: no error.");
+ } catch (IOException e) {
+ assertEquals("stream closed", e.getMessage().toLowerCase(Locale.US));
+ }
+
+ } finally {
+ // Stream should have been closed already.
+ // This is to prevent from keeping a stream open in case the test fails.
+ //
+ // Note that to really close, we need to invoke the original stream
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+
+ //---
+
+ private InputStream loadResource() throws FileNotFoundException {
+ InputStream is = new BufferedInputStream(new FileInputStream(mFile));
+ assertNotNull("test.txt not found", is);
+ return is;
+ }
+
+ private char parse(@NonNull InputStream is) throws IOException {
+ return (char) is.read();
+ }
+
+ private char parseAndClose(@NonNull InputStream is) throws IOException {
+ try {
+ return (char) is.read();
+ } finally {
+ is.close();
+ }
+ }
+
+}
diff --git a/common/src/test/java/com/android/utils/PositionXmlParserTest.java b/common/src/test/java/com/android/utils/PositionXmlParserTest.java
index 18eda43..990716f 100644
--- a/common/src/test/java/com/android/utils/PositionXmlParserTest.java
+++ b/common/src/test/java/com/android/utils/PositionXmlParserTest.java
@@ -18,9 +18,12 @@
import com.android.utils.PositionXmlParser.Position;
+import junit.framework.TestCase;
+
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
@@ -33,8 +36,6 @@
import java.io.OutputStreamWriter;
import java.io.Writer;
-import junit.framework.TestCase;
-
@SuppressWarnings("javadoc")
public class PositionXmlParserTest extends TestCase {
public void test() throws Exception {
@@ -319,4 +320,100 @@
checkEncoding("ISO-8859-1", false /*bom*/, true /*encoding*/, "\r\n");
checkEncoding("iso-8859-1", false /*bom*/, true /*encoding*/, "\r\n");
}
+
+ public void testOneLineComment() throws Exception {
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <!-- this is a comment ! -->\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n" +
+ " some text\n" +
+ "\n" +
+ "</LinearLayout>\n";
+ PositionXmlParser parser = new PositionXmlParser();
+ File file = File.createTempFile("parsertest", ".xml");
+ file.deleteOnExit();
+ Writer fw = new BufferedWriter(new FileWriter(file));
+ fw.write(xml);
+ fw.close();
+ Document document = parser.parse(new FileInputStream(file));
+ assertNotNull(document);
+
+ // Basic parsing heart beat tests
+ Element linearLayout = (Element) document.getElementsByTagName("LinearLayout").item(0);
+ assertNotNull(linearLayout);
+
+ // first child is a comment.
+ org.w3c.dom.Node commentNode = linearLayout.getFirstChild().getNextSibling();
+ assertEquals(Node.COMMENT_NODE, commentNode.getNodeType());
+ Position position = parser.getPosition(commentNode);
+ assertNotNull(position);
+ assertEquals(6, position.getLine());
+ assertEquals(4, position.getColumn());
+ assertEquals(xml.indexOf("<!--"), position.getOffset());
+
+ // ensure that the next siblings' position start at the right location.
+ Element button = (Element) document.getElementsByTagName("Button").item(0);
+ Position buttonPosition = parser.getPosition(button);
+ assertNotNull(buttonPosition);
+ assertEquals(7, buttonPosition.getLine());
+ assertEquals(4, buttonPosition.getColumn());
+ }
+
+ public void testMultipleLineComment() throws Exception {
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <!-- this is a more complicated \n" +
+ " which spans on multiple lines\n" +
+ " in the source xml -->\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n" +
+ " some text\n" +
+ "\n" +
+ "</LinearLayout>\n";
+ PositionXmlParser parser = new PositionXmlParser();
+ File file = File.createTempFile("parsertest", ".xml");
+ file.deleteOnExit();
+ Writer fw = new BufferedWriter(new FileWriter(file));
+ fw.write(xml);
+ fw.close();
+ Document document = parser.parse(new FileInputStream(file));
+ assertNotNull(document);
+
+ // Basic parsing heart beat tests
+ Element linearLayout = (Element) document.getElementsByTagName("LinearLayout").item(0);
+ assertNotNull(linearLayout);
+
+ // first child is a comment.
+ Node commentNode = linearLayout.getFirstChild().getNextSibling();
+ assertEquals(Node.COMMENT_NODE, commentNode.getNodeType());
+ Position position = parser.getPosition(commentNode);
+ assertNotNull(position);
+ assertEquals(6, position.getLine());
+ assertEquals(4, position.getColumn());
+
+
+ // ensure that the next siblings' position start at the right location.
+ Element button = (Element) document.getElementsByTagName("Button").item(0);
+ Position buttonPosition = parser.getPosition(button);
+ assertNotNull(buttonPosition);
+ assertEquals(9, buttonPosition.getLine());
+ assertEquals(4, buttonPosition.getColumn());
+ }
}
diff --git a/common/src/test/java/com/android/utils/SdkUtilsTest.java b/common/src/test/java/com/android/utils/SdkUtilsTest.java
index e893d91..02a9a52 100644
--- a/common/src/test/java/com/android/utils/SdkUtilsTest.java
+++ b/common/src/test/java/com/android/utils/SdkUtilsTest.java
@@ -332,4 +332,11 @@
deleted = dest.delete();
assertTrue(deleted);
}
+
+ public void testNameConversionRoutines() {
+ assertEquals("xml-name", SdkUtils.constantNameToXmlName("XML_NAME"));
+ assertEquals("XML_NAME", SdkUtils.xmlNameToConstantName("xml-name"));
+ assertEquals("xmlName", SdkUtils.constantNameToCamelCase("XML_NAME"));
+ assertEquals("XML_NAME", SdkUtils.camelCaseToConstantName("xmlName"));
+ }
}
\ 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 95adb4b..1a0a1aa 100644
--- a/common/src/test/java/com/android/utils/XmlUtilsTest.java
+++ b/common/src/test/java/com/android/utils/XmlUtilsTest.java
@@ -19,6 +19,7 @@
import com.android.SdkConstants;
import com.android.annotations.Nullable;
+import com.google.common.base.Charsets;
import junit.framework.TestCase;
@@ -29,6 +30,12 @@
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
import java.io.StringReader;
import java.util.Locale;
@@ -104,6 +111,18 @@
assertEquals("<"'>&", XmlUtils.toXmlAttributeValue("<\"'>&"));
}
+ public void testFromXmlAttributeValue() throws Exception {
+ assertEquals("", XmlUtils.fromXmlAttributeValue(""));
+ assertEquals("foo", XmlUtils.fromXmlAttributeValue("foo"));
+ assertEquals("foo<bar", XmlUtils.fromXmlAttributeValue("foo<bar"));
+ assertEquals("foo>bar", XmlUtils.fromXmlAttributeValue("foo>bar"));
+
+ assertEquals("\"", XmlUtils.fromXmlAttributeValue("""));
+ assertEquals("'", XmlUtils.fromXmlAttributeValue("'"));
+ assertEquals("foo\"b''ar", XmlUtils.fromXmlAttributeValue("foo"b''ar"));
+ assertEquals("<\"'>&", XmlUtils.fromXmlAttributeValue("<"'>&"));
+ }
+
public void testAppendXmlAttributeValue() throws Exception {
StringBuilder sb = new StringBuilder();
XmlUtils.appendXmlAttributeValue(sb, "<\"'>&");
@@ -314,4 +333,101 @@
Locale.setDefault(originalDefaultLocale);
}
}
+
+ public void testGetUtfReader() throws IOException {
+ File file = File.createTempFile(getName(), SdkConstants.DOT_XML);
+
+ BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
+ OutputStreamWriter writer = new OutputStreamWriter(stream, Charsets.UTF_8);
+ try {
+ stream.write(0xef);
+ stream.write(0xbb);
+ stream.write(0xbf);
+ writer.write("OK");
+ } finally {
+ writer.close();
+ }
+
+ Reader reader = XmlUtils.getUtfReader(file);
+ assertEquals('O', reader.read());
+ assertEquals('K', reader.read());
+ assertEquals(-1, reader.read());
+
+ //noinspection ResultOfMethodCallIgnored
+ file.delete();
+ }
+
+ public void testStripBom() {
+ assertEquals("", XmlUtils.stripBom(""));
+ assertEquals("Hello", XmlUtils.stripBom("Hello"));
+ assertEquals("Hello", XmlUtils.stripBom("\uFEFFHello"));
+ }
+
+ public void testParseDocument() throws Exception {
+ String xml = "" +
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n" +
+ " some text\n" +
+ "\n" +
+ "</LinearLayout>\n";
+
+ Document document = XmlUtils.parseDocument(xml, true);
+ assertNotNull(document);
+ assertNotNull(document.getDocumentElement());
+ assertEquals("LinearLayout", document.getDocumentElement().getTagName());
+
+ // Add BOM
+ xml = '\uFEFF' + xml;
+ document = XmlUtils.parseDocument(xml, true);
+ assertNotNull(document);
+ assertNotNull(document.getDocumentElement());
+ assertEquals("LinearLayout", document.getDocumentElement().getTagName());
+ }
+
+ public void testParseUtfXmlFile() throws Exception {
+ File file = File.createTempFile(getName(), SdkConstants.DOT_XML);
+ String xml = "" +
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n" +
+ " some text\n" +
+ "\n" +
+ "</LinearLayout>\n";
+
+ BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
+ OutputStreamWriter writer = new OutputStreamWriter(stream, Charsets.UTF_8);
+ try {
+ stream.write(0xef);
+ stream.write(0xbb);
+ stream.write(0xbf);
+ writer.write(xml);
+ } finally {
+ writer.close();
+ }
+
+ Document document = XmlUtils.parseUtfXmlFile(file, true);
+ assertNotNull(document);
+ assertNotNull(document.getDocumentElement());
+ assertEquals("LinearLayout", document.getDocumentElement().getTagName());
+
+ //noinspection ResultOfMethodCallIgnored
+ file.delete();
+ }
}
diff --git a/ddmlib/build.gradle b/ddmlib/build.gradle
index 3dc7c8e..dd455da 100644
--- a/ddmlib/build.gradle
+++ b/ddmlib/build.gradle
@@ -1,11 +1,12 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools.ddms'
archivesBaseName = 'ddmlib'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':common')
+ compile project(':base:common')
compile 'net.sf.kxml:kxml2:2.3.0'
@@ -18,13 +19,8 @@
test.resources.srcDir 'src/test/java'
}
-jar {
- from 'NOTICE'
-}
-
project.ext.pomName = 'Android Tools ddmlib'
project.ext.pomDesc = 'Library providing APIs to talk to Android devices'
-apply from: '../baseVersion.gradle'
-apply from: '../publish.gradle'
-apply from: '../javadoc.gradle'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/ddmlib/src/main/java/com/android/ddmlib/AllocationInfo.java b/ddmlib/src/main/java/com/android/ddmlib/AllocationInfo.java
index 110a715..e8cab45 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/AllocationInfo.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/AllocationInfo.java
@@ -16,6 +16,8 @@
package com.android.ddmlib;
+import com.android.annotations.NonNull;
+
import java.util.Comparator;
import java.util.Locale;
@@ -29,7 +31,7 @@
private final short mThreadId;
private final StackTraceElement[] mStackTrace;
- public static enum SortMode {
+ public enum SortMode {
NUMBER, SIZE, CLASS, THREAD, IN_CLASS, IN_METHOD
}
@@ -41,7 +43,7 @@
public AllocationSorter() {
}
- public void setSortMode(SortMode mode) {
+ public void setSortMode(@NonNull SortMode mode) {
if (mSortMode == mode) {
mDescending = !mDescending;
} else {
@@ -49,6 +51,12 @@
}
}
+ public void setSortMode(@NonNull SortMode mode, boolean descending) {
+ mSortMode = mode;
+ mDescending = descending;
+ }
+
+ @NonNull
public SortMode getSortMode() {
return mSortMode;
}
@@ -99,7 +107,7 @@
}
/** compares two strings that could be null */
- private int compareOptionalString(String str1, String str2) {
+ private static int compareOptionalString(String str1, String str2) {
if (str1 != null) {
if (str2 == null) {
return -1;
diff --git a/ddmlib/src/main/java/com/android/ddmlib/AllocationsParser.java b/ddmlib/src/main/java/com/android/ddmlib/AllocationsParser.java
new file mode 100644
index 0000000..7d6aa0b
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/AllocationsParser.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotations.NonNull;
+
+import java.nio.ByteBuffer;
+
+public class AllocationsParser {
+ /**
+ * Converts a VM class descriptor string ("Landroid/os/Debug;") to
+ * a dot-notation class name ("android.os.Debug").
+ */
+ private static String descriptorToDot(String str) {
+ // count the number of arrays.
+ int array = 0;
+ while (str.startsWith("[")) {
+ str = str.substring(1);
+ array++;
+ }
+
+ int len = str.length();
+
+ /* strip off leading 'L' and trailing ';' if appropriate */
+ if (len >= 2 && str.charAt(0) == 'L' && str.charAt(len - 1) == ';') {
+ str = str.substring(1, len-1);
+ str = str.replace('/', '.');
+ } else {
+ // convert the basic types
+ if ("C".equals(str)) {
+ str = "char";
+ } else if ("B".equals(str)) {
+ str = "byte";
+ } else if ("Z".equals(str)) {
+ str = "boolean";
+ } else if ("S".equals(str)) {
+ str = "short";
+ } else if ("I".equals(str)) {
+ str = "int";
+ } else if ("J".equals(str)) {
+ str = "long";
+ } else if ("F".equals(str)) {
+ str = "float";
+ } else if ("D".equals(str)) {
+ str = "double";
+ }
+ }
+
+ // now add the array part
+ for (int a = 0 ; a < array; a++) {
+ str += "[]";
+ }
+
+ return str;
+ }
+
+ /**
+ * Reads a string table out of "data".
+ *
+ * This is just a serial collection of strings, each of which is a
+ * four-byte length followed by UTF-16 data.
+ */
+ private static void readStringTable(ByteBuffer data, String[] strings) {
+ int count = strings.length;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ int nameLen = data.getInt();
+ String descriptor = ByteBufferUtil.getString(data, nameLen);
+ strings[i] = descriptorToDot(descriptor);
+ }
+ }
+
+ /*
+ * Message format:
+ * Message header (all values big-endian):
+ * (1b) message header len (to allow future expansion); includes itself
+ * (1b) entry header len
+ * (1b) stack frame len
+ * (2b) number of entries
+ * (4b) offset to string table from start of message
+ * (2b) number of class name strings
+ * (2b) number of method name strings
+ * (2b) number of source file name strings
+ * For each entry:
+ * (4b) total allocation size
+ * (2b) threadId
+ * (2b) allocated object's class name index
+ * (1b) stack depth
+ * For each stack frame:
+ * (2b) method's class name
+ * (2b) method name
+ * (2b) method source file
+ * (2b) line number, clipped to 32767; -2 if native; -1 if no source
+ * (xb) class name strings
+ * (xb) method name strings
+ * (xb) source file strings
+ *
+ * As with other DDM traffic, strings are sent as a 4-byte length
+ * followed by UTF-16 data.
+ */
+ @NonNull
+ public static AllocationInfo[] parse(@NonNull ByteBuffer data) {
+ int messageHdrLen, entryHdrLen, stackFrameLen;
+ int numEntries, offsetToStrings;
+ int numClassNames, numMethodNames, numFileNames;
+
+ /*
+ * Read the header.
+ */
+ messageHdrLen = (data.get() & 0xff);
+ entryHdrLen = (data.get() & 0xff);
+ stackFrameLen = (data.get() & 0xff);
+ numEntries = (data.getShort() & 0xffff);
+ offsetToStrings = data.getInt();
+ numClassNames = (data.getShort() & 0xffff);
+ numMethodNames = (data.getShort() & 0xffff);
+ numFileNames = (data.getShort() & 0xffff);
+
+
+ /*
+ * Skip forward to the strings and read them.
+ */
+ data.position(offsetToStrings);
+
+ String[] classNames = new String[numClassNames];
+ String[] methodNames = new String[numMethodNames];
+ String[] fileNames = new String[numFileNames];
+
+ readStringTable(data, classNames);
+ readStringTable(data, methodNames);
+ readStringTable(data, fileNames);
+
+ /*
+ * Skip back to a point just past the header and start reading
+ * entries.
+ */
+ data.position(messageHdrLen);
+
+ AllocationInfo[] allocations = new AllocationInfo[numEntries];
+ for (int i = 0; i < numEntries; i++) {
+ int totalSize;
+ int threadId, classNameIndex, stackDepth;
+
+ totalSize = data.getInt();
+ threadId = (data.getShort() & 0xffff);
+ classNameIndex = (data.getShort() & 0xffff);
+ stackDepth = (data.get() & 0xff);
+ /* we've consumed 9 bytes; gobble up any extra */
+ for (int skip = 9; skip < entryHdrLen; skip++)
+ data.get();
+
+ StackTraceElement[] steArray = new StackTraceElement[stackDepth];
+
+ /*
+ * Pull out the stack trace.
+ */
+ for (int sti = 0; sti < stackDepth; sti++) {
+ int methodClassNameIndex, methodNameIndex;
+ int methodSourceFileIndex;
+ short lineNumber;
+ String methodClassName, methodName, methodSourceFile;
+
+ methodClassNameIndex = (data.getShort() & 0xffff);
+ methodNameIndex = (data.getShort() & 0xffff);
+ methodSourceFileIndex = (data.getShort() & 0xffff);
+ lineNumber = data.getShort();
+
+ methodClassName = classNames[methodClassNameIndex];
+ methodName = methodNames[methodNameIndex];
+ methodSourceFile = fileNames[methodSourceFileIndex];
+
+ steArray[sti] = new StackTraceElement(methodClassName,
+ methodName, methodSourceFile, lineNumber);
+
+ /* we've consumed 8 bytes; gobble up any extra */
+ for (int skip = 8; skip < stackFrameLen; skip++)
+ data.get();
+ }
+
+ allocations[i] = new AllocationInfo(numEntries - i, classNames[classNameIndex], totalSize, (short) threadId, steArray);
+ }
+ return allocations;
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java b/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
index f807a79..abb8e2a 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
@@ -16,7 +16,9 @@
package com.android.ddmlib;
+import com.android.annotations.NonNull;
import com.android.ddmlib.Log.LogLevel;
+import com.google.common.base.Joiner;
import java.io.BufferedReader;
import java.io.File;
@@ -28,6 +30,7 @@
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -56,8 +59,11 @@
private static final String SERVER_PORT_ENV_VAR = "ANDROID_ADB_SERVER_PORT"; //$NON-NLS-1$
// Where to find the ADB bridge.
- static final String ADB_HOST = "127.0.0.1"; //$NON-NLS-1$
- static final int ADB_PORT = 5037;
+ static final String DEFAULT_ADB_HOST = "127.0.0.1"; //$NON-NLS-1$
+ static final int DEFAULT_ADB_PORT = 5037;
+
+ /** Port where adb server will be started **/
+ private static int sAdbServerPort = 0;
private static InetAddress sHostAddr;
private static InetSocketAddress sSocketAddr;
@@ -566,8 +572,8 @@
}
/**
- * Queries adb for its version number and checks it against {@link #MIN_VERSION_NUMBER} and
- * {@link #MAX_VERSION_NUMBER}
+ * Queries adb for its version number and checks it against {@link #ADB_VERSION_MICRO_MIN} and
+ * {@link #ADB_VERSION_MICRO_MAX}
*/
private void checkAdbVersion() {
// default is bad check
@@ -702,10 +708,11 @@
/**
* Starts the debug bridge.
+ *
* @return true if success.
*/
boolean start() {
- if (mAdbOsLocation != null && (!mVersionCheck || !startAdb())) {
+ if (mAdbOsLocation != null && sAdbServerPort != 0 && (!mVersionCheck || !startAdb())) {
return false;
}
@@ -753,6 +760,11 @@
return false;
}
+ if (sAdbServerPort == 0) {
+ Log.e(ADB, "ADB server port for restarting AndroidDebugBridge is not set."); //$NON-NLS-1$
+ return false;
+ }
+
if (!mVersionCheck) {
Log.logAndDisplay(LogLevel.ERROR, ADB,
"Attempting to restart adb, but version check failed!"); //$NON-NLS-1$
@@ -890,7 +902,7 @@
* For this reason, any call to this method from a method of {@link DeviceMonitor},
* {@link IDevice} which is also inside a synchronized block, should first synchronize on
* the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
- * @param device the modified <code>Client</code>.
+ * @param client the modified <code>Client</code>.
* @param changeMask the mask indicating what changed in the <code>Client</code>
* @see #getLock()
*/
@@ -936,16 +948,18 @@
return false;
}
+ if (sAdbServerPort == 0) {
+ Log.w(ADB, "ADB server port for starting AndroidDebugBridge is not set."); //$NON-NLS-1$
+ return false;
+ }
+
Process proc;
int status = -1;
+ String[] command = getAdbLaunchCommand("start-server");
+ String commandString = Joiner.on(',').join(command);
try {
- String[] command = new String[2];
- command[0] = mAdbOsLocation;
- command[1] = "start-server"; //$NON-NLS-1$
- Log.d(DDMS,
- String.format("Launching '%1$s %2$s' to ensure ADB is running.", //$NON-NLS-1$
- mAdbOsLocation, command[1]));
+ Log.d(DDMS, String.format("Launching '%1$s' to ensure ADB is running.", commandString));
ProcessBuilder processBuilder = new ProcessBuilder(command);
if (DdmPreferences.getUseAdbHost()) {
String adbHostValue = DdmPreferences.getAdbHostValue();
@@ -959,46 +973,58 @@
ArrayList<String> errorOutput = new ArrayList<String>();
ArrayList<String> stdOutput = new ArrayList<String>();
- status = grabProcessOutput(proc, errorOutput, stdOutput,
- false /* waitForReaders */);
-
+ status = grabProcessOutput(proc, errorOutput, stdOutput, false /* waitForReaders */);
} catch (IOException ioe) {
- Log.d(DDMS, "Unable to run 'adb': " + ioe.getMessage()); //$NON-NLS-1$
+ Log.e(DDMS, "Unable to run 'adb': " + ioe.getMessage()); //$NON-NLS-1$
// we'll return false;
} catch (InterruptedException ie) {
- Log.d(DDMS, "Unable to run 'adb': " + ie.getMessage()); //$NON-NLS-1$
+ Log.e(DDMS, "Unable to run 'adb': " + ie.getMessage()); //$NON-NLS-1$
// we'll return false;
}
if (status != 0) {
- Log.w(DDMS,
- "'adb start-server' failed -- run manually if necessary"); //$NON-NLS-1$
+ Log.e(DDMS,
+ String.format("'%1$s' failed -- run manually if necessary", commandString)); //$NON-NLS-1$
return false;
+ } else {
+ Log.d(DDMS, String.format("'%1$s' succeeded", commandString)); //$NON-NLS-1$
+ return true;
}
+ }
- Log.d(DDMS, "'adb start-server' succeeded"); //$NON-NLS-1$
-
- return true;
+ private String[] getAdbLaunchCommand(String option) {
+ List<String> command = new ArrayList<String>(4);
+ command.add(mAdbOsLocation);
+ if (sAdbServerPort != DEFAULT_ADB_PORT) {
+ command.add("-P"); //$NON-NLS-1$
+ command.add(Integer.toString(sAdbServerPort));
+ }
+ command.add(option);
+ return command.toArray(new String[command.size()]);
}
/**
* Stops the adb host side server.
+ *
* @return true if success
*/
private synchronized boolean stopAdb() {
if (mAdbOsLocation == null) {
Log.e(ADB,
- "Cannot stop adb when AndroidDebugBridge is created without the location of adb."); //$NON-NLS-1$
+ "Cannot stop adb when AndroidDebugBridge is created without the location of adb.");
+ return false;
+ }
+
+ if (sAdbServerPort == 0) {
+ Log.e(ADB, "ADB server port for restarting AndroidDebugBridge is not set");
return false;
}
Process proc;
int status = -1;
+ String[] command = getAdbLaunchCommand("kill-server"); //$NON-NLS-1$
try {
- String[] command = new String[2];
- command[0] = mAdbOsLocation;
- command[1] = "kill-server"; //$NON-NLS-1$
proc = Runtime.getRuntime().exec(command);
status = proc.waitFor();
}
@@ -1009,14 +1035,14 @@
// we'll return false;
}
+ String commandString = Joiner.on(',').join(command);
if (status != 0) {
- Log.w(DDMS,
- "'adb kill-server' failed -- run manually if necessary"); //$NON-NLS-1$
+ Log.w(DDMS, String.format("'%1$s' failed -- run manually if necessary", commandString));
return false;
+ } else {
+ Log.d(DDMS, String.format("'%1$s' succeeded", commandString));
+ return true;
}
-
- Log.d(DDMS, "'adb kill-server' succeeded"); //$NON-NLS-1$
- return true;
}
/**
@@ -1117,51 +1143,46 @@
*/
private static void initAdbSocketAddr() {
try {
- int adb_port = determineAndValidateAdbPort();
- sHostAddr = InetAddress.getByName(ADB_HOST);
- sSocketAddr = new InetSocketAddress(sHostAddr, adb_port);
+ sAdbServerPort = getAdbServerPort();
+ sHostAddr = InetAddress.getByName(DEFAULT_ADB_HOST);
+ sSocketAddr = new InetSocketAddress(sHostAddr, sAdbServerPort);
} catch (UnknownHostException e) {
// localhost should always be known.
}
}
/**
- * Determines port where ADB is expected by looking at an env variable.
- * <p/>
- * The value for the environment variable ANDROID_ADB_SERVER_PORT is validated,
- * IllegalArgumentException is thrown on illegal values.
- * <p/>
+ * Returns the port where adb server should be launched. This looks at:
+ * <ol>
+ * <li>The system property ANDROID_ADB_SERVER_PORT</li>
+ * <li>The environment variable ANDROID_ADB_SERVER_PORT</li>
+ * <li>Defaults to {@link #DEFAULT_ADB_PORT} if neither the system property nor the env var
+ * are set.</li>
+ * </ol>
+ *
* @return The port number where the host's adb should be expected or started.
- * @throws IllegalArgumentException if ANDROID_ADB_SERVER_PORT has a non-numeric value.
*/
- private static int determineAndValidateAdbPort() {
- String adb_env_var;
- int result = ADB_PORT;
+ private static int getAdbServerPort() {
+ // check system property
+ Integer prop = Integer.getInteger(SERVER_PORT_ENV_VAR);
+ if (prop != null) {
+ try {
+ return validateAdbServerPort(prop.toString());
+ } catch (IllegalArgumentException e) {
+ String msg = String.format(
+ "Invalid value (%1$s) for ANDROID_ADB_SERVER_PORT system property.",
+ prop);
+ Log.w(DDMS, msg);
+ }
+ }
+
+ // when system property is not set or is invalid, parse environment property
try {
- adb_env_var = System.getenv(SERVER_PORT_ENV_VAR);
-
- if (adb_env_var != null) {
- adb_env_var = adb_env_var.trim();
+ String env = System.getenv(SERVER_PORT_ENV_VAR);
+ if (env != null) {
+ return validateAdbServerPort(env);
}
-
- if (adb_env_var != null && !adb_env_var.isEmpty()) {
- // C tools (adb, emulator) accept hex and octal port numbers, so need to accept
- // them too.
- result = Integer.decode(adb_env_var);
-
- if (result <= 0) {
- String errMsg = "env var " + SERVER_PORT_ENV_VAR //$NON-NLS-1$
- + ": must be >=0, got " //$NON-NLS-1$
- + System.getenv(SERVER_PORT_ENV_VAR);
- throw new IllegalArgumentException(errMsg);
- }
- }
- } catch (NumberFormatException nfEx) {
- String errMsg = "env var " + SERVER_PORT_ENV_VAR //$NON-NLS-1$
- + ": illegal value '" //$NON-NLS-1$
- + System.getenv(SERVER_PORT_ENV_VAR) + "'"; //$NON-NLS-1$
- throw new IllegalArgumentException(errMsg);
- } catch (SecurityException secEx) {
+ } catch (SecurityException ex) {
// A security manager has been installed that doesn't allow access to env vars.
// So an environment variable might have been set, but we can't tell.
// Let's log a warning and continue with ADB's default port.
@@ -1172,10 +1193,38 @@
// thing seems to be to continue using the default port, as forking is likely to
// fail later on in the scenario of the security manager.
Log.w(DDMS,
- "No access to env variables allowed by current security manager. " //$NON-NLS-1$
- + "If you've set ANDROID_ADB_SERVER_PORT: it's being ignored."); //$NON-NLS-1$
+ "No access to env variables allowed by current security manager. "
+ + "If you've set ANDROID_ADB_SERVER_PORT: it's being ignored.");
+ } catch (IllegalArgumentException e) {
+ String msg = String.format(
+ "Invalid value (%1$s) for ANDROID_ADB_SERVER_PORT environment variable (%2$s).",
+ prop, e.getMessage());
+ Log.w(DDMS, msg);
}
- return result;
+
+ // use default port if neither are set
+ return DEFAULT_ADB_PORT;
+ }
+
+ /**
+ * Returns the integer port value if it is a valid value for adb server port
+ * @param adbServerPort adb server port to validate
+ * @return {@code adbServerPort} as a parsed integer
+ * @throws IllegalArgumentException when {@code adbServerPort} is not bigger than 0 or it is
+ * not a number at all
+ */
+ private static int validateAdbServerPort(@NonNull String adbServerPort)
+ throws IllegalArgumentException {
+ try {
+ // C tools (adb, emulator) accept hex and octal port numbers, so need to accept them too
+ int port = Integer.decode(adbServerPort);
+ if (port <= 0 || port >= 65535) {
+ throw new IllegalArgumentException("Should be > 0 and < 65535");
+ }
+ return port;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Not a valid port number");
+ }
}
}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/ByteBufferUtil.java b/ddmlib/src/main/java/com/android/ddmlib/ByteBufferUtil.java
new file mode 100644
index 0000000..d419dfe
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/ByteBufferUtil.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotations.NonNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+public class ByteBufferUtil {
+
+ @NonNull
+ public static ByteBuffer mapFile(@NonNull File f, long offset, @NonNull ByteOrder byteOrder) throws IOException {
+ FileInputStream dataFile = new FileInputStream(f);
+ try {
+ FileChannel fc = dataFile.getChannel();
+ MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, f.length() - offset);
+ buffer.order(byteOrder);
+ return buffer;
+ } finally {
+ dataFile.close(); // this *also* closes the associated channel, fc
+ }
+ }
+
+ @NonNull
+ public static String getString(@NonNull ByteBuffer buf, int len) {
+ char[] data = new char[len];
+ for (int i = 0; i < len; i++)
+ data[i] = buf.getChar();
+ return new String(data);
+ }
+
+ public static void putString(@NonNull ByteBuffer buf, @NonNull String str) {
+ int len = str.length();
+ for (int i = 0; i < len; i++)
+ buf.putChar(str.charAt(i));
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java b/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java
index 2cc6494..36e24a3 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java
@@ -77,7 +77,7 @@
errorCode = data.getInt();
msgLen = data.getInt();
- msg = getString(data, msgLen);
+ msg = ByteBufferUtil.getString(data, msgLen);
Log.w("ddms", "WARNING: failure code=" + errorCode + " msg=" + msg);
} else {
Log.w("ddms", "WARNING: received unknown chunk " + name(type)
@@ -87,30 +87,14 @@
Log.w("ddms", " client " + client + ", handler " + this);
}
+ /**
+ * Utility function to copy a String out of a ByteBuffer.
+ */
+ public static String getString(ByteBuffer buf, int len) {
+ return ByteBufferUtil.getString(buf, len);
+ }
- /**
- * Utility function to copy a String out of a ByteBuffer.
- *
- * This is here because multiple chunk handlers can make use of it,
- * and there's nowhere better to put it.
- */
- public static String getString(ByteBuffer buf, int len) {
- char[] data = new char[len];
- for (int i = 0; i < len; i++)
- data[i] = buf.getChar();
- return new String(data);
- }
-
- /**
- * Utility function to copy a String into a ByteBuffer.
- */
- static void putString(ByteBuffer buf, String str) {
- int len = str.length();
- for (int i = 0; i < len; i++)
- buf.putChar(str.charAt(i));
- }
-
- /**
+ /**
* Convert a 4-character string to a 32-bit type.
*/
static int type(String typeName) {
diff --git a/ddmlib/src/main/java/com/android/ddmlib/ClientData.java b/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
index 76f5f97..07aa161 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
@@ -16,6 +16,8 @@
package com.android.ddmlib;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ddmlib.HeapSegment.HeapSegmentElement;
import java.nio.BufferUnderflowException;
@@ -163,6 +165,7 @@
private static IHprofDumpHandler sHprofDumpHandler;
private static IMethodProfilingHandler sMethodProfilingHandler;
+ private static IAllocationTrackingHandler sAllocationTrackingHandler;
// is this a DDM-aware client?
private boolean mIsDdmAware;
@@ -182,6 +185,12 @@
// client's user id is valid
private boolean mValidUserId;
+ // client's ABI
+ private String mAbi;
+
+ // jvm flag: currently only indicates whether checkJni is enabled
+ private String mJvmFlags;
+
// how interested are we in a debugger?
private DebuggerStatus mDebuggerInterest;
@@ -376,6 +385,19 @@
void onEndFailure(Client client, String message);
}
+ /*
+ * Handlers able to act on allocation tracking info
+ */
+ public interface IAllocationTrackingHandler {
+ /**
+ * Called when an allocation tracking was successful.
+ * @param data the data containing the encoded allocations.
+ * See {@link AllocationsParser#parse(java.nio.ByteBuffer)} for parsing this data.
+ * @param client the client for which allocations were tracked.
+ */
+ void onSuccess(@NonNull byte[] data, @NonNull Client client);
+ }
+
/**
* Sets the handler to receive notifications when an HPROF dump succeeded or failed.
*/
@@ -398,6 +420,15 @@
return sMethodProfilingHandler;
}
+ public static void setAllocationTrackingHandler(@NonNull IAllocationTrackingHandler handler) {
+ sAllocationTrackingHandler = handler;
+ }
+
+ @Nullable
+ static IAllocationTrackingHandler getAllocationTrackingHandler() {
+ return sAllocationTrackingHandler;
+ }
+
/**
* Generic constructor.
*/
@@ -472,6 +503,17 @@
return mValidUserId;
}
+ /** Returns the abi flavor (32-bit or 64-bit) of the application, null if unknown or not set. */
+ @Nullable
+ public String getAbi() {
+ return mAbi;
+ }
+
+ /** Returns the VM flags in use, or null if unknown. */
+ public String getJvmFlags() {
+ return mJvmFlags;
+ }
+
/**
* Sets client description.
*
@@ -499,6 +541,14 @@
mValidUserId = true;
}
+ void setAbi(String abi) {
+ mAbi = abi;
+ }
+
+ void setJvmFlags(String jvmFlags) {
+ mJvmFlags = jvmFlags;
+ }
+
/**
* Returns the debugger connection status.
*/
@@ -671,8 +721,9 @@
* Returns the list of tracked allocations.
* @see Client#requestAllocationDetails()
*/
+ @Nullable
public synchronized AllocationInfo[] getAllocations() {
- return mAllocations;
+ return mAllocations;
}
void addFeature(String feature) {
diff --git a/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java b/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java
index efe092c..903eb2d 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java
@@ -16,7 +16,8 @@
package com.android.ddmlib;
-import java.io.UnsupportedEncodingException;
+import com.google.common.base.Charsets;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -56,12 +57,7 @@
public void addOutput(byte[] data, int offset, int length) {
if (!isCancelled()) {
String s;
- try {
- s = new String(data, offset, length, "UTF-8"); //$NON-NLS-1$
- } catch (UnsupportedEncodingException e) {
- // normal encoding didn't work, try the default one
- s = new String(data, offset,length);
- }
+ s = new String(data, offset, length, Charsets.UTF_8);
mOutputBuffer.append(s);
}
}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/Device.java b/ddmlib/src/main/java/com/android/ddmlib/Device.java
index 8e9bece..02cb10a 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/Device.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/Device.java
@@ -21,15 +21,19 @@
import com.android.annotations.VisibleForTesting;
import com.android.annotations.concurrency.GuardedBy;
import com.android.ddmlib.log.LogReceiver;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Sets;
import java.io.File;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -49,7 +53,7 @@
static final String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
/** Serial number of the device */
- private String mSerialNumber = null;
+ private final String mSerialNumber;
/** Name of the AVD */
private String mAvdName = null;
@@ -89,6 +93,9 @@
/** Flag indicating whether the device has the screen recorder binary. */
private Boolean mHasScreenRecorder;
+ /** Cached list of hardware characteristics */
+ private Set<String> mHardwareCharacteristics;
+
private int mApiLevel;
private String mName;
@@ -232,6 +239,7 @@
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getSerialNumber()
*/
+ @NonNull
@Override
public String getSerialNumber() {
return mSerialNumber;
@@ -424,6 +432,23 @@
}
}
+ // The full list of features can be obtained from /etc/permissions/features*
+ // However, since we only support the "watch" feature, we can determine that by simply
+ // reading the build characteristics property.
+ @Override
+ public boolean supportsFeature(@NonNull HardwareFeature feature) {
+ if (mHardwareCharacteristics == null) {
+ try {
+ String characteristics = getPropertyCacheOrSync(PROP_BUILD_CHARACTERISTICS);
+ mHardwareCharacteristics = Sets.newHashSet(Splitter.on(',').split(characteristics));
+ } catch (Exception e) {
+ mHardwareCharacteristics = Collections.emptySet();
+ }
+ }
+
+ return mHardwareCharacteristics.contains(feature.getCharacteristic());
+ }
+
private int getApiLevel() {
if (mApiLevel > 0) {
return mApiLevel;
diff --git a/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java b/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java
index b177615..5ccf7ef 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java
@@ -293,12 +293,7 @@
}
}
- /**
- * Processes an incoming device message from the socket
- * @param socket
- * @param length
- * @throws IOException
- */
+ /** Processes an incoming device message from the socket */
private void processIncomingDeviceData(int length) throws IOException {
ArrayList<Device> list = new ArrayList<Device>();
@@ -460,6 +455,7 @@
EmulatorConsole console = EmulatorConsole.getConsole(device);
if (console != null) {
device.setAvdName(console.getAvdName());
+ console.close();
}
}
} catch (TimeoutException e) {
diff --git a/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java b/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java
index 4a87625..77a00d0 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java
@@ -16,11 +16,14 @@
package com.android.ddmlib;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.GuardedBy;
+
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.security.InvalidParameterException;
@@ -169,9 +172,12 @@
private static final Pattern sMinLatencyRegexp = Pattern.compile(
"\\s+minimum\\s+latency:\\s+(\\d+)\\s+ms", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+ @GuardedBy(value = "sEmulators")
private static final HashMap<Integer, EmulatorConsole> sEmulators =
new HashMap<Integer, EmulatorConsole>();
+ private static final String LOG_TAG = "EmulatorConsole";
+
/** Gsm Status class */
public static class GsmStatus {
/** Voice status. */
@@ -188,7 +194,7 @@
public int latency = -1;
}
- private int mPort;
+ private int mPort = -1;
private SocketChannel mSocketChannel;
@@ -197,36 +203,27 @@
/**
* Returns an {@link EmulatorConsole} object for the given {@link Device}. This can
* be an already existing console, or a new one if it hadn't been created yet.
+ * Note: emulator consoles don't automatically close when an emulator exists. It is the
+ * responsibility of higher level code to explicitly call {@link #close()} when the emulator
+ * corresponding to a open console is killed.
* @param d The device that the console links to.
* @return an <code>EmulatorConsole</code> object or <code>null</code> if the connection failed.
*/
- public static synchronized EmulatorConsole getConsole(IDevice d) {
+ @Nullable
+ public static EmulatorConsole getConsole(IDevice d) {
// we need to make sure that the device is an emulator
// get the port number. This is the console port.
Integer port = getEmulatorPort(d.getSerialNumber());
if (port == null) {
+ Log.w(LOG_TAG, "Failed to find emulator port from serial: " + d.getSerialNumber());
return null;
}
- EmulatorConsole console = sEmulators.get(port);
+ EmulatorConsole console = retrieveConsole(port);
- if (console != null) {
- // if the console exist, we ping the emulator to check the connection.
- if (!console.ping()) {
- RemoveConsole(console.mPort);
- console = null;
- }
- }
-
- if (console == null) {
- // no console object exists for this port so we create one, and start
- // the connection.
- console = new EmulatorConsole(port);
- if (console.start()) {
- sEmulators.put(port, console);
- } else {
- console = null;
- }
+ if (!console.checkConnection()) {
+ removeConsole(console.mPort);
+ console = null;
}
return console;
@@ -258,42 +255,59 @@
}
/**
+ * Retrieve a console object for this port, creating if necessary.
+ */
+ @NonNull
+ private static EmulatorConsole retrieveConsole(int port) {
+ synchronized (sEmulators) {
+ EmulatorConsole console = sEmulators.get(port);
+ if (console == null) {
+ Log.v(LOG_TAG, "Creating emulator console for " + Integer.toString(port));
+ console = new EmulatorConsole(port);
+ sEmulators.put(port, console);
+ }
+ return console;
+ }
+ }
+
+ /**
* Removes the console object associated with a port from the map.
* @param port The port of the console to remove.
*/
- private static synchronized void RemoveConsole(int port) {
- sEmulators.remove(port);
+ private static void removeConsole(int port) {
+ synchronized (sEmulators) {
+ Log.v(LOG_TAG, "Removing emulator console for " + Integer.toString(port));
+ sEmulators.remove(port);
+ }
}
private EmulatorConsole(int port) {
- super();
mPort = port;
}
/**
- * Starts the connection of the console.
+ * Determine if connection to emulator console is functioning. Starts the connection if
+ * necessary
* @return true if success.
*/
- private boolean start() {
-
- InetSocketAddress socketAddr;
- try {
- InetAddress hostAddr = InetAddress.getByName(HOST);
- socketAddr = new InetSocketAddress(hostAddr, mPort);
- } catch (UnknownHostException e) {
- return false;
+ private synchronized boolean checkConnection() {
+ if (mSocketChannel == null) {
+ // connection not established, try to connect
+ InetSocketAddress socketAddr;
+ try {
+ InetAddress hostAddr = InetAddress.getByName(HOST);
+ socketAddr = new InetSocketAddress(hostAddr, mPort);
+ mSocketChannel = SocketChannel.open(socketAddr);
+ mSocketChannel.configureBlocking(false);
+ // read initial output from console
+ readLines();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Failed to start Emulator console for " + Integer.toString(mPort));
+ return false;
+ }
}
- try {
- mSocketChannel = SocketChannel.open(socketAddr);
- } catch (IOException e1) {
- return false;
- }
-
- // read some stuff from it
- readLines();
-
- return true;
+ return ping();
}
/**
@@ -315,7 +329,27 @@
*/
public synchronized void kill() {
if (sendCommand(COMMAND_KILL)) {
- RemoveConsole(mPort);
+ close();
+ }
+ }
+
+ /**
+ * Closes this instance of the emulator console.
+ */
+ public synchronized void close() {
+ if (mPort == -1) {
+ return;
+ }
+
+ removeConsole(mPort);
+ try {
+ if (mSocketChannel != null) {
+ mSocketChannel.close();
+ }
+ mSocketChannel = null;
+ mPort = -1;
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Failed to close EmulatorConsole channel");
}
}
@@ -331,6 +365,10 @@
if (m.matches()) {
return m.group(1);
}
+ Log.w(LOG_TAG, "avd name result did not match expected");
+ for (int i=0; i < result.length; i++) {
+ Log.d(LOG_TAG, result[i]);
+ }
}
}
@@ -553,6 +591,8 @@
try {
bCommand = command.getBytes(DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
+ Log.w(LOG_TAG, "wrong encoding when sending " + command + " to " +
+ Integer.toString(mPort));
// wrong encoding...
return result;
}
@@ -562,11 +602,13 @@
result = true;
} catch (Exception e) {
+ Log.d(LOG_TAG, "Exception sending command " + command + " to " +
+ Integer.toString(mPort));
return false;
} finally {
if (!result) {
// FIXME connection failed somehow, we need to disconnect the console.
- RemoveConsole(mPort);
+ removeConsole(mPort);
}
}
@@ -643,6 +685,7 @@
String msg = new String(mBuffer, 0, buf.position(), DEFAULT_ENCODING);
return msg.split("\r\n"); //$NON-NLS-1$
} catch (IOException e) {
+ Log.d(LOG_TAG, "Exception reading lines for " + Integer.toString(mPort));
return null;
}
}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleAppName.java b/ddmlib/src/main/java/com/android/ddmlib/HandleAppName.java
index da4ade3..e6b151e 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/HandleAppName.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleAppName.java
@@ -76,7 +76,7 @@
String appName;
appNameLen = data.getInt();
- appName = getString(data, appNameLen);
+ appName = ByteBufferUtil.getString(data, appNameLen);
// Newer devices send user id in the APNM packet.
int userId = -1;
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java b/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java
index 1761b79..9a755c3 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java
@@ -22,7 +22,6 @@
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
/**
* Handle heap status updates.
@@ -242,7 +241,7 @@
ByteBuffer buf = getChunkDataBuf(rawBuf);
buf.putInt(fileName.length());
- putString(buf, fileName);
+ ByteBufferUtil.putString(buf, fileName);
finishChunkPacket(packet, CHUNK_HPDU, buf.position());
Log.d("ddm-heap", "Sending " + name(CHUNK_HPDU) + " '" + fileName +"'");
@@ -373,222 +372,28 @@
enabled = (data.get() != 0);
Log.d("ddm-heap", "REAQ says: enabled=" + enabled);
- client.getClientData().setAllocationStatus(enabled ?
- AllocationTrackingStatus.ON : AllocationTrackingStatus.OFF);
+ client.getClientData().setAllocationStatus(enabled ? AllocationTrackingStatus.ON : AllocationTrackingStatus.OFF);
client.update(Client.CHANGE_HEAP_ALLOCATION_STATUS);
}
- /**
- * Converts a VM class descriptor string ("Landroid/os/Debug;") to
- * a dot-notation class name ("android.os.Debug").
- */
- private String descriptorToDot(String str) {
- // count the number of arrays.
- int array = 0;
- while (str.startsWith("[")) {
- str = str.substring(1);
- array++;
- }
-
- int len = str.length();
-
- /* strip off leading 'L' and trailing ';' if appropriate */
- if (len >= 2 && str.charAt(0) == 'L' && str.charAt(len - 1) == ';') {
- str = str.substring(1, len-1);
- str = str.replace('/', '.');
- } else {
- // convert the basic types
- if ("C".equals(str)) {
- str = "char";
- } else if ("B".equals(str)) {
- str = "byte";
- } else if ("Z".equals(str)) {
- str = "boolean";
- } else if ("S".equals(str)) {
- str = "short";
- } else if ("I".equals(str)) {
- str = "int";
- } else if ("J".equals(str)) {
- str = "long";
- } else if ("F".equals(str)) {
- str = "float";
- } else if ("D".equals(str)) {
- str = "double";
- }
- }
-
- // now add the array part
- for (int a = 0 ; a < array; a++) {
- str = str + "[]";
- }
-
- return str;
- }
-
- /**
- * Reads a string table out of "data".
- *
- * This is just a serial collection of strings, each of which is a
- * four-byte length followed by UTF-16 data.
- */
- private void readStringTable(ByteBuffer data, String[] strings) {
- int count = strings.length;
- int i;
-
- for (i = 0; i < count; i++) {
- int nameLen = data.getInt();
- String descriptor = getString(data, nameLen);
- strings[i] = descriptorToDot(descriptor);
- }
- }
-
/*
* Handle a REcent ALlocation response.
- *
- * Message header (all values big-endian):
- * (1b) message header len (to allow future expansion); includes itself
- * (1b) entry header len
- * (1b) stack frame len
- * (2b) number of entries
- * (4b) offset to string table from start of message
- * (2b) number of class name strings
- * (2b) number of method name strings
- * (2b) number of source file name strings
- * For each entry:
- * (4b) total allocation size
- * (2b) threadId
- * (2b) allocated object's class name index
- * (1b) stack depth
- * For each stack frame:
- * (2b) method's class name
- * (2b) method name
- * (2b) method source file
- * (2b) line number, clipped to 32767; -2 if native; -1 if no source
- * (xb) class name strings
- * (xb) method name strings
- * (xb) source file strings
- *
- * As with other DDM traffic, strings are sent as a 4-byte length
- * followed by UTF-16 data.
*/
private void handleREAL(Client client, ByteBuffer data) {
Log.e("ddm-heap", "*** Received " + name(CHUNK_REAL));
- int messageHdrLen, entryHdrLen, stackFrameLen;
- int numEntries, offsetToStrings;
- int numClassNames, numMethodNames, numFileNames;
+ ClientData.IAllocationTrackingHandler handler = ClientData.getAllocationTrackingHandler();
- /*
- * Read the header.
- */
- messageHdrLen = (data.get() & 0xff);
- entryHdrLen = (data.get() & 0xff);
- stackFrameLen = (data.get() & 0xff);
- numEntries = (data.getShort() & 0xffff);
- offsetToStrings = data.getInt();
- numClassNames = (data.getShort() & 0xffff);
- numMethodNames = (data.getShort() & 0xffff);
- numFileNames = (data.getShort() & 0xffff);
+ if (handler != null) {
+ byte[] stuff = new byte[data.capacity()];
+ data.get(stuff, 0, stuff.length);
-
- /*
- * Skip forward to the strings and read them.
- */
- data.position(offsetToStrings);
-
- String[] classNames = new String[numClassNames];
- String[] methodNames = new String[numMethodNames];
- String[] fileNames = new String[numFileNames];
-
- readStringTable(data, classNames);
- readStringTable(data, methodNames);
- //System.out.println("METHODS: "
- // + java.util.Arrays.deepToString(methodNames));
- readStringTable(data, fileNames);
-
- /*
- * Skip back to a point just past the header and start reading
- * entries.
- */
- data.position(messageHdrLen);
-
- ArrayList<AllocationInfo> list = new ArrayList<AllocationInfo>(numEntries);
- int allocNumber = numEntries; // order value for the entry. This is sent in reverse order.
- for (int i = 0; i < numEntries; i++) {
- int totalSize;
- int threadId, classNameIndex, stackDepth;
-
- totalSize = data.getInt();
- threadId = (data.getShort() & 0xffff);
- classNameIndex = (data.getShort() & 0xffff);
- stackDepth = (data.get() & 0xff);
- /* we've consumed 9 bytes; gobble up any extra */
- for (int skip = 9; skip < entryHdrLen; skip++)
- data.get();
-
- StackTraceElement[] steArray = new StackTraceElement[stackDepth];
-
- /*
- * Pull out the stack trace.
- */
- for (int sti = 0; sti < stackDepth; sti++) {
- int methodClassNameIndex, methodNameIndex;
- int methodSourceFileIndex;
- short lineNumber;
- String methodClassName, methodName, methodSourceFile;
-
- methodClassNameIndex = (data.getShort() & 0xffff);
- methodNameIndex = (data.getShort() & 0xffff);
- methodSourceFileIndex = (data.getShort() & 0xffff);
- lineNumber = data.getShort();
-
- methodClassName = classNames[methodClassNameIndex];
- methodName = methodNames[methodNameIndex];
- methodSourceFile = fileNames[methodSourceFileIndex];
-
- steArray[sti] = new StackTraceElement(methodClassName,
- methodName, methodSourceFile, lineNumber);
-
- /* we've consumed 8 bytes; gobble up any extra */
- for (int skip = 9; skip < stackFrameLen; skip++)
- data.get();
- }
-
- list.add(new AllocationInfo(allocNumber--, classNames[classNameIndex],
- totalSize, (short) threadId, steArray));
- }
-
- client.getClientData().setAllocations(list.toArray(new AllocationInfo[numEntries]));
- client.update(Client.CHANGE_HEAP_ALLOCATIONS);
- }
-
- /*
- * For debugging: dump the contents of an AllocRecord array.
- *
- * The array starts with the oldest known allocation and ends with
- * the most recent allocation.
- */
- @SuppressWarnings("unused")
- private static void dumpRecords(AllocationInfo[] records) {
- System.out.println("Found " + records.length + " records:");
-
- for (AllocationInfo rec: records) {
- System.out.println("tid=" + rec.getThreadId() + " "
- + rec.getAllocatedClass() + " (" + rec.getSize() + " bytes)");
-
- for (StackTraceElement ste: rec.getStackTrace()) {
- if (ste.isNativeMethod()) {
- System.out.println(" " + ste.getClassName()
- + "." + ste.getMethodName()
- + " (Native method)");
- } else {
- System.out.println(" " + ste.getClassName()
- + "." + ste.getMethodName()
- + " (" + ste.getFileName()
- + ":" + ste.getLineNumber() + ")");
- }
- }
+ Log.d("ddm-prof", "got allocations file, size: " + stuff.length + " bytes");
+ handler.onSuccess(stuff, client);
+ } else {
+ // Allocation tracking did not start from Android Studio's device panel
+ client.getClientData().setAllocations(AllocationsParser.parse(data));
+ client.update(Client.CHANGE_HEAP_ALLOCATIONS);
}
}
-
}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java b/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java
index b5c2968..6bf9150 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java
@@ -98,8 +98,8 @@
vmIdentLen = data.getInt();
appNameLen = data.getInt();
- vmIdent = getString(data, vmIdentLen);
- appName = getString(data, appNameLen);
+ vmIdent = ByteBufferUtil.getString(data, vmIdentLen);
+ appName = ByteBufferUtil.getString(data, appNameLen);
// Newer devices send user id in the APNM packet.
int userId = -1;
@@ -118,24 +118,55 @@
}
}
+ // check if the VM has reported information about the ABI
+ boolean validAbi = false;
+ String abi = null;
+ if (data.hasRemaining()) {
+ try {
+ int abiLength = data.getInt();
+ abi = ByteBufferUtil.getString(data, abiLength);
+ validAbi = true;
+ } catch (BufferUnderflowException e) {
+ Log.e("ddm-hello", "Insufficient data in HELO chunk to retrieve ABI.");
+ }
+ }
+
+ boolean hasJvmFlags = false;
+ String jvmFlags = null;
+ if (data.hasRemaining()) {
+ try {
+ int jvmFlagsLength = data.getInt();
+ jvmFlags = ByteBufferUtil.getString(data, jvmFlagsLength);
+ hasJvmFlags = true;
+ } catch (BufferUnderflowException e) {
+ Log.e("ddm-hello", "Insufficient data in HELO chunk to retrieve JVM flags");
+ }
+ }
+
Log.d("ddm-hello", "HELO: v=" + version + ", pid=" + pid
+ ", vm='" + vmIdent + "', app='" + appName + "'");
ClientData cd = client.getClientData();
- synchronized (cd) {
- if (cd.getPid() == pid) {
- cd.setVmIdentifier(vmIdent);
- cd.setClientDescription(appName);
- cd.isDdmAware(true);
+ if (cd.getPid() == pid) {
+ cd.setVmIdentifier(vmIdent);
+ cd.setClientDescription(appName);
+ cd.isDdmAware(true);
- if (validUserId) {
- cd.setUserId(userId);
- }
- } else {
- Log.e("ddm-hello", "Received pid (" + pid + ") does not match client pid ("
- + cd.getPid() + ")");
+ if (validUserId) {
+ cd.setUserId(userId);
}
+
+ if (validAbi) {
+ cd.setAbi(abi);
+ }
+
+ if (hasJvmFlags) {
+ cd.setJvmFlags(jvmFlags);
+ }
+ } else {
+ Log.e("ddm-hello", "Received pid (" + pid + ") does not match client pid ("
+ + cd.getPid() + ")");
}
client = checkDebuggerPortForAppName(client, appName);
@@ -174,7 +205,7 @@
featureCount = data.getInt();
for (i = 0; i < featureCount; i++) {
int len = data.getInt();
- String feature = getString(data, len);
+ String feature = ByteBufferUtil.getString(data, len);
client.getClientData().addFeature(feature);
Log.d("ddm-hello", "Feature: " + feature);
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java b/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
index cdefd2c..ade7f27 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
@@ -106,7 +106,7 @@
buf.putInt(bufferSize);
buf.putInt(flags);
buf.putInt(fileName.length());
- putString(buf, fileName);
+ ByteBufferUtil.putString(buf, fileName);
finishChunkPacket(packet, CHUNK_MPRS, buf.position());
Log.d("ddm-prof", "Sending " + name(CHUNK_MPRS) + " '" + fileName
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java b/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java
index 95b9a8e..f3874ce 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java
@@ -102,7 +102,7 @@
threadId = data.getInt();
nameLen = data.getInt();
- name = getString(data, nameLen);
+ name = ByteBufferUtil.getString(data, nameLen);
Log.v("ddm-thread", "THCR: " + threadId + " '" + name + "'");
@@ -198,7 +198,7 @@
threadId = data.getInt();
nameLen = data.getInt();
- name = getString(data, nameLen);
+ name = ByteBufferUtil.getString(data, nameLen);
Log.v("ddm-thread", "THNM: " + threadId + " '" + name + "'");
@@ -234,14 +234,14 @@
int len, lineNumber;
len = data.getInt();
- className = getString(data, len);
+ className = ByteBufferUtil.getString(data, len);
len = data.getInt();
- methodName = getString(data, len);
+ methodName = ByteBufferUtil.getString(data, len);
len = data.getInt();
if (len == 0) {
fileName = null;
} else {
- fileName = getString(data, len);
+ fileName = ByteBufferUtil.getString(data, len);
}
lineNumber = data.getInt();
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java b/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java
index 1a279bd..83eba76 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java
@@ -147,7 +147,7 @@
chunkBuf.putInt(VURT_DUMP_HIERARCHY);
chunkBuf.putInt(viewRoot.length());
- putString(chunkBuf, viewRoot);
+ ByteBufferUtil.putString(chunkBuf, viewRoot);
chunkBuf.putInt(skipChildren ? 1 : 0);
chunkBuf.putInt(includeProperties ? 1 : 0);
@@ -165,7 +165,7 @@
chunkBuf.putInt(VURT_CAPTURE_LAYERS);
chunkBuf.putInt(viewRoot.length());
- putString(chunkBuf, viewRoot);
+ ByteBufferUtil.putString(chunkBuf, viewRoot);
finishChunkPacket(packet, CHUNK_VURT, chunkBuf.position());
client.sendAndConsume(packet, handler);
@@ -188,10 +188,10 @@
chunkBuf.putInt(op);
chunkBuf.putInt(viewRoot.length());
- putString(chunkBuf, viewRoot);
+ ByteBufferUtil.putString(chunkBuf, viewRoot);
chunkBuf.putInt(view.length());
- putString(chunkBuf, view);
+ ByteBufferUtil.putString(chunkBuf, view);
if (extra != null) {
chunkBuf.put(extra);
@@ -258,7 +258,7 @@
ByteBuffer b = ByteBuffer.wrap(extra);
b.putInt(method.length());
- putString(b, method);
+ ByteBufferUtil.putString(b, method);
if (args != null) {
b.putInt(args.length);
@@ -307,7 +307,7 @@
ByteBuffer b = ByteBuffer.wrap(extra);
b.putInt(parameter.length());
- putString(b, parameter);
+ ByteBufferUtil.putString(b, parameter);
b.putInt(value);
sendViewOpPacket(client, VUOP_SET_LAYOUT_PARAMETER, viewRoot, view, extra,
sViewOpNullChunkHandler);
diff --git a/ddmlib/src/main/java/com/android/ddmlib/IDevice.java b/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
index c8d72ae..51e25ee 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
@@ -17,6 +17,7 @@
package com.android.ddmlib;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ddmlib.log.LogReceiver;
import java.io.IOException;
@@ -34,6 +35,7 @@
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_BUILD_CHARACTERISTICS = "ro.build.characteristics";
public static final String PROP_DEBUGGABLE = "ro.debuggable";
@@ -46,12 +48,27 @@
/** Device change bit mask: build info change. */
public static final int CHANGE_BUILD_INFO = 0x0004;
- /** List of device level features. */
+ /** Device level software features. */
public enum Feature {
SCREEN_RECORD, // screen recorder available?
PROCSTATS, // procstats service (dumpsys procstats) available
};
+ /** Device level hardware features. */
+ public enum HardwareFeature {
+ WATCH("watch"); // supports feature watch
+
+ private final String mCharacteristic;
+
+ private HardwareFeature(String characteristic) {
+ mCharacteristic = characteristic;
+ }
+
+ public String getCharacteristic() {
+ return mCharacteristic;
+ }
+ }
+
/** @deprecated Use {@link #PROP_BUILD_API_LEVEL}. */
@Deprecated
public static final String PROP_BUILD_VERSION_NUMBER = PROP_BUILD_API_LEVEL;
@@ -67,7 +84,8 @@
BOOTLOADER("bootloader"), //$NON-NLS-1$
OFFLINE("offline"), //$NON-NLS-1$
ONLINE("device"), //$NON-NLS-1$
- RECOVERY("recovery"); //$NON-NLS-1$
+ RECOVERY("recovery"), //$NON-NLS-1$
+ UNAUTHORIZED("unauthorized"); //$NON-NLS-1$
private String mState;
@@ -81,6 +99,7 @@
* @param state the device state.
* @return a {@link DeviceState} object or <code>null</code> if the state is unknown.
*/
+ @Nullable
public static DeviceState getState(String state) {
for (DeviceState deviceState : values()) {
if (deviceState.mState.equals(state)) {
@@ -110,9 +129,8 @@
}
}
- /**
- * Returns the serial number of the device.
- */
+ /** Returns the serial number of the device. */
+ @NonNull
public String getSerialNumber();
/**
@@ -123,6 +141,7 @@
*
* @return the name of the AVD or <code>null</code> if there isn't any.
*/
+ @Nullable
public String getAvdName();
/**
@@ -185,9 +204,12 @@
public String getPropertyCacheOrSync(String name) throws TimeoutException,
AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException;
- /** Returns whether this device supports the given feature. */
+ /** Returns whether this device supports the given software feature. */
boolean supportsFeature(@NonNull Feature feature);
+ /** Returns whether this device supports the given hardware feature. */
+ boolean supportsFeature(@NonNull HardwareFeature feature);
+
/**
* Returns a mount point.
*
diff --git a/ddmlib/src/main/java/com/android/ddmlib/MultiLineReceiver.java b/ddmlib/src/main/java/com/android/ddmlib/MultiLineReceiver.java
index 52e0416..48afd1e 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/MultiLineReceiver.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/MultiLineReceiver.java
@@ -16,7 +16,8 @@
package com.android.ddmlib;
-import java.io.UnsupportedEncodingException;
+import com.google.common.base.Charsets;
+
import java.util.ArrayList;
/**
@@ -50,13 +51,7 @@
@Override
public final void addOutput(byte[] data, int offset, int length) {
if (!isCancelled()) {
- String s = null;
- try {
- s = new String(data, offset, length, "UTF-8"); //$NON-NLS-1$
- } catch (UnsupportedEncodingException e) {
- // normal encoding didn't work, try the default one
- s = new String(data, offset,length);
- }
+ String s = new String(data, offset, length, Charsets.UTF_8);
// ok we've got a string
// if we had an unfinished line we add it.
diff --git a/ddmlib/src/main/java/com/android/ddmlib/RawImage.java b/ddmlib/src/main/java/com/android/ddmlib/RawImage.java
index adb0cc9..b164044 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/RawImage.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/RawImage.java
@@ -171,28 +171,29 @@
*/
public int getARGB(int index) {
int value;
+ int r, g, b, a;
if (bpp == 16) {
value = data[index] & 0x00FF;
value |= (data[index+1] << 8) & 0x0FF00;
+ // RGB565 to RGB888
+ // Multiply by 255/31 to convert from 5 bits (31 max) to 8 bits (255)
+ r = ((value >>> 11) & 0x1f) * 255/31;
+ g = ((value >>> 5) & 0x3f) * 255/63;
+ b = ((value) & 0x1f) * 255/31;
+ a = 0xFF; // force alpha to opaque if there's no alpha value in the framebuffer.
} else if (bpp == 32) {
value = data[index] & 0x00FF;
value |= (data[index+1] & 0x00FF) << 8;
value |= (data[index+2] & 0x00FF) << 16;
value |= (data[index+3] & 0x00FF) << 24;
+ r = ((value >>> red_offset) & getMask(red_length)) << (8 - red_length);
+ g = ((value >>> green_offset) & getMask(green_length)) << (8 - green_length);
+ b = ((value >>> blue_offset) & getMask(blue_length)) << (8 - blue_length);
+ a = ((value >>> alpha_offset) & getMask(alpha_length)) << (8 - alpha_length);
} else {
throw new UnsupportedOperationException("RawImage.getARGB(int) only works in 16 and 32 bit mode.");
}
- int r = ((value >>> red_offset) & getMask(red_length)) << (8 - red_length);
- int g = ((value >>> green_offset) & getMask(green_length)) << (8 - green_length);
- int b = ((value >>> blue_offset) & getMask(blue_length)) << (8 - blue_length);
- int a;
- if (alpha_length == 0) {
- a = 0xFF; // force alpha to opaque if there's no alpha value in the framebuffer.
- } else {
- a = ((value >>> alpha_offset) & getMask(alpha_length)) << (8 - alpha_length);
- }
-
return a << 24 | r << 16 | g << 8 | b;
}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/log/EventLogParser.java b/ddmlib/src/main/java/com/android/ddmlib/log/EventLogParser.java
index 568c1be..c6fd882 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/log/EventLogParser.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/log/EventLogParser.java
@@ -23,13 +23,13 @@
import com.android.ddmlib.log.EventValueDescription.ValueType;
import com.android.ddmlib.log.LogReceiver.LogEntry;
import com.android.ddmlib.utils.ArrayHelper;
+import com.google.common.base.Charsets;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Map;
@@ -423,11 +423,8 @@
return -1;
// get the string
- try {
- String str = new String(eventData, offset, strLen, "UTF-8"); //$NON-NLS-1$
- list.add(str);
- } catch (UnsupportedEncodingException e) {
- }
+ String str = new String(eventData, offset, strLen, Charsets.UTF_8);
+ list.add(str);
offset += strLen;
break;
}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
index 19594fc..d495b7b 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
@@ -16,7 +16,7 @@
package com.android.ddmlib.testrunner;
-
+import com.android.annotations.NonNull;
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IShellEnabledDevice;
import com.android.ddmlib.Log;
@@ -62,6 +62,7 @@
private static final String COVERAGE_ARG_NAME = "coverage";
private static final String PACKAGE_ARG_NAME = "package";
private static final String SIZE_ARG_NAME = "size";
+ private String mRunOptions = "";
/**
* Creates a remote Android test runner.
@@ -208,8 +209,8 @@
public void run(Collection<ITestRunListener> listeners)
throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
IOException {
- final String runCaseCommandStr = String.format("am instrument -w -r %1$s %2$s",
- getArgsCommand(), getRunnerPath());
+ final String runCaseCommandStr = String.format("am instrument -w %1$s-r %2$s %3$s",
+ getRunOptions(), getArgsCommand(), getRunnerPath());
Log.i(LOG_TAG, String.format("Running %1$s on %2$s", runCaseCommandStr,
mRemoteDevice.getName()));
String runName = mRunName == null ? mPackageName : mRunName;
@@ -248,6 +249,18 @@
}
}
+ @NonNull private String getRunOptions() {
+ return mRunOptions;
+ }
+
+ /**
+ * Sets options for the am instrument command.
+ * See com/android/commands/am/Am.java for full list of options.
+ */
+ public void setRunOptions(@NonNull String options) {
+ mRunOptions = options + " ";
+ }
+
@Override
public void cancel() {
if (mParser != null) {
diff --git a/ddmlib/src/main/java/com/android/ddmlib/testrunner/XmlTestRunListener.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/XmlTestRunListener.java
index 18ce841..1a0b1da 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/testrunner/XmlTestRunListener.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/testrunner/XmlTestRunListener.java
@@ -16,6 +16,7 @@
package com.android.ddmlib.testrunner;
+import com.android.SdkConstants;
import com.android.ddmlib.Log;
import com.android.ddmlib.Log.LogLevel;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
@@ -144,8 +145,8 @@
try {
stream = createOutputResultStream(reportDir);
KXmlSerializer serializer = new KXmlSerializer();
- serializer.setOutput(stream, "UTF-8");
- serializer.startDocument("UTF-8", null);
+ serializer.setOutput(stream, SdkConstants.UTF_8);
+ serializer.startDocument(SdkConstants.UTF_8, null);
serializer.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
// TODO: insert build info
diff --git a/ddmlib/src/test/java/com/android/ddmlib/allocations/AllocationsParserTest.java b/ddmlib/src/test/java/com/android/ddmlib/allocations/AllocationsParserTest.java
new file mode 100644
index 0000000..f0a2076
--- /dev/null
+++ b/ddmlib/src/test/java/com/android/ddmlib/allocations/AllocationsParserTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.allocations;
+
+import com.android.ddmlib.AllocationInfo;
+import com.android.ddmlib.AllocationsParser;
+import com.google.common.base.Charsets;
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.net.URL;
+import java.nio.ByteBuffer;
+
+public class AllocationsParserTest extends TestCase {
+
+ public void testParsingOnNoAllocations() throws IOException {
+ ByteBuffer data = putAllocationInfo(new String[0], new String[0], new String[0], new int[0][], new short[0][][]);
+ assertEquals(0, AllocationsParser.parse(data).length);
+ }
+
+ public void testParsingOnOneAllocationWithoutStackTrace() throws IOException {
+ ByteBuffer data =
+ putAllocationInfo(new String[]{"path.Foo"}, new String[0], new String[0], new int[][]{{32, 4, 0, 0}}, new short[][][]{{}});
+ AllocationInfo[] info = AllocationsParser.parse(data);
+ assertEquals(1, info.length);
+
+ AllocationInfo alloc = info[0];
+ checkEntry(1, "path.Foo", 32, 4, alloc);
+ checkFirstTrace(null, null, alloc);
+ assertEquals(0, alloc.getStackTrace().length);
+ }
+
+ public void testParsingOnOneAllocationWithStackTrace() throws IOException {
+ ByteBuffer data = putAllocationInfo(new String[]{"path.Foo", "path.Bar", "path.Baz"}, new String[]{"foo", "bar", "baz"},
+ new String[]{"Foo.java", "Bar.java"}, new int[][]{{64, 0, 1, 3}},
+ new short[][][]{{{1, 1, 1, -1}, {2, 0, 1, 2000}, {0, 2, 0, 10}}});
+ AllocationInfo[] info = AllocationsParser.parse(data);
+ assertEquals(1, info.length);
+
+ AllocationInfo alloc = info[0];
+ checkEntry(1, "path.Bar", 64, 0, alloc);
+ checkFirstTrace("path.Bar", "bar", alloc);
+
+ StackTraceElement[] elems = alloc.getStackTrace();
+ assertEquals(3, elems.length);
+
+ checkStackFrame("path.Bar", "bar", "Bar.java", -1, elems[0]);
+ checkStackFrame("path.Baz", "foo", "Bar.java", 2000, elems[1]);
+ checkStackFrame("path.Foo", "baz", "Foo.java", 10, elems[2]);
+ }
+
+ public void testParsing() throws IOException {
+ ByteBuffer data = putAllocationInfo(new String[]{"path.Red", "path.Green", "path.Blue", "path.LightCanaryishGrey"},
+ new String[]{"eatTiramisu", "failUnitTest", "watchCatVideos", "passGo", "collectFakeMoney", "findWaldo"},
+ new String[]{"Red.java", "SomewhatBlue.java", "LightCanaryishGrey.java"},
+ new int[][]{{128, 8, 0, 2}, {16, 8, 2, 1}, {42, 2, 1, 3}},
+ new short[][][]{{{1, 0, 1, 100}, {2, 5, 1, -2}}, {{0, 1, 0, -1}}, {{3, 4, 2, 10001}, {0, 3, 0, 0}, {2, 2, 1, 16}}});
+ AllocationInfo[] info = AllocationsParser.parse(data);
+ assertEquals(3, info.length);
+
+ AllocationInfo alloc1 = info[0];
+ checkEntry(3, "path.Red", 128, 8, alloc1);
+ checkFirstTrace("path.Green", "eatTiramisu", alloc1);
+
+ StackTraceElement[] elems1 = alloc1.getStackTrace();
+ assertEquals(2, elems1.length);
+
+ checkStackFrame("path.Green", "eatTiramisu", "SomewhatBlue.java", 100, elems1[0]);
+ checkStackFrame("path.Blue", "findWaldo", "SomewhatBlue.java", -2, elems1[1]);
+
+ AllocationInfo alloc2 = info[1];
+ checkEntry(2, "path.Blue", 16, 8, alloc2);
+ checkFirstTrace("path.Red", "failUnitTest", alloc2);
+
+ StackTraceElement[] elems2 = alloc2.getStackTrace();
+ assertEquals(1, elems2.length);
+
+ checkStackFrame("path.Red", "failUnitTest", "Red.java", -1, elems2[0]);
+
+ AllocationInfo alloc3 = info[2];
+ checkEntry(1, "path.Green", 42, 2, alloc3);
+ checkFirstTrace("path.LightCanaryishGrey", "collectFakeMoney", alloc3);
+
+ StackTraceElement[] elems3 = alloc3.getStackTrace();
+ assertEquals(3, elems3.length);
+
+ checkStackFrame("path.LightCanaryishGrey", "collectFakeMoney", "LightCanaryishGrey.java", 10001, elems3[0]);
+ checkStackFrame("path.Red", "passGo", "Red.java", 0, elems3[1]);
+ checkStackFrame("path.Blue", "watchCatVideos", "SomewhatBlue.java", 16, elems3[2]);
+ }
+
+ private static void checkEntry(int order, String className, int size, int thread, AllocationInfo alloc) {
+ assertEquals(order, alloc.getAllocNumber());
+ assertEquals(className, alloc.getAllocatedClass());
+ assertEquals(size, alloc.getSize());
+ assertEquals(thread, alloc.getThreadId());
+ }
+
+ private static void checkFirstTrace(String className, String methodName, AllocationInfo alloc) {
+ assertEquals(className, alloc.getFirstTraceClassName());
+ assertEquals(methodName, alloc.getFirstTraceMethodName());
+ }
+
+ private static void checkStackFrame(String className, String methodName, String fileName, int lineNumber, StackTraceElement elem) {
+ assertEquals(className, elem.getClassName());
+ assertEquals(methodName, elem.getMethodName());
+ assertEquals(fileName, elem.getFileName());
+ assertEquals(lineNumber, elem.getLineNumber());
+ }
+
+ public static ByteBuffer putAllocationInfo(String[] classNames, String[] methodNames, String[] fileNames, int[][] entries,
+ short[][][] stackFrames) throws IOException {
+ byte msgHdrLen = 15, entryHdrLen = 9, stackFrameLen = 8;
+
+ // Number of bytes from start of message to string tables
+ int offset = msgHdrLen;
+ for (int[] entry : entries) {
+ offset += entryHdrLen + (stackFrameLen * entry[3]);
+ }
+
+ // Number of bytes in string tables
+ int strNamesLen = 0;
+ for (String name : classNames) { strNamesLen += 4 + (2 * name.length()); }
+ for (String name : methodNames) { strNamesLen += 4 + (2 * name.length()); }
+ for (String name : fileNames) { strNamesLen += 4 + (2 * name.length()); }
+
+ ByteBuffer data = ByteBuffer.allocate(offset + strNamesLen);
+
+ data.put(new byte[]{msgHdrLen, entryHdrLen, stackFrameLen});
+ data.putShort((short) entries.length);
+ data.putInt(offset);
+ data.putShort((short) classNames.length);
+ data.putShort((short) methodNames.length);
+ data.putShort((short) fileNames.length);
+
+ for (short i = 0; i < entries.length; ++i) {
+ data.putInt(entries[i][0]); // total alloc size
+ data.putShort((short) entries[i][1]); // thread id
+ data.putShort((short) entries[i][2]); // allocated class index
+ data.put((byte) entries[i][3]); // stack depth
+
+ short[][] frames = stackFrames[i];
+ for (short[] frame : frames) {
+ data.putShort(frame[0]); // class name
+ data.putShort(frame[1]); // method name
+ data.putShort(frame[2]); // source file
+ data.putShort(frame[3]); // line number
+ }
+ }
+
+ for (String name : classNames) {
+ data.putInt(name.length());
+ data.put(strToBytes(name));
+ }
+ for (String name : methodNames) {
+ data.putInt(name.length());
+ data.put(strToBytes(name));
+ }
+ for (String name : fileNames) {
+ data.putInt(name.length());
+ data.put(strToBytes(name));
+ }
+ data.rewind();
+ return data;
+ }
+
+ private static byte[] strToBytes(String str) {
+ return str.getBytes(Charsets.UTF_16BE);
+ }
+}
\ No newline at end of file
diff --git a/device_validator/dvlib/.settings/org.eclipse.jdt.core.prefs b/device_validator/dvlib/.settings/org.eclipse.jdt.core.prefs
new file mode 100755
index 0000000..4ea3ba3
--- /dev/null
+++ b/device_validator/dvlib/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Tue May 13 11:18:24 PDT 2014
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/device_validator/dvlib/build.gradle b/device_validator/dvlib/build.gradle
index 8a0e5ee..7756284 100644
--- a/device_validator/dvlib/build.gradle
+++ b/device_validator/dvlib/build.gradle
@@ -1,24 +1,20 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
dependencies {
- compile project(':common')
+ compile project(':base:common')
testCompile 'junit:junit:3.8.1'
}
group = 'com.android.tools'
archivesBaseName = 'dvlib'
+version = rootProject.ext.baseVersion
-// configure the manifest of the buildDistributionJar task
-buildDistributionJar.manifest.attributes("Main-Class": "com.android.validator.DeviceValidator")
-
-jar {
- from 'NOTICE'
-}
+// configure the manifest of the sdkJar task
+sdkJar.manifest.attributes("Main-Class": "com.android.validator.DeviceValidator")
project.ext.pomName = 'Android Tools dvlib'
project.ext.pomDesc = 'A Library to manage the Android device database XML files.'
-apply from: '../../baseVersion.gradle'
-apply from: '../../publish.gradle'
-apply from: '../../javadoc.gradle'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/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 965c368..b538872 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
@@ -17,8 +17,14 @@
package com.android.dvlib;
import com.android.annotations.Nullable;
+import com.android.io.NonClosingInputStream;
+import com.android.io.NonClosingInputStream.CloseBehavior;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
import org.xml.sax.Attributes;
+import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
@@ -28,8 +34,12 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -40,7 +50,24 @@
public class DeviceSchema {
- public static final String NS_DEVICES_XSD = "http://schemas.android.com/sdk/devices/1";
+ // ---- XSD ----
+
+ /**
+ * The latest version of the device XML Schema.
+ * Valid version numbers are between 1 and this number, included.
+ */
+ public static final int NS_LATEST_VERSION = 2;
+
+ /** The XML namespace of the latest device XML. */
+ public static final String NS_DEVICES_URI = getSchemaUri(NS_LATEST_VERSION);
+
+ /** Base for the devices XSD URI, without the terminal version number. */
+ private static final String NS_DEVICES_URI_BASE = "http://schemas.android.com/sdk/devices/";
+
+ /** Regex pattern to find the terminal version number in an XSD URI. */
+ static final String NS_DEVICES_URI_PATTERN = NS_DEVICES_URI_BASE + "([0-9]+)"; //$NON-NLS-1$
+
+ // ----- XML ----
/**
* The "devices" element is the root element of this schema.
@@ -186,6 +213,16 @@
public static final String NODE_MANUFACTURER = "manufacturer";
+ public static final String NODE_TAG_ID = "tag-id";
+
+ public static final String NODE_BOOT_PROPS = "boot-props";
+
+ public static final String NODE_BOOT_PROP = "boot-prop";
+
+ public static final String NODE_PROP_NAME = "prop-name";
+
+ public static final String NODE_PROP_VALUE = "prop-value";
+
public static final String ATTR_DEFAULT = "default";
public static final String ATTR_UNIT = "unit";
@@ -193,29 +230,80 @@
public static final String ATTR_NAME = "name";
/**
- * Validates the input stream.
+ * Returns the URI of the SDK Repository schema for the given version number.
+ * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
+ */
+ public static String getSchemaUri(int version) {
+ return String.format(NS_DEVICES_URI_BASE + "%d", version); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a stream to the requested {@code device} XML Schema.
*
- * @param deviceXml
- * The XML InputStream to validate.
- * @param out
- * The OutputStream for error messages.
- * @param parent
- * The parent directory of the input stream.
+ * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
+ * @return An {@link InputStream} object for the local XSD file or
+ * null if there is no schema for the requested version.
+ */
+ public static InputStream getXsdStream(int version) {
+ assert version >= 1 && version <= NS_LATEST_VERSION;
+ String rootElement = DeviceSchema.NODE_DEVICES; //$NON-NLS-1$
+ String filename = String.format("%1$s-%2$d.xsd", rootElement, version); //$NON-NLS-1$
+
+ try {
+ return DeviceSchema.class.getResourceAsStream(filename);
+ } catch (Exception ignore) {
+ // Some implementations seem to return null on failure,
+ // others throw an exception. We want to return null.
+ }
+ return null;
+ }
+
+ /**
+ * Validates the input stream against the corresponding Devices XSD schema
+ * and then does a sanity check on the content.
+ *
+ * @param deviceXml The XML InputStream to validate.
+ * The XML input stream must supports the mark/reset() methods
+ * (that is its {@link InputStream#markSupported()} must return true)
+ * and which mark has already been set to the beginning of the stream.
+ * @param out The OutputStream for error messages.
+ * @param parent The parent directory of the input stream.
* @return Whether the given input constitutes a valid devices file.
*/
public static boolean validate(InputStream deviceXml, OutputStream out, File parent) {
- Schema s;
- SAXParserFactory factory = SAXParserFactory.newInstance();
PrintWriter writer = new PrintWriter(out);
+
try {
- s = DeviceSchema.getSchema();
+ if (!(deviceXml instanceof NonClosingInputStream)) {
+ deviceXml = new NonClosingInputStream(deviceXml);
+ ((NonClosingInputStream) deviceXml).setCloseBehavior(CloseBehavior.RESET);
+ }
+
+ int version = getXmlSchemaVersion(deviceXml);
+ if (version < 1 || version > NS_LATEST_VERSION) {
+ writer.println(String.format("Devices XSD version %1$d is out of valid range 1..%2$d",
+ version, NS_LATEST_VERSION));
+ return false;
+ }
+
+ assert deviceXml.markSupported();
+
+ // First check the input against the XSD schema
+
+ // Check the input, both against the XSD schema discovered above and also
+ // by using a custom validation which tests some properties not encoded in the XSD.
+
+ Schema schema = DeviceSchema.getSchema(version);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(true);
- factory.setSchema(s);
- ValidationHandler validator = new ValidationHandler(parent, writer);
+ factory.setSchema(schema);
+ DevicesValidationHandler devicesValidator = new DevicesValidationHandler(parent, writer);
SAXParser parser = factory.newSAXParser();
- parser.parse(deviceXml, validator);
- return validator.isValidDevicesFile();
+
+ deviceXml.reset();
+ parser.parse(deviceXml, devicesValidator);
+ return devicesValidator.isValidDevicesFile();
} catch (SAXException e) {
writer.println(e.getMessage());
return false;
@@ -233,16 +321,14 @@
}
/**
- * Helper to get an input stream of the device config XML schema.
+ * Helper method that returns a validator for a specific version of the XSD.
+ *
+ * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
+ * @return A {@link Schema} validator or null.
*/
- public static InputStream getXsdStream() {
- return DeviceSchema.class.getResourceAsStream("devices.xsd"); //$NON-NLS-1$
- }
-
- /** Helper method that returns a {@link Validator} for our XSD */
@Nullable
- public static Schema getSchema() throws SAXException {
- InputStream xsdStream = getXsdStream();
+ public static Schema getSchema(int version) throws SAXException {
+ InputStream xsdStream = getXsdStream(version);
if (xsdStream == null) {
return null;
}
@@ -252,11 +338,126 @@
}
/**
+ * Manually parses the root element of the XML to extract the schema version
+ * at the end of the xmlns:sdk="http://schemas.android.com/sdk/devices/$N"
+ * declaration.
+ *
+ * @param xml An XML input stream that supports the mark/reset() methods
+ * (that is its {@link InputStream#markSupported()} must return true)
+ * and which mark has already been set to the beginning of the stream.
+ * @return 1+ for a valid schema version
+ * or 0 if no schema could be found.
+ */
+ public static int getXmlSchemaVersion(InputStream xml) {
+ if (xml == null) {
+ return 0;
+ }
+
+ // Get an XML document
+ Document doc = null;
+ try {
+ assert xml.markSupported();
+ xml.reset();
+
+ if (!(xml instanceof NonClosingInputStream)) {
+ xml = new NonClosingInputStream(xml);
+ ((NonClosingInputStream) xml).setCloseBehavior(CloseBehavior.RESET);
+ }
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments(false);
+ factory.setValidating(false);
+
+ // Parse the document using a non namespace aware builder
+ factory.setNamespaceAware(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) throws SAXException {
+ // pass
+ }
+ @Override
+ public void fatalError(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ @Override
+ public void error(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ });
+
+ doc = builder.parse(xml);
+
+ // Prepare a new document using a namespace aware builder
+ factory.setNamespaceAware(true);
+ builder = factory.newDocumentBuilder();
+
+ } catch (Exception e) {
+ // Failed to reset XML stream
+ // Failed to get builder factor
+ // Failed to create XML document builder
+ // Failed to parse XML document
+ // Failed to read XML document
+ }
+
+ if (doc == null) {
+ return 0;
+ }
+
+ // Check the root element is an XML with at least the following properties:
+ // <sdk:sdk-repository
+ // xmlns:sdk="http://schemas.android.com/sdk/devices/$N">
+ //
+ // Note that we don't have namespace support enabled, we just do it manually.
+
+ Pattern nsPattern = Pattern.compile(NS_DEVICES_URI_PATTERN);
+
+ String prefix = null;
+ for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ prefix = null;
+ String name = child.getNodeName();
+ int pos = name.indexOf(':');
+ if (pos > 0 && pos < name.length() - 1) {
+ prefix = name.substring(0, pos);
+ name = name.substring(pos + 1);
+ }
+ if (NODE_DEVICES.equals(name)) {
+ NamedNodeMap attrs = child.getAttributes();
+ String xmlns = "xmlns"; //$NON-NLS-1$
+ if (prefix != null) {
+ xmlns += ":" + prefix; //$NON-NLS-1$
+ }
+ Node attr = attrs.getNamedItem(xmlns);
+ if (attr != null) {
+ String uri = attr.getNodeValue();
+ if (uri != null) {
+ Matcher m = nsPattern.matcher(uri);
+ if (m.matches()) {
+ String version = m.group(1);
+ try {
+ return Integer.parseInt(version);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /**
* A DefaultHandler that parses only to validate the XML is actually a valid
* devices config, since validation can't be entirely encoded in the devices
* schema.
*/
- private static class ValidationHandler extends DefaultHandler {
+ private static class DevicesValidationHandler extends DefaultHandler {
private boolean mValidDevicesFile = true;
private boolean mDefaultSeen = false;
private String mDeviceName;
@@ -264,7 +465,7 @@
private final PrintWriter mWriter;
private final StringBuilder mStringAccumulator = new StringBuilder();
- public ValidationHandler(File directory, PrintWriter writer) {
+ public DevicesValidationHandler(File directory, PrintWriter writer) {
mDirectory = directory; // Possibly null
mWriter = writer;
}
diff --git a/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-1.xsd b/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-1.xsd
new file mode 100644
index 0000000..320a154
--- /dev/null
+++ b/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-1.xsd
@@ -0,0 +1,931 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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.
+-->
+
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/devices/1"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:c="http://schemas.android.com/sdk/devices/1"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <xsd:element name="devices" type="c:devicesType" />
+
+ <xsd:complexType name="devicesType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The "devices" element is the root element of this schema.
+
+ It must contain one or more "device" elements that each define the configurations
+ and states available for a given device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="device" minOccurs="1" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A device element contains one hardware profile for a device, along with
+ 1 or more software profiles and 1 or more states. Each software profile
+ defines the supported software for a given API release, and each state
+ profile defines a different possible state of the device (screen in
+ portrait orientation, screen in landscape orientation with the keyboard
+ out, etc.)
+ </xsd:documentation>
+ </xsd:annotation>
+ <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" />
+ <xsd:element name="software" type= "c:softwareType"
+ maxOccurs="unbounded" />
+ <xsd:element name="state" type= "c:stateType"
+ maxOccurs="unbounded" />
+ <xsd:element name="tag-id" type= "c:idType" minOccurs="0" />
+ <xsd:element name="boot-props" type= "c:bootPropsType" minOccurs="0" />
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A tag string for a system image can only be a simple alphanumeric string.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_-]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="bootPropsType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ List of boot properties.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="boot-prop">
+ <xsd:complexType>
+ <xsd:all>
+ <xsd:element name="prop-name">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[^\n\r\t =]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="prop-value" type= "xsd:string" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="hardwareType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The hardwareType contains all of the hardware information for
+ a given device. This includes things like the GPU type, screen
+ size, mic presence, etc.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="screen" type= "c:screenType" />
+ <xsd:element name="networking" type= "c:networkingType" />
+ <xsd:element name="sensors" type= "c:sensorsType" />
+ <xsd:element name="mic" type= "c:micType" />
+ <xsd:element name="camera" type= "c:cameraType"
+ minOccurs="0" maxOccurs="unbounded" />
+ <xsd:element name="keyboard" type= "c:keyboardType" />
+ <xsd:element name="nav" type= "c:navType" />
+ <xsd:element name="ram" type= "c:ramType" />
+ <xsd:element name="buttons" type= "c:buttonsType" />
+ <xsd:element name="internal-storage" type= "c:internalStorageType" />
+ <xsd:element name="removable-storage" type= "c:removableStorageType" />
+ <xsd:element name="cpu" type= "c:cpuType" />
+ <xsd:element name="gpu" type= "c:gpuType" />
+ <xsd:element name="abi" type= "c:abiType" />
+ <xsd:element name="dock" type= "c:dockType" />
+ <xsd:element name="power-type" type= "c:powerType" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="softwareType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The softwareType contains all of the device's software
+ information for a given API version. This includes things like
+ live wallpaper support, OpenGL version, etc.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="api-level">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies which API version(s) this this element is
+ defining. This can in the form of a single number
+ or a range of low to high, separated with a dash and
+ with either limit missing. The default lower limit is
+ one, and the default upper limit is unbounded.
+ The following are valid:
+ 10
+ 7-10
+ -10
+ 7-
+ -
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[\d]*-[\d]*|[\d]+" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="live-wallpaper-support" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device supports live wallpapers.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="bluetooth-profiles">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies all of the available Bluetooth profiles.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="A2DP" />
+ <xsd:enumeration value="ATT" />
+ <xsd:enumeration value="AVRCP" />
+ <xsd:enumeration value="AVDTP" />
+ <xsd:enumeration value="BIP" />
+ <xsd:enumeration value="BPP" />
+ <xsd:enumeration value="CIP" />
+ <xsd:enumeration value="CTP" />
+ <xsd:enumeration value="DIP" />
+ <xsd:enumeration value="DUN" />
+ <xsd:enumeration value="FAX" />
+ <xsd:enumeration value="FTP" />
+ <xsd:enumeration value="GAVDP" />
+ <xsd:enumeration value="GAP" />
+ <xsd:enumeration value="GATT" />
+ <xsd:enumeration value="GOEP" />
+ <xsd:enumeration value="HCRP" />
+ <xsd:enumeration value="HDP" />
+ <xsd:enumeration value="HFP" />
+ <xsd:enumeration value="HID" />
+ <xsd:enumeration value="HSP" />
+ <xsd:enumeration value="ICP" />
+ <xsd:enumeration value="LAP" />
+ <xsd:enumeration value="MAP" />
+ <xsd:enumeration value="OPP" />
+ <xsd:enumeration value="PAN" />
+ <xsd:enumeration value="PBA" />
+ <xsd:enumeration value="PBAP" />
+ <xsd:enumeration value="SPP" />
+ <xsd:enumeration value="SDAP" />
+ <xsd:enumeration value="SAP" />
+ <xsd:enumeration value="SIM" />
+ <xsd:enumeration value="rSAP" />
+ <xsd:enumeration value="SYNCH" />
+ <xsd:enumeration value="VDP" />
+ <xsd:enumeration value="WAPB" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="gl-version">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the OpenGL version supported for this release.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <xsd:pattern value="[0-9]\.[0-9]" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="gl-extensions">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies all of the supported OpenGL extensions for
+ this release.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:list itemType="xsd:NMTOKEN" />
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="status-bar" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device has a status bar in this
+ software configuration.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="stateType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The stateType contains the information for a given state of
+ of the device. States include things like portrait mode,
+ landscape with the keyboard exposed, etc. States can also
+ modify the hardware attributes of a device. For instance, if
+ sliding out the keyboard increased the available screen
+ real estate, you can define a new screenType to override the
+ default one defined in the device's hardwareType.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="description" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A description of the defined state.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="screen-orientation">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines the orientation of the screen. Use square if
+ the device's screen has equal height and width,
+ otherwise use landscape or portrait.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="port" />
+ <xsd:enumeration value="land" />
+ <xsd:enumeration value="square" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="keyboard-state">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines the state of the keyboard. If the device has no
+ keyboard use keysoft, otherwise use keysexposed or keyshidden.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="keyssoft" />
+ <xsd:enumeration value="keyshidden" />
+ <xsd:enumeration value="keysexposed" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="nav-state">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines the state of the primary non-touchscreen
+ navigation hardware on the devices. If the device
+ doesn't have non-touchscreen navigation hardware use
+ nonav, otherwise use navexposed or navhidden.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="nonav" />
+ <xsd:enumeration value="navhidden" />
+ <xsd:enumeration value="navexposed" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="screen" type="c:screenType" minOccurs="0" />
+ <xsd:element name="networking" type="c:networkingType"
+ minOccurs="0" />
+ <xsd:element name="sensors" type="c:sensorsType" minOccurs="0" />
+ <xsd:element name="mic" type="c:micType" minOccurs="0" />
+ <xsd:element name="camera" type="c:cameraType"
+ minOccurs="0" maxOccurs="unbounded" />
+ <xsd:element name="keyboard" type="c:keyboardType" minOccurs="0" />
+ <xsd:element name="nav" type="c:navType" minOccurs="0" />
+ <xsd:element name="ram" type="c:ramType" minOccurs="0" />
+ <xsd:element name="buttons" type="c:buttonsType" minOccurs="0" />
+ <xsd:element name="internal-storage" type="c:internalStorageType"
+ minOccurs="0" />
+ <xsd:element name="removable-storage" type="c:removableStorageType"
+ minOccurs="0" />
+ <xsd:element name="cpu" type="c:cpuType" minOccurs="0" />
+ <xsd:element name="gpu" type="c:gpuType" minOccurs="0" />
+ <xsd:element name="abi" type="c:abiType" minOccurs="0" />
+ <xsd:element name="dock" type="c:dockType" minOccurs="0" />
+ <xsd:element name="power-type" type="c:powerType"
+ minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:token" />
+ <xsd:attribute name="default" use="optional" type="xsd:boolean" />
+ </xsd:complexType>
+
+ <xsd:complexType name="metaType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Details where more device information can be found, such as
+ icons and frame images.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="icons" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Contains the relative paths to the icon files for this
+ device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="sixty-four" type="xsd:normalizedString">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Relative path for the 64x64 icon.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="sixteen" type="xsd:normalizedString"
+ minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Relative path for the 16x16 icon.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="frame" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Contains information about the frame for the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="path"
+ type="xsd:normalizedString">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The relative path to the emulator frame for
+ the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="portrait-x-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the x direction,
+ in portrait mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="portrait-y-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the y direction,
+ in portrait mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="landscape-x-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the x direction,
+ in landscape mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="landscape-y-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the y direction,
+ in landscape mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="screenType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Contains the specifications for the device's screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="screen-size">
+ <xsd:simpleType>
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the class of the screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="small" />
+ <xsd:enumeration value="normal" />
+ <xsd:enumeration value="large" />
+ <xsd:enumeration value="xlarge" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="diagonal-length">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the diagonal length of the screen in inches.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <!-- Negative lengths are not valid -->
+ <xsd:minInclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="pixel-density">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the screen density of the device. The
+ medium density of traditional HVGA screens (mdpi)
+ is defined to be approximately 160dpi; low density
+ (ldpi) is 120, and high density (hdpi) is 240. There
+ is thus a 4:3 scaling factor between each density,
+ so a 9x9 bitmap in ldpi would be 12x12 in mdpi and
+ 16x16 in hdpi.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="ldpi" />
+ <xsd:enumeration value="mdpi" />
+ <xsd:enumeration value="tvdpi" />
+ <xsd:enumeration value="hdpi" />
+ <xsd:enumeration value="xhdpi" />
+ <xsd:enumeration value="xxhdpi" />
+ <xsd:enumeration value="xxxhdpi" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-ratio">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the configuration is for a taller or
+ wider than traditional screen. This is based purely on
+ the aspect ratio of the screen: QVGA, HVGA, and VGA are
+ notlong; WQVGA, WVGA, FWVGA are long. Note that long may
+ mean either wide or tall, depending on the current
+ orientation.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notlong" />
+ <xsd:enumeration value="long" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="dimensions">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device screen resolution in pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="x-dimension">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the x-dimension's resolution in
+ pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:positiveInteger" />
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="y-dimension">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the y-dimension's resolution in
+ pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:positiveInteger" />
+ </xsd:simpleType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="xdpi">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the actual density in X of the device screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <!-- Negative DPIs are not valid -->
+ <xsd:minInclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="ydpi">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the actual density in Y of the device screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <!-- Negative DPIs are not valid -->
+ <xsd:minInclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="touch">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the touch properties of the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="multitouch">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the multitouch capabilities of the
+ device. This can be none if multitouch is
+ not supported, basic if the device can track
+ only basic two finger gestures, distinct if
+ the device can track two or more fingers
+ simultaneously, or jazz-hands if the device
+ can track 5 or more fingers simultaneously.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="none" />
+ <xsd:enumeration value="basic" />
+ <xsd:enumeration value="distinct" />
+ <xsd:enumeration value="jazz-hands" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="mechanism">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the mechanism the device was
+ created for.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notouch" />
+ <xsd:enumeration value="stylus" />
+ <xsd:enumeration value="finger" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-type">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the type of touch screen on the
+ device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notouch" />
+ <xsd:enumeration value="capacitive" />
+ <xsd:enumeration value="resistive" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:simpleType name="networkingType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the available networking hardware.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="NFC" />
+ <xsd:enumeration value="Bluetooth" />
+ <xsd:enumeration value="Wifi" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="sensorsType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the available sensors.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="Accelerometer" />
+ <xsd:enumeration value="Barometer" />
+ <xsd:enumeration value="Compass" />
+ <xsd:enumeration value="GPS" />
+ <xsd:enumeration value="Gyroscope" />
+ <xsd:enumeration value="LightSensor" />
+ <xsd:enumeration value="ProximitySensor" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="micType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device has a mic or not.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:boolean" />
+ </xsd:simpleType>
+
+ <xsd:complexType name="cameraType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the attributes of the camera.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="location">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the location of the camera.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="front" />
+ <xsd:enumeration value="back" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="autofocus" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the camera can autofocus
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="flash" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the camera has flash.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:simpleType name="keyboardType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the type of keyboard on the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="qwerty" />
+ <xsd:enumeration value="12key" />
+ <xsd:enumeration value="nokeys" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="navType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the primary non-touchscreen navigation
+ hardware on the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="dpad" />
+ <xsd:enumeration value="trackball" />
+ <xsd:enumeration value="wheel" />
+ <xsd:enumeration value="nonav" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="ramType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the amount of RAM on the device in the unit provided.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:positiveInteger">
+ <xsd:attribute name="unit" type="c:storageUnitType" use="required" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="buttonsType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device has physical (hard) buttons
+ (Home, Search, etc.), or uses soft buttons.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="hard" />
+ <xsd:enumeration value="soft" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="internalStorageType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A list specifying the sizes of internal storage in
+ the device, in the storage size unit provided.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="c:storageListType">
+ <xsd:attribute name="unit" type="c:storageUnitType"
+ use="required" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="removableStorageType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the range of available removable storage sizes
+ in the unit provided. A positive value indicates the device is
+ available with that storage size included while a zero value
+ indicates an empty storage slot.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="c:storageListType">
+ <xsd:attribute name="unit" type="c:storageUnitType"
+ use="required" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="storageListType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines a list for storage configurations such as internal or
+ removable storage. A positive value indicates the the device
+ has a storage unit of that size, while a zero value indicates
+ there is an empty location for a storage unit (such as an empty
+ SD card slot).
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:nonNegativeInteger" />
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+ <xsd:simpleType name="gpuType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device's GPU.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:minLength value="1" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="cpuType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device's CPU.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:minLength value="1" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies which ABIs the device conforms to.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="dockType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the official docks available for the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="desk" />
+ <xsd:enumeration value="television" />
+ <xsd:enumeration value="car" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="powerType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device is plugged in.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="plugged-in" />
+ <xsd:enumeration value="battery" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="storageUnitType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the unit of storage. This can be MiB, GiB, etc.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="B" />
+ <xsd:enumeration value="KiB" />
+ <xsd:enumeration value="MiB" />
+ <xsd:enumeration value="GiB" />
+ <xsd:enumeration value="TiB" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-2.xsd b/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-2.xsd
new file mode 100644
index 0000000..3c0acaf
--- /dev/null
+++ b/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-2.xsd
@@ -0,0 +1,943 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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.
+-->
+
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/devices/2"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:c="http://schemas.android.com/sdk/devices/2"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The devices element contains a collection of device definitions.
+
+ History:
+ - v1 is used by the dvlib in Tools r20-22..
+
+ - v2 is used by the dvlib in Tools r23.
+ - It adds support for new ABIs arm64-v8a, x86_64 and mips64.
+ -->
+ <xsd:element name="devices" type="c:devicesType" />
+
+ <xsd:complexType name="devicesType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The "devices" element is the root element of this schema.
+
+ It must contain one or more "device" elements that each define the configurations
+ and states available for a given device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="device" minOccurs="1" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A device element contains one hardware profile for a device, along with
+ 1 or more software profiles and 1 or more states. Each software profile
+ defines the supported software for a given API release, and each state
+ profile defines a different possible state of the device (screen in
+ portrait orientation, screen in landscape orientation with the keyboard
+ out, etc.)
+ </xsd:documentation>
+ </xsd:annotation>
+ <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" />
+ <xsd:element name="software" type= "c:softwareType"
+ maxOccurs="unbounded" />
+ <xsd:element name="state" type= "c:stateType"
+ maxOccurs="unbounded" />
+ <xsd:element name="tag-id" type= "c:idType" minOccurs="0" />
+ <xsd:element name="boot-props" type= "c:bootPropsType" minOccurs="0" />
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A tag string for a system image can only be a simple alphanumeric string.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_-]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="bootPropsType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ List of boot properties.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="boot-prop">
+ <xsd:complexType>
+ <xsd:all>
+ <xsd:element name="prop-name">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[^\n\r\t =]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="prop-value" type= "xsd:string" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="hardwareType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The hardwareType contains all of the hardware information for
+ a given device. This includes things like the GPU type, screen
+ size, mic presence, etc.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="screen" type= "c:screenType" />
+ <xsd:element name="networking" type= "c:networkingType" />
+ <xsd:element name="sensors" type= "c:sensorsType" />
+ <xsd:element name="mic" type= "c:micType" />
+ <xsd:element name="camera" type= "c:cameraType"
+ minOccurs="0" maxOccurs="unbounded" />
+ <xsd:element name="keyboard" type= "c:keyboardType" />
+ <xsd:element name="nav" type= "c:navType" />
+ <xsd:element name="ram" type= "c:ramType" />
+ <xsd:element name="buttons" type= "c:buttonsType" />
+ <xsd:element name="internal-storage" type= "c:internalStorageType" />
+ <xsd:element name="removable-storage" type= "c:removableStorageType" />
+ <xsd:element name="cpu" type= "c:cpuType" />
+ <xsd:element name="gpu" type= "c:gpuType" />
+ <xsd:element name="abi" type= "c:abiType" />
+ <xsd:element name="dock" type= "c:dockType" />
+ <xsd:element name="power-type" type= "c:powerType" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="softwareType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The softwareType contains all of the device's software
+ information for a given API version. This includes things like
+ live wallpaper support, OpenGL version, etc.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="api-level">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies which API version(s) this this element is
+ defining. This can in the form of a single number
+ or a range of low to high, separated with a dash and
+ with either limit missing. The default lower limit is
+ one, and the default upper limit is unbounded.
+ The following are valid:
+ 10
+ 7-10
+ -10
+ 7-
+ -
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[\d]*-[\d]*|[\d]+" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="live-wallpaper-support" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device supports live wallpapers.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="bluetooth-profiles">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies all of the available Bluetooth profiles.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="A2DP" />
+ <xsd:enumeration value="ATT" />
+ <xsd:enumeration value="AVRCP" />
+ <xsd:enumeration value="AVDTP" />
+ <xsd:enumeration value="BIP" />
+ <xsd:enumeration value="BPP" />
+ <xsd:enumeration value="CIP" />
+ <xsd:enumeration value="CTP" />
+ <xsd:enumeration value="DIP" />
+ <xsd:enumeration value="DUN" />
+ <xsd:enumeration value="FAX" />
+ <xsd:enumeration value="FTP" />
+ <xsd:enumeration value="GAVDP" />
+ <xsd:enumeration value="GAP" />
+ <xsd:enumeration value="GATT" />
+ <xsd:enumeration value="GOEP" />
+ <xsd:enumeration value="HCRP" />
+ <xsd:enumeration value="HDP" />
+ <xsd:enumeration value="HFP" />
+ <xsd:enumeration value="HID" />
+ <xsd:enumeration value="HSP" />
+ <xsd:enumeration value="ICP" />
+ <xsd:enumeration value="LAP" />
+ <xsd:enumeration value="MAP" />
+ <xsd:enumeration value="OPP" />
+ <xsd:enumeration value="PAN" />
+ <xsd:enumeration value="PBA" />
+ <xsd:enumeration value="PBAP" />
+ <xsd:enumeration value="SPP" />
+ <xsd:enumeration value="SDAP" />
+ <xsd:enumeration value="SAP" />
+ <xsd:enumeration value="SIM" />
+ <xsd:enumeration value="rSAP" />
+ <xsd:enumeration value="SYNCH" />
+ <xsd:enumeration value="VDP" />
+ <xsd:enumeration value="WAPB" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="gl-version">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the OpenGL version supported for this release.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <xsd:pattern value="[0-9]\.[0-9]" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="gl-extensions">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies all of the supported OpenGL extensions for
+ this release.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:list itemType="xsd:NMTOKEN" />
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="status-bar" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device has a status bar in this
+ software configuration.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="stateType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The stateType contains the information for a given state of
+ of the device. States include things like portrait mode,
+ landscape with the keyboard exposed, etc. States can also
+ modify the hardware attributes of a device. For instance, if
+ sliding out the keyboard increased the available screen
+ real estate, you can define a new screenType to override the
+ default one defined in the device's hardwareType.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="description" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A description of the defined state.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="screen-orientation">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines the orientation of the screen. Use square if
+ the device's screen has equal height and width,
+ otherwise use landscape or portrait.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="port" />
+ <xsd:enumeration value="land" />
+ <xsd:enumeration value="square" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="keyboard-state">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines the state of the keyboard. If the device has no
+ keyboard use keysoft, otherwise use keysexposed or keyshidden.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="keyssoft" />
+ <xsd:enumeration value="keyshidden" />
+ <xsd:enumeration value="keysexposed" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="nav-state">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines the state of the primary non-touchscreen
+ navigation hardware on the devices. If the device
+ doesn't have non-touchscreen navigation hardware use
+ nonav, otherwise use navexposed or navhidden.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="nonav" />
+ <xsd:enumeration value="navhidden" />
+ <xsd:enumeration value="navexposed" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="screen" type="c:screenType" minOccurs="0" />
+ <xsd:element name="networking" type="c:networkingType"
+ minOccurs="0" />
+ <xsd:element name="sensors" type="c:sensorsType" minOccurs="0" />
+ <xsd:element name="mic" type="c:micType" minOccurs="0" />
+ <xsd:element name="camera" type="c:cameraType"
+ minOccurs="0" maxOccurs="unbounded" />
+ <xsd:element name="keyboard" type="c:keyboardType" minOccurs="0" />
+ <xsd:element name="nav" type="c:navType" minOccurs="0" />
+ <xsd:element name="ram" type="c:ramType" minOccurs="0" />
+ <xsd:element name="buttons" type="c:buttonsType" minOccurs="0" />
+ <xsd:element name="internal-storage" type="c:internalStorageType"
+ minOccurs="0" />
+ <xsd:element name="removable-storage" type="c:removableStorageType"
+ minOccurs="0" />
+ <xsd:element name="cpu" type="c:cpuType" minOccurs="0" />
+ <xsd:element name="gpu" type="c:gpuType" minOccurs="0" />
+ <xsd:element name="abi" type="c:abiType" minOccurs="0" />
+ <xsd:element name="dock" type="c:dockType" minOccurs="0" />
+ <xsd:element name="power-type" type="c:powerType"
+ minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:token" />
+ <xsd:attribute name="default" use="optional" type="xsd:boolean" />
+ </xsd:complexType>
+
+ <xsd:complexType name="metaType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Details where more device information can be found, such as
+ icons and frame images.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="icons" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Contains the relative paths to the icon files for this
+ device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="sixty-four" type="xsd:normalizedString">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Relative path for the 64x64 icon.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="sixteen" type="xsd:normalizedString"
+ minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Relative path for the 16x16 icon.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="frame" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Contains information about the frame for the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="path"
+ type="xsd:normalizedString">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The relative path to the emulator frame for
+ the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="portrait-x-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the x direction,
+ in portrait mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="portrait-y-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the y direction,
+ in portrait mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="landscape-x-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the x direction,
+ in landscape mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="landscape-y-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the y direction,
+ in landscape mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="screenType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Contains the specifications for the device's screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="screen-size">
+ <xsd:simpleType>
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the class of the screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="small" />
+ <xsd:enumeration value="normal" />
+ <xsd:enumeration value="large" />
+ <xsd:enumeration value="xlarge" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="diagonal-length">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the diagonal length of the screen in inches.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <!-- Negative lengths are not valid -->
+ <xsd:minInclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="pixel-density">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the screen density of the device. The
+ medium density of traditional HVGA screens (mdpi)
+ is defined to be approximately 160dpi; low density
+ (ldpi) is 120, and high density (hdpi) is 240. There
+ is thus a 4:3 scaling factor between each density,
+ so a 9x9 bitmap in ldpi would be 12x12 in mdpi and
+ 16x16 in hdpi.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="ldpi" />
+ <xsd:enumeration value="mdpi" />
+ <xsd:enumeration value="tvdpi" />
+ <xsd:enumeration value="hdpi" />
+ <xsd:enumeration value="xhdpi" />
+ <xsd:enumeration value="xxhdpi" />
+ <xsd:enumeration value="xxxhdpi" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-ratio">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the configuration is for a taller or
+ wider than traditional screen. This is based purely on
+ the aspect ratio of the screen: QVGA, HVGA, and VGA are
+ notlong; WQVGA, WVGA, FWVGA are long. Note that long may
+ mean either wide or tall, depending on the current
+ orientation.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notlong" />
+ <xsd:enumeration value="long" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="dimensions">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device screen resolution in pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="x-dimension">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the x-dimension's resolution in
+ pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:positiveInteger" />
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="y-dimension">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the y-dimension's resolution in
+ pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:positiveInteger" />
+ </xsd:simpleType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="xdpi">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the actual density in X of the device screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <!-- Negative DPIs are not valid -->
+ <xsd:minInclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="ydpi">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the actual density in Y of the device screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <!-- Negative DPIs are not valid -->
+ <xsd:minInclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="touch">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the touch properties of the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="multitouch">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the multitouch capabilities of the
+ device. This can be none if multitouch is
+ not supported, basic if the device can track
+ only basic two finger gestures, distinct if
+ the device can track two or more fingers
+ simultaneously, or jazz-hands if the device
+ can track 5 or more fingers simultaneously.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="none" />
+ <xsd:enumeration value="basic" />
+ <xsd:enumeration value="distinct" />
+ <xsd:enumeration value="jazz-hands" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="mechanism">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the mechanism the device was
+ created for.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notouch" />
+ <xsd:enumeration value="stylus" />
+ <xsd:enumeration value="finger" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-type">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the type of touch screen on the
+ device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notouch" />
+ <xsd:enumeration value="capacitive" />
+ <xsd:enumeration value="resistive" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:simpleType name="networkingType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the available networking hardware.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="NFC" />
+ <xsd:enumeration value="Bluetooth" />
+ <xsd:enumeration value="Wifi" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="sensorsType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the available sensors.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="Accelerometer" />
+ <xsd:enumeration value="Barometer" />
+ <xsd:enumeration value="Compass" />
+ <xsd:enumeration value="GPS" />
+ <xsd:enumeration value="Gyroscope" />
+ <xsd:enumeration value="LightSensor" />
+ <xsd:enumeration value="ProximitySensor" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="micType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device has a mic or not.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:boolean" />
+ </xsd:simpleType>
+
+ <xsd:complexType name="cameraType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the attributes of the camera.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="location">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the location of the camera.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="front" />
+ <xsd:enumeration value="back" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="autofocus" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the camera can autofocus
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="flash" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the camera has flash.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:simpleType name="keyboardType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the type of keyboard on the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="qwerty" />
+ <xsd:enumeration value="12key" />
+ <xsd:enumeration value="nokeys" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="navType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the primary non-touchscreen navigation
+ hardware on the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="dpad" />
+ <xsd:enumeration value="trackball" />
+ <xsd:enumeration value="wheel" />
+ <xsd:enumeration value="nonav" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="ramType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the amount of RAM on the device in the unit provided.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:positiveInteger">
+ <xsd:attribute name="unit" type="c:storageUnitType" use="required" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="buttonsType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device has physical (hard) buttons
+ (Home, Search, etc.), or uses soft buttons.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="hard" />
+ <xsd:enumeration value="soft" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="internalStorageType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A list specifying the sizes of internal storage in
+ the device, in the storage size unit provided.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="c:storageListType">
+ <xsd:attribute name="unit" type="c:storageUnitType"
+ use="required" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="removableStorageType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the range of available removable storage sizes
+ in the unit provided. A positive value indicates the device is
+ available with that storage size included while a zero value
+ indicates an empty storage slot.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="c:storageListType">
+ <xsd:attribute name="unit" type="c:storageUnitType"
+ use="required" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="storageListType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines a list for storage configurations such as internal or
+ removable storage. A positive value indicates the the device
+ has a storage unit of that size, while a zero value indicates
+ there is an empty location for a storage unit (such as an empty
+ SD card slot).
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:nonNegativeInteger" />
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+ <xsd:simpleType name="gpuType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device's GPU.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:minLength value="1" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="cpuType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device's CPU.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:minLength value="1" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies which ABIs the device conforms to.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="arm64-v8a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ <xsd:enumeration value="mips" />
+ <!-- TODO double-check this is appropriate value for mips64 -->
+ <xsd:enumeration value="mips64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="dockType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the official docks available for the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="desk" />
+ <xsd:enumeration value="television" />
+ <xsd:enumeration value="car" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="powerType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device is plugged in.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="plugged-in" />
+ <xsd:enumeration value="battery" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="storageUnitType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the unit of storage. This can be MiB, GiB, etc.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="B" />
+ <xsd:enumeration value="KiB" />
+ <xsd:enumeration value="MiB" />
+ <xsd:enumeration value="GiB" />
+ <xsd:enumeration value="TiB" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
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
deleted file mode 100644
index 34d197a..0000000
--- a/device_validator/dvlib/src/main/resources/com/android/dvlib/devices.xsd
+++ /dev/null
@@ -1,894 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- * 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.
--->
-
-<xsd:schema
- targetNamespace="http://schemas.android.com/sdk/devices/1"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:c="http://schemas.android.com/sdk/devices/1"
- elementFormDefault="qualified"
- attributeFormDefault="unqualified"
- version="1">
-
- <xsd:element name="devices" type="c:devicesType" />
-
- <xsd:complexType name="devicesType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The "devices" element is the root element of this schema.
-
- It must contain one or more "device" elements that each define the configurations
- and states available for a given device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="device" minOccurs="1" maxOccurs="unbounded">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- A device element contains one hardware profile for a device, along with
- 1 or more software profiles and 1 or more states. Each software profile
- defines the supported software for a given API release, and each state
- profile defines a different possible state of the device (screen in
- portrait orientation, screen in landscape orientation with the keyboard
- out, etc.)
- </xsd:documentation>
- </xsd:annotation>
- <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" />
- <xsd:element name="software" type= "c:softwareType"
- maxOccurs="unbounded" />
- <xsd:element name="state" type= "c:stateType"
- maxOccurs="unbounded" />
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:complexType name="hardwareType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The hardwareType contains all of the hardware information for
- a given device. This includes things like the GPU type, screen
- size, mic presence, etc.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="screen" type= "c:screenType" />
- <xsd:element name="networking" type= "c:networkingType" />
- <xsd:element name="sensors" type= "c:sensorsType" />
- <xsd:element name="mic" type= "c:micType" />
- <xsd:element name="camera" type= "c:cameraType"
- minOccurs="0" maxOccurs="unbounded" />
- <xsd:element name="keyboard" type= "c:keyboardType" />
- <xsd:element name="nav" type= "c:navType" />
- <xsd:element name="ram" type= "c:ramType" />
- <xsd:element name="buttons" type= "c:buttonsType" />
- <xsd:element name="internal-storage" type= "c:internalStorageType" />
- <xsd:element name="removable-storage" type= "c:removableStorageType" />
- <xsd:element name="cpu" type= "c:cpuType" />
- <xsd:element name="gpu" type= "c:gpuType" />
- <xsd:element name="abi" type= "c:abiType" />
- <xsd:element name="dock" type= "c:dockType" />
- <xsd:element name="power-type" type= "c:powerType" />
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:complexType name="softwareType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The softwareType contains all of the device's software
- information for a given API version. This includes things like
- live wallpaper support, OpenGL version, etc.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="api-level">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies which API version(s) this this element is
- defining. This can in the form of a single number
- or a range of low to high, separated with a dash and
- with either limit missing. The default lower limit is
- one, and the default upper limit is unbounded.
- The following are valid:
- 10
- 7-10
- -10
- 7-
- -
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:pattern value="[\d]*-[\d]*|[\d]+" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="live-wallpaper-support" type="xsd:boolean">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device supports live wallpapers.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
-
- <xsd:element name="bluetooth-profiles">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies all of the available Bluetooth profiles.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:NMTOKEN">
- <xsd:enumeration value="A2DP" />
- <xsd:enumeration value="ATT" />
- <xsd:enumeration value="AVRCP" />
- <xsd:enumeration value="AVDTP" />
- <xsd:enumeration value="BIP" />
- <xsd:enumeration value="BPP" />
- <xsd:enumeration value="CIP" />
- <xsd:enumeration value="CTP" />
- <xsd:enumeration value="DIP" />
- <xsd:enumeration value="DUN" />
- <xsd:enumeration value="FAX" />
- <xsd:enumeration value="FTP" />
- <xsd:enumeration value="GAVDP" />
- <xsd:enumeration value="GAP" />
- <xsd:enumeration value="GATT" />
- <xsd:enumeration value="GOEP" />
- <xsd:enumeration value="HCRP" />
- <xsd:enumeration value="HDP" />
- <xsd:enumeration value="HFP" />
- <xsd:enumeration value="HID" />
- <xsd:enumeration value="HSP" />
- <xsd:enumeration value="ICP" />
- <xsd:enumeration value="LAP" />
- <xsd:enumeration value="MAP" />
- <xsd:enumeration value="OPP" />
- <xsd:enumeration value="PAN" />
- <xsd:enumeration value="PBA" />
- <xsd:enumeration value="PBAP" />
- <xsd:enumeration value="SPP" />
- <xsd:enumeration value="SDAP" />
- <xsd:enumeration value="SAP" />
- <xsd:enumeration value="SIM" />
- <xsd:enumeration value="rSAP" />
- <xsd:enumeration value="SYNCH" />
- <xsd:enumeration value="VDP" />
- <xsd:enumeration value="WAPB" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="gl-version">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the OpenGL version supported for this release.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:decimal">
- <xsd:pattern value="[0-9]\.[0-9]" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="gl-extensions">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies all of the supported OpenGL extensions for
- this release.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:list itemType="xsd:NMTOKEN" />
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="status-bar" type="xsd:boolean">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device has a status bar in this
- software configuration.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:complexType name="stateType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The stateType contains the information for a given state of
- of the device. States include things like portrait mode,
- landscape with the keyboard exposed, etc. States can also
- modify the hardware attributes of a device. For instance, if
- sliding out the keyboard increased the available screen
- real estate, you can define a new screenType to override the
- default one defined in the device's hardwareType.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="description" type="xsd:token">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- A description of the defined state.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
-
- <xsd:element name="screen-orientation">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Defines the orientation of the screen. Use square if
- the device's screen has equal height and width,
- otherwise use landscape or portrait.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="port" />
- <xsd:enumeration value="land" />
- <xsd:enumeration value="square" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="keyboard-state">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Defines the state of the keyboard. If the device has no
- keyboard use keysoft, otherwise use keysexposed or keyshidden.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="keyssoft" />
- <xsd:enumeration value="keyshidden" />
- <xsd:enumeration value="keysexposed" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="nav-state">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Defines the state of the primary non-touchscreen
- navigation hardware on the devices. If the device
- doesn't have non-touchscreen navigation hardware use
- nonav, otherwise use navexposed or navhidden.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="nonav" />
- <xsd:enumeration value="navhidden" />
- <xsd:enumeration value="navexposed" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="screen" type="c:screenType" minOccurs="0" />
- <xsd:element name="networking" type="c:networkingType"
- minOccurs="0" />
- <xsd:element name="sensors" type="c:sensorsType" minOccurs="0" />
- <xsd:element name="mic" type="c:micType" minOccurs="0" />
- <xsd:element name="camera" type="c:cameraType"
- minOccurs="0" maxOccurs="unbounded" />
- <xsd:element name="keyboard" type="c:keyboardType" minOccurs="0" />
- <xsd:element name="nav" type="c:navType" minOccurs="0" />
- <xsd:element name="ram" type="c:ramType" minOccurs="0" />
- <xsd:element name="buttons" type="c:buttonsType" minOccurs="0" />
- <xsd:element name="internal-storage" type="c:internalStorageType"
- minOccurs="0" />
- <xsd:element name="removable-storage" type="c:removableStorageType"
- minOccurs="0" />
- <xsd:element name="cpu" type="c:cpuType" minOccurs="0" />
- <xsd:element name="gpu" type="c:gpuType" minOccurs="0" />
- <xsd:element name="abi" type="c:abiType" minOccurs="0" />
- <xsd:element name="dock" type="c:dockType" minOccurs="0" />
- <xsd:element name="power-type" type="c:powerType"
- minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:token" />
- <xsd:attribute name="default" use="optional" type="xsd:boolean" />
- </xsd:complexType>
-
- <xsd:complexType name="metaType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Details where more device information can be found, such as
- icons and frame images.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="icons" minOccurs="0">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Contains the relative paths to the icon files for this
- device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="sixty-four" type="xsd:normalizedString">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Relative path for the 64x64 icon.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="sixteen" type="xsd:normalizedString"
- minOccurs="0">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Relative path for the 16x16 icon.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="frame" minOccurs="0">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Contains information about the frame for the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="path"
- type="xsd:normalizedString">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The relative path to the emulator frame for
- the device.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="portrait-x-offset"
- type="xsd:nonNegativeInteger">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The offset for the frame in the x direction,
- in portrait mode.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="portrait-y-offset"
- type="xsd:nonNegativeInteger">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The offset for the frame in the y direction,
- in portrait mode.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="landscape-x-offset"
- type="xsd:nonNegativeInteger">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The offset for the frame in the x direction,
- in landscape mode.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="landscape-y-offset"
- type="xsd:nonNegativeInteger">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The offset for the frame in the y direction,
- in landscape mode.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:complexType name="screenType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Contains the specifications for the device's screen.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="screen-size">
- <xsd:simpleType>
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the class of the screen.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="small" />
- <xsd:enumeration value="normal" />
- <xsd:enumeration value="large" />
- <xsd:enumeration value="xlarge" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="diagonal-length">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the diagonal length of the screen in inches.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:decimal">
- <!-- Negative lengths are not valid -->
- <xsd:minInclusive value="0" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="pixel-density">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the screen density of the device. The
- medium density of traditional HVGA screens (mdpi)
- is defined to be approximately 160dpi; low density
- (ldpi) is 120, and high density (hdpi) is 240. There
- is thus a 4:3 scaling factor between each density,
- so a 9x9 bitmap in ldpi would be 12x12 in mdpi and
- 16x16 in hdpi.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="ldpi" />
- <xsd:enumeration value="mdpi" />
- <xsd:enumeration value="tvdpi" />
- <xsd:enumeration value="hdpi" />
- <xsd:enumeration value="xhdpi" />
- <xsd:enumeration value="xxhdpi" />
- <xsd:enumeration value="xxxhdpi" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="screen-ratio">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the configuration is for a taller or
- wider than traditional screen. This is based purely on
- the aspect ratio of the screen: QVGA, HVGA, and VGA are
- notlong; WQVGA, WVGA, FWVGA are long. Note that long may
- mean either wide or tall, depending on the current
- orientation.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="notlong" />
- <xsd:enumeration value="long" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="dimensions">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the device screen resolution in pixels.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="x-dimension">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the x-dimension's resolution in
- pixels.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:positiveInteger" />
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="y-dimension">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the y-dimension's resolution in
- pixels.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:positiveInteger" />
- </xsd:simpleType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
-
- <xsd:element name="xdpi">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the actual density in X of the device screen.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:decimal">
- <!-- Negative DPIs are not valid -->
- <xsd:minInclusive value="0" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="ydpi">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the actual density in Y of the device screen.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:decimal">
- <!-- Negative DPIs are not valid -->
- <xsd:minInclusive value="0" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="touch">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the touch properties of the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="multitouch">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the multitouch capabilities of the
- device. This can be none if multitouch is
- not supported, basic if the device can track
- only basic two finger gestures, distinct if
- the device can track two or more fingers
- simultaneously, or jazz-hands if the device
- can track 5 or more fingers simultaneously.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="none" />
- <xsd:enumeration value="basic" />
- <xsd:enumeration value="distinct" />
- <xsd:enumeration value="jazz-hands" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="mechanism">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the mechanism the device was
- created for.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="notouch" />
- <xsd:enumeration value="stylus" />
- <xsd:enumeration value="finger" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="screen-type">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the type of touch screen on the
- device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="notouch" />
- <xsd:enumeration value="capacitive" />
- <xsd:enumeration value="resistive" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
-
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:simpleType name="networkingType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the available networking hardware.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="NFC" />
- <xsd:enumeration value="Bluetooth" />
- <xsd:enumeration value="Wifi" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
-
- <xsd:simpleType name="sensorsType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the available sensors.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="Accelerometer" />
- <xsd:enumeration value="Barometer" />
- <xsd:enumeration value="Compass" />
- <xsd:enumeration value="GPS" />
- <xsd:enumeration value="Gyroscope" />
- <xsd:enumeration value="LightSensor" />
- <xsd:enumeration value="ProximitySensor" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
-
- <xsd:simpleType name="micType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device has a mic or not.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:boolean" />
- </xsd:simpleType>
-
- <xsd:complexType name="cameraType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the attributes of the camera.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="location">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the location of the camera.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="front" />
- <xsd:enumeration value="back" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="autofocus" type="xsd:boolean">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the camera can autofocus
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
-
- <xsd:element name="flash" type="xsd:boolean">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the camera has flash.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:simpleType name="keyboardType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the type of keyboard on the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="qwerty" />
- <xsd:enumeration value="12key" />
- <xsd:enumeration value="nokeys" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:simpleType name="navType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the primary non-touchscreen navigation
- hardware on the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="dpad" />
- <xsd:enumeration value="trackball" />
- <xsd:enumeration value="wheel" />
- <xsd:enumeration value="nonav" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:complexType name="ramType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the amount of RAM on the device in the unit provided.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleContent>
- <xsd:extension base="xsd:positiveInteger">
- <xsd:attribute name="unit" type="c:storageUnitType" use="required" />
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-
- <xsd:simpleType name="buttonsType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device has physical (hard) buttons
- (Home, Search, etc.), or uses soft buttons.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="hard" />
- <xsd:enumeration value="soft" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:complexType name="internalStorageType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- A list specifying the sizes of internal storage in
- the device, in the storage size unit provided.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleContent>
- <xsd:extension base="c:storageListType">
- <xsd:attribute name="unit" type="c:storageUnitType"
- use="required" />
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-
- <xsd:complexType name="removableStorageType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the range of available removable storage sizes
- in the unit provided. A positive value indicates the device is
- available with that storage size included while a zero value
- indicates an empty storage slot.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleContent>
- <xsd:extension base="c:storageListType">
- <xsd:attribute name="unit" type="c:storageUnitType"
- use="required" />
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-
- <xsd:simpleType name="storageListType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Defines a list for storage configurations such as internal or
- removable storage. A positive value indicates the the device
- has a storage unit of that size, while a zero value indicates
- there is an empty location for a storage unit (such as an empty
- SD card slot).
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:nonNegativeInteger" />
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
- <xsd:simpleType name="gpuType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the device's GPU.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:minLength value="1" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:simpleType name="cpuType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the device's CPU.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:minLength value="1" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:simpleType name="abiType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies which ABIs the device conforms to.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="armeabi" />
- <xsd:enumeration value="armeabi-v7a" />
- <xsd:enumeration value="x86" />
- <xsd:enumeration value="mips" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
-
- <xsd:simpleType name="dockType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the official docks available for the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="desk" />
- <xsd:enumeration value="television" />
- <xsd:enumeration value="car" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
-
- <xsd:simpleType name="powerType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device is plugged in.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="plugged-in" />
- <xsd:enumeration value="battery" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:simpleType name="storageUnitType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the unit of storage. This can be MiB, GiB, etc.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="B" />
- <xsd:enumeration value="KiB" />
- <xsd:enumeration value="MiB" />
- <xsd:enumeration value="GiB" />
- <xsd:enumeration value="TiB" />
- </xsd:restriction>
- </xsd:simpleType>
-
-</xsd:schema>
diff --git a/device_validator/dvlib/src/test/java/com/android/dvlib/DeviceSchemaTest.java b/device_validator/dvlib/src/test/java/com/android/dvlib/DeviceSchemaTest.java
index de70ef3..7c22252 100644
--- a/device_validator/dvlib/src/test/java/com/android/dvlib/DeviceSchemaTest.java
+++ b/device_validator/dvlib/src/test/java/com/android/dvlib/DeviceSchemaTest.java
@@ -46,63 +46,46 @@
public class DeviceSchemaTest extends TestCase {
- private void checkFailure(Map<String, String> replacements, String regex) throws Exception {
- // Generate XML stream with replacements
- InputStream xmlStream = getReplacedStream(replacements);
+ //---- actual tests -----
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- assertFalse(
- "Validation Assertion Failed, XML failed to validate when it was expected to pass\n",
- DeviceSchema.validate(xmlStream, baos, null));
- assertTrue(String.format("Regex Assertion Failed:\nExpected: %s\nActual: %s\n", regex, baos
- .toString().trim()), baos.toString().trim().matches(regex));
- }
-
- private void checkFailure(String resource, String regex) throws Exception {
- InputStream xml = DeviceSchemaTest.class.getResourceAsStream(resource);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- assertFalse("Validation Assertion Failed, XML validated when it was expected to fail\n",
- DeviceSchema.validate(xml, baos, null));
- assertTrue(String.format("Regex Assertion Failed:\nExpected: %s\nActual: %s\n", regex, baos
- .toString().trim()), baos.toString().trim().matches(regex));
- }
-
- private void checkSuccess(Map<String, String> replacements) throws Exception {
- InputStream xmlStream = getReplacedStream(replacements);
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- assertTrue(DeviceSchema.validate(xmlStream, baos, null));
- assertTrue(baos.toString().trim().matches(""));
- }
-
- public static InputStream getReplacedStream(Map<String, String> replacements) throws Exception {
- InputStream xml = DeviceSchema.class.getResourceAsStream("devices_minimal.xml");
- SAXParserFactory factory = SAXParserFactory.newInstance();
- factory.setNamespaceAware(true);
- SAXParser parser = factory.newSAXParser();
- ReplacementHandler replacer = new ReplacementHandler(replacements);
- parser.parse(xml, replacer);
- Document doc = replacer.getGeneratedDocument();
- Transformer tf = TransformerFactory.newInstance().newTransformer();
- // Add indents so we're closer to user generated output
- tf.setOutputProperty(OutputKeys.INDENT, "yes");
- DOMSource source = new DOMSource(doc);
- StringWriter out = new StringWriter();
- StreamResult result = new StreamResult(out);
- tf.transform(source, result);
- return new ByteArrayInputStream(out.toString().getBytes("UTF-8"));
- }
-
- public void testValidXml() throws Exception {
+ public void testValidXml_v1() throws Exception {
InputStream xml = DeviceSchemaTest.class.getResourceAsStream("devices.xml");
+ assertTrue(xml.markSupported());
+ xml.mark(500000); // set mark to beginning of stream
+
+ // Check schema version
+ assertEquals(1, DeviceSchema.getXmlSchemaVersion(xml));
+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
boolean result = DeviceSchema.validate(xml, baos, null);
String output = baos.toString().trim();
assertTrue(
- String.format(
- "Validation Assertion Failed, XML failed to validate when it was expected to pass\n%s\n",output), result);
- assertTrue(String.format("Regex Assertion Failed\nExpected No Output\nActual: %s\n", baos
- .toString().trim()), baos.toString().trim().matches(""));
+ String.format("Validation Assertion Failed, XML failed to validate when it was expected to pass\n%s\n", output),
+ result);
+ assertTrue(String.format(
+ "Regex Assertion Failed\nExpected No Output\nActual: %s\n",
+ baos.toString().trim()),
+ baos.toString().trim().isEmpty());
+ }
+
+ public void testValidXml_v2() throws Exception {
+ InputStream xml = DeviceSchemaTest.class.getResourceAsStream("devices_v2.xml");
+ assertTrue(xml.markSupported());
+ xml.mark(500000); // set mark to beginning of stream
+
+ // Check schema version
+ assertEquals(2, DeviceSchema.getXmlSchemaVersion(xml));
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ boolean result = DeviceSchema.validate(xml, baos, null);
+ String output = baos.toString().trim();
+ assertTrue(
+ String.format("Validation Assertion Failed, XML failed to validate when it was expected to pass\n%s\n", output),
+ result);
+ assertTrue(String.format(
+ "Regex Assertion Failed\nExpected No Output\nActual: %s\n",
+ baos.toString().trim()),
+ baos.toString().trim().isEmpty());
}
public void testNoHardware() throws Exception {
@@ -162,7 +145,6 @@
checkFailure(replacements, "Error: cvc-minInclusive-valid: Value '-1.0'.*\n"
+ "Error: cvc-type.3.1.3: The value '-1.0' of element 'd:diagonal-length'.*");
-
}
public void testInvalidOpenGLVersion() throws Exception {
@@ -204,6 +186,69 @@
+ "Error: cvc-type.3.1.3: The value '' of element 'd:gpu' is not valid.*");
}
+ //---- helper methods -----
+
+ private void checkFailure(Map<String, String> replacements, String regex) throws Exception {
+ // Generate XML stream with replacements
+ InputStream xmlStream = getReplacedStream(replacements);
+ assertTrue(xmlStream.markSupported());
+ xmlStream.mark(500000); // set mark to beginning of stream
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ assertFalse(
+ "Validation Assertion Failed, XML failed to validate when it was expected to pass\n",
+ DeviceSchema.validate(xmlStream, baos, null));
+ String actual = baos.toString().trim();
+ actual = actual.replace("\r\n", "\n"); // Fix Windows CRLF
+ assertTrue(
+ String.format("Regex Assertion Failed:\nExpected: %s\nActual: %s\n", regex, actual),
+ actual.matches(regex));
+ }
+
+ private void checkFailure(String resource, String regex) throws Exception {
+ InputStream xml = DeviceSchemaTest.class.getResourceAsStream(resource);
+ assertTrue(xml.markSupported());
+ xml.mark(500000); // set mark to beginning of stream
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ assertFalse("Validation Assertion Failed, XML validated when it was expected to fail\n",
+ DeviceSchema.validate(xml, baos, null));
+
+ String actual = baos.toString().trim();
+ actual = actual.replace("\r\n", "\n"); // Fix Windows CRLF
+ assertTrue(
+ String.format("Regex Assertion Failed:\nExpected: %s\nActual: %s\n", regex, actual),
+ actual.matches(regex));
+ }
+
+ private void checkSuccess(Map<String, String> replacements) throws Exception {
+ InputStream xmlStream = getReplacedStream(replacements);
+ assertTrue(xmlStream.markSupported());
+ xmlStream.mark(500000); // set mark to beginning of stream
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ assertTrue(DeviceSchema.validate(xmlStream, baos, null));
+ assertTrue(baos.toString().trim().matches(""));
+ }
+
+ public static InputStream getReplacedStream(Map<String, String> replacements) throws Exception {
+ InputStream xml = DeviceSchema.class.getResourceAsStream("devices_minimal.xml");
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ SAXParser parser = factory.newSAXParser();
+ ReplacementHandler replacer = new ReplacementHandler(replacements);
+ parser.parse(xml, replacer);
+ Document doc = replacer.getGeneratedDocument();
+ Transformer tf = TransformerFactory.newInstance().newTransformer();
+ // Add indents so we're closer to user generated output
+ tf.setOutputProperty(OutputKeys.INDENT, "yes");
+ DOMSource source = new DOMSource(doc);
+ StringWriter out = new StringWriter();
+ StreamResult result = new StreamResult(out);
+ tf.transform(source, result);
+ return new ByteArrayInputStream(out.toString().getBytes("UTF-8"));
+ }
+
/**
* Reads in a valid devices XML file and if an element tag is in the
* replacements map, it replaces its text content with the corresponding
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 142d426..59587b8 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
@@ -141,6 +141,7 @@
<d:nav-state>nonav</d:nav-state>
</d:state>
</d:device>
+
<d:device>
<d:name>Droid</d:name>
<d:manufacturer>Motorola</d:manufacturer>
@@ -272,5 +273,130 @@
<d:keyboard-state>keysexposed</d:keyboard-state>
<d:nav-state>navexposed</d:nav-state>
</d:state>
+ <!-- tag-id is optional and can be omitted. -->
+ <!-- boot-props is optional can be omitted or missing. -->
+ <d:boot-props>
+ <!-- boot props list can be empty. -->
+ </d:boot-props>
</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:tag-id>tag-1</d:tag-id>
+ <d:boot-props>
+ <d:boot-prop>
+ <d:prop-name>ro.build.display.id</d:prop-name>
+ <d:prop-value>sdk-eng 4.3 JB_MR2 774058 test-keys</d:prop-value>
+ </d:boot-prop>
+ <d:boot-prop>
+ <d:prop-name>ro-myservice-port</d:prop-name>
+ <d:prop-value>1234</d:prop-value>
+ </d:boot-prop>
+ <d:boot-prop>
+ <d:prop-name>ro.RAM.Size</d:prop-name>
+ <d:prop-value>1024 MiB</d:prop-value>
+ </d:boot-prop>
+ </d:boot-props>
+ </d:device>
+
+
</d:devices>
diff --git a/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_minimal.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_minimal.xml
index e063fd1..13455d5 100644
--- a/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_minimal.xml
+++ b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_minimal.xml
@@ -131,5 +131,12 @@
<d:keyboard-state>keyssoft</d:keyboard-state>
<d:nav-state>nonav</d:nav-state>
</d:state>
+ <d:tag-id>tag-id</d:tag-id>
+ <d:boot-props>
+ <d:boot-prop>
+ <d:prop-name>boot.prop.property</d:prop-name>
+ <d:prop-value>boot.prop.value</d:prop-value>
+ </d:boot-prop>
+ </d:boot-props>
</d:device>
</d:devices>
diff --git a/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_v2.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_v2.xml
new file mode 100755
index 0000000..54a283c
--- /dev/null
+++ b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_v2.xml
@@ -0,0 +1,410 @@
+<?xml version="1.0"?>
+<d:devices
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:d="http://schemas.android.com/sdk/devices/2">
+
+ <d:device>
+ <d:name>
+ Galaxy 64
+ </d:name>
+ <d:id>
+ galaxy_64
+ </d:id>
+ <d:manufacturer>
+ Gnusmas
+ </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>arm64</d:cpu>
+ <d:gpu>PowerVR SGX540</d:gpu>
+ <d:abi>
+ arm64-v8a
+ </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>Droid X86</d:name>
+ <d:manufacturer>Letni</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>854</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>265</d:xdpi>
+ <d:ydpi>265</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</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
+ Compass
+ GPS
+ ProximitySensor
+ 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:keyboard>qwerty</d:keyboard>
+ <d:nav>dpad</d:nav>
+ <d:ram unit="MiB">256</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="MiB">512</d:internal-storage>
+ <d:removable-storage unit="GiB">16</d:removable-storage>
+ <d:cpu>Intel Atom 64</d:cpu>
+ <d:gpu>Intel HD 4000</d:gpu>
+ <d:abi>
+ x86_64
+ </d:abi>
+ <d:dock>
+ car
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+ <d:software>
+ <d:api-level>5-8</d:api-level>
+ <d:live-wallpaper-support>false</d:live-wallpaper-support>
+ <d:bluetooth-profiles>
+ GAP
+ SPP
+ HSP
+ HFP
+ A2DP
+ AVRCP
+ SDAP
+ </d:bluetooth-profiles>
+ <d:gl-version>1.1</d:gl-version>
+ <!--
+ These can be gotten via
+ javax.microedition.khronos.opengles.GL10.glGetString(GL10.GL_EXTENSIONS);
+ -->
+ <d:gl-extensions>
+ GL_OES_byte_coordinates
+ GL_OES_fixed_point
+ GL_OES_single_precision
+ GL_OES_matrix_get
+ GL_OES_read_format
+ GL_OES_compressed_paletted_texture
+ GL_OES_point_sprite
+ GL_OES_point_size_array
+ GL_OES_matrix_palette
+ GL_OES_draw_texture
+ GL_OES_query_matrix
+ GL_OES_texture_env_crossbar
+ GL_OES_texture_mirrored_repeat
+ GL_OES_texture_cube_map
+ GL_OES_blend_subtract
+ GL_OES_blend_func_separate
+ GL_OES_blend_equation_separate
+ GL_OES_stencil_wrap
+ GL_OES_extended_matrix_palette
+ GL_OES_framebuffer_object
+ GL_OES_rgb8_rgba8
+ GL_OES_depth24
+ GL_OES_stencil8
+ GL_OES_compressed_ETC1_RGB8_texture
+ GL_OES_mapbuffer
+ GL_OES_EGL_image
+ GL_EXT_multi_draw_arrays
+ GL_OES_required_internalformat
+ GL_IMG_read_format
+ GL_IMG_texture_compression_pvrtc
+ GL_IMG_texture_format_BGRA8888
+ GL_EXT_texture_format_BGRA8888
+ GL_IMG_texture_stream
+ GL_IMG_vertex_program
+ </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>keyshidden</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape, closed">
+ <d:description>The phone in landscape view with the keyboard closed</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyshidden</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape, open">
+ <d:description>The phone in landscape view with the keyboard open</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keysexposed</d:keyboard-state>
+ <d:nav-state>navexposed</d:nav-state>
+ </d:state>
+ <d:tag-id>tag-1</d:tag-id>
+ <d:boot-props>
+ <d:boot-prop>
+ <d:prop-name>ro.build.display.id</d:prop-name>
+ <d:prop-value>sdk-eng 4.3 JB_MR2 774058 test-keys</d:prop-value>
+ </d:boot-prop>
+ <d:boot-prop>
+ <d:prop-name>ro-myservice-port</d:prop-name>
+ <d:prop-value>1234</d:prop-value>
+ </d:boot-prop>
+ <d:boot-prop>
+ <d:prop-name>ro.RAM.Size</d:prop-name>
+ <d:prop-value>1024 MiB</d:prop-value>
+ </d:boot-prop>
+ </d:boot-props>
+ </d:device>
+
+ <d:device>
+ <d:name>Mips 64</d:name>
+ <d:id>Mips 64</d:id>
+ <d:manufacturer>Mips</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>MIPS32+64</d:cpu>
+ <d:gpu>Adreno 330</d:gpu>
+ <d:abi>
+ mips
+ mips64
+ </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:tag-id>tag-2</d:tag-id>
+ <d:boot-props>
+ <d:boot-prop>
+ <d:prop-name>ro.build.display.id</d:prop-name>
+ <d:prop-value>sdk-eng 4.3 JB_MR2 774058 test-keys</d:prop-value>
+ </d:boot-prop>
+ <d:boot-prop>
+ <d:prop-name>ro-myservice-port</d:prop-name>
+ <d:prop-value>1234</d:prop-value>
+ </d:boot-prop>
+ <d:boot-prop>
+ <d:prop-name>ro.RAM.Size</d:prop-name>
+ <d:prop-value>1024 MiB</d:prop-value>
+ </d:boot-prop>
+ </d:boot-props>
+ </d:device>
+
+
+</d:devices>
diff --git a/draw9patch/build.gradle b/draw9patch/build.gradle
index d4e04b1..b13dd80 100644
--- a/draw9patch/build.gradle
+++ b/draw9patch/build.gradle
@@ -1,8 +1,9 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools'
archivesBaseName = 'draw9patch'
+version = rootProject.ext.baseVersion
dependencies {
testCompile 'junit:junit:3.8.1'
@@ -13,10 +14,16 @@
test.resources.srcDir 'src/test/java'
}
-shipping {
- launcherScripts = ['etc/draw9patch', 'etc/draw9patch.bat']
+sdk {
+ linux {
+ item('etc/draw9patch') { executable true}
+ }
+ mac {
+ item('etc/draw9patch') { executable true}
+ }
+ windows {
+ item 'etc/draw9patch.bat'
+ }
}
-buildDistributionJar.manifest.attributes("Main-Class": "com.android.draw9patch.Application")
-
-apply from: '../baseVersion.gradle'
\ No newline at end of file
+sdkJar.manifest.attributes("Main-Class": "com.android.draw9patch.Application")
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 60e5671..9bc8cae 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
@@ -74,12 +74,18 @@
setOpaque(false);
setLayout(new BorderLayout());
+ is9Patch = name.endsWith(EXTENSION_9PATCH);
+ if (!is9Patch) {
+ this.image = convertTo9Patch(image);
+ this.name = name.substring(0, name.lastIndexOf('.')) + ".9.png";
+ } else {
+ ensure9Patch(image);
+ }
+
loadSupport();
buildImageViewer();
buildStatusPanel();
- checkImage();
-
addAncestorListener(new AncestorListener() {
@Override
public void ancestorAdded(AncestorEvent event) {
@@ -297,16 +303,7 @@
add(status, BorderLayout.SOUTH);
}
- private void checkImage() {
- is9Patch = name.endsWith(EXTENSION_9PATCH);
- if (!is9Patch) {
- convertTo9Patch();
- } else {
- ensure9Patch();
- }
- }
-
- private void ensure9Patch() {
+ private static void ensure9Patch(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
for (int i = 0; i < width; i++) {
@@ -331,7 +328,7 @@
}
}
- private void convertTo9Patch() {
+ private static BufferedImage convertTo9Patch(BufferedImage image) {
BufferedImage buffer = GraphicsUtilities.createTranslucentCompatibleImage(
image.getWidth() + 2, image.getHeight() + 2);
@@ -339,9 +336,7 @@
g2.drawImage(image, 1, 1, null);
g2.dispose();
- image = buffer;
- viewer.setImage(image);
- name = name.substring(0, name.lastIndexOf('.')) + ".9.png";
+ return buffer;
}
File chooseSaveFile() {
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 2eb824f..1b9c73d 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
@@ -85,14 +85,14 @@
fixed = getRectangles(left.fixed, top.fixed);
patches = getRectangles(left.patches, top.patches);
- if (fixed.size() > 0) {
+ if (!fixed.isEmpty()) {
horizontalPatches = getRectangles(left.fixed, top.patches);
verticalPatches = getRectangles(left.patches, top.fixed);
} else {
- if (top.fixed.size() > 0) {
+ if (!top.fixed.isEmpty()) {
horizontalPatches = new ArrayList<Rectangle>(0);
verticalPatches = getVerticalRectangles(top.fixed);
- } else if (left.fixed.size() > 0) {
+ } else if (!left.fixed.isEmpty()) {
horizontalPatches = getHorizontalRectangles(left.fixed);
verticalPatches = new ArrayList<Rectangle>(0);
} else {
@@ -187,6 +187,7 @@
List<Pair<Integer>> fixed = new ArrayList<Pair<Integer>>();
List<Pair<Integer>> patches = new ArrayList<Pair<Integer>>();
+ assert pixels.length > 2 : "Invalid 9-patch, cannot be less than 3 pixels in a dimension";
// ignore layout bound markers for the purpose of patch calculation
lastPixel = pixels[1] != PatchInfo.RED_TICK ? pixels[1] : 0;
diff --git a/gradle-import/NOTICE b/gradle-import/NOTICE
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/gradle-import/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/gradle-import/build.gradle b/gradle-import/build.gradle
new file mode 100644
index 0000000..5549651
--- /dev/null
+++ b/gradle-import/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'java'
+apply plugin: 'sdk-java-lib'
+
+group = 'com.android.tools'
+archivesBaseName = 'gradle-import'
+version = rootProject.ext.baseVersion
+
+dependencies {
+ compile project(':base:sdklib')
+ compile project(':base:lint-api')
+
+ testCompile 'junit:junit:3.8.1'
+}
+
+sourceSets {
+ main.resources.srcDir 'src/main/java'
+ test.resources.srcDir 'src/test/java'
+}
diff --git a/gradle-import/gradle-import.iml b/gradle-import/gradle-import.iml
new file mode 100644
index 0000000..ede78ce
--- /dev/null
+++ b/gradle-import/gradle-import.iml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module 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/test" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="sdklib" />
+ <orderEntry type="library" name="guava-tools" level="project" />
+ <orderEntry type="library" scope="TEST" name="JUnit3" level="project" />
+ <orderEntry type="module" module-name="sdk-common" />
+ <orderEntry type="module" module-name="lint-api" />
+ <orderEntry type="module" module-name="testutils" scope="TEST" />
+ </component>
+</module>
+
diff --git a/gradle-import/src/main/java/com/android/tools/gradle/eclipse/EclipseImportModule.java b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/EclipseImportModule.java
new file mode 100755
index 0000000..0347b5a
--- /dev/null
+++ b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/EclipseImportModule.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.gradle.eclipse;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.repository.GradleCoordinate;
+import com.android.tools.lint.client.api.DefaultConfiguration;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.List;
+
+/** An imported module from Eclipse */
+class EclipseImportModule extends ImportModule {
+ private final EclipseProject mProject;
+ private List<ImportModule> mDirectDependencies;
+ private List<ImportModule> mAllDependencies;
+
+ public EclipseImportModule(@NonNull GradleImport importer, @NonNull EclipseProject project) {
+ super(importer);
+ mProject = project;
+ mProject.setModule(this);
+ }
+
+ @Nullable
+ @Override
+ protected File getLintXml() {
+ File lintXml = new File(mProject.getDir(), DefaultConfiguration.CONFIG_FILE_NAME);
+ return lintXml.exists() ? lintXml : null;
+ }
+
+ @Override
+ @NonNull
+ protected File resolveFile(@NonNull File file) {
+ if (file.isAbsolute()) {
+ return file;
+ } else {
+ return new File(mProject.getDir(), file.getPath());
+ }
+ }
+
+ @Override
+ protected void initDependencies() {
+ super.initDependencies();
+ for (File jar : mProject.getJarPaths()) {
+ if (mImporter.isReplaceJars()) {
+ GradleCoordinate dependency = guessDependency(jar);
+ if (dependency != null) {
+ mDependencies.add(dependency);
+ mImporter.getSummary().reportReplacedJar(jar, dependency);
+ continue;
+ }
+ }
+ mJarDependencies.add(getJarOutputRelativePath(jar));
+ }
+
+ for (File jar : mProject.getTestJarPaths()) {
+ if (mImporter.isReplaceJars()) {
+ GradleCoordinate dependency = guessDependency(jar);
+ if (dependency != null) {
+ mTestDependencies.add(dependency);
+ mImporter.getSummary().reportReplacedJar(jar, dependency);
+ continue;
+ }
+ }
+ // Test jars unconditionally get copied into the libs/ folder
+ mTestJarDependencies.add(getTestJarOutputRelativePath(jar));
+ }
+ }
+
+ public void addDependencies(@NonNull List<GradleCoordinate> dependencies) {
+ for (GradleCoordinate dependency : dependencies) {
+ if (!mDependencies.contains(dependency)) {
+ mDependencies.addAll(dependencies);
+ }
+ }
+ }
+
+ @Override
+ protected boolean dependsOnLibrary(@NonNull String pkg) {
+ if (!isAndroidProject()) {
+ return false;
+ }
+ if (pkg.equals(mProject.getPackage())) {
+ return true;
+ }
+
+ for (EclipseProject project : mProject.getAllLibraries()) {
+ if (project.isAndroidProject() && pkg.equals(project.getPackage())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @NonNull
+ @Override
+ protected List<ImportModule> getDirectDependencies() {
+ if (mDirectDependencies == null) {
+ mDirectDependencies = Lists.newArrayList();
+ for (EclipseProject project : mProject.getDirectLibraries()) {
+ EclipseImportModule module = project.getModule();
+ if (module != null) {
+ mDirectDependencies.add(module);
+ }
+ }
+ }
+
+ return mDirectDependencies;
+ }
+
+ @NonNull
+ @Override
+ protected List<ImportModule> getAllDependencies() {
+ if (mAllDependencies == null) {
+ mAllDependencies = Lists.newArrayList();
+ for (EclipseProject project : mProject.getAllLibraries()) {
+ EclipseImportModule module = project.getModule();
+ if (module != null) {
+ mAllDependencies.add(module);
+ }
+ }
+ }
+
+ return mAllDependencies;
+ }
+
+ @NonNull
+ @Override
+ public File getDir() {
+ return mProject.getDir();
+ }
+
+ @Override
+ protected boolean isAndroidProject() {
+ return mProject.isAndroidProject();
+ }
+
+ @Override
+ protected boolean isLibrary() {
+ return mProject.isLibrary();
+ }
+
+ @Nullable
+ @Override
+ protected String getPackage() {
+ return mProject.getPackage();
+ }
+
+ @NonNull
+ @Override
+ protected String getOriginalName() {
+ return mProject.getName();
+ }
+
+ @Override
+ public boolean isApp() {
+ return mProject.isAndroidProject() && !mProject.isLibrary();
+ }
+
+ @Override
+ public boolean isAndroidLibrary() {
+ return mProject.isAndroidProject() && mProject.isLibrary();
+ }
+
+ @Override
+ public boolean isJavaLibrary() {
+ return !mProject.isAndroidProject();
+ }
+
+ @Override
+ public boolean isNdkProject() {
+ return mProject.isNdkProject();
+ }
+
+ @Override
+ @Nullable protected File getManifestFile() {
+ return mProject.getManifestFile();
+ }
+
+ @Override
+ @Nullable protected File getResourceDir() {
+ return mProject.getResourceDir();
+ }
+
+ @Override
+ @Nullable protected File getAssetsDir() {
+ return mProject.getAssetsDir();
+ }
+
+ @Override
+ @NonNull
+ protected List<File> getSourcePaths() {
+ return mProject.getSourcePaths();
+ }
+
+ @Override
+ @NonNull
+ protected List<File> getJarPaths() {
+ return mProject.getJarPaths();
+ }
+
+ @Override
+ @NonNull
+ protected List<File> getTestJarPaths() {
+ return mProject.getTestJarPaths();
+ }
+
+ @Override
+ @NonNull
+ protected List<File> getNativeLibs() {
+ return mProject.getNativeLibs();
+ }
+
+ @Override
+ @Nullable
+ protected File getNativeSources() {
+ return mProject.getNativeSources();
+ }
+
+ @Nullable
+ @Override
+ protected String getNativeModuleName() {
+ return mProject.getNativeModuleName();
+ }
+
+ @Override
+ @NonNull
+ protected List<File> getLocalProguardFiles() {
+ return mProject.getLocalProguardFiles();
+ }
+
+ @NonNull
+ @Override
+ protected List<File> getSdkProguardFiles() {
+ return mProject.getSdkProguardFiles();
+ }
+
+ @NonNull
+ @Override
+ protected File getCanonicalModuleDir() {
+ return mProject.getCanonicalDir();
+ }
+
+ @Nullable
+ @Override
+ protected File getOutputDir() {
+ return mProject.getOutputDir();
+ }
+
+ @NonNull
+ @Override
+ protected String getLanguageLevel() {
+ return mProject.getLanguageLevel();
+ }
+
+ @Override
+ protected int getCompileSdkVersion() {
+ return mProject.getCompileSdkVersion();
+ }
+
+ @Override
+ protected int getTargetSdkVersion() {
+ return mProject.getTargetSdkVersion();
+ }
+
+ @Override
+ protected int getMinSdkVersion() {
+ return mProject.getMinSdkVersion();
+ }
+
+ @Override
+ protected boolean dependsOn(@NonNull ImportModule other) {
+ return mProject.getAllLibraries().contains(((EclipseImportModule)other).mProject);
+ }
+
+ @NonNull
+ public EclipseProject getProject() {
+ return mProject;
+ }
+
+ @Nullable
+ @Override
+ protected File getInstrumentationDir() {
+ return mProject.getInstrumentationDir();
+ }
+}
diff --git a/gradle-import/src/main/java/com/android/tools/gradle/eclipse/EclipseProject.java b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/EclipseProject.java
new file mode 100755
index 0000000..26fcb3f
--- /dev/null
+++ b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/EclipseProject.java
@@ -0,0 +1,1168 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.gradle.eclipse;
+
+import static com.android.SdkConstants.ANDROID_LIBRARY;
+import static com.android.SdkConstants.ANDROID_LIBRARY_REFERENCE_FORMAT;
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_PACKAGE;
+import static com.android.SdkConstants.CURRENT_PLATFORM;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.FD_ASSETS;
+import static com.android.SdkConstants.FD_RES;
+import static com.android.SdkConstants.FD_SOURCES;
+import static com.android.SdkConstants.FN_PROJECT_PROPERTIES;
+import static com.android.SdkConstants.GEN_FOLDER;
+import static com.android.SdkConstants.LIBS_FOLDER;
+import static com.android.SdkConstants.PLATFORM_WINDOWS;
+import static com.android.SdkConstants.PROGUARD_CONFIG;
+import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_SDK;
+import static com.android.tools.gradle.eclipse.GradleImport.CURRENT_COMPILE_VERSION;
+import static com.android.tools.gradle.eclipse.GradleImport.ECLIPSE_DOT_CLASSPATH;
+import static com.android.tools.gradle.eclipse.GradleImport.ECLIPSE_DOT_PROJECT;
+import static com.android.tools.gradle.eclipse.GradleImport.isEclipseProjectDir;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_TARGET_PACKAGE;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION;
+import static com.android.xml.AndroidManifest.NODE_INSTRUMENTATION;
+import static com.android.xml.AndroidManifest.NODE_USES_SDK;
+import static java.io.File.separator;
+import static java.io.File.separatorChar;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.utils.SdkUtils;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+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.ListIterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Provides information about an Eclipse project */
+class EclipseProject implements Comparable<EclipseProject> {
+ static final String DEFAULT_LANGUAGE_LEVEL = "1.6";
+
+ private final GradleImport mImporter;
+ private final File mDir;
+ private final File mCanonicalDir;
+ private boolean mLibrary;
+ private boolean mAndroidProject;
+ private boolean mNdkProject;
+ private int mMinSdkVersion;
+ private int mTargetSdkVersion;
+ private Document mProjectDoc;
+ private Document mManifestDoc;
+ private Properties mProjectProperties;
+ private AndroidVersion mVersion;
+ private String mName;
+ private String mLanguageLevel;
+ private List<EclipseProject> mDirectLibraries;
+ private List<File> mSourcePaths;
+ private List<File> mJarPaths;
+ private List<File> mInstrumentationJarPaths;
+ private List<File> mNativeLibs;
+ private File mNativeSources;
+ private String mNativeModuleName;
+ private File mOutputDir;
+ private String mPackage;
+ private List<File> mLocalProguardFiles;
+ private List<File> mSdkProguardFiles;
+ private List<EclipseProject> mAllLibraries;
+ private EclipseImportModule mModule;
+ private Map<String,String> mProjectVariableMap;
+ private Map<String,String> mLinkedResourceMap;
+ private File mInstrumentationDir;
+
+ private EclipseProject(
+ @NonNull GradleImport importer,
+ @NonNull File dir) throws IOException {
+ mImporter = importer;
+ mDir = dir;
+ mCanonicalDir = dir.getCanonicalFile();
+
+ // Ensure that the library references (which are canonicalized) find this project
+ // if included from multiple locations
+ mImporter.registerProject(this);
+
+ initProjectName();
+ initAndroidProject();
+ initLanguageLevel();
+
+ if (isAndroidProject()) {
+ Properties properties = getProjectProperties();
+ initProguard(properties);
+ initVersion(properties);
+ initLibraries(properties);
+ initLibrary(properties);
+ initPackage();
+ initMinSdkVersion();
+ initInstrumentation();
+ } else {
+ mDirectLibraries = new ArrayList<EclipseProject>(4);
+ }
+
+ initClassPathEntries();
+ initJni();
+ }
+
+ @NonNull
+ public static EclipseProject getProject(@NonNull GradleImport importer, @NonNull File dir)
+ throws IOException {
+ Map<File,EclipseProject> mProjectMap = importer.getProjectMap();
+ EclipseProject project = mProjectMap.get(dir);
+
+ if (project == null) {
+ project = createProject(importer, dir);
+ // The project should register itself in the map; we don't have to do that here.
+ // (The code used to do that here, but it turns out project creation can recursively
+ // visit library references as part of initialization, so have the projects register
+ // themselves prior to initialization instead)
+ assert mProjectMap.get(dir) != null;
+ }
+
+ return project;
+ }
+
+ @NonNull
+ private static EclipseProject createProject(@NonNull GradleImport importer, @NonNull File dir)
+ throws IOException {
+ // Read the .classpath, .project, project.properties and local.properties files (if there)
+ return new EclipseProject(importer, dir);
+ }
+
+ private void initVersion(Properties properties) {
+ String target = properties.getProperty("target"); //$NON-NLS-1$
+ if (target != null) {
+ mVersion = AndroidTargetHash.getPlatformVersion(target);
+ }
+ }
+
+ private void initLibraries(Properties properties) throws IOException {
+ mDirectLibraries = new ArrayList<EclipseProject>(4);
+
+ for (int i = 0; i < 1000; i++) {
+ String key = String.format(ANDROID_LIBRARY_REFERENCE_FORMAT, i);
+ String library = properties.getProperty(key);
+ if (library == null || library.isEmpty()) {
+ // No holes in the numbering sequence is allowed
+ if (i == 0) {
+ // Except for i=0; library projects are supposed to start with 1, and
+ // all the ADT, sdklib and ant code which reads and writes these start with
+ // 1, but I've encountered several projects in the wild that start with 0;
+ // presumably from manual edits or because some older version of the tools
+ // did this.
+ // Instead of bailing here, try 1 too.
+ continue;
+ } else {
+ // After 1, we don't allow any gaps in the sequence
+ break;
+ }
+ }
+
+ // Handle importing Windows-relative paths in project.properties on non-Windows,
+ // and vice versa
+ if (CURRENT_PLATFORM == PLATFORM_WINDOWS) {
+ library = library.replace('/', '\\');
+ } else {
+ library = library.replace('\\', '/');
+ }
+
+ File path = new File(library);
+ File joined = path.isAbsolute() ? path : new File(mDir, library);
+ File libraryDir = joined.getCanonicalFile();
+ if (!libraryDir.exists()) {
+ String message = "Library reference " + library + " could not be found";
+ if (!path.isAbsolute()) {
+ message += "\nPath is " + joined + " which resolves to " + libraryDir.getPath();
+ }
+ mImporter.reportError(this, getProjectPropertiesFile(), message);
+ }
+
+ EclipseProject libraryPrj = getProject(mImporter, libraryDir);
+ mDirectLibraries.add(libraryPrj);
+ }
+ }
+
+ private void initLibrary(Properties properties) throws IOException {
+ // This initialization must run after we've initialized the set of library
+ // projects so we know whether or not we're including/merging manifests
+ assert mDirectLibraries != null;
+ String value = properties.getProperty(ANDROID_LIBRARY);
+ mLibrary = VALUE_TRUE.equals(value);
+
+ if (!mLibrary) {
+ boolean mergeManifests = VALUE_TRUE.equals(properties.getProperty(
+ "manifestmerger.enabled")); //$NON-NLS-1$
+ if (!mergeManifests) {
+ // See if we (transitively) depend on libraries, and if any of them are
+ // android library projects with non-empty manifests
+ for (EclipseProject library : getAllLibraries()) {
+ if (library.isAndroidProject() && library.isLibrary() &&
+ library.getManifestFile().exists() &&
+ library.getManifestDoc().getDocumentElement() != null &&
+ XmlUtils.hasElementChildren(library.getManifestDoc().
+ getDocumentElement())) {
+ mImporter.getSummary().reportManifestsMayDiffer();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private void initPackage() throws IOException {
+ mPackage = getManifestDoc().getDocumentElement().getAttribute(ATTR_PACKAGE);
+ }
+
+ private void initMinSdkVersion() throws IOException {
+ NodeList usesSdks = getManifestDoc().getDocumentElement().getElementsByTagName(
+ NODE_USES_SDK);
+ if (usesSdks.getLength() > 0) {
+ Element usesSdk = (Element) usesSdks.item(0);
+ mMinSdkVersion = getApiVersion(usesSdk, ATTRIBUTE_MIN_SDK_VERSION, 1);
+ mTargetSdkVersion = getApiVersion(usesSdk, ATTRIBUTE_TARGET_SDK_VERSION,
+ mMinSdkVersion);
+ } else {
+ mMinSdkVersion = -1;
+ mTargetSdkVersion = -1;
+ }
+ }
+
+ private void initProjectName() throws IOException {
+ Document document = getProjectDocument();
+ if (document == null) {
+ return;
+ }
+ NodeList names = document.getElementsByTagName("name");
+
+ for (int i = 0; i < names.getLength(); i++) {
+ Node element = names.item(i);
+ mName = getStringValue((Element) element);
+ //noinspection VariableNotUsedInsideIf
+ if (mName != null) {
+ break;
+ }
+ }
+
+ if (mName == null) {
+ mName = mDir.getName();
+ }
+ }
+
+ private static int getApiVersion(Element usesSdk, String attribute, int defaultApiLevel) {
+ String valueString = null;
+ if (usesSdk.hasAttributeNS(ANDROID_URI, attribute)) {
+ valueString = usesSdk.getAttributeNS(ANDROID_URI, attribute);
+ }
+
+ if (valueString != null) {
+ int apiLevel = -1;
+ try {
+ apiLevel = Integer.valueOf(valueString);
+ } catch (NumberFormatException e) {
+ // TODO: Handle code names?
+ }
+
+ return apiLevel;
+ }
+
+ return defaultApiLevel;
+ }
+
+ private void initJni() throws IOException {
+ File jniDir = new File(mDir, "jni");
+ if (!jniDir.exists()) {
+ return;
+ }
+
+ //noinspection SpellCheckingInspection
+ if (mNdkProject) {
+ mNativeSources = jniDir;
+
+ File makefile = new File(jniDir, "Android.mk");
+ if (makefile.exists()) {
+ Pattern pattern = Pattern.compile("\\s*LOCAL_MODULE\\s*:=\\s*(\\S+)\\s*");
+ for (String line : Files.readLines(makefile, Charsets.UTF_8)) {
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.matches()) {
+ mNativeModuleName = matcher.group(1);
+
+ if (mNativeLibs != null) {
+ // Remove libs from the libs/<abi> folder if they are just
+ // outputs from these sources
+ String libName = "lib" + mNativeModuleName + ".so";
+ ListIterator<File> iterator = mNativeLibs.listIterator();
+ while (iterator.hasNext()) {
+ File lib = iterator.next();
+ if (libName.equals(lib.getName())) {
+ iterator.remove();
+ }
+ }
+ if (mNativeLibs.isEmpty()) {
+ mNativeLibs = null;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private void initInstrumentation() throws IOException {
+ // Find unit test projects pointing to this Gradle project. Where do we look?
+ // For now, in direct sub directories of the project, as well as sibling directories
+
+ File projectDir = findInstrumentationTests(mDir);
+ if (projectDir == null && mDir.getParentFile() != null) {
+ projectDir = findInstrumentationTests(mDir.getParentFile());
+ }
+
+ if (projectDir != null && !projectDir.equals(mDir)) {
+ mInstrumentationDir = projectDir;
+
+ File libs = new File(mInstrumentationDir, LIBS_FOLDER);
+ if (libs.exists()) {
+ File[] files = libs.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile() && endsWithIgnoreCase(file.getPath(), DOT_JAR)) {
+ if (mInstrumentationJarPaths == null) {
+ mInstrumentationJarPaths = Lists.newArrayList();
+ }
+ mInstrumentationJarPaths.add(file);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private File findInstrumentationTests(File parent) {
+ File[] files = parent.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ File manifest = new File(file, ANDROID_MANIFEST_XML);
+ if (manifest.exists()) {
+ try {
+ String target = getInstrumentationTarget(mImporter, manifest);
+ if (target != null && target.equals(mPackage)) {
+ return file;
+ }
+ } catch (IOException e) {
+ // Ignore this manifest
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static String getInstrumentationTarget(
+ @NonNull GradleImport importer,
+ @NonNull File manifest) throws IOException {
+ Document doc = importer.getXmlDocument(manifest, true);
+ NodeList list = doc.getElementsByTagName(NODE_INSTRUMENTATION);
+ for (int i = 0; i < list.getLength(); i++) {
+ Element tag = (Element) list.item(i);
+ String target = tag.getAttributeNS(ANDROID_URI, ATTRIBUTE_TARGET_PACKAGE);
+ if (target != null && !target.isEmpty()) {
+ return target;
+ }
+ }
+
+ return null;
+ }
+
+ private void initClassPathEntries() throws IOException {
+ assert mSourcePaths == null && mJarPaths == null;
+ mSourcePaths = Lists.newArrayList();
+ mJarPaths = Lists.newArrayList();
+
+ Document document = null;
+ File classPathFile = getClassPathFile();
+ if (!classPathFile.exists()) {
+ File src = new File(mDir, FD_SOURCES);
+ if (src.exists()) {
+ mSourcePaths.add(src);
+ }
+ } else {
+ document = mImporter.getXmlDocument(classPathFile, false);
+ }
+
+ if (document != null) {
+ NodeList entries = document.getElementsByTagName("classpathentry");
+ for (int i = 0; i < entries.getLength(); i++) {
+ Node entry = entries.item(i);
+ assert entry.getNodeType() == Node.ELEMENT_NODE;
+ Element element = (Element) entry;
+ String kind = element.getAttribute("kind");
+ String path = element.getAttribute("path");
+ if (kind.equals("var")) {
+ File resolved = resolveVariableExpression(path);
+ if (resolved != null) {
+ mSourcePaths.add(resolved);
+ } else {
+ mImporter.reportWarning(this, getClassPathFile(),
+ "Could not resolve path variable " + path);
+ }
+ } else if (kind.equals("src") && !path.isEmpty()) {
+ if (!path.equals(GEN_FOLDER)) { // ignore special generated source folder
+ File resolved = resolveVariableExpression(path);
+ if (resolved != null) {
+ if (path.startsWith("/") && isEclipseProjectDir(resolved)) {
+ // It's pointing to another project. Just add a dependency.
+ EclipseProject lib = getProject(mImporter, resolved);
+ if (!mDirectLibraries.contains(lib)) {
+ mDirectLibraries.add(lib);
+ mAllLibraries = null; // force refresh if already consulted
+ }
+ } else {
+ // It's some other source directory: just include as a source path
+ mSourcePaths.add(resolved);
+ }
+ } else {
+ mImporter.reportWarning(this, getClassPathFile(),
+ "Could not resolve source path " + path + " in project "
+ + getName() + ": ignored. The project may not "
+ + "compile if the given source path provided "
+ + "source code.");
+ }
+ }
+ } else if (kind.equals("lib") && !path.isEmpty()) {
+ // Java library dependency. In Android projects we don't need these since
+ // we pick up the information from the project.properties file for library
+ // dependencies and the libs/ folder for jar files.
+ if (!isAndroidProject()) {
+ File resolved = resolveVariableExpression(path);
+ if (resolved != null) {
+ mJarPaths.add(resolved);
+ } else {
+ mImporter.reportWarning(this, getClassPathFile(),
+ "Absolute path in the path entry: If outside project, may not "
+ + "work correctly: " + path);
+ }
+ }
+ } else if (kind.equals("output") && !path.isEmpty()) {
+ String relative = path.replace('/', separatorChar);
+ File file = new File(relative);
+ if (!file.isAbsolute()) {
+ mOutputDir = file;
+ }
+ }
+ // else: ignore kind="con"
+ }
+ }
+
+ // Automatically add in libraries in libs
+ File[] libs = new File(mDir, LIBS_FOLDER).listFiles();
+ if (libs != null) {
+ for (File lib : libs) {
+ if (!lib.isFile()) {
+ // ABI folder?
+ File[] libraries = lib.listFiles();
+ if (libraries != null) {
+ for (File library : libraries) {
+ String name = library.getName();
+ if (library.isFile() && name.startsWith("lib")
+ && name.contains(".so")) { // or .endsWith? Allow libfoo.so.1 ?
+ if (mNativeLibs == null) {
+ mNativeLibs = Lists.newArrayList();
+ }
+ File relative = new File(LIBS_FOLDER,
+ lib.getName() + separator + library.getName());
+ mNativeLibs.add(relative);
+ }
+ }
+ }
+ continue;
+ }
+ assert lib.isFile();
+ if (!endsWithIgnoreCase(lib.getPath(), DOT_JAR)) {
+ continue;
+ }
+ File relative = new File(LIBS_FOLDER, lib.getName());
+ if (!(mJarPaths.contains(relative) || mJarPaths.contains(lib))) {
+ // Skip jars that are the result of a library project dependency
+ boolean isLibraryJar = false;
+ for (EclipseProject project : getAllLibraries()) {
+ if (!project.isAndroidProject()) {
+ continue;
+ }
+ String pkg = project.getPackage();
+ if (pkg != null) {
+ String jarName = pkg.replace('.', '-') + DOT_JAR;
+ if (jarName.equals(lib.getName())) {
+ isLibraryJar = true;
+ break;
+ }
+ }
+ }
+ if (!isLibraryJar) {
+ mJarPaths.add(relative);
+ }
+ }
+ }
+ }
+ }
+
+ private Map<String,String> getProjectVariableMap() {
+ if (mProjectVariableMap == null) {
+ mProjectVariableMap = Maps.newHashMap();
+
+ Document document;
+ try {
+ document = getProjectDocument();
+ if (document == null) {
+ return mProjectVariableMap;
+ }
+ } catch (IOException e) {
+ return mProjectVariableMap;
+ }
+ assert document != null;
+ NodeList variables = document.getElementsByTagName("variable");
+ for (int i = 0, n = variables.getLength(); i < n; i++) {
+ Element variable = (Element) variables.item(i);
+ NodeList names = variable.getElementsByTagName("name");
+ NodeList values = variable.getElementsByTagName("value");
+ if (names.getLength() == 1 && values.getLength() == 1) {
+ String value = getStringValue((Element)values.item(0));
+ String key = getStringValue((Element) names.item(0));
+ mProjectVariableMap.put(key, value);
+ }
+ }
+ }
+
+ return mProjectVariableMap;
+ }
+
+ private Map<String,String> getLinkedResourceMap() {
+ if (mLinkedResourceMap == null) {
+ mLinkedResourceMap = Maps.newHashMap();
+
+ Document document;
+ try {
+ document = getProjectDocument();
+ if (document == null) {
+ return mProjectVariableMap;
+ }
+ } catch (IOException e) {
+ return mLinkedResourceMap;
+ }
+ assert document != null;
+ NodeList links = document.getElementsByTagName("link");
+ for (int i = 0, n = links.getLength(); i < n; i++) {
+ Element variable = (Element) links.item(i);
+ NodeList names = variable.getElementsByTagName("name");
+ NodeList values = variable.getElementsByTagName("locationURI");
+ if (names.getLength() == 1 && values.getLength() == 1) {
+ String value = getStringValue((Element)values.item(0));
+ String key = getStringValue((Element) names.item(0));
+ mLinkedResourceMap.put(key, value);
+ }
+ }
+
+ }
+
+ return mLinkedResourceMap;
+ }
+
+ @VisibleForTesting
+ @Nullable
+ File resolveVariableExpression(@NonNull String path) throws IOException {
+ File file = resolveVariableExpression(path, true, 0);
+ if (file != null && mImporter.getPathMap().containsKey(path)) {
+ mImporter.getPathMap().put(path, file);
+ }
+ return file;
+ }
+
+ @Nullable
+ private File resolveVariableExpression(@NonNull String path, boolean record, int depth)
+ throws IOException {
+ if (depth > 50) { // probably cyclical definition of variables
+ return null;
+ }
+ if (path.equals("PROJECT_LOC")) {
+ return mDir;
+ } else if (path.equals("PARENT_LOC")) {
+ return mDir.getParentFile();
+ } else if (path.equals("WORKSPACE_LOC")) {
+ return mImporter.getEclipseWorkspace();
+ } else if (path.startsWith("PARENT-")) {
+ Pattern pattern = Pattern.compile("PARENT-(\\d+)-(.+)");
+ Matcher matcher = pattern.matcher(path);
+ if (matcher.matches()) {
+ // Replace suffix a given number of times
+ int count = Integer.parseInt(matcher.group(1));
+ String target = matcher.group(2);
+ int index = target.indexOf('/');
+ if (index == -1) {
+ index = target.indexOf('\\');
+ }
+ String var = index == -1 ? target : target.substring(0, index);
+ File file = resolveVariableExpression(var, false, depth + 1);
+ if (file != null){
+ File original = file;
+ for (int i = 0; i < count; i++) {
+ if (file == null) {
+ break;
+ }
+ file = file.getParentFile();
+ }
+ if (file == null) {
+ // Try again but with canonical files
+ file = original.getCanonicalFile();
+ for (int i = 0; i < count; i++) {
+ if (file == null) {
+ break;
+ }
+ file = file.getParentFile();
+ }
+
+ }
+ }
+
+ if (file != null && index != -1) {
+ file = new File(file, target.substring(index + 1));
+ }
+ return file;
+ }
+ }
+
+ // See if it's an absolute path
+ String filePath = path.replace('/', separatorChar);
+ File resolved = new File(filePath);
+ if (resolved.exists()) {
+ return resolved;
+ }
+
+ // See if it's a relative path
+ resolved = new File(mDir, filePath);
+ if (resolved.exists()) {
+ return resolved;
+ }
+
+ // Look up in shared path map (and record path for user editing in wizard
+ // if not resolvable)
+ // No -- this needs to be per project?? Only if it's used in multiple projects...
+ resolved = mImporter.getPathMap().get(path);
+ if (resolved != null) {
+ return resolved;
+ }
+
+ if (record) {
+ // Record the path expression such that the user can provide a resolution
+ mImporter.getPathMap().put(path, null);
+ }
+
+ // Workspace path?
+ if (path.startsWith("/")) { // It's / on Windows too
+ // Workspace path
+ resolved = mImporter.resolveWorkspacePath(this, path, record);
+ if (resolved != null) {
+ return resolved;
+ }
+
+ if (path.indexOf('/', 1) == -1 && path.indexOf('\\', 1) == -1) {
+ String name = path.substring(1);
+ // If we can't resolve workspace paths, try looking relative
+ // to the current project; dependent projects are often there
+ File parent = mDir.getParentFile();
+ if (parent != null) {
+ File sibling = new File(parent, name);
+ if (sibling.exists()) {
+ return sibling;
+ }
+ }
+
+ // Libraries are also often children
+ File child = new File(mDir, name);
+ if (child.exists()) {
+ return child;
+ }
+ }
+ } else if (path.startsWith("$%7B")) {
+ // E.g. "<value>$%7BPARENT-2-PARENT_LOC%7D/Users</value>"
+ // This corresponds to {PARENT_LOC}/../../
+ int start = 4;
+ int end = path.indexOf("%7D", 4);
+ if (end != -1) {
+ String sub = path.substring(start, end);
+ File expression = resolveVariableExpression(sub, false, depth + 1);
+ if (expression != null) {
+ String suffix = path.substring(end + 3);
+ if (suffix.isEmpty()) {
+ return expression;
+ } else {
+ resolved = new File(expression, suffix.replace('/', separatorChar));
+ if (resolved.exists()) {
+ return resolved;
+ }
+ }
+ }
+ }
+ } else {
+ // Path variable?
+ int index = path.indexOf('/');
+ if (index == -1) {
+ index = path.indexOf('\\');
+ }
+ String var;
+ if (index == -1) {
+ var = path;
+ } else {
+ var = path.substring(0, index);
+ }
+
+ Map<String, String> map = getLinkedResourceMap();
+ String expression = map.get(var);
+ if (expression == null || expression.equals(var)) {
+ map = getProjectVariableMap();
+ expression = map.get(var);
+ }
+ File file;
+ if (expression != null) {
+ if (expression.startsWith("file:")) {
+ file = SdkUtils.urlToFile(expression);
+ } else {
+ file = resolveVariableExpression(expression, false, depth + 1);
+ }
+ } else {
+ file = mImporter.resolvePathVariable(this, var, false);
+ }
+ if (file != null) {
+ if (index == -1) {
+ return file;
+ } else {
+ resolved = new File(file, path.substring(index + 1));
+ if (resolved.exists()) {
+ return resolved;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void initAndroidProject() throws IOException {
+ mAndroidProject = hasNature("com.android.ide.eclipse.adt.AndroidNature");
+ if (!mAndroidProject && getProjectDocument() == null) {
+ mAndroidProject = GradleImport.isAdtProjectDir(mDir);
+ }
+ mNdkProject = mAndroidProject && (
+ hasNature("org.eclipse.cdt.core.cnature") ||
+ hasNature("org.eclipse.cdt.core.ccnature") ||
+ new File(mDir, "jni" + separator + "Android.mk").exists()
+ );
+ }
+
+ private boolean hasNature(String nature) throws IOException {
+ Document document = getProjectDocument();
+ if (document != null) {
+ NodeList natures = document.getElementsByTagName("nature");
+ for (int i = 0; i < natures.getLength(); i++) {
+ Node element = natures.item(i);
+ String value = getStringValue((Element) element);
+ if (nature.equals(value)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void initLanguageLevel() throws IOException {
+ if (mLanguageLevel == null) {
+ mLanguageLevel = DEFAULT_LANGUAGE_LEVEL; // default
+ File file = new File(mDir, ".settings" + separator + "org.eclipse.jdt.core.prefs");
+ if (file.exists()) {
+ Properties properties = GradleImport.getProperties(file);
+ if (properties != null) {
+ String source =
+ properties.getProperty("org.eclipse.jdt.core.compiler.source");
+ if (source != null) {
+ mLanguageLevel = source;
+ }
+ }
+ }
+ }
+ }
+
+ private void initProguard(Properties properties) {
+ mLocalProguardFiles = Lists.newArrayList();
+ mSdkProguardFiles = Lists.newArrayList();
+
+ String proguardConfig = properties.getProperty(PROGUARD_CONFIG);
+ if (proguardConfig != null && !proguardConfig.isEmpty()) {
+ // Be tolerant with respect to file and path separators just like
+ // Ant is. Allow "/" in the property file to mean whatever the file
+ // separator character is:
+ if (File.separatorChar != '/' && proguardConfig.indexOf('/') != -1) {
+ proguardConfig = proguardConfig.replace('/', File.separatorChar);
+ }
+
+ Iterable<String> paths = LintUtils.splitPath(proguardConfig);
+ for (String path : paths) {
+ if (path.startsWith(SDK_PROPERTY_REF)) {
+ mSdkProguardFiles.add(new File(path.substring(SDK_PROPERTY_REF.length())
+ .replace('/', separatorChar)));
+ } else if (path.startsWith(HOME_PROPERTY_REF)) {
+ mImporter.getSummary().reportIgnoredUserHomeProGuardFile(path);
+ } else {
+ File proguardConfigFile = new File(path.replace('/', separatorChar));
+ if (!proguardConfigFile.isAbsolute()) {
+ proguardConfigFile = new File(mDir, proguardConfigFile.getPath());
+ }
+ if (proguardConfigFile.isFile()) {
+ mLocalProguardFiles.add(proguardConfigFile);
+ }
+ }
+ }
+ }
+ }
+
+ @NonNull
+ public File getDir() {
+ return mDir;
+ }
+
+ @NonNull
+ public File getCanonicalDir() {
+ return mCanonicalDir;
+ }
+
+ public boolean isLibrary() {
+ return mLibrary;
+ }
+
+ private static final String HOME_PROPERTY = "user.home"; //$NON-NLS-1$
+ private static final String HOME_PROPERTY_REF = "${" + HOME_PROPERTY + '}'; //$NON-NLS-1$
+ private static final String SDK_PROPERTY_REF = "${" + PROPERTY_SDK + '}'; //$NON-NLS-1$
+
+ @NonNull
+ public List<File> getLocalProguardFiles() {
+ assert isAndroidProject();
+ return mLocalProguardFiles;
+ }
+
+ @NonNull
+ public List<File> getSdkProguardFiles() {
+ assert isAndroidProject();
+ return mSdkProguardFiles;
+ }
+
+ @NonNull
+ public File getResourceDir() {
+ assert isAndroidProject();
+ return new File(mDir, FD_RES);
+ }
+
+ @NonNull
+ public File getAssetsDir() {
+ assert isAndroidProject();
+ return new File(mDir, FD_ASSETS);
+ }
+
+ @NonNull
+ private File getClassPathFile() {
+ return new File(mDir, ECLIPSE_DOT_CLASSPATH);
+ }
+
+ @NonNull
+ public Document getManifestDoc() throws IOException {
+ assert isAndroidProject();
+ if (mManifestDoc == null) {
+ File file = getManifestFile();
+ mManifestDoc = mImporter.getXmlDocument(file, true);
+ }
+
+ return mManifestDoc;
+ }
+
+ @NonNull
+ File getManifestFile() {
+ assert isAndroidProject();
+ return new File(mDir, ANDROID_MANIFEST_XML);
+ }
+
+ @Nullable
+ public Properties getProjectProperties() throws IOException {
+ if (mProjectProperties == null) {
+ assert isAndroidProject();
+ File file = getProjectPropertiesFile();
+ if (file.exists()) {
+ mProjectProperties = GradleImport.getProperties(file);
+ } else {
+ mProjectProperties = new Properties();
+ }
+ }
+
+ return mProjectProperties;
+ }
+
+ private File getProjectPropertiesFile() {
+ return new File(mDir, FN_PROJECT_PROPERTIES);
+ }
+
+ @Nullable
+ private Document getProjectDocument() throws IOException {
+ if (mProjectDoc == null) {
+ File file = new File(mDir, ECLIPSE_DOT_PROJECT);
+ if (file.exists()) {
+ mProjectDoc = mImporter.getXmlDocument(file, false);
+ }
+ }
+
+ return mProjectDoc;
+ }
+
+ public boolean isAndroidProject() {
+ return mAndroidProject;
+ }
+
+ public boolean isNdkProject() {
+ return mNdkProject;
+ }
+
+ @Nullable
+ public File getInstrumentationDir() {
+ return mInstrumentationDir;
+ }
+
+ @Nullable
+ private static String getStringValue(@NonNull Element element) {
+ NodeList children = element.getChildNodes();
+ for (int j = 0; j < children.getLength(); j++) {
+ Node child = children.item(j);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ return child.getNodeValue().trim();
+ }
+
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public String getPackage() {
+ assert isAndroidProject();
+ return mPackage;
+ }
+
+ @NonNull
+ public List<File> getSourcePaths() {
+ return mSourcePaths;
+ }
+
+ @NonNull
+ public List<File> getJarPaths() {
+ return mJarPaths;
+ }
+
+ @NonNull
+ public List<File> getTestJarPaths() {
+ return mInstrumentationJarPaths != null
+ ? mInstrumentationJarPaths : Collections.<File>emptyList();
+ }
+
+ @NonNull
+ public List<File> getNativeLibs() {
+ return mNativeLibs != null ? mNativeLibs : Collections.<File>emptyList();
+ }
+
+ @Nullable
+ public File getNativeSources() {
+ return mNativeSources;
+ }
+
+ @Nullable
+ public String getNativeModuleName() {
+ return mNativeModuleName;
+ }
+
+ @Nullable
+ public File getOutputDir() {
+ return mOutputDir;
+ }
+
+ /** Returns "1.6", "1.7", etc */
+ @NonNull
+ public String getLanguageLevel() {
+ return mLanguageLevel;
+ }
+
+ @NonNull
+ public String getName() {
+ return mName != null ? mName : mDir.getName();
+ }
+
+ public int getMinSdkVersion() {
+ assert isAndroidProject();
+ return mMinSdkVersion;
+ }
+
+ public int getTargetSdkVersion() {
+ assert isAndroidProject();
+ return mTargetSdkVersion;
+ }
+
+ public int getCompileSdkVersion() {
+ assert isAndroidProject();
+ return mVersion != null ? mVersion.getApiLevel() : CURRENT_COMPILE_VERSION;
+ }
+
+ @NonNull
+ public List<EclipseProject> getDirectLibraries() {
+ return mDirectLibraries;
+ }
+
+ @NonNull
+ public List<EclipseProject> getAllLibraries() {
+ if (mAllLibraries == null) {
+ if (mDirectLibraries.isEmpty()) {
+ return mDirectLibraries;
+ }
+
+ List<EclipseProject> all = new ArrayList<EclipseProject>();
+ Set<EclipseProject> seen = Sets.newHashSet();
+ Set<EclipseProject> path = Sets.newHashSet();
+ seen.add(this);
+ path.add(this);
+ addLibraryProjects(all, seen, path);
+ mAllLibraries = all;
+ }
+
+ return mAllLibraries;
+ }
+
+ private void addLibraryProjects(@NonNull Collection<EclipseProject> collection,
+ @NonNull Set<EclipseProject> seen, @NonNull Set<EclipseProject> path) {
+ for (EclipseProject library : mDirectLibraries) {
+ if (seen.contains(library)) {
+ if (path.contains(library)) {
+ mImporter.reportWarning(library, library.getDir(),
+ "Internal error: cyclic library dependency for " +
+ library);
+ }
+ continue;
+ }
+ collection.add(library);
+ seen.add(library);
+ path.add(library);
+ // Recurse
+ library.addLibraryProjects(collection, seen, path);
+ path.remove(library);
+ }
+ }
+
+ @Override
+ public int compareTo(@NonNull EclipseProject other) {
+ return mDir.compareTo(other.mDir);
+ }
+
+ @Override
+ public String toString() {
+ return mDir.getPath();
+ }
+
+ /**
+ * Creates a list of modules from the given set of projects. The returned list
+ * is in dependency order.
+ */
+ public static List<? extends ImportModule> performImport(
+ @NonNull GradleImport importer,
+ @NonNull Collection<EclipseProject> projects) {
+ List<EclipseImportModule> modules = Lists.newArrayList();
+ List<EclipseImportModule> replacedByDependencies = Lists.newArrayList();
+
+ for (EclipseProject project : projects) {
+ EclipseImportModule module = new EclipseImportModule(importer, project);
+ module.initialize();
+ if (module.isReplacedWithDependency()) {
+ replacedByDependencies.add(module);
+ } else {
+ modules.add(module);
+ }
+ }
+
+ // Some libraries may be replaced by just a dependency (for example,
+ // instead of copying in a whole copy of ActionBarSherlock, just
+ // replace by the corresponding dependency.
+ for (EclipseImportModule replaced : replacedByDependencies) {
+ assert replaced.getReplaceWithDependencies() != null;
+ EclipseProject project = replaced.getProject();
+ for (EclipseImportModule module : modules) {
+ if (module.getProject().getAllLibraries().contains(project)) {
+ module.addDependencies(replaced.getReplaceWithDependencies());
+ }
+ }
+ }
+
+ // Strip out .jar files from the libs/ folder if already implied by
+ // library dependencies
+ for (EclipseImportModule module : modules) {
+ module.removeJarDependencies();
+ }
+
+ // Sort by dependency order
+ Collections.sort(modules);
+
+ return modules;
+ }
+
+ @Nullable
+ public EclipseImportModule getModule() {
+ return mModule;
+ }
+
+ public void setModule(@Nullable EclipseImportModule module) {
+ mModule = module;
+ }
+}
diff --git a/gradle-import/src/main/java/com/android/tools/gradle/eclipse/GradleImport.java b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/GradleImport.java
new file mode 100755
index 0000000..0304ce4
--- /dev/null
+++ b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/GradleImport.java
@@ -0,0 +1,1469 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.gradle.eclipse;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+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.FD_EXTRAS;
+import static com.android.SdkConstants.FD_GRADLE;
+import static com.android.SdkConstants.FD_RES;
+import static com.android.SdkConstants.FD_SOURCES;
+import static com.android.SdkConstants.FN_BUILD_GRADLE;
+import static com.android.SdkConstants.FN_GRADLE_WRAPPER_UNIX;
+import static com.android.SdkConstants.FN_GRADLE_WRAPPER_WIN;
+import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
+import static com.android.SdkConstants.FN_SETTINGS_GRADLE;
+import static com.android.SdkConstants.GRADLE_PLUGIN_LATEST_VERSION;
+import static com.android.SdkConstants.GRADLE_PLUGIN_NAME;
+import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_NDK;
+import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_SDK;
+import static com.android.xml.AndroidManifest.NODE_INSTRUMENTATION;
+import static com.google.common.base.Charsets.UTF_8;
+import static java.io.File.separator;
+import static java.io.File.separatorChar;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.repository.GradleCoordinate;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repository.local.LocalPkgInfo;
+import com.android.sdklib.repository.local.LocalSdk;
+import com.android.utils.ILogger;
+import com.android.utils.SdkUtils;
+import com.android.utils.StdLogger;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+import com.google.common.primitives.Bytes;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Importer which can generate Android Gradle projects.
+ * <p>
+ * It currently only supports importing ADT projects, so it will require
+ * some tweaks to handle importing from other types of projects.
+ * <p>
+ * The importer primarily imports complete ADT projects into complete (new) Gradle
+ * projects, but it can also be used to import one or more ADT projects into an
+ * existing Gradle project as new modules. See {@link #exportIntoProject} for
+ * details on how to do this.
+ * <p>
+ * TODO:
+ * <ul>
+ * <li>Migrate SDK folder from local.properties. If should make doubly sure that
+ * the repository you point to contains the app support library and other
+ * libraries that may be needed.</li>
+ * <li>Consider whether I can make this import mechanism work for Maven and plain
+ * sources as well?</li>
+ * <li>Make it optional whether we replace the directory structure with the Gradle one?</li>
+ * <li>Allow migrating a project in-place?</li>
+ * <li>If I have a workspace, check to see if there are problem markers and if
+ * so warn that the project may not be buildable</li>
+ * <li>Optional: at the end of the import, migrate Eclipse settings too --
+ * such as code styles, compiler flags (especially those for the
+ * project), ask about enabling eclipse key bindings, etc?</li>
+ * <li>If replaceJars=false, insert *comments* in the source code for potential
+ * replacements such that users don't forget and consider switching in the future</li>
+ * <li>Figure out if we can reuse fragments from the default freemarker templates for
+ * the code generation part.</li>
+ * <li>Allow option to preserve module nesting hierarchy. It currently flattens.</li>
+ * <li>Make it possible to use this wizard to migrate an already exported Eclipse project?</li>
+ * <li>Consider making the export create an HTML file and open in browser?</li>
+ * </ul>
+ */
+public class GradleImport {
+ public static final String NL = SdkUtils.getLineSeparator();
+ public static final int CURRENT_COMPILE_VERSION = 19;
+ public static final String CURRENT_BUILD_TOOLS_VERSION = SdkConstants.MIN_BUILD_TOOLS_VERSION;
+ public static final String ANDROID_GRADLE_PLUGIN =
+ GRADLE_PLUGIN_NAME + GRADLE_PLUGIN_LATEST_VERSION;
+ public static final String MAVEN_URL_PROPERTY = "android.mavenRepoUrl";
+ private static final String WORKSPACE_PROPERTY = "android.eclipseWorkspace";
+
+ static final String MAVEN_REPOSITORY;
+ static {
+ String repository = System.getProperty(MAVEN_URL_PROPERTY);
+ if (repository == null) {
+ repository = "mavenCentral()";
+ } else {
+ repository = "maven { url '" + repository + "' }";
+ }
+ MAVEN_REPOSITORY = repository;
+ }
+
+ public static final String ECLIPSE_DOT_CLASSPATH = ".classpath";
+ public static final String ECLIPSE_DOT_PROJECT = ".project";
+ public static final String IMPORT_SUMMARY_TXT = "import-summary.txt";
+
+ /**
+ * Whether we should place the repository definitions in the global build.gradle rather
+ * than in each module
+ */
+ static final boolean DECLARE_GLOBAL_REPOSITORIES = true;
+
+ private List<? extends ImportModule> mRootModules;
+ private Set<ImportModule> mModules;
+ private ImportSummary mSummary;
+ private File mWorkspaceLocation;
+ private File mGradleWrapperLocation;
+ private File mSdkLocation;
+ private File mNdkLocation;
+ private SdkManager mSdkManager;
+ private Set<String> mHandledJars = Sets.newHashSet();
+ private Map<String,File> mWorkspaceProjects;
+
+ /** Whether we should convert project names to lowercase module names */
+ private boolean mGradleNameStyle = true;
+ /** Whether we should try to replace jars with dependencies */
+ private boolean mReplaceJars = true;
+ /** Whether we should try to replace libs with dependencies */
+ private boolean mReplaceLibs = true;
+ /** Whether the importer is in "import into existing project" mode. In that case,
+ * some different choices are made; for example, we don't rewrite the module name
+ * to "app" when importing a single project; we preserve the project name.*/
+ private boolean mImportIntoExisting;
+ /** Whether we should emit per-module repository definitions */
+ @SuppressWarnings("PointlessBooleanExpression")
+ private boolean mPerModuleRepositories = !DECLARE_GLOBAL_REPOSITORIES;
+
+ private final List<String> mWarnings = Lists.newArrayList();
+ private final List<String> mErrors = Lists.newArrayList();
+ private Map<String, File> mPathMap = Maps.newTreeMap();
+ /**
+ * Set of modules user chose to import. Can be <code>null</code> when all
+ * modules will be imported
+ */
+ private Set<String> mSelectedModules;
+
+ public GradleImport() {
+ String workspace = System.getProperty(WORKSPACE_PROPERTY);
+ if (workspace != null) {
+ mWorkspaceLocation = new File(workspace);
+ }
+ }
+
+ /** Imports the given projects. Note that this just reads in the project state;
+ * it does not actually write out a Gradle project. For that, you should call
+ * {@link #exportProject(java.io.File, boolean)}.
+ *
+ * @param projectDirs the project directories to import
+ * @throws IOException if something is wrong
+ */
+ public void importProjects(@NonNull List<File> projectDirs) throws IOException {
+ mSummary = new ImportSummary(this);
+ mProjectMap.clear();
+ mHandledJars.clear();
+ mWarnings.clear();
+ mErrors.clear();
+ mWorkspaceProjects = null;
+ mRootModules = Collections.emptyList();
+ mModules = Sets.newHashSet();
+
+ for (File file : projectDirs) {
+ if (file.isFile()) {
+ assert !file.isDirectory();
+ file = file.getParentFile();
+ }
+
+ guessWorkspace(file);
+
+ if (isAdtProjectDir(file)) {
+ guessSdk(file);
+ guessNdk(file);
+
+ try {
+ EclipseProject.getProject(this, file);
+ } catch (ImportException e) {
+ // Already recorded
+ return;
+ } catch (Exception e) {
+ reportError(null, file, e.toString(), false);
+ return;
+ }
+ } else {
+ reportError(null, file, "Not a recognized project: " + file, false);
+ return;
+ }
+ }
+
+ // Find unique projects. (We can register projects under multiple paths
+ // if the dir and the canonical dir differ, so pick unique values here)
+ Set<EclipseProject> projects = Sets.newHashSet(mProjectMap.values());
+ mRootModules = EclipseProject.performImport(this, projects);
+ for (ImportModule module : mRootModules) {
+ mModules.add(module);
+ mModules.addAll(module.getAllDependencies());
+ }
+ }
+
+ public static boolean isEclipseProjectDir(@Nullable File file) {
+ return file != null && file.isDirectory()
+ && new File(file, ECLIPSE_DOT_CLASSPATH).exists()
+ && new File(file, ECLIPSE_DOT_PROJECT).exists();
+ }
+
+ public static boolean isAdtProjectDir(@Nullable File file) {
+ return new File(file, ANDROID_MANIFEST_XML).exists() &&
+ (isEclipseProjectDir(file) ||
+ (new File(file, FD_RES).exists() &&
+ new File(file, FD_SOURCES).exists()));
+ }
+
+ /** Sets location of gradle wrapper to copy into exported project, if known */
+ @NonNull
+ public GradleImport setGradleWrapperLocation(@NonNull File gradleWrapper) {
+ mGradleWrapperLocation = gradleWrapper;
+ return this;
+ }
+
+ /** Sets location of the SDK to use with the import, if known */
+ @NonNull
+ public GradleImport setSdkLocation(@Nullable File sdkLocation) {
+ mSdkLocation = sdkLocation;
+ return this;
+ }
+
+ /** Returns the location of the SDK to use with the import, if known */
+ @Nullable
+ public File getSdkLocation() {
+ return mSdkLocation;
+ }
+
+ /** Sets SDK manager to use with the import, if known */
+ @NonNull
+ public GradleImport setSdkManager(@NonNull SdkManager sdkManager) {
+ mSdkManager = sdkManager;
+ mSdkLocation = new File(sdkManager.getLocation());
+ return this;
+ }
+
+ @Nullable
+ public SdkManager getSdkManager() {
+ if (mSdkManager == null && mSdkLocation != null && mSdkLocation.exists()) {
+ ILogger logger = new StdLogger(StdLogger.Level.INFO);
+ mSdkManager = SdkManager.createManager(mSdkLocation.getPath(), logger);
+ }
+
+ return mSdkManager;
+ }
+
+ /** Sets location of the SDK to use with the import, if known */
+ @NonNull
+ public GradleImport setNdkLocation(@Nullable File ndkLocation) {
+ mNdkLocation = ndkLocation;
+ return this;
+ }
+
+ /** Gets location of the SDK to use with the import, if known */
+ @Nullable
+ public File getNdkLocation() {
+ return mNdkLocation;
+ }
+
+ /** Sets location of Eclipse workspace, if known */
+ public GradleImport setEclipseWorkspace(@NonNull File workspace) {
+ mWorkspaceLocation = workspace;
+ assert mWorkspaceLocation.exists() : workspace.getPath();
+ mWorkspaceProjects = null;
+ return this;
+ }
+
+ /** Gets location of Eclipse workspace, if known */
+ @Nullable
+ public File getEclipseWorkspace() {
+ return mWorkspaceLocation;
+ }
+
+ /** Whether import should attempt to replace jars with dependencies */
+ @NonNull
+ public GradleImport setReplaceJars(boolean replaceJars) {
+ mReplaceJars = replaceJars;
+ return this;
+ }
+
+ /** Whether import should attempt to replace jars with dependencies */
+ public boolean isReplaceJars() {
+ return mReplaceJars;
+ }
+
+ /** Whether import should attempt to replace inlined library projects with dependencies */
+ public boolean isReplaceLibs() {
+ return mReplaceLibs;
+ }
+
+ /** Whether import should attempt to replace inlined library projects with dependencies */
+ public GradleImport setReplaceLibs(boolean replaceLibs) {
+ mReplaceLibs = replaceLibs;
+ return this;
+ }
+
+ /** Whether import should lower-case module names from ADT project names */
+ @NonNull
+ public GradleImport setGradleNameStyle(boolean lowerCase) {
+ mGradleNameStyle = lowerCase;
+ return this;
+ }
+
+ /** Whether we're in "import into existing project" mode, or import complete new project
+ * mode (the default) */
+ public boolean isImportIntoExisting() {
+ return mImportIntoExisting;
+ }
+
+ /** Sets whether we're in "import into existing project" mode, or import complete new project
+ * mode (the default). When importing into existing projects some different behaviors are
+ * applied; for example, we don't change the module name to "app" when there is just one
+ * project being imported. */
+ public void setImportIntoExisting(boolean importIntoExisting) {
+ mImportIntoExisting = importIntoExisting;
+ }
+
+ /**
+ * Returns whether the importer emits the repository definitions in each module's build.gradle
+ * rather than at the top level in the shared build.gradle
+ */
+ public boolean isPerModuleRepositories() {
+ return mPerModuleRepositories;
+ }
+
+ /**
+ * Sets whether the importer emits the repository definitions in each module's build.gradle
+ * rather than at the top level in the shared build.gradle
+ */
+ public void setPerModuleRepositories(boolean perModuleRepositories) {
+ mPerModuleRepositories = perModuleRepositories;
+ }
+
+ /** Whether import should lower-case module names from ADT project names */
+ public boolean isGradleNameStyle() {
+ return mGradleNameStyle;
+ }
+
+ private void guessWorkspace(@NonNull File projectDir) {
+ if (mWorkspaceLocation == null) {
+ File dir = projectDir.getParentFile();
+ while (dir != null) {
+ if (isEclipseWorkspaceDir(dir)) {
+ setEclipseWorkspace(dir);
+ break;
+ }
+ dir = dir.getParentFile();
+ }
+ }
+ }
+
+ private void guessSdk(@NonNull File projectDir) {
+ if (mSdkLocation == null) {
+ mSdkLocation = getDirFromLocalProperties(projectDir, PROPERTY_SDK);
+
+ if (mSdkLocation == null && mWorkspaceLocation != null) {
+ mSdkLocation = getDirFromWorkspaceSetting(getAdtSettingsFile(),
+ "com.android.ide.eclipse.adt.sdk");
+ }
+ }
+ }
+
+ private void guessNdk(@NonNull File projectDir) {
+ if (mNdkLocation == null) {
+ mNdkLocation = getDirFromLocalProperties(projectDir, PROPERTY_NDK);
+
+ if (mNdkLocation == null && mWorkspaceLocation != null) {
+ mNdkLocation = getDirFromWorkspaceSetting(getNdkSettingsFile(), "ndkLocation");
+ }
+ }
+ }
+
+ @Nullable
+ private static File getDirFromLocalProperties(@NonNull File projectDir,
+ @NonNull String property) {
+ File localProperties = new File(projectDir, FN_LOCAL_PROPERTIES);
+ if (localProperties.exists()) {
+ try {
+ Properties properties = getProperties(localProperties);
+ if (properties != null) {
+ String sdk = properties.getProperty(property);
+ if (sdk != null) {
+ File dir = new File(sdk);
+ if (dir.exists()) {
+ return dir;
+ } else {
+ dir = new File(sdk.replace('/', separatorChar));
+ if (dir.exists()) {
+ return dir;
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ // ignore properties
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private File getDirFromWorkspaceSetting(@NonNull File settings, @NonNull String property) {
+ //noinspection VariableNotUsedInsideIf
+ if (mWorkspaceLocation != null) {
+ if (settings.exists()) {
+ try {
+ Properties properties = getProperties(settings);
+ if (properties != null) {
+ String path = properties.getProperty(property);
+ if (path == null) {
+ return null;
+ }
+ File dir = new File(path);
+ if (dir.exists()) {
+ return dir;
+ } else {
+ dir = new File(path.replace('/', separatorChar));
+ if (dir.exists()) {
+ return dir;
+ }
+ }
+ }
+ } catch (IOException e) {
+ // Ignore workspace data
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public static boolean isEclipseWorkspaceDir(@NonNull File file) {
+ return file.isDirectory() &&
+ new File(file, ".metadata" + separator + "version.ini").exists();
+ }
+
+ @Nullable
+ public File resolveWorkspacePath(@Nullable EclipseProject fromProject, @NonNull String path, boolean record) {
+ if (path.isEmpty()) {
+ return null;
+ }
+
+ // If file within project, must match on all prefixes
+ for (Map.Entry<String,File> entry : mPathMap.entrySet()) {
+ String workspacePath = entry.getKey();
+ File file = entry.getValue();
+ if (file != null && path.startsWith(workspacePath)) {
+ if (path.equals(workspacePath)) {
+ return file;
+ } else {
+ path = path.substring(workspacePath.length());
+ if (path.charAt(0) == '/' || path.charAt(0) == separatorChar) {
+ path = path.substring(1);
+ }
+ File resolved = new File(file, path.replace('/', separatorChar));
+ if (resolved.exists()) {
+ return resolved;
+ }
+ }
+ }
+ }
+
+ if (fromProject != null && mWorkspaceLocation == null) {
+ guessWorkspace(fromProject.getDir());
+ }
+
+ if (mWorkspaceLocation != null) {
+ // Is the file present directly in the workspace?
+ char first = path.charAt(0);
+ if (first != '/') {
+ return null;
+ }
+ File f = new File(mWorkspaceLocation, path.substring(1).replace('/', separatorChar));
+ if (f.exists()) {
+ mPathMap.put(path, f);
+ return f;
+ }
+
+ // Other files may be in other file systems, mapped by a .location link in the
+ // workspace metadata
+ if (mWorkspaceProjects == null) {
+ mWorkspaceProjects = Maps.newHashMap();
+ File projectDir = new File(mWorkspaceLocation, ".metadata" + separator + ".plugins"
+ + separator + "org.eclipse.core.resources" + separator + ".projects");
+ File[] projects = projectDir.exists() ? projectDir.listFiles() : null;
+ byte[] target = "URI//file:".getBytes(Charsets.US_ASCII);
+ if (projects != null) {
+ for (File project : projects) {
+ File location = new File(project, ".location");
+ if (location.exists()) {
+ try {
+ byte[] bytes = Files.toByteArray(location);
+ int start = Bytes.indexOf(bytes, target);
+ if (start != -1) {
+ int end = start + target.length;
+ for (; end < bytes.length; end++) {
+ if (bytes[end] == (byte)0) {
+ break;
+ }
+ }
+ try {
+ int length = end - start;
+ String s = new String(bytes, start, length, UTF_8);
+ s = s.substring(5); // skip URI//
+ File file = SdkUtils.urlToFile(s);
+ if (file.exists()) {
+ String name = project.getName();
+ mWorkspaceProjects.put('/' + name, file);
+ //noinspection ConstantConditions
+ }
+ } catch (Throwable t) {
+ // Ignore binary data we can't read
+ }
+ }
+ } catch (IOException e) {
+ reportWarning((ImportModule) null, location,
+ "Can't read .location file");
+ }
+ }
+ }
+ }
+ }
+
+ // Is it just a project root?
+ File project = mWorkspaceProjects.get(path);
+ if (project != null) {
+ mPathMap.put(path, project);
+ return project;
+ }
+
+ // If file within project, must match on all prefixes
+ for (Map.Entry<String,File> entry : mWorkspaceProjects.entrySet()) {
+ String workspacePath = entry.getKey();
+ File file = entry.getValue();
+ if (file != null && path.startsWith(workspacePath)) {
+ if (path.equals(workspacePath)) {
+ return file;
+ } else {
+ path = path.substring(workspacePath.length());
+ if (path.charAt(0) == '/' || path.charAt(0) == separatorChar) {
+ path = path.substring(1);
+ }
+ File resolved = new File(file, path.replace('/', separatorChar));
+ if (resolved.exists()) {
+ return resolved;
+ }
+ }
+ }
+ }
+
+ // Record path as one we need to resolve
+ if (record) {
+ mPathMap.put(path, null);
+ }
+ } else if (record) {
+ // Record path as one we need to resolve
+ mPathMap.put(path, null);
+ }
+
+ return null;
+ }
+
+ public void exportProject(@NonNull File destDir, boolean allowNonEmpty) throws IOException {
+ mSummary.setDestDir(destDir);
+ if (!isImportIntoExisting()) {
+ createDestDir(destDir, allowNonEmpty);
+ createProjectBuildGradle(new File(destDir, FN_BUILD_GRADLE));
+
+ exportGradleWrapper(destDir);
+ exportLocalProperties(destDir);
+ }
+ exportSettingsGradle(new File(destDir, FN_SETTINGS_GRADLE), isImportIntoExisting());
+ for (ImportModule module : getModulesToImport()) {
+ exportModule(new File(destDir, module.getModuleName()), module);
+ }
+
+ mSummary.write(new File(destDir, IMPORT_SUMMARY_TXT));
+ }
+
+ private Iterable<? extends ImportModule> getModulesToImport() {
+ if (mSelectedModules == null) {
+ return mRootModules;
+ } else {
+ return Iterables.filter(mRootModules, new Predicate<ImportModule>() {
+ @Override
+ public boolean apply(ImportModule input) {
+ return mSelectedModules.contains(input.getModuleName());
+ }
+ });
+ }
+ }
+
+ /**
+ * Like {@link #exportProject(java.io.File, boolean)}, but writes into an existing
+ * project instead of creating a new one.
+ * <p>
+ * <b>NOTE</b>: When performing an import into an existing project, note that
+ * you should call {@link #setImportIntoExisting(boolean)} before the call to
+ * read in projects ({@link #importProjects(java.util.List)}. Note also that
+ * you should call {@link #setPerModuleRepositories(boolean)} with a suitable
+ * value based on whether the existing project defines shared repositories.
+ * This is similar to how we pass the "perModuleRepositories" variable to
+ * our Freemarker templates (such as
+ * templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl ) so it
+ * can decide whether to include this info in the new module. In Studio we
+ * set it based on whether $PROJECT/build.gradle contains "repositories" (this
+ * is done in NewModuleWizard).
+ * </p>
+ *
+ * @param projectDir the root directory containing the project to write into
+ * @param updateSettings whether the importer should attempt to update the settings.gradle
+ * file in the project or not. Clients such as Android Studio may
+ * wish to pass false here in order to handle this part
+ * @param writeSummary whether we should generate an import summary
+ * @param destDirMap optional map from ADT project dir to destination directory to
+ * write each module as.
+ * @return the list of imported module directories
+ */
+ @NonNull
+ public List<File> exportIntoProject(@NonNull File projectDir, boolean updateSettings,
+ boolean writeSummary, @Nullable Map<File,File> destDirMap) throws IOException {
+ mSummary.setDestDir(projectDir);
+
+ List<File> imported = Lists.newArrayListWithExpectedSize(mRootModules.size());
+ for (ImportModule module : getModulesToImport()) {
+ File moduleDir = null;
+ if (destDirMap != null) {
+ moduleDir = destDirMap.get(module.getDir());
+ }
+ if (moduleDir == null) {
+ moduleDir = new File(projectDir, module.getModuleName());
+ if (moduleDir.exists()) {
+ module.pickUniqueName(projectDir);
+ moduleDir = new File(projectDir, module.getModuleName());
+ assert !moduleDir.exists();
+ }
+ }
+ exportModule(moduleDir, module);
+ imported.add(moduleDir);
+ }
+
+ if (updateSettings) {
+ exportSettingsGradle(new File(projectDir, FN_SETTINGS_GRADLE), true);
+ }
+
+ if (writeSummary) {
+ mSummary.write(new File(projectDir, IMPORT_SUMMARY_TXT));
+ }
+
+ return imported;
+ }
+
+ private void exportGradleWrapper(@NonNull File destDir) throws IOException {
+ if (mGradleWrapperLocation != null && mGradleWrapperLocation.exists()) {
+ File gradlewDest = new File(destDir, FN_GRADLE_WRAPPER_UNIX);
+ copyDir(new File(mGradleWrapperLocation, FN_GRADLE_WRAPPER_UNIX), gradlewDest, null);
+ boolean madeExecutable = gradlewDest.setExecutable(true);
+ if (!madeExecutable) {
+ reportWarning((ImportModule) null, gradlewDest,
+ "Could not make gradle wrapper script executable");
+ }
+ copyDir(new File(mGradleWrapperLocation, FN_GRADLE_WRAPPER_WIN),
+ new File(destDir, FN_GRADLE_WRAPPER_WIN), null);
+ copyDir(new File(mGradleWrapperLocation, FD_GRADLE), new File(destDir, FD_GRADLE),
+ null);
+ }
+ }
+
+ // Write local.properties file
+ private void exportLocalProperties(@NonNull File destDir) throws IOException {
+ boolean needsNdk = needsNdk();
+ if (mNdkLocation != null && needsNdk || mSdkLocation != null) {
+ Properties properties = new Properties();
+ if (mSdkLocation != null) {
+ properties.setProperty(PROPERTY_SDK, mSdkLocation.getPath());
+ }
+ if (mNdkLocation != null && needsNdk) {
+ properties.setProperty(PROPERTY_NDK, mNdkLocation.getPath());
+ }
+
+ FileOutputStream out = null;
+ try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ out = new FileOutputStream(new File(destDir, FN_LOCAL_PROPERTIES));
+ properties.store(out,
+ "# This file must *NOT* be checked into Version Control Systems,\n" +
+ "# as it contains information specific to your local configuration.\n" +
+ "\n" +
+ "# Location of the SDK. This is only used by Gradle.\n");
+ } finally {
+ Closeables.close(out, true);
+ }
+ }
+ }
+
+ /** Returns true if this project appears to need the NDK */
+ public boolean needsNdk() {
+ for (ImportModule module : mModules) {
+ if (module.isNdkProject()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void exportModule(File destDir, ImportModule module) throws IOException {
+ mkdirs(destDir);
+ createModuleBuildGradle(new File(destDir, FN_BUILD_GRADLE), module);
+ module.copyInto(destDir);
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ /** Ensure that the given directory exists, and if it can't be created, report an I/O error */
+ public void mkdirs(@NonNull File destDir) throws IOException {
+ if (!destDir.exists()) {
+ boolean ok = destDir.mkdirs();
+ if (!ok) {
+ reportError(null, destDir, "Could not make directory " + destDir);
+ }
+ }
+ }
+
+ private void createModuleBuildGradle(@NonNull File file, ImportModule module)
+ throws IOException {
+ StringBuilder sb = new StringBuilder(500);
+
+ if (module.isApp() || module.isAndroidLibrary()) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mPerModuleRepositories) {
+ appendRepositories(sb, true);
+ }
+
+ if (module.isApp()) {
+ sb.append("apply plugin: 'android'").append(NL);
+ } else {
+ assert module.isAndroidLibrary();
+ sb.append("apply plugin: 'android-library'").append(NL);
+ }
+ sb.append(NL);
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mPerModuleRepositories) {
+ sb.append("repositories {").append(NL);
+ sb.append(" ").append(MAVEN_REPOSITORY).append(NL);
+ sb.append("}").append(NL);
+ sb.append(NL);
+ }
+ sb.append("android {").append(NL);
+ String compileSdkVersion = Integer.toString(module.getCompileSdkVersion());
+ int minSdkVersion = module.getMinSdkVersion();
+ int targetSdkVersion = module.getTargetSdkVersion();
+ String minSdkVersionString = Integer.toString(minSdkVersion);
+ String targetSdkVersionString = Integer.toString(targetSdkVersion);
+ sb.append(" compileSdkVersion ").append(compileSdkVersion).append(NL);
+ sb.append(" buildToolsVersion \"").append(getBuildToolsVersion()).append("\"")
+ .append(NL);
+ sb.append(NL);
+ sb.append(" defaultConfig {").append(NL);
+ if (module.getPackage() != null) {
+ sb.append(" applicationId \"").append(module.getPackage()).append('"')
+ .append(NL);
+ }
+ if (minSdkVersion >= 1) {
+ sb.append(" minSdkVersion ").append(minSdkVersionString).append(NL);
+ }
+ if (targetSdkVersion > 1 && module.getCompileSdkVersion() > 3) {
+ sb.append(" targetSdkVersion ").append(targetSdkVersionString).append(NL);
+ }
+
+ String languageLevel = module.getLanguageLevel();
+ if (!languageLevel.equals(EclipseProject.DEFAULT_LANGUAGE_LEVEL)) {
+ sb.append(" compileOptions {").append(NL);
+ String level = languageLevel.replace('.','_'); // 1.6 => 1_6
+ sb.append(" sourceCompatibility JavaVersion.VERSION_").append(level)
+ .append(NL);
+ sb.append(" targetCompatibility JavaVersion.VERSION_").append(level)
+ .append(NL);
+ sb.append(" }").append(NL);
+ }
+
+ if (module.isNdkProject() && module.getNativeModuleName() != null) {
+ sb.append(NL);
+ sb.append(" ndk {").append(NL);
+ sb.append(" moduleName \"").append(module.getNativeModuleName())
+ .append("\"").append(NL);
+ sb.append(" }").append(NL);
+ }
+
+ if (module.getInstrumentationDir() != null) {
+ sb.append(NL);
+ File manifestFile = new File(module.getInstrumentationDir(), ANDROID_MANIFEST_XML);
+ assert manifestFile.exists() : manifestFile;
+ Document manifest = getXmlDocument(manifestFile, true);
+ if (manifest != null && manifest.getDocumentElement() != null) {
+ String pkg = manifest.getDocumentElement().getAttribute(ATTR_PACKAGE);
+ if (pkg != null && !pkg.isEmpty()) {
+ sb.append(" testApplicationId \"").append(pkg).append("\"")
+ .append(NL);
+ }
+ NodeList list = manifest.getElementsByTagName(NODE_INSTRUMENTATION);
+ if (list.getLength() > 0) {
+ Element tag = (Element) list.item(0);
+ String runner = tag.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (runner != null && !runner.isEmpty()) {
+ sb.append(" testInstrumentationRunner \"").append(runner)
+ .append("\"").append(NL);
+ }
+ Attr attr = tag.getAttributeNodeNS(ANDROID_URI, "functionalTest");
+ if (attr != null) {
+ sb.append(" testFunctionalTest ").append(attr.getValue())
+ .append(NL);
+ }
+ attr = tag.getAttributeNodeNS(ANDROID_URI, "handleProfiling");
+ if (attr != null) {
+ sb.append(" testHandlingProfiling ").append(attr.getValue())
+ .append(NL);
+ }
+ }
+ }
+ }
+
+ sb.append(" }").append(NL);
+ sb.append(NL);
+
+ List<File> localRules = module.getLocalProguardFiles();
+ List<File> sdkRules = module.getSdkProguardFiles();
+ if (!localRules.isEmpty() || !sdkRules.isEmpty()) {
+ // User specified ProGuard rules; replicate exactly
+ sb.append(" buildTypes {").append(NL);
+ sb.append(" release {").append(NL);
+ sb.append(" runProguard true").append(NL);
+ sb.append(" proguardFiles ");
+ sb.append(generateProguardFileList(localRules, sdkRules)).append(NL);
+ sb.append(" }").append(NL);
+ sb.append(" }").append(NL);
+ } else {
+ // User didn't specify ProGuard rules; put in defaults (but off)
+ sb.append(" buildTypes {").append(NL);
+ sb.append(" release {").append(NL);
+ sb.append(" runProguard false").append(NL);
+ sb.append(" proguardFiles getDefaultProguardFile('proguard-"
+ + "android.txt'), 'proguard-rules.txt'").append(NL);
+ sb.append(" }").append(NL);
+ sb.append(" }").append(NL);
+ }
+ sb.append("}").append(NL);
+ appendDependencies(sb, module);
+
+ } else if (module.isJavaLibrary()) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mPerModuleRepositories) {
+ appendRepositories(sb, false);
+ }
+
+ sb.append("apply plugin: 'java'").append(NL);
+
+ String languageLevel = module.getLanguageLevel();
+ if (!languageLevel.equals(EclipseProject.DEFAULT_LANGUAGE_LEVEL)) {
+ sb.append(NL);
+ sb.append("sourceCompatibility = \"");
+ sb.append(languageLevel);
+ sb.append("\"").append(NL);
+ sb.append("targetCompatibility = \"");
+ sb.append(languageLevel);
+ sb.append("\"").append(NL);
+ }
+
+ appendDependencies(sb, module);
+ } else {
+ assert false : module;
+ }
+
+ Files.write(sb.toString(), file, UTF_8);
+ }
+
+ String getBuildToolsVersion() {
+ SdkManager sdkManager = getSdkManager();
+ if (sdkManager != null) {
+ final BuildToolInfo buildTool = sdkManager.getLatestBuildTool();
+ if (buildTool != null) {
+ return buildTool.getRevision().toString();
+ }
+ }
+
+ return CURRENT_BUILD_TOOLS_VERSION;
+ }
+
+ private static String generateProguardFileList(List<File> localRules, List<File> sdkRules) {
+ assert !localRules.isEmpty() || !sdkRules.isEmpty();
+ StringBuilder sb = new StringBuilder();
+ for (File rule : sdkRules) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append("getDefaultProguardFile('");
+ sb.append(escapeGroovyStringLiteral(rule.getName()));
+ sb.append("')");
+ }
+
+ for (File rule : localRules) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append("'");
+ // Note: project config files are flattened into the module structure (see
+ // ImportModule#copyInto handler)
+ sb.append(escapeGroovyStringLiteral(rule.getName()));
+ sb.append("'");
+ }
+
+ return sb.toString();
+ }
+
+ private static void appendDependencies(@NonNull StringBuilder sb,
+ @NonNull ImportModule module)
+ throws IOException {
+ if (!module.getDirectDependencies().isEmpty()
+ || !module.getDependencies().isEmpty()
+ || !module.getJarDependencies().isEmpty()
+ || !module.getTestDependencies().isEmpty()
+ || !module.getTestJarDependencies().isEmpty()) {
+ sb.append(NL);
+ sb.append("dependencies {").append(NL);
+ for (ImportModule lib : module.getDirectDependencies()) {
+ if (lib.isReplacedWithDependency()) {
+ continue;
+ }
+ sb.append(" compile project('").append(lib.getModuleReference()).append("')")
+ .append(NL);
+ }
+ for (GradleCoordinate dependency : module.getDependencies()) {
+ sb.append(" compile '").append(dependency.toString()).append("'").append(NL);
+ }
+ for (File jar : module.getJarDependencies()) {
+ String path = jar.getPath().replace(separatorChar, '/'); // Always / in gradle
+ sb.append(" compile files('").append(escapeGroovyStringLiteral(path))
+ .append("')").append(NL);
+ }
+ for (GradleCoordinate dependency : module.getTestDependencies()) {
+ sb.append(" androidTestCompile '").append(dependency.toString()).append("'")
+ .append(NL);
+ }
+ for (File jar : module.getTestJarDependencies()) {
+ String path = jar.getPath().replace(separatorChar, '/');
+ sb.append(" androidTestCompile files('")
+ .append(escapeGroovyStringLiteral(path)).append("')").append(NL);
+ }
+ sb.append("}").append(NL);
+ }
+ }
+
+ private static String escapeGroovyStringLiteral(String s) {
+ StringBuilder sb = new StringBuilder(s.length() + 5);
+ for (int i = 0, n = s.length(); i < n; i++) {
+ char c = s.charAt(i);
+ if (c == '\\' || c == '\'') {
+ sb.append('\\');
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ private void appendRepositories(@NonNull StringBuilder sb, boolean needAndroidPlugin) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mPerModuleRepositories) {
+ sb.append("buildscript {").append(NL);
+ sb.append(" repositories {").append(NL);
+ sb.append(" ").append(MAVEN_REPOSITORY).append(NL);
+ sb.append(" }").append(NL);
+ if (needAndroidPlugin) {
+ sb.append(" dependencies {").append(NL);
+ sb.append(" classpath '" + ANDROID_GRADLE_PLUGIN + "'").append(NL);
+ sb.append(" }").append(NL);
+ }
+ sb.append("}").append(NL);
+ }
+ }
+
+ private void createProjectBuildGradle(@NonNull File file) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ sb.append(
+ "// Top-level build file where you can add configuration options common to all sub-projects/modules.");
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (!mPerModuleRepositories) {
+ sb.append(NL);
+ sb.append("buildscript {").append(NL);
+ sb.append(" repositories {").append(NL);
+ sb.append(" ").append(MAVEN_REPOSITORY).append(NL);
+ sb.append(" }").append(NL);
+ sb.append(" dependencies {").append(NL);
+ sb.append(" classpath '" + ANDROID_GRADLE_PLUGIN + "'").append(NL);
+ sb.append(" }").append(NL);
+ sb.append("}").append(NL);
+ sb.append(NL);
+ sb.append("allprojects {").append(NL);
+ sb.append(" repositories {").append(NL);
+ sb.append(" ").append(MAVEN_REPOSITORY).append(NL);
+ sb.append(" }").append(NL);
+ sb.append("}");
+ }
+ sb.append(NL);
+ Files.write(sb.toString(), file, UTF_8);
+ }
+
+ private void exportSettingsGradle(@NonNull File file, boolean append) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ if (append) {
+ if (!file.exists()) {
+ append = false;
+ } else {
+ // Ensure that the new include statements are separate code statements, not
+ // for example inserted at the end of a // line comment
+ String existing = Files.toString(file, UTF_8);
+ if (!existing.endsWith(NL)) {
+ sb.append(NL);
+ }
+ }
+ }
+
+ for (ImportModule module : getModulesToImport()) {
+ sb.append("include '");
+ sb.append(module.getModuleReference());
+ sb.append("'");
+ sb.append(NL);
+ }
+
+ String code = sb.toString();
+ if (append) {
+ Files.append(code, file, UTF_8);
+ } else {
+ Files.write(code, file, UTF_8);
+ }
+ }
+
+ private void createDestDir(@NonNull File destDir, boolean allowNonEmpty) throws IOException {
+ if (destDir.exists()) {
+ if (!allowNonEmpty) {
+ File[] files = destDir.listFiles();
+ if (files != null && files.length > 0) {
+ throw new IOException("Destination directory " + destDir + " should be empty");
+ }
+ }
+ } else {
+ mkdirs(destDir);
+ }
+ }
+
+ @NonNull
+ public List<String> getWarnings() {
+ return mWarnings;
+ }
+
+ @NonNull
+ public List<String> getErrors() {
+ return mErrors;
+ }
+
+ /**
+ * Returns module names to module source locations mappings.
+ */
+ @NonNull
+ public Map<String, File> getDetectedModuleLocations() {
+ TreeMap<String, File> modules = new TreeMap<String, File>();
+ for (ImportModule module : mModules) {
+ modules.put(module.getModuleName(), module.getCanonicalModuleDir());
+ }
+ return modules;
+ }
+
+ public void setModulesToImport(Map<String, File> modules) {
+ mSelectedModules = ImmutableSet.copyOf(modules.keySet());
+ }
+
+ private static class ImportException extends RuntimeException {
+ private String mMessage;
+
+ private ImportException(@NonNull String message) {
+ mMessage = message;
+ }
+
+ @Override
+ public String getMessage() {
+ return mMessage;
+ }
+
+ @Override
+ public String toString() {
+ return getMessage();
+ }
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ public void reportError(
+ @Nullable EclipseProject project,
+ @Nullable File file,
+ @NonNull String message) {
+ reportError(project, file, message, true);
+ }
+
+ public void reportError(
+ @Nullable EclipseProject project,
+ @Nullable File file,
+ @NonNull String message,
+ boolean abort) {
+ String text = formatMessage(project != null ? project.getName() : null, file, message);
+ mErrors.add(text);
+ if (abort) {
+ throw new ImportException(text);
+ }
+ }
+
+ public void reportWarning(
+ @Nullable ImportModule module,
+ @Nullable File file,
+ @NonNull String message) {
+ String moduleName = module != null ? module.getOriginalName() : null;
+ mWarnings.add(formatMessage(moduleName, file, message));
+ }
+
+ public void reportWarning(
+ @Nullable EclipseProject project,
+ @Nullable File file,
+ @NonNull String message) {
+ String moduleName = project != null ? project.getName() : null;
+ mWarnings.add(formatMessage(moduleName, file, message));
+ }
+
+ private static String formatMessage(
+ @Nullable String project,
+ @Nullable File file,
+ @NonNull String message) {
+ StringBuilder sb = new StringBuilder();
+ if (project != null) {
+ sb.append("Project ").append(project).append(":");
+ }
+ if (file != null) {
+ sb.append(file.getPath());
+ sb.append(":\n");
+ }
+
+ sb.append(message);
+
+ return sb.toString();
+ }
+
+ @Nullable
+ File resolvePathVariable(@Nullable EclipseProject fromProject, @NonNull String name, boolean record) throws IOException {
+ File file = mPathMap.get(name);
+ if (file != null) {
+ return file;
+ }
+
+ if (fromProject != null && mWorkspaceLocation == null) {
+ guessWorkspace(fromProject.getDir());
+ }
+
+ String value = null;
+ Properties properties = getJdtSettingsProperties(false);
+ if (properties != null) {
+ value = properties.getProperty("org.eclipse.jdt.core.classpathVariable." + name);
+ }
+ if (value == null) {
+ properties = getPathSettingsProperties(false);
+ if (properties != null) {
+ value = properties.getProperty("pathvariable." + name);
+ }
+ }
+
+ if (value == null) {
+ if (record) {
+ mPathMap.put(name, null);
+ }
+ return null;
+ }
+
+ file = new File(value.replace('/', separatorChar));
+
+ return file;
+ }
+
+ @Nullable
+ private Properties getJdtSettingsProperties(boolean mustExist) throws IOException {
+ File settings = getJdtSettingsFile();
+ if (!settings.exists()) {
+ if (mustExist) {
+ reportError(null, settings, "Settings file does not exist");
+ }
+ return null;
+ }
+
+ return getProperties(settings);
+ }
+
+ private File getRuntimeSettingsDir() {
+ return new File(getWorkspaceLocation(),
+ ".metadata" + separator +
+ ".plugins" + separator +
+ "org.eclipse.core.runtime" + separator +
+ ".settings");
+ }
+
+ private File getJdtSettingsFile() {
+ return new File(getRuntimeSettingsDir(), "org.eclipse.jdt.core.prefs");
+ }
+
+ private File getPathSettingsFile() {
+ return new File(getRuntimeSettingsDir(), "org.eclipse.core.resources.prefs");
+ }
+
+ private File getNdkSettingsFile() {
+ return new File(getRuntimeSettingsDir(), "com.android.ide.eclipse.ndk.prefs");
+ }
+
+ private File getAdtSettingsFile() {
+ return new File(getRuntimeSettingsDir(), "com.android.ide.eclipse.adt.prefs");
+ }
+
+ @Nullable
+ private Properties getPathSettingsProperties(boolean mustExist) throws IOException {
+ File settings = getPathSettingsFile();
+ if (!settings.exists()) {
+ if (mustExist) {
+ reportError(null, settings, "Settings file does not exist");
+ }
+ return null;
+ }
+
+ return getProperties(settings);
+ }
+
+ private File getWorkspaceLocation() {
+ return mWorkspaceLocation;
+ }
+
+ Document getXmlDocument(File file, boolean namespaceAware) throws IOException {
+ String xml = Files.toString(file, UTF_8);
+ try {
+ return XmlUtils.parseDocument(xml, namespaceAware);
+ } catch (Exception e) {
+ reportError(null, file, "Invalid XML file: " + file.getPath() + ":\n"
+ + e.getMessage());
+ return null;
+ }
+ }
+
+ static Properties getProperties(File file) throws IOException {
+ Properties properties = new Properties();
+ InputStreamReader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file)), Charsets.UTF_8);
+ properties.load(reader);
+ Closeables.close(reader, true);
+ return properties;
+ }
+
+ private Map<File, EclipseProject> mProjectMap = Maps.newHashMap();
+
+ Map<File, EclipseProject> getProjectMap() {
+ return mProjectMap;
+ }
+
+ public ImportSummary getSummary() {
+ return mSummary;
+ }
+
+ void registerProject(@NonNull EclipseProject project) {
+ // Register not just this directory but the canonical versions too, since library
+ // references in project.properties can be relative and can be made canonical;
+ // we want to make sure that a project known by any of these versions of the paths
+ // are treated as the same
+ mProjectMap.put(project.getDir(), project);
+ mProjectMap.put(project.getDir().getAbsoluteFile(), project);
+ mProjectMap.put(project.getCanonicalDir(), project);
+ }
+
+ int getModuleCount() {
+ int moduleCount = 0;
+ for (ImportModule module : mModules) {
+ if (!module.isReplacedWithDependency()) {
+ moduleCount++;
+ }
+ }
+ return moduleCount;
+ }
+
+ /** Returns a path map for workspace paths */
+ public Map<String, File> getPathMap() {
+ return mPathMap;
+ }
+
+ /** Interface used by the {@link #copyDir(java.io.File, java.io.File, CopyHandler)} handler */
+ public interface CopyHandler {
+ /**
+ * Optionally handle the given file; returns true if the file has been
+ * handled
+ */
+ boolean handle(@NonNull File source, @NonNull File dest) throws IOException;
+ }
+
+ /**
+ * Handles copying the given source into the given destination, whether the source
+ * is a file or directory. An optional handler can be used to perform special handling,
+ * such as skipping files or changing the destination.
+ */
+ public void copyDir(@NonNull File source, @NonNull File dest, @Nullable CopyHandler handler)
+ throws IOException {
+ if (handler != null && handler.handle(source, dest)) {
+ return;
+ }
+ if (source.isDirectory()) {
+ if (isIgnoredFile(source)) {
+ // Skip version control files when generating the migrated project;
+ // it will only have fragments of the project, and in some cases moved
+ // around, so don't pick up partial VCS state
+ return;
+ }
+
+ mkdirs(dest);
+ File[] files = source.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ copyDir(child, new File(dest, child.getName()), handler);
+ }
+ }
+ } else {
+ Files.copy(source, dest);
+ }
+ }
+
+ /**
+ * Returns true if the given file should be ignored (note: this may not return
+ * true for files inside ignored folders, so to determine if a given file should
+ * really be ignored you should check all ancestors as well, or only call this as
+ * part of a recursive directory traversal)
+ */
+ static boolean isIgnoredFile(File file) {
+ String name = file.getName();
+ return name.equals(".svn") || name.equals(".git") || name.equals(".hg")
+ || name.equals(".DS_Store") || name.endsWith("~") && name.length() > 1;
+ }
+
+ /** Computes the relative path for the given file inside another directory */
+ @Nullable
+ public static File computeRelativePath(@NonNull File canonicalBase, @NonNull File file)
+ throws IOException {
+ File canonical = file.getCanonicalFile();
+ String canonicalPath = canonical.getPath();
+ if (canonicalPath.startsWith(canonicalBase.getPath())) {
+ int length = canonicalBase.getPath().length();
+ if (canonicalPath.length() == length) {
+ return new File(".");
+ } else if (canonicalPath.charAt(length) == separatorChar) {
+ return new File(canonicalPath.substring(length + 1));
+ } else {
+ return new File(canonicalPath.substring(length));
+ }
+ }
+
+ return null;
+ }
+
+ void markJarHandled(@NonNull File file) {
+ mHandledJars.add(file.getName());
+ }
+
+ boolean isJarHandled(@NonNull File file) {
+ return mHandledJars.contains(file.getName());
+ }
+
+ private boolean haveLocalRepository(String vendor) {
+ SdkManager sdkManager = getSdkManager();
+ if (sdkManager != null) {
+ LocalSdk localSdk = sdkManager.getLocalSdk();
+ LocalPkgInfo[] infos = localSdk.getPkgsInfos(PkgType.PKG_EXTRA);
+ for (LocalPkgInfo info : infos) {
+ IPkgDesc d = info.getDesc();
+ //noinspection ConstantConditions,ConstantConditions
+ if (d.hasVendor() && vendor.equals(d.getVendor().getId()) &&
+ d.hasPath() && "m2repository".equals(d.getPath())) {
+ return true;
+ }
+ }
+ }
+
+ if (mSdkLocation != null) {
+ File repository = new File(mSdkLocation,
+ FD_EXTRAS + separator + vendor + separator + "m2repository");
+ return repository.exists();
+ }
+
+ return false;
+ }
+
+ public boolean needSupportRepository() {
+ return haveArtifact("com.android.support");
+ }
+
+ public boolean needGoogleRepository() {
+ return haveArtifact("com.google.android.gms");
+ }
+
+ private boolean haveArtifact(String groupId) {
+ for (ImportModule module : getModulesToImport()) {
+ for (GradleCoordinate dependency : module.getDependencies()) {
+ if (groupId.equals(dependency.getGroupId())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isMissingSupportRepository() {
+ return !haveLocalRepository("android");
+ }
+
+ public boolean isMissingGoogleRepository() {
+ return !haveLocalRepository("google");
+ }
+}
diff --git a/gradle-import/src/main/java/com/android/tools/gradle/eclipse/ImportModule.java b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/ImportModule.java
new file mode 100644
index 0000000..c938d5f
--- /dev/null
+++ b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/ImportModule.java
@@ -0,0 +1,666 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.gradle.eclipse;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.BIN_FOLDER;
+import static com.android.SdkConstants.DOT_AIDL;
+import static com.android.SdkConstants.DOT_FS;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.DOT_RS;
+import static com.android.SdkConstants.DOT_RSH;
+import static com.android.SdkConstants.FD_AIDL;
+import static com.android.SdkConstants.FD_ASSETS;
+import static com.android.SdkConstants.FD_JAVA;
+import static com.android.SdkConstants.FD_MAIN;
+import static com.android.SdkConstants.FD_RENDERSCRIPT;
+import static com.android.SdkConstants.FD_RES;
+import static com.android.SdkConstants.FD_SOURCES;
+import static com.android.SdkConstants.FD_TEST;
+import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
+import static com.android.SdkConstants.FN_PROJECT_PROPERTIES;
+import static com.android.SdkConstants.GEN_FOLDER;
+import static com.android.SdkConstants.LIBS_FOLDER;
+import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT;
+import static com.android.tools.gradle.eclipse.GradleImport.ECLIPSE_DOT_CLASSPATH;
+import static com.android.tools.gradle.eclipse.GradleImport.ECLIPSE_DOT_PROJECT;
+import static java.io.File.separator;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.repository.GradleCoordinate;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Set;
+
+abstract class ImportModule implements Comparable<ImportModule> {
+ // TODO: Tie libraries to the compile SDK?
+ //private static final String LATEST = GradleImport.CURRENT_COMPILE_VERSION + ".0.0";
+ private static final String LATEST = "+";
+
+ private static final String APPCOMPAT_DEP = "com.android.support:appcompat-v7:" + LATEST;
+ private static final String GRID_LAYOUT_DEP = "com.android.support:gridlayout-v7:" + LATEST;
+ private static final String SUPPORT_LIB_DEP = SUPPORT_LIB_ARTIFACT + ":" + LATEST;
+ @SuppressWarnings("SpellCheckingInspection")
+ private static final String SHERLOCK_DEP = "com.actionbarsherlock:actionbarsherlock:4.4.0@aar";
+ private static final String PLAY_SERVICES_DEP = "com.google.android.gms:play-services:+";
+
+ protected final GradleImport mImporter;
+ protected final List<GradleCoordinate> mDependencies = Lists.newArrayList();
+ protected final List<GradleCoordinate> mTestDependencies = Lists.newArrayList();
+ protected final List<File> mJarDependencies = Lists.newArrayList();
+ protected final List<File> mTestJarDependencies = Lists.newArrayList();
+ protected List<GradleCoordinate> mReplaceWithDependencies;
+ private String mModuleName;
+
+ public ImportModule(@NonNull GradleImport importer) {
+ mImporter = importer;
+ }
+
+ protected abstract boolean isLibrary();
+ protected abstract boolean isApp();
+ protected abstract boolean isAndroidLibrary();
+ protected abstract boolean isAndroidProject();
+ protected abstract boolean isJavaLibrary();
+ protected abstract boolean isNdkProject();
+ protected abstract int getCompileSdkVersion();
+ protected abstract int getMinSdkVersion();
+ protected abstract int getTargetSdkVersion();
+ @NonNull public abstract File getDir();
+ @NonNull protected abstract String getOriginalName();
+ @NonNull protected abstract List<File> getSourcePaths();
+ @NonNull protected abstract List<File> getJarPaths();
+ @NonNull protected abstract List<File> getTestJarPaths();
+ @NonNull protected abstract List<File> getNativeLibs();
+ @NonNull protected abstract File resolveFile(@NonNull File file);
+ @NonNull protected abstract File getCanonicalModuleDir();
+ @NonNull protected abstract List<File> getLocalProguardFiles();
+ @NonNull protected abstract List<File> getSdkProguardFiles();
+ @NonNull protected abstract String getLanguageLevel();
+ @NonNull protected abstract List<ImportModule> getDirectDependencies();
+ @NonNull protected abstract List<ImportModule> getAllDependencies();
+ @Nullable protected abstract String getPackage();
+ @Nullable protected abstract File getLintXml();
+ @Nullable protected abstract File getOutputDir();
+ @Nullable protected abstract File getManifestFile();
+ @Nullable protected abstract File getResourceDir();
+ @Nullable protected abstract File getAssetsDir();
+ @Nullable protected abstract File getNativeSources();
+ @Nullable protected abstract String getNativeModuleName();
+ @Nullable protected abstract File getInstrumentationDir();
+
+ public void initialize() {
+ initDependencies();
+ initReplaceWithDependency();
+ }
+
+ protected void initDependencies() {
+ }
+
+ @SuppressWarnings("SpellCheckingInspection")
+ @Nullable
+ GradleCoordinate guessDependency(@NonNull File jar) {
+ // Make guesses based on library. For now, we do simple name checks, but
+ // later consider looking at jar contents, md5 sums etc, especially to
+ // pick up exact version numbers of popular libraries.
+ // (This list was generated by just looking at some existing projects
+ // and seeing which .jar files they depended on and then consulting available
+ // gradle dependencies via http://gradleplease.appspot.com/ )
+ String name = jar.getName().toLowerCase(Locale.US);
+ if (name.equals("android-support-v4.jar")) {
+ mImporter.markJarHandled(jar);
+ return GradleCoordinate.parseCoordinateString(SUPPORT_LIB_DEP);
+ } else if (name.equals("android-support-v7-gridlayout.jar")) {
+ mImporter.markJarHandled(jar);
+ return GradleCoordinate.parseCoordinateString(GRID_LAYOUT_DEP);
+ } else if (name.equals("android-support-v7-appcompat.jar")) {
+ mImporter.markJarHandled(jar);
+ return GradleCoordinate.parseCoordinateString(APPCOMPAT_DEP);
+ } else if (name.equals("com_actionbarsherlock.jar") ||
+ name.equalsIgnoreCase("actionbarsherlock.jar")) {
+ mImporter.markJarHandled(jar);
+ return GradleCoordinate.parseCoordinateString(SHERLOCK_DEP);
+ } else if (name.equals("guava.jar") || name.startsWith("guava-")) {
+ mImporter.markJarHandled(jar);
+ String version = getVersion(jar, "guava-", name, "15.0");
+ if (version.startsWith("r")) { // really old versions
+ version = "15.0";
+ }
+ return GradleCoordinate.parseCoordinateString("com.google.guava:guava:" + version);
+ } else if (name.startsWith("joda-time")) {
+ mImporter.markJarHandled(jar);
+ // Convert joda-time-2.1 jar into joda-time:joda-time:2.1 etc
+ String version = getVersion(jar, "joda-time-", name, "2.3");
+ return GradleCoordinate.parseCoordinateString("joda-time:joda-time:" + version);
+ } else if (name.startsWith("robotium-solo-")) {
+ mImporter.markJarHandled(jar);
+ String version = getVersion(jar, "robotium-solo-", name, "4.3.1");
+ return GradleCoordinate.parseCoordinateString(
+ "com.jayway.android.robotium:robotium-solo:" + version);
+ } else if (name.startsWith("protobuf-java-")) {
+ mImporter.markJarHandled(jar);
+ String version = getVersion(jar, "protobuf-java-", name, "2.5");
+ return GradleCoordinate.parseCoordinateString("com.google.protobuf:protobuf-java:"
+ + version);
+ } else if (name.startsWith("gson-")) {
+ mImporter.markJarHandled(jar);
+ String version = getVersion(jar, "gson-", name, "2.2.4");
+ return GradleCoordinate.parseCoordinateString("com.google.code.gson:gson:" + version);
+ } else if (name.startsWith("google-http-client-gson-")) {
+ mImporter.markJarHandled(jar);
+ return GradleCoordinate.parseCoordinateString(
+ "com.google.http-client:google-http-client-gson:1.17.0-rc");
+ } else if (name.startsWith("svg-android")) {
+ mImporter.markJarHandled(jar);
+ return GradleCoordinate.parseCoordinateString(
+ "com.github.japgolly.android:svg-android:2.0.5");
+ } else if (name.equals("gcm.jar")) {
+ mImporter.markJarHandled(jar);
+ return GradleCoordinate.parseCoordinateString(PLAY_SERVICES_DEP);
+ }
+
+ // TODO: Consider other libraries if and when they get Gradle dependencies:
+ // analytics, volley, ...
+
+ return null;
+ }
+
+ private String getVersion(File jar, String prefix, String jarName, String defaultVersion) {
+ if (jarName.matches(prefix + "([\\d\\.]+)\\.jar")) {
+ String version = jarName.substring(prefix.length(), jarName.length() - 4);
+ if (!defaultVersion.equals(version)) {
+ mImporter.getSummary().reportGuessedVersion(jar);
+ }
+ return version;
+ }
+
+ return defaultVersion;
+ }
+
+ /**
+ * See if this is a library that looks like a known dependency; if so, just
+ * use a dependency instead of the library
+ */
+ @SuppressWarnings("SpellCheckingInspection")
+ private void initReplaceWithDependency() {
+ if (isLibrary() && mImporter.isReplaceLibs()) {
+ String pkg = getPackage();
+ if (pkg != null) {
+ if (pkg.equals("com.actionbarsherlock")) {
+ mReplaceWithDependencies = Arrays.asList(
+ GradleCoordinate.parseCoordinateString(SHERLOCK_DEP),
+ GradleCoordinate.parseCoordinateString(SUPPORT_LIB_DEP));
+ } else if (pkg.equals("android.support.v7.gridlayout")) {
+ mReplaceWithDependencies = Collections.singletonList(
+ GradleCoordinate.parseCoordinateString(GRID_LAYOUT_DEP));
+ } else if (pkg.equals("com.google.android.gms")) {
+ mReplaceWithDependencies = Collections.singletonList(
+ GradleCoordinate.parseCoordinateString(
+ PLAY_SERVICES_DEP));
+ } else if (pkg.equals("android.support.v7.appcompat")) {
+ mReplaceWithDependencies = Collections.singletonList(
+ GradleCoordinate.parseCoordinateString(APPCOMPAT_DEP));
+ } else if (pkg.equals("android.support.v7.mediarouter")) {
+ mReplaceWithDependencies = Collections.singletonList(
+ GradleCoordinate.parseCoordinateString(
+ "com.android.support:support-v7-mediarouter:+"));
+ }
+
+ if (mReplaceWithDependencies != null) {
+ mImporter.getSummary().reportReplacedLib(getOriginalName(),
+ mReplaceWithDependencies);
+ }
+ }
+ }
+ }
+
+ public boolean isReplacedWithDependency() {
+ return mReplaceWithDependencies != null && !mReplaceWithDependencies.isEmpty();
+ }
+
+ public List<GradleCoordinate> getReplaceWithDependencies() {
+ return mReplaceWithDependencies;
+ }
+
+
+ public String getModuleName() {
+ if (mModuleName == null) {
+ if (mImporter.isGradleNameStyle() && !mImporter.isImportIntoExisting()
+ && mImporter.getModuleCount() == 1) {
+ mModuleName = "app";
+ return mModuleName;
+ }
+
+ String string = getOriginalName();
+ // Strip whitespace and characters which can pose a problem when the module
+ // name is referenced as a module name in Gradle (Groovy) files
+ StringBuilder sb = new StringBuilder(string.length());
+ for (int i = 0, n = string.length(); i < n; i++) {
+ char c = string.charAt(i);
+ if (Character.isJavaIdentifierPart(c)) {
+ sb.append(c);
+ }
+ }
+
+ String moduleName = sb.toString();
+ if (!moduleName.isEmpty() && !Character.isJavaIdentifierStart(moduleName.charAt(0))) {
+ moduleName = '_' + moduleName;
+ }
+
+ if (mImporter.isGradleNameStyle() && !moduleName.isEmpty()) {
+ moduleName = Character.toLowerCase(moduleName.charAt(0)) + moduleName.substring(1);
+ }
+ mModuleName = moduleName;
+ }
+ return mModuleName;
+ }
+
+ public void pickUniqueName(@NonNull File projectDir) {
+ assert projectDir.exists() : projectDir;
+ String preferredName = getModuleName();
+
+ // If the name ends with a number, strip that off and increment from it.
+ // In other words if the module name is "foo", we test "foo2", "foo3", and so on.
+ // But if the name already is "module49", we don't do "module492", ... we try "module50"
+ int length = preferredName.length();
+ int lastDigit = length;
+ for (int i = length - 1; i >= 1; i--) { // 1: name cannot start with a digit!
+ if (!Character.isDigit(preferredName.charAt(i))) {
+ break;
+ } else {
+ lastDigit = i;
+ }
+ }
+ int startingNumber = 2;
+ if (lastDigit < length) {
+ startingNumber = Integer.parseInt(preferredName.substring(lastDigit)) + 1;
+ preferredName = preferredName.substring(0, lastDigit);
+ }
+
+ for (int i = startingNumber; ; i++) {
+ String name = preferredName + i;
+ if (!(new File(projectDir, name)).exists()) {
+ mModuleName = name;
+ break;
+ }
+ }
+ }
+
+ public String getModuleReference() {
+ return ':' + getModuleName();
+ }
+
+ protected File getJarOutputRelativePath(File jar) {
+ if (jar.isAbsolute()) {
+ File relative;
+ try {
+ relative = GradleImport.computeRelativePath(getCanonicalModuleDir(), jar);
+ } catch (IOException ioe) {
+ relative = null;
+ }
+ if (relative != null) {
+ jar = relative;
+ } else {
+ jar = new File(LIBS_FOLDER, jar.getName());
+ }
+ }
+
+ return jar;
+ }
+
+ protected static File getTestJarOutputRelativePath(File jar) {
+ return new File(LIBS_FOLDER, jar.getName());
+ }
+
+ public void copyInto(@NonNull File destDir) throws IOException {
+ ImportSummary summary = mImporter.getSummary();
+
+ Set<File> copied = Sets.newHashSet();
+
+ final File main = new File(destDir, FD_SOURCES + separator + FD_MAIN);
+ mImporter.mkdirs(main);
+ if (isAndroidProject()) {
+ File srcManifest = getManifestFile();
+ if (srcManifest != null && srcManifest.exists()) {
+ File destManifest = new File(main, ANDROID_MANIFEST_XML);
+ Files.copy(srcManifest, destManifest);
+ summary.reportMoved(this, srcManifest, destManifest);
+ recordCopiedFile(copied, srcManifest);
+ }
+ File srcRes = getResourceDir();
+ if (srcRes != null && srcRes.exists()) {
+ File destRes = new File(main, FD_RES);
+ mImporter.mkdirs(destRes);
+ mImporter.copyDir(srcRes, destRes, null);
+ summary.reportMoved(this, srcRes, destRes);
+ recordCopiedFile(copied, srcRes);
+ }
+ File srcAssets = getAssetsDir();
+ if (srcAssets != null && srcAssets.exists()) {
+ File destAssets = new File(main, FD_ASSETS);
+ mImporter.mkdirs(destAssets);
+ mImporter.copyDir(srcAssets, destAssets, null);
+ summary.reportMoved(this, srcAssets, destAssets);
+ recordCopiedFile(copied, srcAssets);
+ }
+
+ File lintXml = getLintXml();
+ if (lintXml != null) {
+ File destLintXml = new File(destDir, lintXml.getName());
+ Files.copy(lintXml, destLintXml);
+ summary.reportMoved(this, lintXml, destLintXml);
+ recordCopiedFile(copied, lintXml);
+ }
+ }
+
+ for (final File src : getSourcePaths()) {
+ final File srcJava = resolveFile(src);
+ File destJava = new File(main, FD_JAVA);
+
+ if (srcJava.isDirectory()) {
+ // Merge all the separate source folders into a single one; they aren't allowed
+ // to contain source file conflicts anyway
+ mImporter.mkdirs(destJava);
+ } else {
+ destJava = new File(main, srcJava.getName());
+ }
+
+ mImporter.copyDir(srcJava, destJava, new GradleImport.CopyHandler() {
+ // Handle moving .rs/.rsh/.fs files to main/rs/ and .aidl files to the
+ // corresponding aidl package under main/aidl
+ @Override
+ public boolean handle(@NonNull File source, @NonNull File dest)
+ throws IOException {
+ String sourcePath = source.getPath();
+ if (sourcePath.endsWith(DOT_AIDL)) {
+ File aidlDir = new File(main, FD_AIDL);
+ File relative = GradleImport.computeRelativePath(srcJava, source);
+ if (relative == null) {
+ relative = GradleImport.computeRelativePath(
+ srcJava.getCanonicalFile(), source);
+ }
+ if (relative != null) {
+ File destAidl = new File(aidlDir, relative.getPath());
+ mImporter.mkdirs(destAidl.getParentFile());
+ Files.copy(source, destAidl);
+ mImporter.getSummary().reportMoved(ImportModule.this, source,
+ destAidl);
+ return true;
+ }
+ } else if (sourcePath.endsWith(DOT_RS) ||
+ sourcePath.endsWith(DOT_RSH) ||
+ sourcePath.endsWith(DOT_FS)) {
+ // Copy to flattened rs dir
+ // TODO: Ensure the file names are unique!
+ File destRs = new File(main, FD_RENDERSCRIPT + separator +
+ source.getName());
+ mImporter.mkdirs(destRs.getParentFile());
+ Files.copy(source, destRs);
+ mImporter.getSummary().reportMoved(ImportModule.this, source, destRs);
+ return true;
+ }
+ return false;
+ }
+ });
+ summary.reportMoved(this, srcJava, destJava);
+ recordCopiedFile(copied, srcJava);
+ }
+
+ for (File jar : getJarPaths()) {
+ File srcJar = resolveFile(jar);
+ File destJar = new File(destDir, getJarOutputRelativePath(jar).getPath());
+ if (destJar.getParentFile() != null) {
+ mImporter.mkdirs(destJar.getParentFile());
+ }
+ Files.copy(srcJar, destJar);
+ summary.reportMoved(this, srcJar, destJar);
+ recordCopiedFile(copied, srcJar);
+ }
+
+ for (File lib : getNativeLibs()) {
+ File srcLib = resolveFile(lib);
+ String abi = lib.getParentFile().getName();
+ File destLib = new File(destDir, FD_SOURCES + separator + FD_MAIN + separator
+ + "jniLibs" + separator + abi + separator + lib.getName());
+ if (destLib.getParentFile() != null) {
+ mImporter.mkdirs(destLib.getParentFile());
+ }
+ Files.copy(srcLib, destLib);
+ summary.reportMoved(this, srcLib, destLib);
+ recordCopiedFile(copied, srcLib);
+ }
+
+ File jni = getNativeSources();
+ if (jni != null) {
+ File srcJni = resolveFile(jni);
+ File destJni = new File(destDir, FD_SOURCES + separator + FD_MAIN + separator
+ + "jni");
+ mImporter.copyDir(srcJni, destJni, null);
+ summary.reportMoved(this, srcJni, destJni);
+ recordCopiedFile(copied, srcJni);
+ }
+
+ File instrumentation = getInstrumentationDir();
+ if (instrumentation != null) {
+ final File test = new File(destDir, FD_SOURCES + separator + FD_TEST);
+ mImporter.mkdirs(test);
+
+ // We should NOT copy the Android manifest file. Don't mark it as "ignored"
+ // either since we'll pull everything we need out of it and put it into the
+ // Gradle file.
+ recordCopiedFile(copied, new File(instrumentation, ANDROID_MANIFEST_XML));
+
+ File srcRes = new File(instrumentation, FD_RES);
+ if (srcRes.isDirectory()) {
+ File destRes = new File(test, FD_RES);
+ mImporter.mkdirs(destRes);
+ mImporter.copyDir(srcRes, destRes, null);
+ summary.reportMoved(this, srcRes, destRes);
+ recordCopiedFile(copied, srcRes);
+ }
+
+ File srcJava = new File(instrumentation, FD_SOURCES);
+ if (srcJava.isDirectory()) {
+ File destRes = new File(test, FD_JAVA);
+ mImporter.mkdirs(destRes);
+ mImporter.copyDir(srcJava, destRes, null);
+ summary.reportMoved(this, srcJava, destRes);
+ recordCopiedFile(copied, srcJava);
+ }
+
+ for (File jar : getTestJarPaths()) {
+ File srcJar = resolveFile(jar);
+ File destJar = new File(destDir, getTestJarOutputRelativePath(jar).getPath());
+ if (destJar.exists()) {
+ continue;
+ }
+ if (destJar.getParentFile() != null) {
+ mImporter.mkdirs(destJar.getParentFile());
+ }
+ Files.copy(srcJar, destJar);
+ summary.reportMoved(this, srcJar, destJar);
+ recordCopiedFile(copied, srcJar);
+ }
+ }
+
+ if (isAndroidProject()) {
+ for (File srcProguard : getLocalProguardFiles()) {
+ File destProguard = new File(destDir, srcProguard.getName());
+ if (!destProguard.exists()) {
+ Files.copy(srcProguard, destProguard);
+ summary.reportMoved(this, srcProguard, destProguard);
+ recordCopiedFile(copied, srcProguard);
+ } else {
+ mImporter.reportWarning(this, destProguard,
+ "Local proguard config file name is not unique");
+ }
+ }
+ }
+
+ reportIgnored(copied);
+ }
+
+ private static void recordCopiedFile(@NonNull Set<File> copied, @NonNull File file)
+ throws IOException {
+ copied.add(file);
+ copied.add(file.getCanonicalFile());
+ }
+
+ private void reportIgnored(Set<File> copied) throws IOException {
+ File canonicalDir = getCanonicalModuleDir();
+
+ // Ignore output folder (if not under bin/ as usual)
+ File outputDir = getOutputDir();
+ if (outputDir != null) {
+ copied.add(resolveFile(outputDir).getCanonicalFile());
+ }
+
+ // These files are either not useful (bin, gen) or already handled (project metadata files)
+ copied.add(new File(canonicalDir, BIN_FOLDER));
+ copied.add(new File(canonicalDir, GEN_FOLDER));
+ copied.add(new File(canonicalDir, ECLIPSE_DOT_CLASSPATH));
+ copied.add(new File(canonicalDir, ECLIPSE_DOT_PROJECT));
+ copied.add(new File(canonicalDir, FN_PROJECT_PROPERTIES));
+ copied.add(new File(canonicalDir, FN_PROJECT_PROPERTIES));
+ copied.add(new File(canonicalDir, FN_LOCAL_PROPERTIES));
+ copied.add(new File(canonicalDir, LIBS_FOLDER));
+ copied.add(new File(canonicalDir, ".settings"));
+ copied.add(new File(canonicalDir, ".cproject"));
+ if (isNdkProject()) {
+ // TODO: Also resolve NDK_OUT in the Makefile in case a custom location is used
+ copied.add(new File(canonicalDir, "obj"));
+ }
+
+ reportIgnored(canonicalDir, copied, 0);
+ }
+
+ /**
+ * Report ignored files. Returns true if the file (and all its children) were
+ * ignored too.
+ */
+ private boolean reportIgnored(@NonNull File file, @NonNull Set<File> copied, int depth)
+ throws IOException {
+ if (depth > 0 && copied.contains(file)) {
+ return true;
+ }
+
+ boolean ignore = true;
+ boolean isDirectory = file.isDirectory();
+ if (isDirectory) {
+ // Don't recursively list contents of .git etc
+ if (depth == 1) {
+ if (GradleImport.isIgnoredFile(file)) {
+ return false;
+ }
+ }
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ ignore &= reportIgnored(child, copied, depth + 1);
+ }
+ }
+ } else {
+ ignore = false;
+ }
+
+ if (depth > 0 && !ignore) {
+ File relative = GradleImport.computeRelativePath(getCanonicalModuleDir(), file);
+ if (relative == null) {
+ relative = file;
+ }
+ String path = relative.getPath();
+ if (isDirectory) {
+ path += separator;
+ }
+ mImporter.getSummary().reportIgnored(getOriginalName(), path);
+ }
+
+ return ignore;
+ }
+
+ public List<File> getJarDependencies() {
+ return mJarDependencies;
+ }
+
+ public List<GradleCoordinate> getDependencies() {
+ return mDependencies;
+ }
+
+ public List<File> getTestJarDependencies() {
+ return mTestJarDependencies;
+ }
+
+ public List<GradleCoordinate> getTestDependencies() {
+ return mTestDependencies;
+ }
+
+ @Nullable
+ public File computeProjectRelativePath(@NonNull File file) throws IOException {
+ return GradleImport.computeRelativePath(getCanonicalModuleDir(), file);
+ }
+
+ protected abstract boolean dependsOn(@NonNull ImportModule other);
+
+ protected abstract boolean dependsOnLibrary(@NonNull String pkg);
+
+ /**
+ * Strip out .jar file dependencies (on files in libs/) that correspond
+ * to code pulled in from a library dependency
+ */
+ void removeJarDependencies() {
+ // For each module, remove any .jar files in its path that
+ // provided b
+ ListIterator<File> iterator = getJarPaths().listIterator();
+ while (iterator.hasNext()) {
+ File jar = iterator.next();
+ if (mImporter.isJarHandled(jar)) {
+ iterator.remove();
+ } else {
+ String pkg = jar.getName();
+ if (pkg.endsWith(DOT_JAR)) {
+ pkg = pkg.substring(0, pkg.length() - DOT_JAR.length());
+ }
+ pkg = pkg.replace('-','.');
+ if (dependsOnLibrary(pkg)) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ // Sort by dependency order
+ @Override
+ public int compareTo(@NonNull ImportModule other) {
+ if (dependsOn(other)) {
+ return 1;
+ } else if (other.dependsOn(this)) {
+ return -1;
+ } else {
+ return getOriginalName().compareTo(other.getOriginalName());
+ }
+ }
+}
diff --git a/gradle-import/src/main/java/com/android/tools/gradle/eclipse/ImportSummary.java b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/ImportSummary.java
new file mode 100644
index 0000000..8478a37
--- /dev/null
+++ b/gradle-import/src/main/java/com/android/tools/gradle/eclipse/ImportSummary.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.gradle.eclipse;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.repository.GradleCoordinate;
+import com.android.sdklib.repository.FullRevision;
+import com.android.utils.SdkUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Records information about the import to be presented to the user:
+ * <ul>
+ * <li>List of files *not* migrated</li>
+ * <li>Explain that the files were moved into the canonical gradle directory
+ * structure and explain what it is</li>
+ * <li>A summary of the file changes (files moved from where to where)</li>
+ * <li>Tips for things to do next (e.g. create signing configs, flavors, etc</li>
+ * <li>Warning if manifest merger was not enabled before AND there are libraries
+ * without empty manifests</li>
+ * <li>Warning if I've replaced a .jar with a dependency of unknown version</li>
+ * <li>TODO: End with a section of migration tips for Eclipse users (e.g. to not look
+ * for the Problems view, how to use Eclipse key bindings, etc.</li>
+ * </ul>
+ */
+public class ImportSummary {
+ static final String MSG_HEADER = ""
+ + "ECLIPSE ANDROID PROJECT IMPORT SUMMARY\n"
+ + "======================================\n";
+
+ static final String MSG_MANIFEST = "\n"
+ + "Manifest Merging:\n"
+ + "-----------------\n"
+ + "Your project uses libraries that provide manifests, and your Eclipse\n"
+ + "project did not explicitly turn on manifest merging. In Android Gradle\n"
+ + "projects, manifests are always merged (meaning that contents from your\n"
+ + "libraries' manifests will be merged into the app manifest. If you had\n"
+ + "manually copied contents from library manifests into your app manifest\n"
+ + "you may need to remove these for the app to build correctly.\n";
+
+ static final String MSG_UNHANDLED = "\n"
+ + "Ignored Files:\n"
+ + "--------------\n"
+ + "The following files were *not* copied into the new Gradle project; you\n"
+ + "should evaluate whether these are still needed in your project and if\n"
+ + "so manually move them:\n\n";
+
+ static final String MSG_REPLACED_JARS = "\n"
+ + "Replaced Jars with Dependencies:\n"
+ + "--------------------------------\n"
+ + "The importer recognized the following .jar files as third party\n"
+ + "libraries and replaced them with Gradle dependencies instead. This has\n"
+ + "the advantage that more explicit version information is known, and the\n"
+ + "libraries can be updated automatically. However, it is possible that\n"
+ + "the .jar file in your project was of an older version than the\n"
+ + "dependency we picked, which could render the project not compileable.\n"
+ + "You can disable the jar replacement in the import wizard and try again:\n\n";
+
+ static final String MSG_REPLACED_LIBS = "\n"
+ + "Replaced Libraries with Dependencies:\n"
+ + "-------------------------------------\n"
+ + "The importer recognized the following library projects as third party\n"
+ + "libraries and replaced them with Gradle dependencies instead. This has\n"
+ + "the advantage that more explicit version information is known, and the\n"
+ + "libraries can be updated automatically. However, it is possible that\n"
+ + "the source files in your project were of an older version than the\n"
+ + "dependency we picked, which could render the project not compileable.\n"
+ + "You can disable the library replacement in the import wizard and try\n"
+ + "again:\n\n";
+
+ static final String MSG_FOOTER = "\n"
+ + "Next Steps:\n"
+ + "-----------\n"
+ + "You can now build the project. The Gradle project needs network\n"
+ + "connectivity to download dependencies.\n"
+ + "\n"
+ + "Bugs:\n"
+ + "-----\n"
+ + "If for some reason your project does not build, and you determine that\n"
+ + "it is due to a bug or limitation of the Eclipse to Gradle importer,\n"
+ + "please file a bug at http://b.android.com with category\n"
+ + "Component-Tools.\n"
+ + "\n"
+ + "(This import summary is for your information only, and can be deleted\n"
+ + "after import once you are satisfied with the results.)\n";
+
+ static final String MSG_FOLDER_STRUCTURE = "\n"
+ + "Moved Files:\n"
+ + "------------\n"
+ + "Android Gradle projects use a different directory structure than ADT\n"
+ + "Eclipse projects. Here's how the projects were restructured:\n\n";
+
+ static final String MSG_MISSING_REPO_1 = "\n"
+ + "Missing Android Support Repository:\n"
+ + "-----------------------------------\n"
+ + "Some useful libraries, such as the Android Support Library, are\n"
+ + "installed from a special Maven repository, which should be installed\n"
+ + "via the SDK manager.\n"
+ + "\n"
+ + "It looks like this library is missing from your SDK installation at:\n";
+
+ static final String MSG_MISSING_REPO_2 = "\n"
+ + "To install it, open the SDK manager, and in the Extras category,\n"
+ + "select \"Android Support Repository\". You may also want to install the\n"
+ + "\"Google Repository\" if you want to use libraries like Google Play\n"
+ + "Services.\n";
+
+ static final String MSG_MISSING_GOOGLE_REPOSITORY_1 = "\n"
+ + "Missing Google Repository:\n"
+ + "--------------------------\n"
+ + "The Google Play Services library is installed from a special Maven\n"
+ + "Repository, which should be installed via the SDK manager.\n"
+ + "\n"
+ + "It looks like this library is missing from your SDK installation at:\n";
+
+ static final String MSG_MISSING_GOOGLE_REPOSITORY_2 = "\n"
+ + "To install it, open the SDK manager, and in the Extras category,\n"
+ + "select \"Google Repository\".\n";
+
+ static final String MSG_BUILD_TOOLS_VERSION = "\n"
+ + "Old Build Tools:\n"
+ + "----------------\n"
+ + "The version of the build tools installed with your SDK is old. It\n"
+ + "should be at least version 19.0.1 to work well with the Gradle build\n"
+ + "system. To update it, open the Android SDK Manager, and install the\n"
+ + "highest available version of Tools > Android SDK Build-tools.\n";
+
+ static final String MSG_GUESSED_VERSIONS = "\n"
+ + "Potentially Missing Dependency:\n"
+ + "-------------------------------\n"
+ + "When we replaced the following .jar files with a Gradle dependency, we\n"
+ + "inferred the dependency version number from the filename. This\n"
+ + "specific version may not actually be available from the repository.\n"
+ + "If you get a build error stating that the dependency is missing, edit\n"
+ + "the version number to for example \"+\" to pick up the latest version\n"
+ + "instead. (This may require you to update your code if the library APIs\n"
+ + "have changed.)\n\n";
+
+ static final String MSG_USER_HOME_PROGUARD = "\n"
+ + "Ignored Per-User ProGuard Configuration File:\n"
+ + "---------------------------------------------\n"
+ + "The ProGuard configuration in the imported project pointed to a\n"
+ + "ProGuard rule file in the current user's home directory. This is not\n"
+ + "supported from the Android Gradle build system (which emphasizes\n"
+ + "repeatable builds). If you want to share ProGuard rules between\n"
+ + "projects, use relative paths (from the project location) instead.\n";
+
+ static final String MSG_RISKY_PROJECT_LOCATION = "\n"
+ + "Risky Project Location:\n"
+ + "-----------------------\n"
+ + "The tools *should* handle project locations in any directory. However,\n"
+ + "due to bugs, placing projects in directories containing spaces in the\n"
+ + "path, or characters like \", ' and &, have had issues. We're working to\n"
+ + "eliminate these bugs, but to save yourself headaches you may want to\n"
+ + "move your project to a location where this is not a problem.\n";
+
+ private final GradleImport mImporter;
+ private File mDestDir;
+ private boolean mManifestsMayDiffer;
+ private Map<String,List<String>> mNotMigrated = Maps.newHashMap();
+ private Map<ImportModule,Map<File,File>> mMoved = Maps.newHashMap();
+ private Map<File,GradleCoordinate> mJarDependencies = Maps.newHashMap();
+ private Map<String,List<GradleCoordinate>> mLibDependencies = Maps.newHashMap();
+ private List<String> mGuessedDependencyVersions = Lists.newArrayList();
+ private File mLastGuessedJar;
+ private List<String> mIgnoredUserHomeProGuardFiles = Lists.newArrayList();
+ private boolean mHasRiskyPathChars;
+ private boolean mWrapErrorMessages = true;
+
+ ImportSummary(@NonNull GradleImport importer) {
+ mImporter = importer;
+ }
+
+ /**
+ * Writes the summary to the given file. The file should be in a directory which
+ * has already been created by the caller.
+ */
+ public void write(@NonNull File file) throws IOException {
+ String summary = createSummary();
+ assert file.getParentFile().exists();
+ Files.write(summary, file, Charsets.UTF_8);
+ }
+
+ public void setDestDir(File destDir) {
+ mDestDir = destDir;
+
+ mHasRiskyPathChars = false;
+ String path = destDir.getPath();
+ for (int i = 0, n = path.length(); i < n; i++) {
+ char c = path.charAt(i);
+ if (isRiskyPathChar(c)) {
+ mHasRiskyPathChars = true;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void setWrapErrorMessages(boolean wrap) {
+ mWrapErrorMessages = wrap;
+ }
+
+ private static boolean isRiskyPathChar(char c) {
+ return (c == ' ' || c == '\'' || c == '"' || c == '&');
+ }
+
+ public void reportManifestsMayDiffer() {
+ mManifestsMayDiffer = true;
+ }
+
+ public void reportReplacedJar(@NonNull File jar, @NonNull GradleCoordinate dependency) {
+ mJarDependencies.put(jar, dependency);
+ if (jar.equals(mLastGuessedJar)) {
+ boolean replaced = mGuessedDependencyVersions.remove(jar.getName());
+ if (replaced) {
+ mGuessedDependencyVersions.add(jar.getName() + " => version " +
+ dependency.getFullRevision() + " in " + dependency.toString());
+ }
+ mLastGuessedJar = null;
+ }
+ }
+
+ public void reportReplacedLib(@NonNull String module,
+ @NonNull List<GradleCoordinate> dependencies) {
+ mLibDependencies.put(module, dependencies);
+ }
+
+ public void reportGuessedVersion(@NonNull File jar) {
+ mGuessedDependencyVersions.add(jar.getName());
+ mLastGuessedJar = jar;
+ }
+
+ public void reportIgnoredUserHomeProGuardFile(@NonNull String relativePath) {
+ mIgnoredUserHomeProGuardFiles.add(relativePath);
+ }
+
+ public void reportMoved(@NonNull ImportModule module, @NonNull File from,
+ @NonNull File to) {
+ Map<File, File> map = mMoved.get(module);
+ if (map == null) {
+ map = new LinkedHashMap<File, File>(); // preserve insert order
+ mMoved.put(module, map);
+ }
+ map.put(from, to);
+ }
+
+ /**
+ * Reports an ignored relative path. (We use a path string rather than a file since
+ * we want to include a trailing file separator on directories; these relative paths are
+ * not interpreted in any way other than to display in the report.)
+ */
+ public void reportIgnored(@NonNull String module, @NonNull String path) {
+ List<String> list = mNotMigrated.get(module);
+ if (list == null) {
+ list = Lists.newArrayList();
+ mNotMigrated.put(module, list);
+ }
+ list.add(path);
+ }
+
+ /** Provides the summary */
+ @NonNull
+ public String createSummary() {
+ StringBuilder sb = new StringBuilder(2000);
+ sb.append(MSG_HEADER);
+
+ List<String> problems = Lists.newArrayList();
+ problems.addAll(mImporter.getErrors());
+ problems.addAll(mImporter.getWarnings());
+ if (!problems.isEmpty()) {
+ sb.append("\n");
+ for (String warning : problems) {
+ sb.append(" * ");
+ if (mWrapErrorMessages) {
+ sb.append(SdkUtils.wrap(warning, 80, " "));
+ } else {
+ sb.append(warning);
+ }
+ sb.append("\n");
+ }
+ }
+
+ if (mHasRiskyPathChars) {
+ sb.append(MSG_RISKY_PROJECT_LOCATION);
+ String path = mDestDir.getPath();
+ sb.append(path).append("\n");
+ for (int i = 0, n = path.length(); i < n; i++) {
+ char c = path.charAt(i);
+ sb.append(isRiskyPathChar(c) ? '-' : ' ');
+ }
+ sb.append("\n");
+ }
+
+ if (mManifestsMayDiffer) {
+ sb.append(MSG_MANIFEST);
+ }
+
+ if (!mNotMigrated.isEmpty()) {
+ sb.append(MSG_UNHANDLED);
+ List<String> modules = Lists.newArrayList(mNotMigrated.keySet());
+ Collections.sort(modules);
+ for (String module : modules) {
+ if (modules.size() > 1) {
+ sb.append("From ").append(module).append(":\n");
+ }
+ List<String> sorted = new ArrayList<String>(mNotMigrated.get(module));
+ Collections.sort(sorted);
+ for (String path : sorted) {
+ sb.append("* ").append(path).append("\n");
+ }
+ }
+ }
+
+ if (!mJarDependencies.isEmpty()) {
+ sb.append(MSG_REPLACED_JARS);
+ // TODO: Also add note here about switching to AAR's potentially also creating
+ // compilation errors because it now enforces that app min sdk version is >= library
+ // min sdk version, and suggesting that they re-run import with replaceJars=false
+ // if this leads to problems.
+ List<File> files = Lists.newArrayList(mJarDependencies.keySet());
+ Collections.sort(files);
+ for (File file : files) {
+ String jar = file.getName();
+ GradleCoordinate dependency = mJarDependencies.get(file);
+ sb.append(jar).append(" => ").append(dependency).append("\n");
+ }
+ }
+
+ if (!mGuessedDependencyVersions.isEmpty()) {
+ sb.append(MSG_GUESSED_VERSIONS);
+ Collections.sort(mGuessedDependencyVersions);
+ for (String replaced : mGuessedDependencyVersions) {
+ sb.append(replaced).append("\n");
+ }
+ }
+
+ if (!mLibDependencies.isEmpty()) {
+ sb.append(MSG_REPLACED_LIBS);
+ List<String> modules = Lists.newArrayList(mLibDependencies.keySet());
+ Collections.sort(modules);
+ for (String module : modules) {
+ List<GradleCoordinate> dependencies = mLibDependencies.get(module);
+ if (dependencies.size() == 1) {
+ sb.append(module).append(" => ").append(dependencies).append("\n");
+ } else {
+ sb.append(module).append(" =>\n");
+ for (GradleCoordinate dependency : dependencies) {
+ sb.append(" ").append(dependency).append("\n");
+ }
+ }
+ }
+ }
+
+ if (!mMoved.isEmpty()) {
+ sb.append(MSG_FOLDER_STRUCTURE);
+ List<ImportModule> modules = Lists.newArrayList(mMoved.keySet());
+ Collections.sort(modules);
+ for (ImportModule module : modules) {
+ if (modules.size() > 1) {
+ sb.append("In ").append(module.getOriginalName()).append(":\n");
+ }
+ Map<File, File> map = mMoved.get(module);
+ List<File> sorted = new ArrayList<File>(map.keySet());
+ Collections.sort(sorted);
+ for (File from : sorted) {
+ sb.append("* ");
+ File to = map.get(from);
+ assert to != null : from;
+
+ File fromRelative = null;
+ File toRelative = null;
+ try {
+ fromRelative = module.computeProjectRelativePath(from);
+ if (mDestDir != null) {
+ toRelative = GradleImport.computeRelativePath(
+ mDestDir.getCanonicalFile(), to);
+ }
+ } catch (IOException ioe) {
+ // pass; use full path
+ }
+ if (fromRelative == null) {
+ fromRelative = from;
+ }
+ if (toRelative == null) {
+ toRelative = to;
+ }
+ sb.append(fromRelative.getPath());
+ if (from.isDirectory()) {
+ sb.append(File.separator);
+ }
+ sb.append(" => ");
+ sb.append(toRelative.getPath());
+ if (to.isDirectory()) {
+ sb.append(File.separator);
+ }
+ sb.append("\n");
+ }
+ }
+ }
+
+ if (mImporter.needSupportRepository() && mImporter.isMissingSupportRepository()) {
+ sb.append(MSG_MISSING_REPO_1);
+ sb.append(mImporter.getSdkLocation()).append("\n");
+ sb.append(MSG_MISSING_REPO_2);
+ }
+
+ if (mImporter.needGoogleRepository() && mImporter.isMissingGoogleRepository()) {
+ sb.append(MSG_MISSING_GOOGLE_REPOSITORY_1);
+ sb.append(mImporter.getSdkLocation()).append("\n");
+ sb.append(MSG_MISSING_GOOGLE_REPOSITORY_2);
+ }
+
+ if (FullRevision.parseRevision(mImporter.getBuildToolsVersion()).getMajor() < 19) {
+ sb.append(MSG_BUILD_TOOLS_VERSION);
+ }
+
+ if (!mIgnoredUserHomeProGuardFiles.isEmpty()) {
+ sb.append(MSG_USER_HOME_PROGUARD);
+ Collections.sort(mIgnoredUserHomeProGuardFiles);
+ for (String path : mIgnoredUserHomeProGuardFiles) {
+ sb.append(path).append("\n");
+ }
+ }
+
+ sb.append(MSG_FOOTER);
+
+ // TODO: Add further suggestions:
+ // - Consider removing uses-sdk elements and versionName/Code from manifest (such that it's
+ // only in the Gradle file)
+ // - Mention that we switched over to compileSdkVersion and buildToolsVersion 19 (to pick
+ // up on necessary gradle support). If the tools relied on building with older APIs,
+ // be aware of changes. (Mention API lint (gradlew lint) to prevent accidental API
+ // usage.)
+
+ return sb.toString().replace("\n", GradleImport.NL);
+ }
+}
diff --git a/gradle-import/src/main/test/com/android/tools/gradle/eclipse/GradleImportTest.java b/gradle-import/src/main/test/com/android/tools/gradle/eclipse/GradleImportTest.java
new file mode 100644
index 0000000..46ceccb
--- /dev/null
+++ b/gradle-import/src/main/test/com/android/tools/gradle/eclipse/GradleImportTest.java
@@ -0,0 +1,3600 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.gradle.eclipse;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.DOT_GRADLE;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.FD_GRADLE;
+import static com.android.SdkConstants.FD_GRADLE_WRAPPER;
+import static com.android.SdkConstants.FD_TEMPLATES;
+import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.FN_GRADLE_WRAPPER_UNIX;
+import static com.android.SdkConstants.FN_GRADLE_WRAPPER_WIN;
+import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
+import static com.android.SdkConstants.FN_PROJECT_PROPERTIES;
+import static com.android.tools.gradle.eclipse.GradleImport.ANDROID_GRADLE_PLUGIN;
+import static com.android.tools.gradle.eclipse.GradleImport.CURRENT_BUILD_TOOLS_VERSION;
+import static com.android.tools.gradle.eclipse.GradleImport.DECLARE_GLOBAL_REPOSITORIES;
+import static com.android.tools.gradle.eclipse.GradleImport.IMPORT_SUMMARY_TXT;
+import static com.android.tools.gradle.eclipse.GradleImport.MAVEN_REPOSITORY;
+import static com.android.tools.gradle.eclipse.GradleImport.NL;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_FOLDER_STRUCTURE;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_FOOTER;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_GUESSED_VERSIONS;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_HEADER;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_MANIFEST;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_MISSING_GOOGLE_REPOSITORY_1;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_MISSING_GOOGLE_REPOSITORY_2;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_MISSING_REPO_1;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_MISSING_REPO_2;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_REPLACED_JARS;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_REPLACED_LIBS;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_RISKY_PROJECT_LOCATION;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_UNHANDLED;
+import static com.android.tools.gradle.eclipse.ImportSummary.MSG_USER_HOME_PROGUARD;
+import static com.google.common.base.Charsets.UTF_8;
+import static java.io.File.separator;
+import static java.io.File.separatorChar;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.SdkManager;
+import com.android.testutils.TestUtils;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+import com.android.utils.SdkUtils;
+import com.android.utils.StdLogger;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Unit tests for the Gradle importer.
+ * <p>
+ * TODO:
+ * - Test emitting proguard rules in android-library (check build.gradle declaration)
+ * - Test compile options
+ * - Skip instrumentation stuff if same as default?
+ * - Test having more than one SDK proguard list?
+ * - Escaped groovy paths
+ * - Path variable in .classpath
+ * - Case where a lib is both in libs/ and in .classpath (cull and only include once)
+ * - Filename clashes (collapsing into libs/, etc.)
+ * - Finding a lib that is using a workspace path which isn't found but is a direct child
+ * - Instrumentation libs/ .jar which gets replaced by a gradle dependency
+ */
+public class GradleImportTest extends TestCase {
+ private static File createProject(String name, String pkg) throws IOException {
+ File dir = Files.createTempDir();
+ return createProject(dir, name, pkg);
+ }
+
+ private static File createProject(File dir, String name, String pkg) throws IOException {
+ createDotProject(dir, name, true);
+ File src = new File("src");
+ File gen = new File("gen");
+
+ createSampleJavaSource(dir, "src", pkg, "MyActivity");
+ createSampleJavaSource(dir, "gen", pkg, "R");
+
+ createClassPath(dir,
+ new File("bin", "classes"),
+ Arrays.asList(src, gen),
+ Collections.<File>emptyList());
+ createProjectProperties(dir, "android-17", null, null, null,
+ Collections.<File>emptyList());
+ createAndroidManifest(dir, pkg, 8, 16, null);
+
+ createDefaultStrings(dir);
+ createDefaultIcon(dir);
+
+ return dir;
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testResolveExpressions() throws Exception {
+ File root = Files.createTempDir();
+ File projectDir = new File(root, "dir1" + separator + "dir2" + separator + "dir3" +
+ separator + "dir4" + separator + "prj");
+ projectDir.mkdirs();
+ createProject(projectDir, "test1", "test.pkg");
+ File var1 = new File(root, "sub1" + separator + "sub2" + separator + "sub3");
+ var1.mkdirs();
+ File var4 = new File(projectDir.getParentFile(), "var4");
+ var4.mkdirs();
+ File tpl = new File(projectDir.getParentFile().getParentFile().getParentFile(), "TARGET" +
+ separator + "android" + separator + "third-party");
+ tpl.mkdirs();
+ File supportLib = new File(tpl, "android-support-v4.r19.jar");
+ supportLib.createNewFile();
+
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<projectDescription>\n"
+ + "\t<name>UnitTest</name>\n"
+ + "\t<comment></comment>\n"
+ + "\t<projects>\n"
+ + "\t</projects>\n"
+ + "\t<buildSpec>\n"
+ + "\t\t<buildCommand>\n"
+ + "\t\t\t<name>org.eclipse.jdt.core.javabuilder</name>\n"
+ + "\t\t\t<arguments>\n"
+ + "\t\t\t</arguments>\n"
+ + "\t\t</buildCommand>\n"
+ + "\t</buildSpec>\n"
+ + "\t<natures>\n"
+ + "\t\t<nature>org.eclipse.jdt.core.javanature</nature>\n"
+ + "\t</natures>\n"
+ + "\t<linkedResources>\n"
+ + "\t\t<link>\n"
+ + "\t\t\t<name>MYLIBS</name>\n"
+ + "\t\t\t<type>2</type>\n"
+ + "\t\t\t<locationURI>MYLIBS</locationURI>\n"
+ + "\t\t</link>\n"
+ + "\t\t<link>\n"
+ + "\t\t\t<name>3rd_java_libs</name>\n"
+ + "\t\t\t<type>2</type>\n"
+ + "\t\t\t<locationURI>PARENT-3-PROJECT_LOC/TARGET/android/third-party</locationURI>\n"
+ + "\t\t</link>\n"
+ + "\t\t<link>\n"
+ + "\t\t\t<name>jnilibs</name>\n"
+ + "\t\t\t<type>2</type>\n"
+ + "\t\t\t<locationURI>virtual:/virtual</locationURI>\n"
+ + "\t\t</link>\n"
+ + "\t</linkedResources>\n"
+ + "\t<variableList>\n"
+ + "\t\t<variable>\n"
+ + "\t\t\t<name>MY_VAR_1</name>\n"
+ + "\t\t\t<value>" + SdkUtils.fileToUrl(var1) + "</value>\n"
+ + "\t\t</variable>\n"
+ + "\t\t<variable>\n"
+ + "\t\t\t<name>MY_VAR_2</name>\n"
+ + "\t\t\t<value>$%7BMY_VAR_1%7D</value>\n"
+ + "\t\t</variable>\n"
+ + "\t\t<variable>\n"
+ + "\t\t\t<name>MY_VAR_3</name>\n"
+ + "\t\t\t<value>$%7BPROJECT_LOC%7D/src</value>\n"
+ + "\t\t</variable>\n"
+ + "\t\t<variable>\n"
+ + "\t\t\t<name>MY_VAR_4</name>\n"
+ + "\t\t\t<value>$%7BPARENT-1-PROJECT_LOC%7D/var4</value>\n"
+ + "\t\t</variable>\n"
+ + "\t\t<variable>\n"
+ + "\t\t\t<name>MY_VAR_5</name>\n"
+ + "\t\t\t<value>$%7BPARENT_LOC%7D/var4</value>\n"
+ + "\t\t</variable>\n"
+ + "\t</variableList>\n"
+ + "</projectDescription>",
+ new File(projectDir, ".project"), UTF_8);
+
+ GradleImport importer = new GradleImport();
+ EclipseProject project = EclipseProject.getProject(importer, projectDir);
+ importer.getPathMap();
+
+ // Test absolute paths
+ assertEquals(var1, project.resolveVariableExpression(var1.getPath()));
+ assertEquals(var1, project.resolveVariableExpression(var1.getAbsolutePath()));
+ assertEquals(var1.getCanonicalFile(),
+ project.resolveVariableExpression(var1.getCanonicalPath()));
+ assertEquals(var1, project.resolveVariableExpression(var1.getPath().replace('/',
+ separatorChar))); // on Windows, make sure we handle workspace files with forwards
+
+
+ // Test project relative paths
+ String relative = "src" + separator + "test" + separator + "pkg" + separator
+ + "MyActivity.java";
+ assertEquals(new File(projectDir, relative), project.resolveVariableExpression(relative));
+ assertEquals(new File(projectDir, relative), project.resolveVariableExpression(
+ relative.replace('/', separatorChar)));
+
+ // Test workspace paths
+ // This is handled by testLibraries2
+
+ // Test path variables
+ assertEquals(var1, project.resolveVariableExpression("MY_VAR_1"));
+ assertEquals(var1, project.resolveVariableExpression("MY_VAR_2"));
+ assertEquals(new File(projectDir, "src"), project.resolveVariableExpression("MY_VAR_3"));
+ assertEquals(var4, project.resolveVariableExpression("MY_VAR_4"));
+ assertEquals(var4, project.resolveVariableExpression("MY_VAR_5"));
+
+ // Test linked variables
+ assertEquals(supportLib, project.resolveVariableExpression(
+ "3rd_java_libs/android-support-v4.r19.jar"));
+
+ // Test user-supplied values
+ assertEquals(var1, project.resolveVariableExpression("MY_VAR_1"));
+ importer.getPathMap().put("MY_VAR_1", projectDir);
+ assertEquals(projectDir, project.resolveVariableExpression("MY_VAR_1"));
+ importer.getPathMap().put("/some/unresolved/path", var4);
+ assertEquals(var4, project.resolveVariableExpression("/some/unresolved/path"));
+
+ // Setup for workspace tests
+
+ assertNull(project.resolveVariableExpression("MY_GLOBAL_VAR"));
+ final File workspace = new File(root, "workspace");
+ workspace.mkdirs();
+ File prefs = new File(workspace, ".metadata" + separator +
+ ".plugins" + separator +
+ "org.eclipse.core.runtime" + separator +
+ ".settings" + separator +
+ "org.eclipse.jdt.core.prefs");
+ prefs.getParentFile().mkdirs();
+ File global1 = var1.getParentFile();
+ Files.write(""
+ + "eclipse.preferences.version=1\n"
+ + "org.eclipse.jdt.core.classpathVariable.MY_GLOBAL_VAR="
+ + global1.getPath().replace(separatorChar,'/').replace(":","\\:") + "\n"
+ + "org.eclipse.jdt.core.codeComplete.visibilityCheck=enabled\n"
+ + "org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\n"
+ + "org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6\n"
+ + "org.eclipse.jdt.core.compiler.compliance=1.6\n"
+ + "org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\n"
+ + "org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\n"
+ + "org.eclipse.jdt.core.compiler.source=1.6\n"
+ + "org.eclipse.jdt.core.formatter.tabulation.char=space", prefs, UTF_8);
+ File global2 = var4.getParentFile();
+ prefs = new File(workspace, ".metadata" + separator +
+ ".plugins" + separator +
+ "org.eclipse.core.runtime" + separator +
+ ".settings" + separator +
+ "org.eclipse.core.resources.prefs");
+ prefs.getParentFile().mkdirs();
+ Files.write(""
+ + "eclipse.preferences.version=1\n"
+ + "pathvariable.MY_GLOBAL_VAR_2="
+ + global2.getPath().replace(separatorChar,'/').replace(":", "\\:") + "\n"
+ + "version=1", prefs, UTF_8);
+
+ importer.setEclipseWorkspace(workspace);
+
+ // Test global path variables
+
+ assertEquals(global1, project.resolveVariableExpression("MY_GLOBAL_VAR"));
+ assertEquals(var1, project.resolveVariableExpression("MY_GLOBAL_VAR/sub3"));
+ assertEquals(var1, project.resolveVariableExpression("MY_GLOBAL_VAR" + separator + "sub3"));
+
+ // Test workspace linked resources
+ assertEquals(global2, project.resolveVariableExpression("MY_GLOBAL_VAR_2"));
+
+ deleteDir(projectDir);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testBasic() throws Exception {
+ File projectDir = createProject("test1", "test.pkg");
+
+ // Add some files in there that we are ignoring
+ new File(projectDir, "ic_launcher-web.png").createNewFile();
+ new File(projectDir, "Android.mk").createNewFile();
+ new File(projectDir, "build.properties").createNewFile();
+ new File(projectDir, "local.properties").createNewFile();
+ new File(projectDir, "src" + separator + ".git").mkdir();
+ new File(projectDir, "src" + separator + ".svn").mkdir();
+ File unhandled = new File(projectDir, "unhandledDir1" + separator + "unhandledDir2");
+ unhandled.mkdirs();
+ new File(unhandled, "unhandledFile").createNewFile();
+ new File(unhandled, "unhandledDir3").mkdirs();
+ new File(unhandled.getParentFile(), "unhandledDir4").mkdirs();
+ new File(projectDir, "lint.xml").createNewFile();
+
+ // Project being imported
+ assertEquals(""
+ + ".classpath\n"
+ + ".project\n"
+ + "Android.mk\n"
+ + "AndroidManifest.xml\n"
+ + "build.properties\n"
+ + "gen\n"
+ + " test\n"
+ + " pkg\n"
+ + " R.java\n"
+ + "ic_launcher-web.png\n"
+ + "lint.xml\n"
+ + "local.properties\n"
+ + "project.properties\n"
+ + "res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "src\n"
+ + " .git\n"
+ + " .svn\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + "unhandledDir1\n"
+ + " unhandledDir2\n"
+ + " unhandledDir3\n"
+ + " unhandledFile\n"
+ + " unhandledDir4\n",
+ fileTree(projectDir, true));
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_UNHANDLED
+ + "* Android.mk\n"
+ + "* build.properties\n"
+ + "* ic_launcher-web.png\n"
+ + "* unhandledDir1/\n"
+ + "* unhandledDir1/unhandledDir2/\n"
+ + "* unhandledDir1/unhandledDir2/unhandledFile\n"
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => app/src/main/AndroidManifest.xml\n"
+ + "* lint.xml => app/lint.xml\n"
+ + "* res/ => app/src/main/res/\n"
+ + "* src/ => app/src/main/java/\n"
+ + MSG_FOOTER,
+ true /* checkBuild */);
+
+ // Imported contents
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " lint.xml\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testImportWithoutMinSdkVersion() throws Exception {
+ // Regression test for importing project which does not explicitly set minSdkVersion
+ // and/or targetSdkVersion; this would earlier result in "-1" being written into
+ // build.gradle which fails the build with "> Cannot invoke method minus() on null object"
+ File projectDir = createProject("test1", "test.pkg");
+
+ // Remove <uses-sdk ...>
+ File manifestFile = new File(projectDir, FN_ANDROID_MANIFEST_XML);
+ String manifestContents = Files.toString(manifestFile, UTF_8);
+ int index = manifestContents.indexOf("<uses-sdk");
+ int endIndex = manifestContents.indexOf('>', index);
+ assertFalse(index == -1);
+ assertFalse(endIndex == -1);
+ manifestContents = manifestContents.substring(0, index) +
+ manifestContents.substring(endIndex + 1);
+ Files.write(manifestContents, manifestFile, UTF_8);
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => app/src/main/AndroidManifest.xml\n"
+ + "* res/ => app/src/main/res/\n"
+ + "* src/ => app/src/main/java/\n"
+ + MSG_FOOTER,
+ true /* checkBuild */);
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testMoveRsAndAidl() throws Exception {
+ File projectDir = createProject("test1", "test.pkg");
+ createSampleAidlFile(projectDir, "src", "test.pkg");
+ createSampleRsFile(projectDir, "src", "test.pkg");
+
+ // Project being imported
+ assertEquals(""
+ + ".classpath\n"
+ + ".project\n"
+ + "AndroidManifest.xml\n"
+ + "gen\n"
+ + " test\n"
+ + " pkg\n"
+ + " R.java\n"
+ + "project.properties\n"
+ + "res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "src\n"
+ + " test\n"
+ + " pkg\n"
+ + " IHardwareService.aidl\n"
+ + " MyActivity.java\n"
+ + " latency.rs\n",
+ fileTree(projectDir, true));
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + DEFAULT_MOVED
+ + "* src/test/pkg/IHardwareService.aidl => app/src/main/aidl/test/pkg/IHardwareService.aidl\n"
+ + "* src/test/pkg/latency.rs => app/src/main/rs/latency.rs\n"
+ + MSG_FOOTER,
+ true /* checkBuild */);
+
+ // Imported contents
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " aidl\n"
+ + " test\n"
+ + " pkg\n"
+ + " IHardwareService.aidl\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + " rs\n"
+ + " latency.rs\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testImportAndroidSample() throws Exception {
+ String testSdkPath = getTestSdkPath();
+ if (testSdkPath == null) {
+ return;
+ }
+ File samples = new File(testSdkPath, "samples");
+ if (!samples.exists()) {
+ return;
+ }
+ File[] platforms = samples.listFiles();
+ if (platforms == null) {
+ return;
+ }
+ File projectDir = null;
+ for (File platform : platforms) {
+ if (platform.isDirectory()) {
+ File apiDemos = new File(platform, "legacy" + separator + "ApiDemos");
+ if (apiDemos.isDirectory()) {
+ projectDir = apiDemos;
+ break;
+ }
+ }
+ }
+ if (projectDir == null) {
+ return;
+ }
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_UNHANDLED
+ + "* README.txt\n"
+ + "* tests/\n"
+ + "* tests/build.properties\n"
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => app/src/main/AndroidManifest.xml\n"
+ + "* assets/ => app/src/main/assets/\n"
+ + "* res/ => app/src/main/res/\n"
+ + "* src/ => app/src/main/java/\n"
+ + "* src/com/example/android/apis/app/IRemoteService.aidl => app/src/main/aidl/com/example/android/apis/app/IRemoteService.aidl\n"
+ + "* src/com/example/android/apis/app/IRemoteServiceCallback.aidl => app/src/main/aidl/com/example/android/apis/app/IRemoteServiceCallback.aidl\n"
+ + "* src/com/example/android/apis/app/ISecondary.aidl => app/src/main/aidl/com/example/android/apis/app/ISecondary.aidl\n"
+ + "* tests/src/ => app/src/androidTest/java/\n"
+ + MSG_FOOTER,
+ true /* checkBuild */);
+
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testNoProjectMetadata() throws Exception {
+ File projectDir = createProject("test1", "test.pkg");
+
+ // Add some files in there that we are ignoring
+ new File(projectDir, "ic_launcher-web.png").createNewFile();
+ new File(projectDir, "Android.mk").createNewFile();
+ new File(projectDir, "build.properties").createNewFile();
+ new File(projectDir, "local.properties").createNewFile();
+ new File(projectDir, "src" + separator + ".git").mkdir();
+ new File(projectDir, "src" + separator + ".svn").mkdir();
+ File unhandled = new File(projectDir, "unhandledDir1" + separator + "unhandledDir2");
+ unhandled.mkdirs();
+ new File(unhandled, "unhandledFile").createNewFile();
+ new File(unhandled, "unhandledDir3").mkdirs();
+ new File(unhandled.getParentFile(), "unhandledDir4").mkdirs();
+ new File(projectDir, ".classpath").delete();
+ new File(projectDir, ".project").delete();
+
+ // Project being imported
+ assertEquals(""
+ + "Android.mk\n"
+ + "AndroidManifest.xml\n"
+ + "build.properties\n"
+ + "gen\n"
+ + " test\n"
+ + " pkg\n"
+ + " R.java\n"
+ + "ic_launcher-web.png\n"
+ + "local.properties\n"
+ + "project.properties\n"
+ + "res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "src\n"
+ + " .git\n"
+ + " .svn\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + "unhandledDir1\n"
+ + " unhandledDir2\n"
+ + " unhandledDir3\n"
+ + " unhandledFile\n"
+ + " unhandledDir4\n",
+ fileTree(projectDir, true));
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_UNHANDLED
+ + "* Android.mk\n"
+ + "* build.properties\n"
+ + "* ic_launcher-web.png\n"
+ + "* unhandledDir1/\n"
+ + "* unhandledDir1/unhandledDir2/\n"
+ + "* unhandledDir1/unhandledDir2/unhandledFile\n"
+ + MSG_FOLDER_STRUCTURE
+ + DEFAULT_MOVED
+ + MSG_FOOTER,
+ true /* checkBuild */);
+
+ // Imported contents
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testNoProjectProperties() throws Exception {
+ // Missing project.properties
+ File projectDir = createProject("testError3", "test.pkg");
+
+ new File(projectDir, FN_PROJECT_PROPERTIES).delete();
+
+ final AtomicReference<GradleImport> importReference = new AtomicReference<GradleImport>();
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + DEFAULT_MOVED
+ + MSG_FOOTER,
+ true /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importReference.set(importer);
+ }
+ });
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testLibraries() throws Exception {
+ File root = Files.createTempDir();
+ File app = createLibrary(root, "test.lib2.pkg", false);
+
+ // ADT Directory structure created by the above:
+ assertEquals(""
+ + "App\n"
+ + " .classpath\n"
+ + " .gitignore\n"
+ + " .project\n"
+ + " AndroidManifest.xml\n"
+ + " gen\n"
+ + " test\n"
+ + " pkg\n"
+ + " R.java\n"
+ + " project.properties\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + " src\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + "Lib1\n"
+ + " .classpath\n"
+ + " .project\n"
+ + " AndroidManifest.xml\n"
+ + " gen\n"
+ + " test\n"
+ + " lib\n"
+ + " pkg\n"
+ + " R.java\n"
+ + " project.properties\n"
+ + " src\n"
+ + " test\n"
+ + " lib\n"
+ + " pkg\n"
+ + " MyLibActivity.java\n"
+ + "Lib2\n"
+ + " .classpath\n"
+ + " .project\n"
+ + " AndroidManifest.xml\n"
+ + " gen\n"
+ + " test\n"
+ + " lib2\n"
+ + " pkg\n"
+ + " R.java\n"
+ + " project.properties\n"
+ + " src\n"
+ + " test\n"
+ + " lib2\n"
+ + " pkg\n"
+ + " MyLib2Activity.java\n"
+ + "subdir1\n"
+ + " subdir2\n"
+ + " JavaLib\n"
+ + " .classpath\n"
+ + " .gitignore\n"
+ + " .project\n"
+ + " src\n"
+ + " test\n"
+ + " lib2\n"
+ + " pkg\n"
+ + " Utilities.java\n",
+ fileTree(root, true));
+
+ File imported = checkProject(app, ""
+ + MSG_HEADER
+ + MSG_MANIFEST
+ + MSG_UNHANDLED
+ + "From App:\n"
+ + "* .gitignore\n"
+ + "From JavaLib:\n"
+ + "* .gitignore\n"
+ + MSG_FOLDER_STRUCTURE
+ + "In JavaLib:\n"
+ + "* src/ => javaLib/src/main/java/\n"
+ + "In Lib1:\n"
+ + "* AndroidManifest.xml => lib1/src/main/AndroidManifest.xml\n"
+ + "* src/ => lib1/src/main/java/\n"
+ + "In Lib2:\n"
+ + "* AndroidManifest.xml => lib2/src/main/AndroidManifest.xml\n"
+ + "* src/ => lib2/src/main/java/\n"
+ + "In App:\n"
+ + "* AndroidManifest.xml => app/src/main/AndroidManifest.xml\n"
+ + "* res/ => app/src/main/res/\n"
+ + "* src/ => app/src/main/java/\n"
+ + MSG_FOOTER,
+ true /* checkBuild */);
+
+ // Imported project
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + "javaLib\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " java\n"
+ + " test\n"
+ + " lib2\n"
+ + " pkg\n"
+ + " Utilities.java\n"
+ + "lib1\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " lib\n"
+ + " pkg\n"
+ + " MyLibActivity.java\n"
+ + "lib2\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " lib2\n"
+ + " pkg\n"
+ + " MyLib2Activity.java\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ // Let's peek at some of the key files to make sure we codegen'ed the right thing
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'java'\n",
+ Files.toString(new File(imported, "javaLib" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ // Let's peek at some of the key files to make sure we codegen'ed the right thing
+ //noinspection ConstantConditions
+ assertEquals(""
+ + "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n"
+ + (DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "allprojects {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + "}\n" : ""),
+ Files.toString(new File(imported, "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 17\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 16\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard false\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile project(':lib1')\n"
+ + " compile project(':lib2')\n"
+ + " compile project(':javaLib')\n"
+ + "}\n",
+ Files.toString(new File(imported, "app" + separator + "build.gradle"), UTF_8)
+ .replace(NL,"\n"));
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android-library'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 18\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.lib2.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 8\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard false\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile project(':lib1')\n"
+ + "}\n",
+ Files.toString(new File(imported, "lib2" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+ assertEquals(""
+ + "include ':javaLib'\n"
+ + "include ':lib1'\n"
+ + "include ':lib2'\n"
+ + "include ':app'\n",
+ Files.toString(new File(imported, "settings.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private static File createLibrary(File root, String lib2Pkg, boolean startLibrariesAt1)
+ throws IOException {
+ // Plain Java library, used by Library 1 and App
+ String javaLibName = "JavaLib";
+ String javaLibRelative = "subdir1" + separator + "subdir2" + separator + javaLibName;
+ File javaLib = new File(root, javaLibRelative);
+ javaLib.mkdirs();
+ String javaLibPkg = "test.lib2.pkg";
+ createDotProject(javaLib, javaLibName, false);
+ File javaLibSrc = new File("src");
+ createSampleJavaSource(javaLib, "src", javaLibPkg, "Utilities");
+ createClassPath(javaLib,
+ new File("bin"),
+ Collections.singletonList(javaLibSrc),
+ Collections.<File>emptyList());
+
+ // Make Android library 1
+
+ String lib1Name = "Lib1";
+ File lib1 = new File(root, lib1Name);
+ lib1.mkdirs();
+ String lib1Pkg = "test.lib.pkg";
+ createDotProject(lib1, lib1Name, true);
+ File lib1Src = new File("src");
+ File lib1Gen = new File("gen");
+ createSampleJavaSource(lib1, "src", lib1Pkg, "MyLibActivity");
+ createSampleJavaSource(lib1, "gen", lib1Pkg, "R");
+ createClassPath(lib1,
+ new File("bin", "classes"),
+ Arrays.asList(lib1Src, lib1Gen),
+ Collections.<File>emptyList());
+ createProjectProperties(lib1, "android-19", null, true, null,
+ // Using \ instead of File.separator deliberately to test path conversion
+ // handling: you can import a Windows relative path on a non-Windows system
+ // and vice versa
+ Collections.singletonList(new File(".." + '\\' + javaLibRelative)),
+ startLibrariesAt1);
+ createAndroidManifest(lib1, lib1Pkg, -1, -1, "<application/>");
+
+ String lib2Name = "Lib2";
+ File lib2 = new File(root, lib2Name);
+ lib2.mkdirs();
+ createDotProject(lib2, lib2Name, true);
+ File lib2Src = new File("src");
+ File lib2Gen = new File("gen");
+ createSampleJavaSource(lib2, "src", lib2Pkg, "MyLib2Activity");
+ createSampleJavaSource(lib2, "gen", lib2Pkg, "R");
+ createClassPath(lib2,
+ new File("bin", "classes"),
+ Arrays.asList(lib2Src, lib2Gen),
+ Collections.<File>emptyList());
+ createProjectProperties(lib2, "android-18", null, true, null,
+ // Deliberately using / instead of Files.separator, for opposite
+ // test of file separator for lib1 above
+ Collections.singletonList(new File(".." + '/' + lib1Name)));
+ createAndroidManifest(lib2, lib2Pkg, 7, -1, "<application/>");
+
+ // Main app project, depends on library1, library2 and java lib
+ String appName = "App";
+ File app = new File(root, appName);
+ app.mkdirs();
+ String appPkg = "test.pkg";
+ createDotProject(app, appName, true);
+ File appSrc = new File("src");
+ File appGen = new File("gen");
+ createSampleJavaSource(app, "src", appPkg, "MyActivity");
+ createSampleJavaSource(app, "gen", appPkg, "R");
+ createClassPath(app,
+ new File("bin", "classes"),
+ Arrays.asList(appSrc, appGen),
+ Collections.<File>emptyList());
+ createProjectProperties(app, "android-17", null, null, null,
+ Arrays.asList(
+ new File(".." + separator + lib1Name),
+ new File(".." + separator + lib2Name),
+ new File(".." + separator + javaLibRelative)));
+ createAndroidManifest(app, appPkg, 8, 16, null);
+ createDefaultStrings(app);
+ createDefaultIcon(app);
+
+ // Add some files in there that we are ignoring
+ new File(app, ".gitignore").createNewFile();
+ new File(javaLib, ".gitignore").createNewFile();
+ return app;
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testReplaceJar() throws Exception {
+ // Add in some well known jars and make sure they get migrated as dependencies
+ File projectDir = createProject("test1", "test.pkg");
+ File libs = new File(projectDir, "libs");
+ libs.mkdirs();
+ new File(libs, "android-support-v4.jar").createNewFile();
+ new File(libs, "android-support-v7-gridlayout.jar").createNewFile();
+ new File(libs, "android-support-v7-appcompat.jar").createNewFile();
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_REPLACED_JARS
+ + "android-support-v4.jar => com.android.support:support-v4:+\n"
+ + "android-support-v7-appcompat.jar => com.android.support:appcompat-v7:+\n"
+ + "android-support-v7-gridlayout.jar => com.android.support:gridlayout-v7:+\n"
+ + MSG_FOLDER_STRUCTURE
+ + DEFAULT_MOVED
+ + (getTestSdkPath() == null ? MSG_MISSING_REPO_1 + "null\n" + MSG_MISSING_REPO_2 : "")
+ + MSG_FOOTER,
+ true /* checkBuild */);
+
+ // Imported contents
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 17\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 16\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard false\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile 'com.android.support:support-v4:+'\n"
+ + " compile 'com.android.support:appcompat-v7:+'\n"
+ + " compile 'com.android.support:gridlayout-v7:+'\n"
+ + "}\n",
+ Files.toString(new File(imported, "app" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testOptions() throws Exception {
+ // Check options like turning off jar replacement and leaving module names capitalized
+ File projectDir = createProject("Test1", "test.pkg");
+ File libs = new File(projectDir, "libs");
+ libs.mkdirs();
+ new File(libs, "android-support-v4.jar").createNewFile();
+ new File(libs, "android-support-v7-gridlayout.jar").createNewFile();
+ new File(libs, "android-support-v7-appcompat.jar").createNewFile();
+ new File(libs, "armeabi").mkdirs();
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => Test1/src/main/AndroidManifest.xml\n"
+ + "* libs/android-support-v4.jar => Test1/libs/android-support-v4.jar\n"
+ + "* libs/android-support-v7-appcompat.jar => Test1/libs/android-support-v7-appcompat.jar\n"
+ + "* libs/android-support-v7-gridlayout.jar => Test1/libs/android-support-v7-gridlayout.jar\n"
+ + "* res/ => Test1/src/main/res/\n"
+ + "* src/ => Test1/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */,
+ new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importer.setGradleNameStyle(false);
+ importer.setReplaceJars(false);
+ importer.setReplaceLibs(false);
+ }
+ });
+
+ // Imported contents
+ assertEquals(""
+ + "Test1\n"
+ + " build.gradle\n"
+ + " libs\n"
+ + " android-support-v4.jar\n"
+ + " android-support-v7-appcompat.jar\n"
+ + " android-support-v7-gridlayout.jar\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 17\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 16\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard false\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile files('libs/android-support-v4.jar')\n"
+ + " compile files('libs/android-support-v7-appcompat.jar')\n"
+ + " compile files('libs/android-support-v7-gridlayout.jar')\n"
+ + "}\n",
+ Files.toString(new File(imported, "test1" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testJni() throws Exception {
+ File root = Files.createTempDir();
+ final File sdkLocation = new File(root, "sdk");
+ sdkLocation.mkdirs();
+ final File ndkLocation = new File(root, "ndk");
+ ndkLocation.mkdirs();
+ File projectDir = new File(root, "project");
+ projectDir.mkdirs();
+ createProject(projectDir, "testJni", "test.pkg");
+ createDotProject(projectDir, "testJni", true, true);
+ File jni = new File(projectDir, "jni");
+ jni.mkdirs();
+ File makefile = new File(jni, "Android.mk");
+ Files.write(""
+ + "LOCAL_PATH := $(call my-dir)\n"
+ + "\n"
+ + "include $(CLEAR_VARS)\n"
+ + "\n"
+ + "LOCAL_MODULE := hello-jni\n"
+ + "LOCAL_SRC_FILES := hello-jni.c\n"
+ + "\n"
+ + "include $(BUILD_SHARED_LIBRARY)",
+ makefile, UTF_8);
+ new File(jni, "Application.mk").createNewFile();
+ new File(jni, "HelloJni.cpp").createNewFile();
+ new File(jni, "hello-jni.c").createNewFile();
+
+ File libs = new File(projectDir, "libs");
+ libs.mkdirs();
+ File armeabi = new File(libs, "armeabi");
+ armeabi.mkdirs();
+ new File(armeabi, "libexternal.so").createNewFile();
+ new File(armeabi, "libhello-jni.so").createNewFile();
+ File mips = new File(libs, "mips");
+ mips.mkdirs();
+ new File(mips, "libexternal.so").createNewFile();
+ new File(mips, "libhello-jni.so").createNewFile();
+
+ Files.write(
+ escapeProperty("sdk.dir", sdkLocation.getPath()) + "\n" +
+ escapeProperty("ndk.dir", ndkLocation.getPath()) + "\n",
+ new File(projectDir, FN_LOCAL_PROPERTIES), UTF_8);
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => testJni/src/main/AndroidManifest.xml\n"
+ + "* jni/ => testJni/src/main/jni/\n"
+ + "* libs/armeabi/libexternal.so => testJni/src/main/jniLibs/armeabi/libexternal.so\n"
+ + "* libs/mips/libexternal.so => testJni/src/main/jniLibs/mips/libexternal.so\n"
+ + "* res/ => testJni/src/main/res/\n"
+ + "* src/ => testJni/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */,
+ new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ assertFalse(importer.isImportIntoExisting());
+ assertFalse(importer.isPerModuleRepositories());
+
+ importer.setGradleNameStyle(false);
+ importer.setSdkLocation(null);
+ importer.setReplaceJars(false);
+ importer.setReplaceLibs(false);
+ }
+ });
+
+ // Imported contents
+ assertEquals(""
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + "local.properties\n"
+ + "settings.gradle\n"
+ + "testJni\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " jni\n"
+ + " Android.mk\n"
+ + " Application.mk\n"
+ + " HelloJni.cpp\n"
+ + " hello-jni.c\n"
+ + " jniLibs\n"
+ + " armeabi\n"
+ + " libexternal.so\n"
+ + " mips\n"
+ + " libexternal.so\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n",
+ fileTree(imported, true));
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 17\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 16\n"
+ + "\n"
+ + " ndk {\n"
+ + " moduleName \"hello-jni\"\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard false\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ Files.toString(new File(imported, "testJni" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ assertEquals(sdkLocation.getPath(),
+ GradleImport.getProperties(new File(imported, FN_LOCAL_PROPERTIES)).
+ getProperty("sdk.dir"));
+ assertEquals(ndkLocation.getPath(),
+ GradleImport.getProperties(new File(imported, FN_LOCAL_PROPERTIES)).
+ getProperty("ndk.dir"));
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testJniLibs() throws Exception {
+ // Check that ABI libs are copied to the right place
+ File projectDir = createProject("Test1", "test.pkg");
+ File libs = new File(projectDir, "libs");
+ libs.mkdirs();
+ new File(libs, "android-support-v4.jar").createNewFile();
+ File armeabi = new File(libs, "armeabi");
+ armeabi.mkdirs();
+ new File(armeabi, "libfoo.so").createNewFile();
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => Test1/src/main/AndroidManifest.xml\n"
+ + "* libs/android-support-v4.jar => Test1/libs/android-support-v4.jar\n"
+ + "* libs/armeabi/libfoo.so => Test1/src/main/jniLibs/armeabi/libfoo.so\n"
+ + "* res/ => Test1/src/main/res/\n"
+ + "* src/ => Test1/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */,
+ new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importer.setGradleNameStyle(false);
+ importer.setReplaceJars(false);
+ importer.setReplaceLibs(false);
+ }
+ });
+
+ // Imported contents
+ assertEquals(""
+ + "Test1\n"
+ + " build.gradle\n"
+ + " libs\n"
+ + " android-support-v4.jar\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " jniLibs\n"
+ + " armeabi\n"
+ + " libfoo.so\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testInstrumentation1() throws Exception {
+ File root = Files.createTempDir();
+ File projectDir = new File(root, "project");
+ projectDir.mkdirs();
+ createProject(projectDir, "Test2", "test.pkg");
+ createDotProject(projectDir, "Test2", true, true);
+
+ File tests = new File(projectDir, "tests");
+ tests.mkdirs();
+ Files.write(""
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"my.test.pkg.name\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <instrumentation\n"
+ + " android:name=\"android.test.InstrumentationTestRunner\"\n"
+ + " android:targetPackage=\"test.pkg\""
+ + " android:functionalTest=\"false\"\n"
+ + " android:handleProfiling=\"true\" />\n"
+ + "\n"
+ + " <uses-sdk\n"
+ + " android:minSdkVersion=\"7\"\n"
+ + " android:targetSdkVersion=\"15\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@android:drawable/sym_def_app_icon\"\n"
+ + " android:label=\"My Unit Test Instrumentation Tests\" >\n"
+ + " <uses-library android:name=\"android.test.runner\" />\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ new File(tests, FN_ANDROID_MANIFEST_XML), UTF_8);
+ File testSrc = new File(tests, "src");
+ testSrc.mkdirs();
+ File testPkg = new File(testSrc, "mytestpkg");
+ testPkg.mkdirs();
+ new File(testPkg, "MyUnitTest.java").createNewFile();
+ File testRes = new File(tests, "res");
+ testRes.mkdirs();
+ File testValues = new File(testRes, "values");
+ testValues.mkdirs();
+ new File(testValues, "strings.xml").createNewFile();
+ File testLibs = new File(tests, "libs");
+ testLibs.mkdirs();
+ new File(testLibs, "myTestSupportLib.jar").createNewFile();
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => Test2/src/main/AndroidManifest.xml\n"
+ + "* res/ => Test2/src/main/res/\n"
+ + "* src/ => Test2/src/main/java/\n"
+ + "* tests/libs/myTestSupportLib.jar => Test2/libs/myTestSupportLib.jar\n"
+ + "* tests/res/ => Test2/src/androidTest/res/\n"
+ + "* tests/src/ => Test2/src/androidTest/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */,
+ new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importer.setGradleNameStyle(false);
+ }
+ });
+
+ // Imported contents
+ assertEquals(""
+ + "Test2\n"
+ + " build.gradle\n"
+ + " libs\n"
+ + " myTestSupportLib.jar\n"
+ + " src\n"
+ + " androidTest\n"
+ + " java\n"
+ + " mytestpkg\n"
+ + " MyUnitTest.java\n"
+ + " res\n"
+ + " values\n"
+ + " strings.xml\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 17\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 16\n"
+ + "\n"
+ + " testApplicationId \"my.test.pkg.name\"\n"
+ + " testInstrumentationRunner \"android.test.InstrumentationTestRunner\"\n"
+ + " testFunctionalTest false\n"
+ + " testHandlingProfiling true\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard false\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "dependencies {\n"
+ + " androidTestCompile files('libs/myTestSupportLib.jar')\n"
+ + "}\n",
+ Files.toString(new File(imported, "Test2" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testInstrumentation2() throws Exception {
+ // Like testInstrumentation1, but the unit test is found in a sibling directory
+ // (which also means various paths should be relative - ../ etc)
+ File root = Files.createTempDir();
+ File projectDir = new File(root, "project");
+ projectDir.mkdirs();
+ createProject(projectDir, "Test2", "test.pkg");
+ createDotProject(projectDir, "Test2", true, true);
+
+ File tests = new File(root, "tests");
+ tests.mkdirs();
+ Files.write(""
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"my.test.pkg.name\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <instrumentation\n"
+ + " android:name=\"android.test.InstrumentationTestRunner\"\n"
+ + " android:targetPackage=\"test.pkg\""
+ + " android:functionalTest=\"false\"\n"
+ + " android:handleProfiling=\"true\" />\n"
+ + "\n"
+ + " <uses-sdk\n"
+ + " android:minSdkVersion=\"7\"\n"
+ + " android:targetSdkVersion=\"15\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@android:drawable/sym_def_app_icon\"\n"
+ + " android:label=\"My Unit Test Instrumentation Tests\" >\n"
+ + " <uses-library android:name=\"android.test.runner\" />\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ new File(tests, FN_ANDROID_MANIFEST_XML), UTF_8);
+ File testSrc = new File(tests, "src");
+ testSrc.mkdirs();
+ File testPkg = new File(testSrc, "mytestpkg");
+ testPkg.mkdirs();
+ new File(testPkg, "MyUnitTest.java").createNewFile();
+ File testRes = new File(tests, "res");
+ testRes.mkdirs();
+ File testValues = new File(testRes, "values");
+ testValues.mkdirs();
+ new File(testValues, "strings.xml").createNewFile();
+ File testLibs = new File(tests, "libs");
+ testLibs.mkdirs();
+ new File(testLibs, "myTestSupportLib.jar").createNewFile();
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => app/src/main/AndroidManifest.xml\n"
+ + "* res/ => app/src/main/res/\n"
+ + "* src/ => app/src/main/java/\n"
+ + "* $ROOT_PARENT/tests/libs/myTestSupportLib.jar => app/libs/myTestSupportLib.jar\n"
+ + "* $ROOT_PARENT/tests/res/ => app/src/androidTest/res/\n"
+ + "* $ROOT_PARENT/tests/src/ => app/src/androidTest/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */,
+ new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ }
+ });
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testReplaceSourceLibraryProject() throws Exception {
+ // Make a library project which looks like it can just be replaced by a project
+
+ File root = Files.createTempDir();
+ // Pretend lib2 is ActionBarSherlock; it should then be stripped out and replaced
+ // by a set of dependencies
+ File app = createLibrary(root, "com.actionbarsherlock", true);
+
+ File imported = checkProject(app, ""
+ + MSG_HEADER
+ + MSG_MANIFEST
+ + MSG_UNHANDLED
+ + "From App:\n"
+ + "* .gitignore\n"
+ + "From JavaLib:\n"
+ + "* .gitignore\n"
+ + MSG_REPLACED_LIBS
+ + "Lib2 =>\n"
+ + " com.actionbarsherlock:actionbarsherlock:4.4.0@aar\n"
+ + " com.android.support:support-v4:+\n"
+ + MSG_FOLDER_STRUCTURE
+ // TODO: The summary should describe the library!!
+ + "In JavaLib:\n"
+ + "* src/ => javaLib/src/main/java/\n"
+ + "In Lib1:\n"
+ + "* AndroidManifest.xml => lib1/src/main/AndroidManifest.xml\n"
+ + "* src/ => lib1/src/main/java/\n"
+ + "In App:\n"
+ + "* AndroidManifest.xml => app/src/main/AndroidManifest.xml\n"
+ + "* res/ => app/src/main/res/\n"
+ + "* src/ => app/src/main/java/\n"
+ + (getTestSdkPath() == null ? MSG_MISSING_REPO_1 + "null\n" + MSG_MISSING_REPO_2 : "")
+ + MSG_FOOTER,
+ false /* checkBuild */);
+
+ // Imported project; note how lib2 is gone
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + "javaLib\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " java\n"
+ + " test\n"
+ + " lib2\n"
+ + " pkg\n"
+ + " Utilities.java\n"
+ + "lib1\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " lib\n"
+ + " pkg\n"
+ + " MyLibActivity.java\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 17\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 16\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard false\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile project(':lib1')\n"
+ + " compile project(':javaLib')\n"
+ + " compile 'com.actionbarsherlock:actionbarsherlock:4.4.0@aar'\n"
+ + " compile 'com.android.support:support-v4:+'\n"
+ + "}\n",
+ Files.toString(new File(imported, "app" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testMissingRepositories() throws Exception {
+ File root = Files.createTempDir();
+ final File sdkLocation = new File(root, "sdk");
+ sdkLocation.mkdirs();
+ File projectDir = new File(root, "project");
+ projectDir.mkdirs();
+ createProject(projectDir, "test1", "test.pkg");
+ File libs = new File(projectDir, "libs");
+ libs.mkdirs();
+ new File(libs, "android-support-v4.jar").createNewFile();
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_REPLACED_JARS
+ + "android-support-v4.jar => com.android.support:support-v4:+\n"
+ + MSG_FOLDER_STRUCTURE
+ + DEFAULT_MOVED
+ + MSG_MISSING_REPO_1
+ + "$ROOT_PARENT/sdk\n"
+ + MSG_MISSING_REPO_2
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importer.setSdkLocation(sdkLocation);
+ }
+ });
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testMissingPlayRepositories() throws Exception {
+ File root = Files.createTempDir();
+ final File sdkLocation = new File(root, "sdk");
+ sdkLocation.mkdirs();
+ File projectDir = new File(root, "project");
+ projectDir.mkdirs();
+ createProject(projectDir, "test1", "test.pkg");
+ File libs = new File(projectDir, "libs");
+ libs.mkdirs();
+ new File(libs, "gcm.jar").createNewFile();
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_REPLACED_JARS
+ + "gcm.jar => com.google.android.gms:play-services:+\n"
+ + MSG_FOLDER_STRUCTURE
+ + DEFAULT_MOVED
+ + MSG_MISSING_GOOGLE_REPOSITORY_1
+ + "$ROOT_PARENT/sdk\n"
+ + MSG_MISSING_GOOGLE_REPOSITORY_2
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importer.setSdkLocation(sdkLocation);
+ }
+ });
+
+ // Imported project: confirm that gcm.jar is not in the output tree
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + "local.properties\n"
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testGuessedVersion() throws Exception {
+ File root = Files.createTempDir();
+ final File sdkLocation = new File(root, "sdk");
+ sdkLocation.mkdirs();
+ File projectDir = new File(root, "project");
+ projectDir.mkdirs();
+ createProject(projectDir, "test1", "test.pkg");
+ File libs = new File(projectDir, "libs");
+ libs.mkdirs();
+ new File(libs, "guava-13.0.1.jar").createNewFile();
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_REPLACED_JARS
+ + "guava-13.0.1.jar => com.google.guava:guava:13.0.1\n"
+ + MSG_GUESSED_VERSIONS
+ + "guava-13.0.1.jar => version 13.0.1 in com.google.guava:guava:13.0.1\n"
+ + MSG_FOLDER_STRUCTURE
+ + DEFAULT_MOVED
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importer.setSdkLocation(sdkLocation);
+ }
+ });
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testClassPathFilePaths() throws Exception {
+ // Test a project where the .classpath file contains additional
+ // issues: workspace-local dependencies for projects,
+ // absolute paths to the framework, etc.
+
+ File root = Files.createTempDir();
+ File projectDir = new File(root, "prj");
+ projectDir.mkdirs();
+ projectDir = createProject(projectDir, "1 Weird 'name' of project!", "test.pkg");
+ File lib = new File(root, "android-support-v7-appcompat");
+ lib.mkdirs();
+
+ File classpath = new File(projectDir, ".classpath");
+ assertTrue(classpath.exists());
+ classpath.delete();
+ //noinspection SpellCheckingInspection
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<classpath>\n"
+ + "\t<classpathentry kind=\"src\" path=\"src\"/>\n"
+ + "\t<classpathentry kind=\"src\" path=\"gen\"/>\n"
+ + "\t<classpathentry kind=\"con\" path=\"com.android.ide.eclipse.adt.ANDROID_FRAMEWORK\"/>\n"
+ + "\t<classpathentry exported=\"true\" kind=\"con\" path=\"com.android.ide.eclipse.adt.DEPENDENCIES\"/>\n"
+ + "\t<classpathentry kind=\"lib\" path=\"libs/basic-http-client-android-0.88.jar\"/>\n"
+ + "\t<classpathentry kind=\"lib\" path=\"/opt/android-sdk/platforms/android-14/android.jar\">\n"
+ + "\t\t<attributes>\n"
+ + "\t\t\t<attribute name=\"javadoc_location\" value=\"file:/opt/android-sdk/docs/reference\"/>\n"
+ + "\t\t</attributes>\n"
+ + "\t\t<accessrules>\n"
+ + "\t\t\t<accessrule kind=\"nonaccessible\" pattern=\"com/android/internal/**\"/>\n"
+ + "\t\t</accessrules>\n"
+ + "\t</classpathentry>\n"
+ + "\t<classpathentry kind=\"lib\" path=\"libs/htmlcleaner-2.6.jar\"/>\n"
+ + "\t<classpathentry exported=\"true\" kind=\"con\" path=\"com.android.ide.eclipse.adt.LIBRARIES\"/>\n"
+ + "\t<classpathentry combineaccessrules=\"false\" kind=\"src\" path=\"/android-support-v7-appcompat\"/>\n"
+ + "\t<classpathentry kind=\"output\" path=\"bin/classes\"/>\n"
+ + "</classpath>",
+ classpath, UTF_8);
+
+ //noinspection SpellCheckingInspection
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + "* $ROOT_PARENT/android-support-v7-appcompat/ => _1Weirdnameofproject/src/main/java/\n"
+ + "* AndroidManifest.xml => _1Weirdnameofproject/src/main/AndroidManifest.xml\n"
+ + "* res/ => _1Weirdnameofproject/src/main/res/\n"
+ + "* src/ => _1Weirdnameofproject/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importer.setGradleNameStyle(false);
+ }
+ });
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private static Pair<File,File> createLibrary2(File library1Dir) throws Exception {
+ File root = Files.createTempDir();
+
+ // Workspace Setup
+ // /Library1, compiled with 1.7, and depends on an external jar outside the project (guava)
+ // /Library2 (depends on /Library1)
+ // /AndroidLibraryProject (depend on /Library1, /Library2)
+ // /AndroidAppProject (depends on /AndroidLibraryProject)
+ // In addition to make things complicated, /Library1 can live outside the workspace
+ // (based on the path we pass in)
+ // and /Library2 lives in a subdirectory of the workspace
+
+ // Plain Java library, used by Library 1 and App
+ // Make Java Library library 1
+ String lib1Name = "Library1";
+ File lib1 = library1Dir.isAbsolute() ? library1Dir :
+ new File(root, library1Dir.getPath());
+ lib1.mkdirs();
+ String lib1Pkg = "test.lib1.pkg";
+ createDotProject(lib1, lib1Name, false);
+ createSampleJavaSource(lib1, "src", lib1Pkg, "Library1");
+ File guavaPath = new File(root, "some" + separator + "path" + separator +
+ "guava-13.0.1.jar");
+ guavaPath.getParentFile().mkdirs();
+ guavaPath.createNewFile();
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<classpath>\n"
+ + "\t<classpathentry kind=\"src\" path=\"src\"/>\n"
+ + "\t<classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/Java 7\"/>\n"
+ + "\t<classpathentry exported=\"true\" kind=\"lib\" path=\"" + guavaPath.getAbsoluteFile().getCanonicalFile().getPath() + "\"/>\n"
+ + "\t<classpathentry kind=\"output\" path=\"bin\"/>\n"
+ + "</classpath>",
+ new File(lib1, ".classpath"), UTF_8);
+ createEclipseSettingsFile(lib1, "1.6");
+
+ // Make Java Library 2
+ String lib2Name = "Library2";
+ File lib2 = new File(root, lib2Name);
+ lib2.mkdirs();
+ createDotProject(lib2, lib2Name, false);
+ String lib2Pkg = "test.lib2.pkg";
+ createSampleJavaSource(lib2, "src", lib2Pkg, "Library2");
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<classpath>\n"
+ + "\t<classpathentry kind=\"src\" path=\"src\"/>\n"
+ + "\t<classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/Java 7\"/>\n"
+ + "\t<classpathentry combineaccessrules=\"false\" kind=\"src\" path=\"/Library1\"/>\n"
+ + "\t<classpathentry kind=\"output\" path=\"bin\"/>\n"
+ + "</classpath>",
+ new File(lib2, ".classpath"), UTF_8);
+ createEclipseSettingsFile(lib2, "1.7");
+
+ // Make Android Library Project 1
+ String androidLibName = "AndroidLibrary";
+ File androidLib = new File(root, androidLibName);
+ androidLib.mkdirs();
+ createDotProject(androidLib, androidLibName, true);
+ String androidLibPkg = "test.android.lib.pkg";
+ createSampleJavaSource(androidLib, "src", androidLibPkg, "AndroidLibrary");
+ createSampleJavaSource(androidLib, "gen", androidLibPkg, "R");
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<classpath>\n"
+ + "\t<classpathentry kind=\"src\" path=\"src\"/>\n"
+ + "\t<classpathentry kind=\"src\" path=\"gen\"/>\n"
+ + "\t<classpathentry kind=\"con\" path=\"com.android.ide.eclipse.adt.ANDROID_FRAMEWORK\"/>\n"
+ + "\t<classpathentry exported=\"true\" kind=\"con\" path=\"com.android.ide.eclipse.adt.LIBRARIES\"/>\n"
+ + "\t<classpathentry exported=\"true\" kind=\"con\" path=\"com.android.ide.eclipse.adt.DEPENDENCIES\"/>\n"
+ + "\t<classpathentry combineaccessrules=\"false\" exported=\"true\" kind=\"src\" path=\"/Library1\"/>\n"
+ + "\t<classpathentry combineaccessrules=\"false\" exported=\"true\" kind=\"src\" path=\"/Library2\"/>\n"
+ + "\t<classpathentry kind=\"output\" path=\"bin/classes\"/>\n"
+ + "</classpath>", new File(androidLib, ".classpath"), UTF_8);
+ createProjectProperties(androidLib, "android-18", null, true, null,
+ // Note how Android library projects don't point to non-Android projects
+ // in the project.properties file; only via the .classpath file!
+ Collections.<File>emptyList());
+ createAndroidManifest(androidLib, androidLibPkg, 7, -1, "");
+
+ // Main app project, depends on library project
+ String appName = "AndroidApp";
+ File app = new File(root, appName);
+ app.mkdirs();
+ String appPkg = "test.pkg";
+ createDotProject(app, appName, true);
+ File appSrc = new File("src");
+ File appGen = new File("gen");
+ createSampleJavaSource(app, "src", appPkg, "AppActivity");
+ createSampleJavaSource(app, "gen", appPkg, "R");
+ createClassPath(app,
+ new File("bin", "classes"),
+ Arrays.asList(appSrc, appGen),
+ Collections.<File>emptyList());
+ createProjectProperties(app, "android-17", null, null, null,
+ Collections.singletonList(new File(".." + separator + androidLibName)));
+ createAndroidManifest(app, appPkg, 8, 16, null);
+ createDefaultStrings(app);
+ createDefaultIcon(app);
+
+ // Add some files in there that we are ignoring
+ new File(app, ".gitignore").createNewFile();
+
+ return Pair.of(root, app);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testLibraries2() throws Exception {
+ Pair<File,File> pair = createLibrary2(new File("Library1"));
+ File root = pair.getFirst();
+ File app = pair.getSecond();
+
+ // ADT Directory structure created by the above:
+ assertEquals(""
+ + "AndroidApp\n"
+ + " .classpath\n"
+ + " .gitignore\n"
+ + " .project\n"
+ + " AndroidManifest.xml\n"
+ + " gen\n"
+ + " test\n"
+ + " pkg\n"
+ + " R.java\n"
+ + " project.properties\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + " src\n"
+ + " test\n"
+ + " pkg\n"
+ + " AppActivity.java\n"
+ + "AndroidLibrary\n"
+ + " .classpath\n"
+ + " .project\n"
+ + " AndroidManifest.xml\n"
+ + " gen\n"
+ + " test\n"
+ + " android\n"
+ + " lib\n"
+ + " pkg\n"
+ + " R.java\n"
+ + " project.properties\n"
+ + " src\n"
+ + " test\n"
+ + " android\n"
+ + " lib\n"
+ + " pkg\n"
+ + " AndroidLibrary.java\n"
+ + "Library1\n"
+ + " .classpath\n"
+ + " .project\n"
+ + " .settings\n"
+ + " org.eclipse.jdt.core.prefs\n"
+ + " src\n"
+ + " test\n"
+ + " lib1\n"
+ + " pkg\n"
+ + " Library1.java\n"
+ + "Library2\n"
+ + " .classpath\n"
+ + " .project\n"
+ + " .settings\n"
+ + " org.eclipse.jdt.core.prefs\n"
+ + " src\n"
+ + " test\n"
+ + " lib2\n"
+ + " pkg\n"
+ + " Library2.java\n"
+ + "some\n"
+ + " path\n"
+ + " guava-13.0.1.jar\n",
+ fileTree(root, true));
+
+ final AtomicReference<GradleImport> importReference = new AtomicReference<GradleImport>();
+ File imported = checkProject(app, ""
+ + MSG_HEADER
+ + MSG_MANIFEST
+ + MSG_UNHANDLED
+ + "* .gitignore\n"
+ + MSG_REPLACED_JARS
+ + "guava-13.0.1.jar => com.google.guava:guava:13.0.1\n"
+ + MSG_GUESSED_VERSIONS
+ + "guava-13.0.1.jar => version 13.0.1 in com.google.guava:guava:13.0.1\n"
+ + MSG_FOLDER_STRUCTURE
+ + "In Library1:\n"
+ + "* src/ => library1/src/main/java/\n"
+ + "In Library2:\n"
+ + "* src/ => library2/src/main/java/\n"
+ + "In AndroidLibrary:\n"
+ + "* AndroidManifest.xml => androidLibrary/src/main/AndroidManifest.xml\n"
+ + "* src/ => androidLibrary/src/main/java/\n"
+ + "In AndroidApp:\n"
+ + "* AndroidManifest.xml => androidApp/src/main/AndroidManifest.xml\n"
+ + "* res/ => androidApp/src/main/res/\n"
+ + "* src/ => androidApp/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importReference.set(importer);
+ }
+ });
+ assertEquals("{/Library1=" + new File(root, "Library1").getCanonicalPath() +
+ ", /Library2=" + new File(root, "Library2").getCanonicalPath() +"}",
+ describePathMap(importReference.get()));
+
+ // Imported project
+ assertEquals(""
+ + "androidApp\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " AppActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "androidLibrary\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " android\n"
+ + " lib\n"
+ + " pkg\n"
+ + " AndroidLibrary.java\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + "library1\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " java\n"
+ + " test\n"
+ + " lib1\n"
+ + " pkg\n"
+ + " Library1.java\n"
+ + "library2\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " java\n"
+ + " test\n"
+ + " lib2\n"
+ + " pkg\n"
+ + " Library2.java\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ // Let's peek at some of the key files to make sure we codegen'ed the right thing
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'java'\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile 'com.google.guava:guava:13.0.1'\n"
+ + "}\n",
+ Files.toString(new File(imported, "library1" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 17\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 16\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard false\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile project(':androidLibrary')\n"
+ + "}\n",
+ Files.toString(new File(imported, "androidApp" + separator + "build.gradle"), UTF_8)
+ .replace(NL,"\n"));
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android-library'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 18\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.android.lib.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 8\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard false\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile project(':library1')\n"
+ + " compile project(':library2')\n"
+ + "}\n",
+ Files.toString(new File(imported, "androidLibrary" + separator + "build.gradle"), UTF_8)
+ .replace(NL,"\n"));
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'java'\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile 'com.google.guava:guava:13.0.1'\n"
+ + "}\n",
+ Files.toString(new File(imported, "library1" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'java'\n"
+ + "\n"
+ + "sourceCompatibility = \"1.7\"\n"
+ + "targetCompatibility = \"1.7\"\n"
+ + "\n"
+ + "dependencies {\n"
+ + " compile project(':library1')\n"
+ + "}\n",
+ Files.toString(new File(imported, "library2" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ // TODO: Should this ONLY include the root module?
+ assertEquals(""
+ + "include ':library1'\n"
+ + "include ':library2'\n"
+ + "include ':androidLibrary'\n"
+ + "include ':androidApp'\n",
+ Files.toString(new File(imported, "settings.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ //noinspection ConstantConditions
+ assertEquals(""
+ + "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n"
+ + (DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n"
+ + "\n"
+ + "allprojects {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + "}\n" : ""),
+ Files.toString(new File(imported, "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testLibrariesWithWorkspaceMapping1() throws Exception {
+ // Provide manually edited workspace mapping /Library1 = actual dir
+ final String library1Path = "subdir1" + separator + "subdir2" + separator +
+ "UnrelatedName";
+ final File library1Dir = new File(library1Path);
+ Pair<File,File> pair = createLibrary2(library1Dir);
+ final File root = pair.getFirst();
+ File app = pair.getSecond();
+
+ final AtomicReference<GradleImport> importReference = new AtomicReference<GradleImport>();
+ File imported = checkProject(app, ""
+ + MSG_HEADER
+ + MSG_MANIFEST
+ + MSG_UNHANDLED
+ + "* .gitignore\n"
+ + MSG_REPLACED_JARS
+ + "guava-13.0.1.jar => com.google.guava:guava:13.0.1\n"
+ + MSG_GUESSED_VERSIONS
+ + "guava-13.0.1.jar => version 13.0.1 in com.google.guava:guava:13.0.1\n"
+ + MSG_FOLDER_STRUCTURE
+ + "In Library1:\n"
+ + "* src/ => library1/src/main/java/\n"
+ + "In Library2:\n"
+ + "* src/ => library2/src/main/java/\n"
+ + "In AndroidLibrary:\n"
+ + "* AndroidManifest.xml => androidLibrary/src/main/AndroidManifest.xml\n"
+ + "* src/ => androidLibrary/src/main/java/\n"
+ + "In AndroidApp:\n"
+ + "* AndroidManifest.xml => androidApp/src/main/AndroidManifest.xml\n"
+ + "* res/ => androidApp/src/main/res/\n"
+ + "* src/ => androidApp/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importReference.set(importer);
+ importer.getPathMap().put("/Library1", new File(root, library1Path));
+ }
+ });
+ assertEquals("{/Library1=" + new File(root, library1Path).getCanonicalPath()
+ + ", /Library2=" + new File(root, "Library2").getCanonicalPath() + "}",
+ describePathMap(importReference.get()));
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void test65167() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=65167
+ Pair<File,File> pair = createLibrary2(new File("Library1"));
+ File root = pair.getFirst();
+ File app = pair.getSecond();
+
+ File libs = new File(app, "libs");
+ libs.mkdirs();
+ new File(libs, "unknown-lib.jar").createNewFile();
+
+ final AtomicReference<GradleImport> importReference = new AtomicReference<GradleImport>();
+ File imported = checkProject(app, ""
+ + MSG_HEADER
+ + MSG_MANIFEST
+ + MSG_UNHANDLED
+ + "* .gitignore\n"
+ + MSG_REPLACED_JARS
+ + "guava-13.0.1.jar => com.google.guava:guava:13.0.1\n"
+ + MSG_GUESSED_VERSIONS
+ + "guava-13.0.1.jar => version 13.0.1 in com.google.guava:guava:13.0.1\n"
+ + MSG_FOLDER_STRUCTURE
+ + "In Library1:\n"
+ + "* src/ => library1/src/main/java/\n"
+ + "In Library2:\n"
+ + "* src/ => library2/src/main/java/\n"
+ + "In AndroidLibrary:\n"
+ + "* AndroidManifest.xml => androidLibrary/src/main/AndroidManifest.xml\n"
+ + "* src/ => androidLibrary/src/main/java/\n"
+ + "In AndroidApp:\n"
+ + "* AndroidManifest.xml => androidApp/src/main/AndroidManifest.xml\n"
+ + "* libs/unknown-lib.jar => androidApp/libs/unknown-lib.jar\n"
+ + "* res/ => androidApp/src/main/res/\n"
+ + "* src/ => androidApp/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importReference.set(importer);
+ }
+ });
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+
+ @SuppressWarnings({"ResultOfMethodCallIgnored", "SpellCheckingInspection"})
+ public void testImportModule() throws Exception {
+ // Create a Gradle project that we'll be importing a new module into
+ File projectDir = createProject("test1", "test.pkg");
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + DEFAULT_MOVED
+ + MSG_FOOTER,
+ false /* checkBuild */);
+ // Pre-module import state of the Gradle project:
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ // Now create a second project, and import that as a *module*
+ File moduleDir = createProject("test2", "test.my.pkg");
+ File destDir = new File(imported, "newmodule");
+ destDir.mkdirs();
+ checkImport(imported, moduleDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => newmodule/src/main/AndroidManifest.xml\n"
+ + "* res/ => newmodule/src/main/res/\n"
+ + "* src/ => newmodule/src/main/java/\n"
+ + MSG_FOOTER,
+ true /* checkBuild */, null, destDir, imported);
+
+ // Imported contents
+ assertEquals(""
+ + "build.gradle\n"
+ + "src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " my\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n",
+ fileTree(destDir, true));
+
+ // Check that it's in the right place:
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "newmodule\n"
+ + " build.gradle\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " my\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ // Make sure that settings.gradle did the right thing
+ assertEquals(""
+ + "include ':app'\n"
+ + "include ':test2'\n",
+ Files.toString(new File(imported, "settings.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ deleteDir(moduleDir);
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testModuleNamesFromDir() throws Exception {
+ // Regression test for issue where there is no .project file and
+ // the project name has to be inferred from the directory name instead
+ Pair<File,File> pair = createLibrary2(new File("Library1"));
+ File root = pair.getFirst();
+ File app = pair.getSecond();
+
+ File propertyFile = new File(app, ".project");
+ propertyFile.delete();
+
+ final AtomicReference<GradleImport> importReference = new AtomicReference<GradleImport>();
+ File imported = checkProject(app, ""
+ + MSG_HEADER
+ + MSG_MANIFEST
+ + MSG_UNHANDLED
+ + "* .gitignore\n"
+ + MSG_REPLACED_JARS
+ + "guava-13.0.1.jar => com.google.guava:guava:13.0.1\n"
+ + MSG_GUESSED_VERSIONS
+ + "guava-13.0.1.jar => version 13.0.1 in com.google.guava:guava:13.0.1\n"
+ + MSG_FOLDER_STRUCTURE
+ + "In Library1:\n"
+ + "* src/ => library1/src/main/java/\n"
+ + "In Library2:\n"
+ + "* src/ => library2/src/main/java/\n"
+ + "In AndroidLibrary:\n"
+ + "* AndroidManifest.xml => androidLibrary/src/main/AndroidManifest.xml\n"
+ + "* src/ => androidLibrary/src/main/java/\n"
+ + "In AndroidApp:\n"
+ + "* AndroidManifest.xml => androidApp/src/main/AndroidManifest.xml\n"
+ + "* res/ => androidApp/src/main/res/\n"
+ + "* src/ => androidApp/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importReference.set(importer);
+ }
+ });
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ private static String describePathMap(GradleImport importer) throws IOException {
+ Map<String, File> map = importer.getPathMap();
+ List<String> keys = Lists.newArrayList(map.keySet());
+ Collections.sort(keys);
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ boolean first = true;
+ for (String key : keys) {
+ File file = map.get(key);
+ if (file != null) {
+ file = file.getCanonicalFile();
+ }
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append(key);
+ sb.append("=");
+ sb.append(file);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testLibrariesWithWorkspaceMapping2() throws Exception {
+ // Provide manually edited workspace location; importer reads workspace data
+ // to find it
+ final String library1Path = "subdir1" + separator + "subdir2" + separator +
+ "UnrelatedName";
+ final File library1Dir = new File(library1Path);
+ Pair<File,File> pair = createLibrary2(library1Dir);
+ final File root = pair.getFirst();
+ File app = pair.getSecond();
+ final File library1AbsDir = new File(root, library1Path);
+
+ final File workspace = new File(root, "workspace");
+ workspace.mkdirs();
+ File metadata = new File(workspace, ".metadata");
+ metadata.mkdirs();
+ new File(metadata, "version.ini").createNewFile();
+ assertTrue(GradleImport.isEclipseWorkspaceDir(workspace));
+ File projects = new File(metadata, ".plugins" + separator + "org.eclipse.core.resources" +
+ separator + ".projects");
+ projects.mkdirs();
+ File library1 = new File(projects, "Library1");
+ library1.mkdirs();
+ File location = new File(library1, ".location");
+ byte[] data = ("blahblahblahURI//" + SdkUtils.fileToUrl(library1AbsDir) +
+ "\000blahblahblah").getBytes(Charsets.UTF_8);
+ Files.write(data, location);
+
+ final AtomicReference<GradleImport> importReference = new AtomicReference<GradleImport>();
+ File imported = checkProject(app, ""
+ + MSG_HEADER
+ + MSG_MANIFEST
+ + MSG_UNHANDLED
+ + "* .gitignore\n"
+ + MSG_REPLACED_JARS
+ + "guava-13.0.1.jar => com.google.guava:guava:13.0.1\n"
+ + MSG_GUESSED_VERSIONS
+ + "guava-13.0.1.jar => version 13.0.1 in com.google.guava:guava:13.0.1\n"
+ + MSG_FOLDER_STRUCTURE
+ + "In Library1:\n"
+ + "* src/ => library1/src/main/java/\n"
+ + "In Library2:\n"
+ + "* src/ => library2/src/main/java/\n"
+ + "In AndroidLibrary:\n"
+ + "* AndroidManifest.xml => androidLibrary/src/main/AndroidManifest.xml\n"
+ + "* src/ => androidLibrary/src/main/java/\n"
+ + "In AndroidApp:\n"
+ + "* AndroidManifest.xml => androidApp/src/main/AndroidManifest.xml\n"
+ + "* res/ => androidApp/src/main/res/\n"
+ + "* src/ => androidApp/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importReference.set(importer);
+ importer.setEclipseWorkspace(workspace);
+ }
+ });
+ assertEquals("{/Library1=" + new File(root, library1Path).getCanonicalPath()
+ + ", /Library2=" + new File(root, "Library2").getCanonicalPath() + "}",
+ describePathMap(importReference.get()));
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testLibrariesWithWorkspacePathVars() throws Exception {
+ // Provide manually edited workspace location which contains workspace locations
+ final String library1Path = "subdir1" + separator + "subdir2" + separator +
+ "UnrelatedName";
+ final File library1Dir = new File(library1Path);
+ Pair<File,File> pair = createLibrary2(library1Dir);
+ final File root = pair.getFirst();
+ File app = pair.getSecond();
+ final File library1AbsDir = new File(root, library1Path);
+
+ final File workspace = new File(root, "workspace");
+ workspace.mkdirs();
+ File metadata = new File(workspace, ".metadata");
+ metadata.mkdirs();
+ new File(metadata, "version.ini").createNewFile();
+ assertTrue(GradleImport.isEclipseWorkspaceDir(workspace));
+ File projects = new File(metadata, ".plugins" + separator + "org.eclipse.core.resources" +
+ separator + ".projects");
+ projects.mkdirs();
+ File library1 = new File(projects, "Library1");
+ library1.mkdirs();
+ File location = new File(library1, ".location");
+ byte[] data = ("blahblahblahURI//" + SdkUtils.fileToUrl(library1AbsDir) +
+ "\000blahblahblah").getBytes(Charsets.UTF_8);
+ Files.write(data, location);
+
+ final AtomicReference<GradleImport> importReference = new AtomicReference<GradleImport>();
+ File imported = checkProject(app, ""
+ + MSG_HEADER
+ + MSG_MANIFEST
+ + MSG_UNHANDLED
+ + "* .gitignore\n"
+ + MSG_REPLACED_JARS
+ + "guava-13.0.1.jar => com.google.guava:guava:13.0.1\n"
+ + MSG_GUESSED_VERSIONS
+ + "guava-13.0.1.jar => version 13.0.1 in com.google.guava:guava:13.0.1\n"
+ + MSG_FOLDER_STRUCTURE
+ + "In Library1:\n"
+ + "* src/ => library1/src/main/java/\n"
+ + "In Library2:\n"
+ + "* src/ => library2/src/main/java/\n"
+ + "In AndroidLibrary:\n"
+ + "* AndroidManifest.xml => androidLibrary/src/main/AndroidManifest.xml\n"
+ + "* src/ => androidLibrary/src/main/java/\n"
+ + "In AndroidApp:\n"
+ + "* AndroidManifest.xml => androidApp/src/main/AndroidManifest.xml\n"
+ + "* res/ => androidApp/src/main/res/\n"
+ + "* src/ => androidApp/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importReference.set(importer);
+ importer.setEclipseWorkspace(workspace);
+ }
+ });
+ assertEquals("{/Library1=" + new File(root, library1Path).getCanonicalPath()
+ + ", /Library2=" + new File(root, "Library2").getCanonicalPath() + "}",
+ describePathMap(importReference.get()));
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testErrorHandling1() throws Exception {
+ // Broken .classpath file
+ File projectDir = createProject("testError1", "test.pkg");
+
+ File classPath = new File(projectDir, ".classpath");
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<classpath>\n"
+ + " <classpathentry kind=\"src\" path=\"src\"/\n" // <== XML error
+ + " <classpathentry kind=\"src\" path=\"gen\"/>\n"
+ + " <classpathentry kind=\"con\" path=\"com.android.ide.eclipse.adt.ANDROID_FRAMEWORK\"/>\n"
+ + " <classpathentry exported=\"true\" kind=\"con\" path=\"com.android.ide.eclipse.adt.LIBRARIES\"/>\n"
+ + " <classpathentry exported=\"true\" kind=\"con\" path=\"com.android.ide.eclipse.adt.DEPENDENCIES\"/>\n"
+ + " <classpathentry kind=\"output\" path=\"bin/classes\"/>\n"
+ + "</classpath>", classPath, UTF_8);
+
+ final AtomicReference<GradleImport> importReference = new AtomicReference<GradleImport>();
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + "\n"
+ + " * $ROOT/.classpath:\n"
+ + "Invalid XML file: $ROOT/.classpath:\n"
+ + "Element type \"classpathentry\" must be followed by either attribute specifications, \">\" or \"/>\".\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importReference.set(importer);
+ }
+ });
+
+ assertEquals("[$CLASSPATH_FILE:\n"
+ + "Invalid XML file: $CLASSPATH_FILE:\n"
+ + "Element type \"classpathentry\" must be followed by either attribute "
+ + "specifications, \">\" or \"/>\".]",
+ importReference.get().getErrors().toString().replace(
+ classPath.getPath(), "$CLASSPATH_FILE").
+ replace(classPath.getCanonicalPath(), "$CLASSPATH_FILE"));
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testErrorHandling2() throws Exception {
+ // Broken manifest
+ File root = Files.createTempDir();
+ // Place project in a deep subdirectory such that it does not leave a broken
+ // sibling project for other unit tests to discover as an instrumentation test
+ File projectDir = new File(root, "sub1" + separator + "sub2" + separator + "sub3");
+ projectDir.mkdirs();
+ createProject(projectDir, "testError2", "test.pkg");
+
+ File manifest = new File(projectDir, "AndroidManifest.xml");
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<x>\n", manifest, UTF_8);
+
+ final AtomicReference<GradleImport> importReference = new AtomicReference<GradleImport>();
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + "\n"
+ + " * $ROOT/AndroidManifest.xml:\n"
+ + "Invalid XML file: $ROOT/AndroidManifest.xml:\n"
+ + "XML document structures must start and end within the same entity.\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, new ImportCustomizer() {
+ @Override
+ public void customize(GradleImport importer) {
+ importReference.set(importer);
+ }
+ });
+
+ assertEquals("[$MANIFEST_FILE:\n"
+ + "Invalid XML file: $MANIFEST_FILE:\n"
+ + "XML document structures must start and end within the same entity.]",
+ importReference.get().getErrors().toString().replace(
+ manifest.getPath(), "$MANIFEST_FILE").
+ replace(manifest.getCanonicalPath(), "$MANIFEST_FILE"));
+
+ deleteDir(root);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testProguardMigration() throws Exception {
+ // Check that ABI libs are copied to the right place
+ File projectDir = createProject("Test1", "test.pkg");
+ Files.write(""
+ + "proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt:proguard/proguard.pro:${user.home}/proguard/shared.pro\n"
+ + "\n"
+ + "# Indicates whether an apk should be generated for each density.\n"
+ + "split.density=false\n"
+ + "# Project target.\n"
+ + "target=android-16\n",
+ new File(projectDir, FN_PROJECT_PROPERTIES), UTF_8);
+
+ File proguard = new File(projectDir, "proguard");
+ proguard.mkdirs();
+ Files.write(""
+ + "-optimizationpasses 2\n"
+ + "-dontusemixedcaseclassnames\n"
+ + "-dontskipnonpubliclibraryclasses\n"
+ + "-dontpreverify\n",
+ new File(proguard, "proguard.pro"), UTF_8);
+ new File(projectDir, "proguard-project.txt").createNewFile();
+
+ File imported = checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => app/src/main/AndroidManifest.xml\n"
+ + "* proguard-project.txt => app/proguard-project.txt\n"
+ + "* proguard/proguard.pro => app/proguard.pro\n"
+ + "* res/ => app/src/main/res/\n"
+ + "* src/ => app/src/main/java/\n"
+ + MSG_USER_HOME_PROGUARD
+ + "${user.home}/proguard/shared.pro\n"
+ + MSG_FOOTER,
+ false /* checkBuild */);
+
+ // Imported contents
+ assertEquals(""
+ + "app\n"
+ + " build.gradle\n"
+ + " proguard-project.txt\n"
+ + " proguard.pro\n"
+ + " src\n"
+ + " main\n"
+ + " AndroidManifest.xml\n"
+ + " java\n"
+ + " test\n"
+ + " pkg\n"
+ + " MyActivity.java\n"
+ + " res\n"
+ + " drawable\n"
+ + " ic_launcher.xml\n"
+ + " values\n"
+ + " strings.xml\n"
+ + "build.gradle\n"
+ + "import-summary.txt\n"
+ + (getTestSdkPath() != null ? "local.properties\n" : "")
+ + "settings.gradle\n",
+ fileTree(imported, true));
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ assertEquals(""
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "buildscript {\n"
+ + " repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + " }\n"
+ + " dependencies {\n"
+ + " classpath '" + ANDROID_GRADLE_PLUGIN + "'\n"
+ + " }\n"
+ + "}\n" : "")
+ + "apply plugin: 'android'\n"
+ + (!DECLARE_GLOBAL_REPOSITORIES ?
+ "\n"
+ + "repositories {\n"
+ + " " + MAVEN_REPOSITORY + "\n"
+ + "}\n" : "")
+ + "\n"
+ + "android {\n"
+ + " compileSdkVersion 16\n"
+ + " buildToolsVersion \"" + BUILD_TOOLS_VERSION + "\"\n"
+ + "\n"
+ + " defaultConfig {\n"
+ + " applicationId \"test.pkg\"\n"
+ + " minSdkVersion 8\n"
+ + " targetSdkVersion 16\n"
+ + " }\n"
+ + "\n"
+ + " buildTypes {\n"
+ + " release {\n"
+ + " runProguard true\n"
+ + " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt', 'proguard.pro'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ Files.toString(new File(imported, "app" + separator + "build.gradle"), UTF_8)
+ .replace(NL, "\n"));
+
+ deleteDir(projectDir);
+ deleteDir(imported);
+ }
+
+ @SuppressWarnings({"ResultOfMethodCallIgnored", "SpellCheckingInspection"})
+ public void testRiskyPathChars() throws Exception {
+ File root = Files.createTempDir();
+ File projectDir = createProject("PathChars", "test.pkg");
+
+ final File destDir = new File(root, "My Code" + separator + "Source & Data" +
+ separator + "Foo's Bar");
+ destDir.mkdirs();
+
+ checkProject(projectDir, ""
+ + MSG_HEADER
+ + MSG_RISKY_PROJECT_LOCATION
+ + "$DESTDIR/My Code/Source & Data/Foo's Bar\n"
+ + " - --- - - \n"
+ + MSG_FOLDER_STRUCTURE
+ + "* AndroidManifest.xml => app/src/main/AndroidManifest.xml\n"
+ + "* res/ => app/src/main/res/\n"
+ + "* src/ => app/src/main/java/\n"
+ + MSG_FOOTER,
+ false /* checkBuild */, null, destDir, root);
+
+ deleteDir(root);
+ }
+
+ @SuppressWarnings("SpellCheckingInspection")
+ public void testIgnoreFile() {
+ assertTrue(GradleImport.isIgnoredFile(new File(".git")));
+ assertTrue(GradleImport.isIgnoredFile(new File(".hg")));
+ assertTrue(GradleImport.isIgnoredFile(new File(".svn")));
+ assertTrue(GradleImport.isIgnoredFile(new File("foo" + File.separator + ".git")));
+ assertTrue(GradleImport.isIgnoredFile(new File("foo" + File.separator + ".hg")));
+ assertTrue(GradleImport.isIgnoredFile(new File("foo" + File.separator + ".svn")));
+ assertTrue(GradleImport.isIgnoredFile(new File("foo~")));
+ assertFalse(GradleImport.isIgnoredFile(new File(".gitt")));
+ assertFalse(GradleImport.isIgnoredFile(new File("~")));
+ assertFalse(GradleImport.isIgnoredFile(new File("~foo")));
+ }
+
+ public void testSdkNdkSetters() {
+ GradleImport importer = new GradleImport();
+ File ndkLocation = new File("ndk");
+ File sdkLocation = new File("sdk");
+ importer.setNdkLocation(ndkLocation);
+ importer.setSdkLocation(sdkLocation);
+ assertSame(sdkLocation, importer.getSdkLocation());
+ assertSame(ndkLocation, importer.getNdkLocation());
+
+ String sdkPath = getTestSdkPath();
+ if (sdkPath != null) {
+ ILogger logger = new StdLogger(StdLogger.Level.INFO);
+ SdkManager sdkManager = SdkManager.createManager(sdkPath, logger);
+ if (sdkManager != null) {
+ importer.setSdkManager(sdkManager);
+ assertSame(sdkManager, importer.getSdkManager());
+ assertEquals(new File(sdkPath), importer.getSdkLocation());
+ }
+ }
+ }
+
+ // --- Unit test infrastructure from this point on ----
+
+ @SuppressWarnings({"ResultOfMethodCallIgnored", "SpellCheckingInspection"})
+ private static void createEclipseSettingsFile(File prj, String languageLevel)
+ throws IOException {
+ File file = new File(prj, ".settings" + separator + "org.eclipse.jdt.core.prefs");
+ file.getParentFile().mkdirs();
+ Files.write("" +
+ "eclipse.preferences.version=1\n" +
+ "org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\n" +
+ "org.eclipse.jdt.core.compiler.codegen.targetPlatform=" +
+ languageLevel +
+ "\n" +
+ "org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve\n" +
+ "org.eclipse.jdt.core.compiler.compliance=" +
+ languageLevel +
+ "\n" +
+ "org.eclipse.jdt.core.compiler.debug.lineNumber=generate\n" +
+ "org.eclipse.jdt.core.compiler.debug.localVariable=generate\n" +
+ "org.eclipse.jdt.core.compiler.debug.sourceFile=generate\n" +
+ "org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\n" +
+ "org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\n" +
+ "org.eclipse.jdt.core.compiler.source=" +
+ languageLevel, file, Charsets.UTF_8);
+ }
+
+ interface ImportCustomizer {
+ void customize(GradleImport importer);
+ }
+
+ private static File checkProject(File adtProjectDir,
+ String expectedSummary, boolean checkBuild) throws Exception {
+ return checkProject(adtProjectDir, expectedSummary, checkBuild, null);
+ }
+
+ private static File checkProject(File adtProjectDir,
+ String expectedSummary, boolean checkBuild,
+ ImportCustomizer customizer) throws Exception {
+ File destDir = Files.createTempDir();
+ return checkProject(adtProjectDir, expectedSummary, checkBuild, customizer, destDir,
+ destDir);
+ }
+
+ private static File checkProject(File adtProjectDir,
+ String expectedSummary, boolean checkBuild,
+ ImportCustomizer customizer,
+ File destDir, File rootDir) throws Exception {
+ return checkImport(null, adtProjectDir, expectedSummary, checkBuild, customizer, destDir,
+ rootDir);
+ }
+
+ private static File checkImport(
+ @Nullable File gradleProjectDir,
+ @NonNull File adtProjectDir,
+ @NonNull String expectedSummary,
+ boolean checkBuild,
+ ImportCustomizer customizer,
+ @NonNull File destDir,
+ @NonNull File rootDir) throws Exception {
+ assertTrue(GradleImport.isAdtProjectDir(adtProjectDir));
+ List<File> projects = Collections.singletonList(adtProjectDir);
+ GradleImport importer = new GradleImport();
+
+ boolean isImport = gradleProjectDir != null;
+ if (isImport) {
+ importer.setImportIntoExisting(true);
+ } else {
+ gradleProjectDir = destDir;
+ }
+
+ String sdkPath = getTestSdkPath();
+ if (sdkPath != null) {
+ importer.setSdkLocation(new File(sdkPath));
+ }
+
+ File wrapper = findGradleWrapper();
+ if (wrapper != null) {
+ importer.setGradleWrapperLocation(wrapper);
+ }
+
+ if (customizer != null) {
+ customizer.customize(importer);
+ }
+ importer.importProjects(projects);
+ importer.getSummary().setWrapErrorMessages(false);
+
+ if (isImport) {
+ Map<File,File> map = Maps.newHashMap();
+ map.put(adtProjectDir, destDir);
+ importer.exportIntoProject(gradleProjectDir, true, true, map);
+ } else {
+ importer.exportProject(destDir, false);
+ }
+ String summary = Files.toString(new File(gradleProjectDir, IMPORT_SUMMARY_TXT), UTF_8);
+ summary = summary.replace("\r", "");
+ summary = stripOutRiskyPathMessage(summary, rootDir);
+
+ String testSdkPath = getTestSdkPath();
+ if (testSdkPath != null) {
+ summary = summary.replace(testSdkPath, "$ADT_TEST_SDK_PATH");
+ }
+ summary = summary.replace(separatorChar, '/');
+ summary = summary.replace(adtProjectDir.getPath().replace(separatorChar,'/'), "$ROOT");
+ File parentFile = adtProjectDir.getParentFile();
+ if (parentFile != null) {
+ summary = summary.replace(parentFile.getPath().replace(separatorChar,'/'),
+ "$ROOT_PARENT");
+ }
+ assertEquals(expectedSummary, summary);
+
+ if (checkBuild) {
+ assertBuildsCleanly(gradleProjectDir, true);
+ }
+
+ return gradleProjectDir;
+ }
+
+ private static String stripOutRiskyPathMessage(String summary, File rootDir) {
+ int index = summary.indexOf(MSG_RISKY_PROJECT_LOCATION);
+ if (index == -1) {
+ return summary;
+ }
+ index += MSG_RISKY_PROJECT_LOCATION.length();
+ String path = rootDir.getPath();
+ assertTrue(summary.startsWith(path, index));
+ int nextLineIndex = summary.indexOf('\n', index) + 1;
+ return summary.substring(0, index) + "$DESTDIR" +
+ summary.substring(index + path.length(), nextLineIndex) +
+ " " + summary.substring(nextLineIndex + path.length());
+ }
+
+ @Nullable
+ private static File findGradleWrapper() throws IOException {
+ File root = TestUtils.getCanonicalRoot("resources", "baseMerge");
+ // The TestUtils call returns results within sdk-common when run from within the IDE
+ // so look relative to it to find the templates folder
+ if (root != null) {
+ File top = root
+ .getParentFile()
+ .getParentFile()
+ .getParentFile()
+ .getParentFile()
+ .getParentFile()
+ .getParentFile()
+ .getParentFile();
+ File wrapper = new File(top, FD_TEMPLATES + separator + FD_GRADLE_WRAPPER);
+ if (wrapper.exists()) {
+ return wrapper;
+ }
+ }
+
+ return null;
+ }
+
+ private static boolean isWindows() {
+ return SdkUtils.startsWithIgnoreCase(System.getProperty("os.name"), "windows");
+ }
+
+ public static void assertBuildsCleanly(File base, boolean allowWarnings) throws Exception {
+ File gradlew = new File(base, isWindows() ? FN_GRADLE_WRAPPER_WIN : FN_GRADLE_WRAPPER_UNIX);
+ if (!gradlew.exists()) {
+ // Not using a wrapper; can't easily test building (we don't have a gradle prebuilt)
+ return;
+ }
+ File pwd = base.getAbsoluteFile();
+ Process process = Runtime.getRuntime().exec(new String[]{gradlew.getPath(),
+ "assembleDebug"}, null, pwd);
+ int exitCode = process.waitFor();
+ byte[] stdout = ByteStreams.toByteArray(process.getInputStream());
+ byte[] stderr = ByteStreams.toByteArray(process.getErrorStream());
+ String errors = new String(stderr, UTF_8);
+ String output = new String(stdout, UTF_8);
+ int expectedExitCode = 0;
+ if (output.contains("BUILD FAILED") && errors.contains(
+ "Could not find any version that matches com.android.tools.build:gradle:")) {
+ // We ignore this assertion. We got here because we are using a version of the
+ // Android Gradle plug-in that is not available in Maven Central yet.
+ expectedExitCode = 1;
+ } else {
+ assertTrue(output + "\n" + errors, output.contains("BUILD SUCCESSFUL"));
+ if (!allowWarnings) {
+ assertEquals(output + "\n" + errors, "", errors);
+ }
+ }
+ assertEquals(expectedExitCode, exitCode);
+ System.out.println("Built project successfully; output was:\n" + output);
+ }
+
+ private static String fileTree(File file, boolean includeDirs) {
+ StringBuilder sb = new StringBuilder(1000);
+ appendFiles(sb, includeDirs, file, 0);
+ return sb.toString();
+ }
+
+ private static void appendFiles(StringBuilder sb, boolean includeDirs, File file, int depth) {
+ // Skip output
+ if ((depth == 1 || depth == 2) && file.getName().equals("build")) {
+ return;
+ }
+
+ // Skip wrapper, since it may or may not be present for unit tests
+ if (depth == 1) {
+ String name = file.getName();
+ if (name.equals(DOT_GRADLE)
+ || name.equals(FD_GRADLE)
+ || name.equals(FN_GRADLE_WRAPPER_UNIX)
+ || name.equals(FN_GRADLE_WRAPPER_WIN)) {
+ return;
+ }
+ }
+
+ boolean isDirectory = file.isDirectory();
+ if (depth > 0 && (!isDirectory || includeDirs)) {
+ for (int i = 0; i < depth - 1; i++) {
+ sb.append(" ");
+ }
+ sb.append(file.getName());
+ sb.append("\n");
+ }
+
+ if (isDirectory) {
+ File[] children = file.listFiles();
+ if (children != null) {
+ Arrays.sort(children, new Comparator<File>() {
+ @Override
+ public int compare(File file, File file2) {
+ return file.getName().compareTo(file2.getName());
+ }
+ });
+ for (File child : children) {
+ appendFiles(sb, includeDirs, child, depth + 1);
+ }
+ }
+ }
+ }
+
+ private static void createDotProject(
+ @NonNull File projectDir,
+ String name,
+ boolean addAndroidNature) throws IOException {
+ createDotProject(projectDir, name, addAndroidNature, addAndroidNature);
+ }
+
+ private static void createDotProject(
+ @NonNull File projectDir,
+ String name,
+ boolean addAndroidNature, boolean addNdkNature) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<projectDescription>\n"
+ + "\t<name>").append(name).append("</name>\n"
+ + "\t<comment></comment>\n"
+ + "\t<projects>\n"
+ + "\t</projects>\n"
+ + "\t<buildSpec>\n"
+ + "\t\t<buildCommand>\n"
+ + "\t\t\t<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>\n"
+ + "\t\t\t<arguments>\n"
+ + "\t\t\t</arguments>\n"
+ + "\t\t</buildCommand>\n"
+ + "\t\t<buildCommand>\n"
+ + "\t\t\t<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>\n"
+ + "\t\t\t<arguments>\n"
+ + "\t\t\t</arguments>\n"
+ + "\t\t</buildCommand>\n"
+ + "\t\t<buildCommand>\n"
+ + "\t\t\t<name>org.eclipse.jdt.core.javabuilder</name>\n"
+ + "\t\t\t<arguments>\n"
+ + "\t\t\t</arguments>\n"
+ + "\t\t</buildCommand>\n"
+ + "\t\t<buildCommand>\n"
+ + "\t\t\t<name>com.android.ide.eclipse.adt.ApkBuilder</name>\n"
+ + "\t\t\t<arguments>\n"
+ + "\t\t\t</arguments>\n"
+ + "\t\t</buildCommand>\n"
+ + "\t</buildSpec>\n"
+ + "\t<natures>\n");
+ if (addAndroidNature) {
+ sb.append("\t\t<nature>com.android.ide.eclipse.adt.AndroidNature</nature>\n");
+ }
+ if (addNdkNature) {
+ sb.append("\t\t<nature>org.eclipse.cdt.core.cnature</nature>\n");
+ sb.append("\t\t<nature>org.eclipse.cdt.core.ccnature</nature>\n");
+ }
+ sb.append("\t\t<nature>org.eclipse.jdt.core.javanature</nature>\n"
+ + "\t</natures>\n"
+ + "</projectDescription>\n");
+ Files.write(sb.toString(), new File(projectDir, ".project"), UTF_8);
+ }
+
+ private static void createClassPath(
+ @NonNull File projectDir,
+ @Nullable File output,
+ @NonNull List<File> sources,
+ @NonNull List<File> jars) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<classpath>\n");
+ for (File source : sources) {
+ sb.append("\t<classpathentry kind=\"src\" path=\"").append(source.getPath()).
+ append("\"/>\n");
+ }
+ sb.append("\t<classpathentry kind=\"con\" path=\"com.android.ide.eclipse.adt.ANDROID_FRAMEWORK\"/>\n"
+ + "\t<classpathentry exported=\"true\" kind=\"con\" path=\"com.android.ide.eclipse.adt.LIBRARIES\"/>\n"
+ + "\t<classpathentry exported=\"true\" kind=\"con\" path=\"com.android.ide.eclipse.adt.DEPENDENCIES\"/>\n");
+ for (File jar : jars) {
+ sb.append("<classpathentry exported=\"true\" kind=\"lib\" path=\"").append(jar.getPath()).append("\"/>\n");
+ }
+ if (output != null) {
+ sb.append("\t<classpathentry kind=\"output\" path=\"").append(output.getPath()).append("\"/>\n");
+ }
+ sb.append("</classpath>");
+ Files.write(sb.toString(), new File(projectDir, ".classpath"), UTF_8);
+ }
+
+ private static void createProjectProperties(
+ @NonNull File projectDir,
+ @Nullable String target,
+ Boolean mergeManifest,
+ Boolean isLibrary,
+ @Nullable String proguardConfig,
+ @NonNull List<File> libraries) throws IOException {
+ createProjectProperties(projectDir, target, mergeManifest, isLibrary, proguardConfig, libraries, true);
+ }
+
+ private static void createProjectProperties(
+ @NonNull File projectDir,
+ @Nullable String target,
+ Boolean mergeManifest,
+ Boolean isLibrary,
+ @Nullable String proguardConfig,
+ @NonNull List<File> libraries,
+ boolean startLibrariesAt1) throws IOException {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("# This file is automatically generated by Android Tools.\n"
+ + "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n"
+ + "#\n"
+ + "# This file must be checked in Version Control Systems.\n"
+ + "#\n"
+ + "# To customize properties used by the Ant build system edit\n"
+ + "# \"ant.properties\", and override values to adapt the script to your\n"
+ + "# project structure.\n"
+ + "#\n");
+ if (proguardConfig != null) {
+ sb.append("# To enable ProGuard to shrink and obfuscate your code, uncomment this "
+ + "(available properties: sdk.dir, user.home):\n");
+ // TODO: When using this, escape proguard properly
+ sb.append(proguardConfig);
+ sb.append("\n");
+ }
+
+ if (target != null) {
+ String escaped = escapeProperty("target", target);
+ sb.append("# Project target.\n").append(escaped).append("\n");
+ }
+
+ if (mergeManifest != null) {
+ String escaped = escapeProperty("manifestmerger.enabled",
+ Boolean.toString(mergeManifest));
+ sb.append(escaped).append("\n");
+ }
+
+ if (isLibrary != null) {
+ String escaped = escapeProperty("android.library", Boolean.toString(isLibrary));
+ sb.append(escaped).append("\n");
+ }
+
+ for (int i = 0, n = libraries.size(); i < n; i++) {
+ String path = libraries.get(i).getPath();
+ // Libraries normally start at index 1, but I want to handle cases where they start
+ // at 0 too so we have a test parameter for that
+ int index = i + (startLibrariesAt1 ? 1 : 0);
+ String escaped = escapeProperty("android.library.reference." + Integer.toString(index),
+ path);
+ sb.append(escaped).append("\n");
+ }
+
+ Files.write(sb.toString(), new File(projectDir, "project.properties"), UTF_8);
+ }
+
+ private static String escapeProperty(@NonNull String key, @NonNull String value)
+ throws IOException {
+ Properties properties = new Properties();
+ properties.setProperty(key, value);
+ StringWriter writer = new StringWriter();
+ properties.store(writer, null);
+ return writer.toString();
+ }
+
+ private static void createAndroidManifest(
+ @NonNull File projectDir,
+ @NonNull String packageName,
+ int minSdkVersion,
+ int targetSdkVersion,
+ @Nullable String customApplicationBlock) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"").append(packageName).append("\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n");
+ if (minSdkVersion != -1 || targetSdkVersion != -1) {
+ sb.append(" <uses-sdk\n");
+ if (minSdkVersion >= 1) {
+ sb.append(" android:minSdkVersion=\"8\"\n");
+ }
+ if (targetSdkVersion >= 1) {
+ sb.append(" android:targetSdkVersion=\"16\"\n");
+ }
+ sb.append(" />\n");
+ sb.append("\n");
+ }
+ if (customApplicationBlock != null) {
+ sb.append(customApplicationBlock);
+ } else {
+ sb.append(""
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " >\n"
+ + " </application>\n");
+ }
+
+ sb.append("\n"
+ + "</manifest>\n");
+ Files.write(sb.toString(), new File(projectDir, ANDROID_MANIFEST_XML), UTF_8);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private static File createSourceFile(@NonNull File projectDir, String relative,
+ String contents) throws IOException {
+ File file = new File(projectDir, relative.replace('/', separatorChar));
+ file.getParentFile().mkdirs();
+ Files.write(contents, file, UTF_8);
+ return file;
+ }
+
+ private static File createSampleJavaSource(@NonNull File projectDir, String src, String pkg,
+ String name) throws IOException {
+ return createSourceFile(projectDir, src + '/' + pkg.replace('.','/') + '/' + name +
+ DOT_JAVA, ""
+ + "package " + pkg + ";\n"
+ + "public class " + name + " {\n"
+ + "}\n");
+ }
+
+ private static File createSampleAidlFile(@NonNull File projectDir, String src, String pkg)
+ throws IOException {
+ return createSourceFile(projectDir, src + '/' + pkg.replace('.','/') +
+ "/IHardwareService.aidl", ""
+ + "package " + pkg + ";\n"
+ + "\n"
+ + "/** {@hide} */\n"
+ + "interface IHardwareService\n"
+ + "{\n"
+ + " // Vibrator support\n"
+ + " void vibrate(long milliseconds);\n"
+ + " void vibratePattern(in long[] pattern, int repeat, IBinder token);\n"
+ + " void cancelVibrate();\n"
+ + "\n"
+ + " // flashlight support\n"
+ + " boolean getFlashlightEnabled();\n"
+ + " void setFlashlightEnabled(boolean on);\n"
+ + " void enableCameraFlash(int milliseconds);\n"
+ + "\n"
+ + " // sets the brightness of the backlights (screen, keyboard, button) 0-255\n"
+ + " void setBacklights(int brightness);\n"
+ + "\n"
+ + " // for the phone\n"
+ + " void setAttentionLight(boolean on);\n"
+ + "}");
+ }
+
+ private static File createSampleRsFile(@NonNull File projectDir, String src, String pkg)
+ throws IOException {
+ return createSourceFile(projectDir, src + '/' + pkg.replace('.', '/') + '/' + "latency.rs",
+ ""
+ + "#pragma version(1)\n"
+ + "#pragma rs java_package_name(com.android.rs.cpptests)\n"
+ + "#pragma rs_fp_relaxed\n"
+ + "\n"
+ + "void root(const uint32_t *v_in, uint32_t *v_out) {\n"
+ + "\n"
+ + "}");
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private static void createDefaultStrings(File dir) throws IOException {
+ File strings = new File(dir, "res" + separator + "values" + separator + "strings.xml");
+ strings.getParentFile().mkdirs();
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + "\n"
+ + " <string name=\"app_name\">Unit Test</string>\n"
+ + "\n"
+ + "</resources>", strings, UTF_8);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private static void createDefaultIcon(File dir) throws IOException {
+ File strings = new File(dir, "res" + separator + "drawable" + separator +
+ "ic_launcher.xml");
+ strings.getParentFile().mkdirs();
+ Files.write(""
+ + "<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <solid android:color=\"#00000000\"/>\n"
+ + " <stroke android:width=\"1dp\" color=\"#ff000000\"/>\n"
+ + " <padding android:left=\"1dp\" android:top=\"1dp\"\n"
+ + " android:right=\"1dp\" android:bottom=\"1dp\" />\n"
+ + "</shape>", strings, UTF_8);
+ }
+
+ private static void deleteDir(File root) {
+ if (root.exists()) {
+ File[] files = root.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteDir(file);
+ } else {
+ boolean deleted = file.delete();
+ assert deleted : file;
+ }
+ }
+ }
+ boolean deleted = root.delete();
+ assert deleted : root;
+ }
+ }
+
+ /** Environment variable or system property containing the full path to an SDK install */
+ public static final String SDK_PATH_PROPERTY = "ADT_TEST_SDK_PATH";
+
+ @Nullable
+ protected static String getTestSdkPath() {
+ String override = System.getProperty(SDK_PATH_PROPERTY);
+ if (override != null) {
+ assertTrue(override, new File(override).exists());
+ return override;
+ }
+ override = System.getenv(SDK_PATH_PROPERTY);
+ if (override != null) {
+ return override;
+ }
+
+ return null;
+ }
+
+ private static final String BUILD_TOOLS_VERSION;
+ static {
+ String candidate = CURRENT_BUILD_TOOLS_VERSION;
+ String sdkLocation = getTestSdkPath();
+ if (sdkLocation != null) {
+ ILogger logger = new StdLogger(StdLogger.Level.INFO);
+ SdkManager sdkManager = SdkManager.createManager(sdkLocation, logger);
+ if (sdkManager != null) {
+ final BuildToolInfo buildTool = sdkManager.getLatestBuildTool();
+ if (buildTool != null) {
+ candidate = buildTool.getRevision().toString();
+ }
+ }
+ }
+
+ BUILD_TOOLS_VERSION = candidate;
+ }
+
+ private static final String DEFAULT_MOVED = ""
+ + "* AndroidManifest.xml => app/src/main/AndroidManifest.xml\n"
+ + "* res/ => app/src/main/res/\n"
+ + "* src/ => app/src/main/java/\n";
+}
diff --git a/gradle.properties b/gradle.properties
deleted file mode 100644
index f66ad4d..0000000
--- a/gradle.properties
+++ /dev/null
@@ -1 +0,0 @@
-org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 7b359d7..0000000
--- a/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 71afaf0..0000000
--- a/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Wed Dec 12 15:16:42 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/gradlew b/gradlew
deleted file mode 100755
index 3033dd6..0000000
--- a/gradlew
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/"
-APP_HOME="`pwd -P`"
-cd "$SAVED"
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=$((i+1))
- done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-# Change the project's .gradle to the android out dir.
-ANDROID_GRADLE_ROOT="$APP_HOME/../../out/host/gradle/tools/base"
-if [[ -z "$ANDROID_CACHE_DIR" ]]; then
- ANDROID_CACHE_DIR="$ANDROID_GRADLE_ROOT/.gradle"
-fi
-
-# Change the local user directories to be under the android out dir
-export GRADLE_USER_HOME="$ANDROID_GRADLE_ROOT/.gradle"
-export M2_HOME="$ANDROID_GRADLE_ROOT/.m2"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" \
- -classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
- --project-cache-dir=$ANDROID_CACHE_DIR \
- "$@"
-
diff --git a/gradlew.bat b/gradlew.bat
deleted file mode 100755
index 17b0181..0000000
--- a/gradlew.bat
+++ /dev/null
@@ -1,96 +0,0 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Change the project's .gradle to the android out dir.
-set ANDROID_GRADLE_ROOT=%APP_HOME%\..\..\out\host\gradle\tools\base
-set ANDROID_CACHE_DIR=%ANDROID_GRADLE_ROOT%\.gradle
-set GRADLE_USER_HOME=%ANDROID_GRADLE_ROOT%\.gradle
-set M2_HOME=%ANDROID_GRADLE_ROOT%\.m2
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% --project-cache-dir=%ANDROID_CACHE_DIR%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/javadoc.gradle b/javadoc.gradle
deleted file mode 100644
index 720844a..0000000
--- a/javadoc.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-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 7541707..bf112f1 100644
--- a/jobb/build.gradle
+++ b/jobb/build.gradle
@@ -1,18 +1,25 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
dependencies {
- compile project(':fat32lib')
+ compile project(':external:fat32lib')
}
group = 'com.android.tools.build'
archivesBaseName = 'jobb'
+version = rootProject.ext.baseVersion
-// configure the manifest of the buildDistributionJar task
-buildDistributionJar.manifest.attributes("Main-Class": "com.android.jobb.Main")
+// configure the manifest of the sdkJar task
+sdkJar.manifest.attributes("Main-Class": "com.android.jobb.Main")
-shipping {
- launcherScripts = ['etc/jobb', 'etc/jobb.bat']
+sdk {
+ linux {
+ item('etc/jobb') { executable true }
+ }
+ mac {
+ item('etc/jobb') { executable true }
+ }
+ windows {
+ item 'etc/jobb.bat'
+ }
}
-
-apply from: '../baseVersion.gradle'
\ No newline at end of file
diff --git a/layoutlib-api/.classpath b/layoutlib-api/.classpath
index 7b6cd60..a9512e7 100644
--- a/layoutlib-api/.classpath
+++ b/layoutlib-api/.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 exported="true" kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/net/sf/kxml/kxml2/2.3.0/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/net/sf/kxml/kxml2/2.3.0/kxml2-2.3.0-sources.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
diff --git a/layoutlib-api/build.gradle b/layoutlib-api/build.gradle
index 9dc4779..e13f725 100644
--- a/layoutlib-api/build.gradle
+++ b/layoutlib-api/build.gradle
@@ -1,24 +1,20 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools.layoutlib'
archivesBaseName = 'layoutlib-api'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':common')
+ compile project(':base:common')
compile 'net.sf.kxml:kxml2:2.3.0'
testCompile 'junit:junit:3.8.1'
}
-jar {
- from 'NOTICE'
-}
-
project.ext.pomName = 'Android Tools layoutlib-api'
project.ext.pomDesc = 'Library to use the rendering library for Android layouts: layoutlib'
-apply from: '../baseVersion.gradle'
-apply from: '../publish.gradle'
-apply from: '../javadoc.gradle'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ActionBarCallback.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ActionBarCallback.java
new file mode 100644
index 0000000..b71dd99
--- /dev/null
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ActionBarCallback.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Callback for Action Bar information needed by the Layout Library.
+ */
+public class ActionBarCallback {
+
+ // The Navigation mode constants correspond to their counterparts in android.app.ActionBar
+ public static final int NAVIGATION_MODE_STANDARD = 0;
+ public static final int NAVIGATION_MODE_LIST = 1;
+ public static final int NAVIGATION_MODE_TABS = 2;
+
+ /**
+ * Types of navigation for home button.
+ */
+ public enum HomeButtonStyle {
+ NONE, SHOW_HOME_AS_UP
+ }
+
+ /**
+ * Returns a list of names of the IDs for menus to add to the action bar.
+ *
+ * @return the list of menu ids. The list is never null, but may be empty.
+ */
+ public List<String> getMenuIdNames() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Returns whether the Action Bar should be split for narrow screens.
+ */
+ public boolean getSplitActionBarWhenNarrow() {
+ return false;
+ }
+
+ /**
+ * Returns which navigation mode the action bar should use.
+ *
+ * @return one of {@link #NAVIGATION_MODE_STANDARD}, {@link #NAVIGATION_MODE_LIST} or
+ * {@link #NAVIGATION_MODE_TABS}
+ */
+ public int getNavigationMode() {
+ return NAVIGATION_MODE_STANDARD;
+ }
+
+ /**
+ * Returns the subtitle to be used with the action bar or null if there is no subtitle.
+ */
+ public String getSubTitle() {
+ return null;
+ }
+
+ /**
+ * Returns the type of navigation for home button to be used in the action bar.
+ * <p/>
+ * For example, for showHomeAsUp, an arrow is shown alongside the "home" icon.
+ *
+ * @return navigation type for home button. Never null.
+ */
+ public HomeButtonStyle getHomeButtonStyle() {
+ return HomeButtonStyle.NONE;
+ }
+
+ /**
+ * Returns whether to draw the overflow menu popup.
+ */
+ public boolean isOverflowPopupNeeded() {
+ return false;
+ }
+}
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 0f35b96..45bd92c 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 = 10;
+ public static final int API_CURRENT = 11;
/**
* Returns the API level of the layout library.
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 f1c0113..1582d4eb 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
@@ -69,5 +69,13 @@
/**
* Ability to render RTL layouts.
*/
- RTL
+ RTL,
+ /**
+ * Ability to render ActionBar.
+ */
+ ACTION_BAR,
+ /**
+ * Ability to simulate older Platform Versions.
+ */
+ SIMULATE_PLATFORM,
}
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IProjectCallback.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IProjectCallback.java
index a88b0d3..e4b1cb8 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IProjectCallback.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IProjectCallback.java
@@ -151,4 +151,12 @@
*/
AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
Object viewObject);
+
+ /**
+ * Returns a callback for Action Bar information needed by the Layout Library. The callback
+ * provides information like the menus to add to the Action Bar.
+ *
+ * @since API 11
+ */
+ ActionBarCallback getActionBarCallback();
}
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 8592128..4683961 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
@@ -40,11 +40,12 @@
private int mCustomBackgroundColor;
private long mTimeout;
- private IImageFactory mImageFactory = null;
+ private IImageFactory mImageFactory;
- private String mAppIcon = null;
- private String mAppLabel = null;
- private String mLocale = null;
+ private String mAppIcon;
+ private String mAppLabel;
+ private String mLocale;
+ private String mActivityName;
private boolean mForceNoDecor;
private boolean mSupportsRtl;
@@ -95,6 +96,7 @@
mAppIcon = params.mAppIcon;
mAppLabel = params.mAppLabel;
mLocale = params.mLocale;
+ mActivityName = params.mActivityName;
mForceNoDecor = params.mForceNoDecor;
mSupportsRtl = params.mSupportsRtl;
}
@@ -124,6 +126,10 @@
mLocale = locale;
}
+ public void setActivityName(String activityName) {
+ mActivityName = activityName;
+ }
+
public void setForceNoDecor() {
mForceNoDecor = true;
}
@@ -236,6 +242,10 @@
return mLocale;
}
+ public String getActivityName() {
+ return mActivityName;
+ }
+
public boolean isForceNoDecor() {
return mForceNoDecor;
}
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderResources.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderResources.java
index d4609f4..1be4247 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderResources.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderResources.java
@@ -18,6 +18,8 @@
import com.android.resources.ResourceType;
+import java.util.List;
+
/**
* A class containing all the resources needed to do a rendering.
* <p/>
@@ -43,12 +45,54 @@
/**
* Returns the {@link StyleResourceValue} representing the current theme.
* @return the theme or null if there is no current theme.
+ * @deprecated Use {@link #getDefaultTheme()} or {@link #getAllThemes()}
*/
+ @Deprecated
public StyleResourceValue getCurrentTheme() {
+ // Default theme is same as the current theme was on older versions of the API.
+ // With the introduction of applyStyle() "current" theme makes little sense.
+ // Hence, simply return defaultTheme.
+ return getDefaultTheme();
+ }
+
+ /**
+ * Returns the {@link StyleResourceValue} representing the default theme.
+ */
+ public StyleResourceValue getDefaultTheme() {
return null;
}
/**
+ * Use this theme to resolve resources.
+ * <p/>
+ * Remember to call {@link #clearStyles()} to clear the applied styles, so the default theme
+ * may be restored.
+ *
+ * @param theme The style to use for resource resolution in addition to the the default theme
+ * and the styles applied earlier. If null, the operation is a no-op.
+ * @param useAsPrimary If true, the {@code theme} is used first to resolve attributes. If
+ * false, the theme is used if the resource cannot be resolved using the default theme and
+ * all the themes that have been applied prior to this call.
+ */
+ public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
+ }
+
+ /**
+ * Clear all the themes applied with {@link #applyStyle(StyleResourceValue, boolean)}
+ */
+ public void clearStyles() {
+ }
+
+ /**
+ * Returns a list of {@link StyleResourceValue} containing a list of themes to be used for
+ * resolving resources. The order of the themes in the list specifies the order in which they
+ * should be used to resolve resources.
+ */
+ public List<StyleResourceValue> getAllThemes() {
+ return null;
+ }
+
+ /**
* Returns a theme by its name.
*
* @param name the name of the theme
@@ -88,8 +132,9 @@
}
/**
- * Returns the {@link ResourceValue} matching a given name in the current theme. If the
- * item is not directly available in the theme, the method looks in its parent theme.
+ * Returns the {@link ResourceValue} matching a given name in the all themes returned by
+ * {@link #getAllThemes()}. If the item is not directly available in the a theme, its parent
+ * theme is used before checking the next theme from the list.
*
* @param itemName the name of the item to search for.
* @return the {@link ResourceValue} object or <code>null</code>
@@ -98,28 +143,40 @@
*/
@Deprecated
public ResourceValue findItemInTheme(String itemName) {
- StyleResourceValue currentTheme = getCurrentTheme();
- if (currentTheme != null) {
- return findItemInStyle(currentTheme, itemName);
+ List<StyleResourceValue> allThemes = getAllThemes();
+ if (allThemes == null) {
+ return null;
}
-
+ for (StyleResourceValue theme : allThemes) {
+ //noinspection deprecation
+ ResourceValue value = findItemInStyle(theme, itemName);
+ if (value != null) {
+ return value;
+ }
+ }
return null;
}
/**
- * Returns the {@link ResourceValue} matching a given attribute in the current theme. If the
- * item is not directly available in the theme, the method looks in its parent theme.
+ * Returns the {@link ResourceValue} matching a given name in the all themes returned by
+ * {@link #getAllThemes()}. If the item is not directly available in the a theme, its parent
+ * theme is used before checking the next theme from the list.
*
* @param attrName the name of the attribute to search for.
* @param isFrameworkAttr whether the attribute is a framework attribute
* @return the {@link ResourceValue} object or <code>null</code>
*/
public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
- StyleResourceValue currentTheme = getCurrentTheme();
- if (currentTheme != null) {
- return findItemInStyle(currentTheme, attrName, isFrameworkAttr);
+ List<StyleResourceValue> allThemes = getAllThemes();
+ if (allThemes == null) {
+ return null;
}
-
+ for (StyleResourceValue theme : allThemes) {
+ ResourceValue value = findItemInStyle(theme, attrName, isFrameworkAttr);
+ if (value != null) {
+ return value;
+ }
+ }
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java
index 96caa6a..13470c4 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java
@@ -45,18 +45,38 @@
/**
* Returns the {@link ViewInfo} objects for the top level views.
* <p/>
- * In most case the list will only contain one item. If the top level node is {@code merge}
- * though then it will contain all the items under the {@code merge} tag.
+ * It contains {@code ViewInfo} for only the views in the layout. For {@code ViewInfo} of the
+ * System UI surrounding the layout use {@link #getSystemRootViews()}. In most cases the list
+ * will only contain one item. If the top level node is a {@code merge} though then it will
+ * contain all the items under the {@code merge} tag.
* <p/>
* This is reset to a new instance every time {@link #render()} is called and can be
* <code>null</code> if the call failed (and the method returned a {@link Result} with
* {@link Status#ERROR_UNKNOWN} or {@link Status#NOT_IMPLEMENTED}.
* <p/>
- * This can be safely modified by the caller.
+ * This can be safely modified by the caller, but {@code #getSystemRootViews} and
+ * {@code #getRootViews} share some view infos, so modifying one result can affect the other.
+ *
+ * @return the list of {@link ViewInfo} or null if there aren't any.
+ *
+ * @see #getSystemRootViews()
+ */
+ public List<ViewInfo> getRootViews() {
+ return null;
+ }
+
+ /**
+ * Returns the {@link ViewInfo} objects for the system decor views, like the ActionBar.
+ * <p/>
+ * This is reset to a new instance every time {@link #render()} is called and can be
+ * <code>null</code> if the call failed, or there was no system decor.
+ * <p/>
+ * This can be safely modified by the caller, but {@code #getSystemRootViews} and
+ * {@code #getRootViews} share some view infos, so modifying one result can affect the other.
*
* @return the list of {@link ViewInfo} or null if there aren't any.
*/
- public List<ViewInfo> getRootViews() {
+ public List<ViewInfo> getSystemRootViews() {
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java
index aa1ba7c..ec7da14 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java
@@ -61,6 +61,18 @@
}
/**
+ * Similar to {@link #getValue()}, but returns the raw XML value. This is <b>usually</b>
+ * the same as getValue, but with a few exceptions. For example, for markup strings,
+ * you can have * {@code <string name="markup">This is <b>bold</b></string>}.
+ * Here, {@link #getValue()} will return "{@code This is bold}" -- e.g. just
+ * the plain text flattened. However, this method will return "{@code This is <b>bold</b>}",
+ * which preserves the XML markup elements.
+ */
+ public String getRawXmlValue() {
+ return getValue();
+ }
+
+ /**
* Sets the value of the resource.
* @param value the new value
*/
@@ -82,9 +94,6 @@
+ " (framework:" + isFramework() + ")]"; //$NON-NLS-1$ //$NON-NLS-2$
}
- /* (non-Javadoc)
- * @see java.lang.Object#hashCode()
- */
@Override
public int hashCode() {
final int prime = 31;
@@ -94,9 +103,6 @@
return result;
}
- /* (non-Javadoc)
- * @see java.lang.Object#equals(java.lang.Object)
- */
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -107,11 +113,13 @@
return false;
ResourceValue other = (ResourceValue) obj;
if (mType == null) {
+ //noinspection VariableNotUsedInsideIf
if (other.mType != null)
return false;
} else if (!mType.equals(other.mType))
return false;
if (mValue == null) {
+ //noinspection VariableNotUsedInsideIf
if (other.mValue != null)
return false;
} else if (!mValue.equals(other.mValue))
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/SessionParams.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/SessionParams.java
index 709207e..cdb5db1 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/SessionParams.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/SessionParams.java
@@ -53,6 +53,7 @@
private boolean mLayoutOnly = false;
private Map<ResourceReference, AdapterBinding> mAdapterBindingMap;
private boolean mExtendedViewInfoMode = false;
+ private final int mSimulatedPlatformVersion;
/**
*
@@ -77,17 +78,47 @@
IProjectCallback projectCallback,
int minSdkVersion, int targetSdkVersion,
LayoutLog log) {
- super(projectKey, hardwareConfig,
- renderResources, projectCallback, minSdkVersion, targetSdkVersion, log);
+ this(layoutDescription, renderingMode, projectKey, hardwareConfig,
+ renderResources, projectCallback, minSdkVersion, targetSdkVersion, log, 0);
+ }
+
+ /**
+ *
+ * @param layoutDescription the {@link ILayoutPullParser} letting the LayoutLib Bridge visit the
+ * layout file.
+ * @param renderingMode The rendering mode.
+ * @param projectKey An Object identifying the project. This is used for the cache mechanism.
+ * @param hardwareConfig the {@link HardwareConfig}.
+ * @param renderResources a {@link RenderResources} object providing access to the resources.
+ * @param projectCallback The {@link IProjectCallback} object to get information from
+ * the project.
+ * @param minSdkVersion the minSdkVersion of the project
+ * @param targetSdkVersion the targetSdkVersion of the project
+ * @param log the object responsible for displaying warning/errors to the user.
+ * @param simulatedPlatformVersion try to simulate an old android platform. 0 means disabled.
+ */
+ public SessionParams(
+ ILayoutPullParser layoutDescription,
+ RenderingMode renderingMode,
+ Object projectKey,
+ HardwareConfig hardwareConfig,
+ RenderResources renderResources,
+ IProjectCallback projectCallback,
+ int minSdkVersion, int targetSdkVersion,
+ LayoutLog log, int simulatedPlatformVersion) {
+ super(projectKey, hardwareConfig, renderResources, projectCallback,
+ minSdkVersion, targetSdkVersion, log);
mLayoutDescription = layoutDescription;
mRenderingMode = renderingMode;
+ mSimulatedPlatformVersion = simulatedPlatformVersion;
}
public SessionParams(SessionParams params) {
super(params);
mLayoutDescription = params.mLayoutDescription;
mRenderingMode = params.mRenderingMode;
+ mSimulatedPlatformVersion = params.mSimulatedPlatformVersion;
if (params.mAdapterBindingMap != null) {
mAdapterBindingMap = new HashMap<ResourceReference, AdapterBinding>(
params.mAdapterBindingMap);
@@ -134,4 +165,8 @@
public boolean getExtendedViewInfoMode() {
return mExtendedViewInfoMode;
}
+
+ public int getSimulatedPlatformVersion() {
+ return mSimulatedPlatformVersion;
+ }
}
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/StyleResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/StyleResourceValue.java
index f6e561b..69b5fc8 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/StyleResourceValue.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/StyleResourceValue.java
@@ -81,6 +81,7 @@
assert value instanceof StyleResourceValue;
super.replaceWith(value);
+ //noinspection ConstantConditions
if (value instanceof StyleResourceValue) {
mItems.clear();
mItems.putAll(((StyleResourceValue)value).mItems);
@@ -94,7 +95,7 @@
@Override
@Deprecated
public IResourceValue findItem(String name) {
- return mItems.get(name);
+ return mItems.get(Pair.of(name, true));
}
/** Returns the names available in this style, intended for diagnostic purposes */
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/TextResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/TextResourceValue.java
new file mode 100644
index 0000000..ae1660d
--- /dev/null
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/TextResourceValue.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api;
+
+import com.android.resources.ResourceType;
+
+/**
+ * A {@link com.android.ide.common.rendering.api.ResourceValue} intended for text nodes
+ * where we need access to the raw XML text
+ */
+public class TextResourceValue extends ResourceValue {
+ private String mRawXmlValue;
+
+ public TextResourceValue(ResourceType type, String name, boolean isFramework) {
+ super(type, name, isFramework);
+ }
+
+ public TextResourceValue(ResourceType type, String name, String textValue, String rawXmlValue,
+ boolean isFramework) {
+ super(type, name, textValue, isFramework);
+ mRawXmlValue = rawXmlValue;
+ }
+
+ @Override
+ public String getRawXmlValue() {
+ if (mRawXmlValue != null) {
+ return mRawXmlValue;
+ }
+ return super.getValue();
+ }
+
+ public void setRawXmlValue(String value) {
+ mRawXmlValue = value;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mRawXmlValue == null) ? 0 : mRawXmlValue.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ TextResourceValue other = (TextResourceValue) obj;
+ if (mRawXmlValue == null) {
+ //noinspection VariableNotUsedInsideIf
+ if (other.mRawXmlValue != null)
+ return false;
+ } else if (!mRawXmlValue.equals(other.mRawXmlValue))
+ return false;
+ return true;
+ }
+
+}
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewInfo.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewInfo.java
index d859e95..6067641 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewInfo.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewInfo.java
@@ -181,4 +181,13 @@
public int getBottomMargin() {
return mBottomMargin;
}
+
+ /**
+ * Returns the type of View.
+ * @see ViewType
+ */
+ public ViewType getViewType() {
+ return ViewType.USER;
+ }
+
}
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewType.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewType.java
new file mode 100644
index 0000000..9518bee
--- /dev/null
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewType.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api;
+
+/**
+ * Lists various types of a view. Used as a possible return value of {@link ViewInfo#getViewType()}
+ */
+public enum ViewType {
+ /**
+ * A view added by the framework. No additional info about the view is
+ * available.
+ */
+ SYSTEM_UNKNOWN,
+ /**
+ * A view that is part of the user's layout.
+ */
+ USER,
+ /**
+ * The overflow menu button in the action bar.
+ */
+ ACTION_BAR_OVERFLOW,
+ /**
+ * A menu item in the action bar.
+ */
+ ACTION_BAR_MENU,
+ /**
+ * A menu item in the action bar overflow popup.
+ */
+ ACTION_BAR_OVERFLOW_MENU,
+ /**
+ * The back button in the Navigation Bar.
+ */
+ NAVIGATION_BAR_BACK,
+ /**
+ * The home button in the Navigation Bar.
+ */
+ NAVIGATION_BAR_HOME,
+ /**
+ * The recents button in the Navigation Bar.
+ */
+ NAVIGATION_BAR_RECENTS
+}
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 e6f3cb3..f221a8a 100644
--- a/layoutlib-api/src/main/java/com/android/resources/Density.java
+++ b/layoutlib-api/src/main/java/com/android/resources/Density.java
@@ -121,12 +121,9 @@
}
public static Density getByIndex(int index) {
- int i = 0;
- for (Density value : values()) {
- if (i == index) {
- return value;
- }
- i++;
+ Density[] values = values();
+ if (index >=0 && index < values.length) {
+ return values[index];
}
return null;
}
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 a951056..c026b6d 100644
--- a/layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java
+++ b/layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java
@@ -19,7 +19,7 @@
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
+import java.util.EnumMap;
import java.util.List;
import java.util.Map;
@@ -30,10 +30,10 @@
public final class FolderTypeRelationship {
private static final Map<ResourceType, List<ResourceFolderType>> mTypeToFolderMap =
- new HashMap<ResourceType, List<ResourceFolderType>>();
+ new EnumMap<ResourceType, List<ResourceFolderType>>(ResourceType.class);
private static final Map<ResourceFolderType, List<ResourceType>> mFolderToTypeMap =
- new HashMap<ResourceFolderType, List<ResourceType>>();
+ new EnumMap<ResourceFolderType, List<ResourceType>>(ResourceFolderType.class);
static {
// generate the relationships in a temporary map
diff --git a/layoutlib-api/src/main/java/com/android/resources/Keyboard.java b/layoutlib-api/src/main/java/com/android/resources/Keyboard.java
index 9fda3b0..5feeb44 100644
--- a/layoutlib-api/src/main/java/com/android/resources/Keyboard.java
+++ b/layoutlib-api/src/main/java/com/android/resources/Keyboard.java
@@ -82,12 +82,9 @@
}
public static Keyboard getByIndex(int index) {
- int i = 0;
- for (Keyboard value : values()) {
- if (i == index) {
- return value;
- }
- i++;
+ Keyboard[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/KeyboardState.java b/layoutlib-api/src/main/java/com/android/resources/KeyboardState.java
index 2eb7e00..a657d8d 100644
--- a/layoutlib-api/src/main/java/com/android/resources/KeyboardState.java
+++ b/layoutlib-api/src/main/java/com/android/resources/KeyboardState.java
@@ -79,12 +79,9 @@
}
public static KeyboardState getByIndex(int index) {
- int i = 0;
- for (KeyboardState value : values()) {
- if (i == index) {
- return value;
- }
- i++;
+ KeyboardState[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/LayoutDirection.java b/layoutlib-api/src/main/java/com/android/resources/LayoutDirection.java
index fbc386d..7d82332 100644
--- a/layoutlib-api/src/main/java/com/android/resources/LayoutDirection.java
+++ b/layoutlib-api/src/main/java/com/android/resources/LayoutDirection.java
@@ -77,14 +77,10 @@
}
public static LayoutDirection getByIndex(int index) {
- int i = 0;
- for (LayoutDirection orient : values()) {
- if (i == index) {
- return orient;
- }
- i++;
+ LayoutDirection[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
-
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/Navigation.java b/layoutlib-api/src/main/java/com/android/resources/Navigation.java
index f857e5f..581703b 100644
--- a/layoutlib-api/src/main/java/com/android/resources/Navigation.java
+++ b/layoutlib-api/src/main/java/com/android/resources/Navigation.java
@@ -80,12 +80,9 @@
}
public static Navigation getByIndex(int index) {
- int i = 0;
- for (Navigation value : values()) {
- if (i == index) {
- return value;
- }
- i++;
+ Navigation[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
return null;
}
@@ -100,4 +97,4 @@
return true;
}
-}
\ No newline at end of file
+}
diff --git a/layoutlib-api/src/main/java/com/android/resources/NavigationState.java b/layoutlib-api/src/main/java/com/android/resources/NavigationState.java
index 63b8fea..dfc6d80 100644
--- a/layoutlib-api/src/main/java/com/android/resources/NavigationState.java
+++ b/layoutlib-api/src/main/java/com/android/resources/NavigationState.java
@@ -78,12 +78,9 @@
}
public static NavigationState getByIndex(int index) {
- int i = 0;
- for (NavigationState value : values()) {
- if (i == index) {
- return value;
- }
- i++;
+ NavigationState[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/NightMode.java b/layoutlib-api/src/main/java/com/android/resources/NightMode.java
index 8fe1dd9..525f52f 100644
--- a/layoutlib-api/src/main/java/com/android/resources/NightMode.java
+++ b/layoutlib-api/src/main/java/com/android/resources/NightMode.java
@@ -78,12 +78,9 @@
}
public static NightMode getByIndex(int index) {
- int i = 0;
- for (NightMode value : values()) {
- if (i == index) {
- return value;
- }
- i++;
+ NightMode[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/ScreenOrientation.java b/layoutlib-api/src/main/java/com/android/resources/ScreenOrientation.java
index b18753d..ed98f87 100644
--- a/layoutlib-api/src/main/java/com/android/resources/ScreenOrientation.java
+++ b/layoutlib-api/src/main/java/com/android/resources/ScreenOrientation.java
@@ -79,14 +79,10 @@
}
public static ScreenOrientation getByIndex(int index) {
- int i = 0;
- for (ScreenOrientation orient : values()) {
- if (i == index) {
- return orient;
- }
- i++;
+ ScreenOrientation[] values = values();
+ if (index >=0 && index < values.length) {
+ return values[index];
}
-
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/ScreenRatio.java b/layoutlib-api/src/main/java/com/android/resources/ScreenRatio.java
index bb575b0..8a300fa 100644
--- a/layoutlib-api/src/main/java/com/android/resources/ScreenRatio.java
+++ b/layoutlib-api/src/main/java/com/android/resources/ScreenRatio.java
@@ -78,14 +78,10 @@
}
public static ScreenRatio getByIndex(int index) {
- int i = 0;
- for (ScreenRatio orient : values()) {
- if (i == index) {
- return orient;
- }
- i++;
+ ScreenRatio[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
-
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/ScreenSize.java b/layoutlib-api/src/main/java/com/android/resources/ScreenSize.java
index 4def540..3662a36 100644
--- a/layoutlib-api/src/main/java/com/android/resources/ScreenSize.java
+++ b/layoutlib-api/src/main/java/com/android/resources/ScreenSize.java
@@ -80,14 +80,10 @@
}
public static ScreenSize getByIndex(int index) {
- int i = 0;
- for (ScreenSize orient : values()) {
- if (i == index) {
- return orient;
- }
- i++;
+ ScreenSize[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
-
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/TouchScreen.java b/layoutlib-api/src/main/java/com/android/resources/TouchScreen.java
index 7eeeb08..368daa2 100644
--- a/layoutlib-api/src/main/java/com/android/resources/TouchScreen.java
+++ b/layoutlib-api/src/main/java/com/android/resources/TouchScreen.java
@@ -79,14 +79,10 @@
}
public static TouchScreen getByIndex(int index) {
- int i = 0;
- for (TouchScreen value : values()) {
- if (i == index) {
- return value;
- }
- i++;
+ TouchScreen[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
-
return null;
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/UiMode.java b/layoutlib-api/src/main/java/com/android/resources/UiMode.java
index 363a2550..33e93ce 100644
--- a/layoutlib-api/src/main/java/com/android/resources/UiMode.java
+++ b/layoutlib-api/src/main/java/com/android/resources/UiMode.java
@@ -21,18 +21,20 @@
* <p/>This is used in the resource folder names.
*/
public enum UiMode implements ResourceEnum {
- NORMAL("", "Normal"),
- CAR("car", "Car Dock"),
- DESK("desk", "Desk Dock"),
- TELEVISION("television", "Television"),
- APPLIANCE("appliance", "Appliance");
+ NORMAL("", "Normal", 1),
+ CAR("car", "Car Dock", 8),
+ DESK("desk", "Desk Dock", 8),
+ TELEVISION("television", "Television", 13),
+ APPLIANCE("appliance", "Appliance", 16);
private final String mValue;
private final String mDisplayValue;
+ private final int mSince;
- private UiMode(String value, String display) {
+ private UiMode(String value, String display, int since) {
mValue = value;
mDisplayValue = display;
+ mSince = since;
}
/**
@@ -55,6 +57,10 @@
return mValue;
}
+ public int since() {
+ return mSince;
+ }
+
@Override
public String getShortDisplayValue() {
return mDisplayValue;
@@ -79,12 +85,9 @@
}
public static UiMode getByIndex(int index) {
- int i = 0;
- for (UiMode value : values()) {
- if (i == index) {
- return value;
- }
- i++;
+ UiMode[] values = values();
+ if (index >= 0 && index < values.length) {
+ return values[index];
}
return null;
}
diff --git a/layoutlib-api/src/test/.classpath b/layoutlib-api/src/test/.classpath
index 7564f2f..d90f8ba 100644
--- a/layoutlib-api/src/test/.classpath
+++ b/layoutlib-api/src/test/.classpath
@@ -4,6 +4,6 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/guava-tools/guava-13.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/guava-tools/src.zip"/>
+ <classpathentry exported="true" kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/legacy/ant-tasks/build.gradle b/legacy/ant-tasks/build.gradle
index 595aa7a..8f884a9 100644
--- a/legacy/ant-tasks/build.gradle
+++ b/legacy/ant-tasks/build.gradle
@@ -1,11 +1,12 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools.build'
archivesBaseName = 'ant-tasks'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':manifest-merger')
+ compile project(':base:manifest-merger')
testCompile 'junit:junit:3.8.1'
}
@@ -24,5 +25,3 @@
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/DexExecTask.java b/legacy/ant-tasks/src/main/java/com/android/ant/DexExecTask.java
index 8c2e4ba..ca0087f 100644
--- a/legacy/ant-tasks/src/main/java/com/android/ant/DexExecTask.java
+++ b/legacy/ant-tasks/src/main/java/com/android/ant/DexExecTask.java
@@ -16,6 +16,7 @@
package com.android.ant;
+import com.google.common.base.Charsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
@@ -174,7 +175,7 @@
// add a hash of the original file path
HashFunction hashFunction = Hashing.md5();
- HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath());
+ HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath(), Charsets.UTF_16LE);
return name + "-" + hashCode.toString() + ".jar";
}
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 b19a8e7..4e89c9e 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
@@ -28,6 +28,8 @@
public class GetBuildToolsTask extends Task {
+ private static final FullRevision MIN_BUILD_TOOLS_REV = new FullRevision(19, 1, 0);
+
private String mName;
private boolean mVerbose = false;
@@ -74,6 +76,12 @@
System.out.println("Using latest Build Tools: " + buildToolInfo.getRevision());
}
+ if (buildToolInfo.getRevision().compareTo(MIN_BUILD_TOOLS_REV) < 0) {
+ throw new BuildException(String.format(
+ "The SDK Build Tools revision (%1$s) is too low for project '%2$s'. Minimum required is %3$s",
+ buildToolInfo.getRevision(), getProject().getName(), MIN_BUILD_TOOLS_REV));
+ }
+
antProject.setProperty(mName, buildToolInfo.getLocation().getAbsolutePath());
}
}
diff --git a/legacy/archquery/build.gradle b/legacy/archquery/build.gradle
index 816a01c..f38f236 100644
--- a/legacy/archquery/build.gradle
+++ b/legacy/archquery/build.gradle
@@ -1,10 +1,9 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools'
archivesBaseName = 'archquery'
+version = rootProject.ext.baseVersion
-// 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
+// configure the manifest of the sdkJar task.
+sdkJar.manifest.attributes("Main-Class": "com.android.archquery.Main")
diff --git a/lint/cli/build.gradle b/lint/cli/build.gradle
index 7407dde..21d993f 100644
--- a/lint/cli/build.gradle
+++ b/lint/cli/build.gradle
@@ -1,15 +1,17 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools.lint'
archivesBaseName = 'lint'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':lint-checks')
+ compile project(':base:lint-checks')
+ compile 'org.eclipse.jdt.core.compiler:ecj:4.2.2'
testCompile 'org.easymock:easymock:3.1'
testCompile 'junit:junit:3.8.1'
- testCompile project(':testutils')
+ testCompile project(':base:testutils')
}
sourceSets {
@@ -17,20 +19,23 @@
test.resources.srcDir 'src/test/java'
}
-jar {
- from 'NOTICE'
+sdk {
+ linux {
+ item('etc/lint') { executable true }
+ }
+ mac {
+ item('etc/lint') { executable true }
+ }
+ windows {
+ item 'etc/lint.bat'
+ }
}
-shipping {
- launcherScripts = ['etc/lint', 'etc/lint.bat']
-}
-
-// configure the manifest of the buildDistributionJar task.
-buildDistributionJar.manifest.attributes("Main-Class": "com.android.tools.lint.Main")
+// configure the manifest of the sdkJar task.
+sdkJar.manifest.attributes("Main-Class": "com.android.tools.lint.Main")
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'
-apply from: '../../baseVersion.gradle'
-apply from: '../../publish.gradle'
-apply from: '../../javadoc.gradle'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/lint/cli/lint-cli.iml b/lint/cli/lint-cli.iml
index ebbb3a1..8dfd6b8 100644
--- a/lint/cli/lint-cli.iml
+++ b/lint/cli/lint-cli.iml
@@ -15,6 +15,8 @@
<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="" />
+ <orderEntry type="library" name="easymock-tools" level="project" />
+ <orderEntry type="library" name="ecj" level="project" />
</component>
</module>
diff --git a/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java b/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java
new file mode 100644
index 0000000..3d61209
--- /dev/null
+++ b/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java
@@ -0,0 +1,1069 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.SdkConstants.UTF_8;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.IAndroidTarget;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.Compiler;
+import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
+import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
+import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
+import org.eclipse.jdt.internal.compiler.IProblemFactory;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.CharLiteral;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral;
+import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
+import org.eclipse.jdt.internal.compiler.ast.FloatLiteral;
+import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
+import org.eclipse.jdt.internal.compiler.ast.Literal;
+import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
+import org.eclipse.jdt.internal.compiler.ast.MagicLiteral;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.NameReference;
+import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
+import org.eclipse.jdt.internal.compiler.ast.NumberLiteral;
+import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
+import org.eclipse.jdt.internal.compiler.batch.FileSystem;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
+import org.eclipse.jdt.internal.compiler.impl.ByteConstant;
+import org.eclipse.jdt.internal.compiler.impl.CharConstant;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.DoubleConstant;
+import org.eclipse.jdt.internal.compiler.impl.FloatConstant;
+import org.eclipse.jdt.internal.compiler.impl.IntConstant;
+import org.eclipse.jdt.internal.compiler.impl.LongConstant;
+import org.eclipse.jdt.internal.compiler.impl.ShortConstant;
+import org.eclipse.jdt.internal.compiler.impl.StringConstant;
+import org.eclipse.jdt.internal.compiler.lookup.Binding;
+import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.eclipse.jdt.internal.compiler.parser.Parser;
+import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.ast.Node;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.ecj.EcjTreeConverter;
+
+/**
+ * Java parser which uses ECJ for parsing and type attribution
+ */
+public class EcjParser extends JavaParser {
+ private static final boolean DEBUG_DUMP_PARSE_ERRORS = false;
+
+ private final LintClient mClient;
+ private final Project mProject;
+ private Map<File, ICompilationUnit> mSourceUnits;
+ private Map<ICompilationUnit, CompilationUnitDeclaration> mCompiled;
+ private Parser mParser;
+
+ public EcjParser(@NonNull LintCliClient client, @Nullable Project project) {
+ mClient = client;
+ mProject = project;
+ mParser = getParser();
+ }
+
+ /**
+ * Create the default compiler options
+ */
+ public static CompilerOptions createCompilerOptions() {
+ CompilerOptions options = new CompilerOptions();
+
+ // Always using JDK 7 rather than basing it on project metadata since we
+ // don't do compilation error validation in lint (we leave that to the IDE's
+ // error parser or the command line build's compilation step); we want an
+ // AST that is as tolerant as possible.
+ long languageLevel = ClassFileConstants.JDK1_7;
+ options.complianceLevel = languageLevel;
+ options.sourceLevel = languageLevel;
+ options.targetJDK = languageLevel;
+ options.originalComplianceLevel = languageLevel;
+ options.originalSourceLevel = languageLevel;
+ options.inlineJsrBytecode = true; // >1.5
+
+ options.parseLiteralExpressionsAsConstants = true;
+ options.analyseResourceLeaks = false;
+ options.docCommentSupport = false;
+ options.defaultEncoding = UTF_8;
+ options.suppressOptionalErrors = true;
+ options.generateClassFiles = false;
+ options.isAnnotationBasedNullAnalysisEnabled = false;
+ options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false;
+ options.reportUnusedDeclaredThrownExceptionIncludeDocCommentReference = false;
+ options.reportUnusedDeclaredThrownExceptionWhenOverriding = false;
+ options.reportUnusedParameterIncludeDocCommentReference = false;
+ options.reportUnusedParameterWhenImplementingAbstract = false;
+ options.reportUnusedParameterWhenOverridingConcrete = false;
+ options.suppressWarnings = true;
+ options.processAnnotations = true;
+ options.verbose = false;
+ return options;
+ }
+
+ public static long getLanguageLevel(int major, int minor) {
+ assert major == 1;
+ switch (minor) {
+ case 5: return ClassFileConstants.JDK1_5;
+ case 6: return ClassFileConstants.JDK1_6;
+ case 7:
+ default:
+ return ClassFileConstants.JDK1_7;
+ }
+ }
+
+ private Parser getParser() {
+ if (mParser == null) {
+ CompilerOptions options = createCompilerOptions();
+ ProblemReporter problemReporter = new ProblemReporter(
+ DefaultErrorHandlingPolicies.exitOnFirstError(),
+ options,
+ new DefaultProblemFactory());
+ mParser = new Parser(problemReporter,
+ options.parseLiteralExpressionsAsConstants);
+ mParser.javadocParser.checkDocComment = false;
+ }
+ return mParser;
+ }
+
+ @Override
+ public void prepareJavaParse(@NonNull final List<JavaContext> contexts) {
+ if (mProject == null || contexts.isEmpty()) {
+ return;
+ }
+
+ List<ICompilationUnit> sources = Lists.newArrayListWithExpectedSize(contexts.size());
+ mSourceUnits = Maps.newHashMapWithExpectedSize(sources.size());
+ for (JavaContext context : contexts) {
+ String contents = context.getContents();
+ if (contents == null) {
+ continue;
+ }
+ File file = context.file;
+ CompilationUnit unit = new CompilationUnit(contents.toCharArray(), file.getPath(),
+ UTF_8);
+ sources.add(unit);
+ mSourceUnits.put(file, unit);
+ }
+ List<String> classPath = computeClassPath(contexts);
+ mCompiled = Maps.newHashMapWithExpectedSize(mSourceUnits.size());
+ try {
+ parse(createCompilerOptions(), sources, classPath, mCompiled, mClient);
+ } catch (Throwable t) {
+ mClient.log(t, "ECJ compiler crashed");
+ }
+
+ if (DEBUG_DUMP_PARSE_ERRORS) {
+ for (CompilationUnitDeclaration unit : mCompiled.values()) {
+ // so maybe I don't need my map!!
+ CategorizedProblem[] problems = unit.compilationResult()
+ .getAllProblems();
+ if (problems != null) {
+ for (IProblem problem : problems) {
+ if (problem == null || !problem.isError()) {
+ continue;
+ }
+ System.out.println(
+ new String(problem.getOriginatingFileName()) + ":"
+ + (problem.isError() ? "Error" : "Warning") + ": "
+ + problem.getSourceLineNumber() + ": " + problem.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ /** Parse the given source units and class path and store it into the given output map */
+ public static void parse(
+ CompilerOptions options,
+ @NonNull List<ICompilationUnit> sourceUnits,
+ @NonNull List<String> classPath,
+ @NonNull Map<ICompilationUnit, CompilationUnitDeclaration> outputMap,
+ @Nullable LintClient client) {
+ INameEnvironment environment = new FileSystem(
+ classPath.toArray(new String[classPath.size()]), new String[0],
+ options.defaultEncoding);
+ IErrorHandlingPolicy policy = DefaultErrorHandlingPolicies.proceedWithAllProblems();
+ IProblemFactory problemFactory = new DefaultProblemFactory(Locale.getDefault());
+ ICompilerRequestor requestor = new ICompilerRequestor() {
+ @Override
+ public void acceptResult(CompilationResult result) {
+ // Not used; we need the corresponding CompilationUnitDeclaration for the source
+ // units (the AST parsed from source) which we don't get access to here, so we
+ // instead subclass AST to get our hands on them.
+ }
+ };
+
+ NonGeneratingCompiler compiler = new NonGeneratingCompiler(environment, policy, options,
+ requestor, problemFactory, outputMap);
+ try {
+ compiler.compile(sourceUnits.toArray(new ICompilationUnit[sourceUnits.size()]));
+ } catch (Throwable t) {
+ if (client != null) {
+ CompilationUnitDeclaration currentUnit = compiler.getCurrentUnit();
+ if (currentUnit == null || currentUnit.getFileName() == null) {
+ client.log(t, "ECJ compiler crashed");
+ } else {
+ client.log(t, "ECJ compiler crashed processing %1$s",
+ new String(currentUnit.getFileName()));
+ }
+ } else {
+ t.printStackTrace();
+ }
+ }
+ }
+
+ @NonNull
+ private List<String> computeClassPath(@NonNull List<JavaContext> contexts) {
+ assert mProject != null;
+ List<String> classPath = Lists.newArrayList();
+
+ IAndroidTarget compileTarget = mProject.getBuildTarget();
+ if (compileTarget != null) {
+ String androidJar = compileTarget.getPath(IAndroidTarget.ANDROID_JAR);
+ if (androidJar != null) {
+ classPath.add(androidJar);
+ }
+ }
+
+ Set<File> libraries = Sets.newHashSet();
+ Set<String> names = Sets.newHashSet();
+ for (File library : mProject.getJavaLibraries()) {
+ libraries.add(library);
+ names.add(getLibraryName(library));
+ }
+ for (Project project : mProject.getAllLibraries()) {
+ for (File library : project.getJavaLibraries()) {
+ String name = getLibraryName(library);
+ // Avoid pulling in android-support-v4.jar from libraries etc
+ // since we're pointing to the local copies rather than the real
+ // maven/gradle source copies
+ if (!names.contains(name)) {
+ libraries.add(library);
+ names.add(name);
+ }
+ }
+ }
+
+ for (File file : libraries) {
+ classPath.add(file.getPath());
+ }
+
+ // In incremental mode we may need to point to other sources in the project
+ // for type resolution
+ EnumSet<Scope> scope = contexts.get(0).getScope();
+ if (!scope.contains(Scope.ALL_JAVA_FILES)) {
+ // May need other compiled classes too
+ for (File dir : mProject.getJavaClassFolders()) {
+ classPath.add(dir.getPath());
+ }
+ }
+
+ return classPath;
+ }
+
+ @NonNull
+ private static String getLibraryName(@NonNull File library) {
+ String name = library.getName();
+ if (name.equals(SdkConstants.FN_CLASSES_JAR)) {
+ // For AAR artifacts they'll all clash with "classes.jar"; include more unique
+ // context
+ String path = library.getPath();
+ int index = path.indexOf("exploded-aar");
+ if (index != -1) {
+ return path.substring(index);
+ } else {
+ index = path.indexOf("exploded-bundles");
+ if (index != -1) {
+ return path.substring(index);
+ }
+ }
+ File parent = library.getParentFile();
+ if (parent != null) {
+ return parent.getName() + File.separatorChar + name;
+ }
+ }
+ return name;
+ }
+
+ @Override
+ public Node parseJava(@NonNull JavaContext context) {
+ String code = context.getContents();
+ if (code == null) {
+ return null;
+ }
+
+ CompilationUnitDeclaration unit = getParsedUnit(context, code);
+ try {
+ EcjTreeConverter converter = new EcjTreeConverter();
+ converter.visit(code, unit);
+ List<? extends Node> nodes = converter.getAll();
+
+ if (nodes != null) {
+ // There could be more than one node when there are errors; pick out the
+ // compilation unit node
+ for (Node node : nodes) {
+ if (node instanceof lombok.ast.CompilationUnit) {
+ return node;
+ }
+ }
+ }
+
+ return null;
+ } catch (Throwable t) {
+ mClient.log(t, "Failed converting ECJ parse tree to Lombok for file %1$s",
+ context.file.getPath());
+ return null;
+ }
+ }
+
+ @Nullable
+ private CompilationUnitDeclaration getParsedUnit(
+ @NonNull JavaContext context,
+ @NonNull String code) {
+ ICompilationUnit sourceUnit = null;
+ if (mSourceUnits != null && mCompiled != null) {
+ sourceUnit = mSourceUnits.get(context.file);
+ if (sourceUnit != null) {
+ CompilationUnitDeclaration unit = mCompiled.get(sourceUnit);
+ if (unit != null) {
+ return unit;
+ }
+ }
+ }
+
+ if (sourceUnit == null) {
+ sourceUnit = new CompilationUnit(code.toCharArray(), context.file.getName(), UTF_8);
+ }
+ try {
+ CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 0);
+ return getParser().parse(sourceUnit, compilationResult);
+ } catch (AbortCompilation e) {
+ // No need to report Java parsing errors while running in Eclipse.
+ // Eclipse itself will already provide problem markers for these files,
+ // so all this achieves is creating "multiple annotations on this line"
+ // tooltips instead.
+ return null;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Location getLocation(@NonNull JavaContext context, @NonNull Node node) {
+ lombok.ast.Position position = node.getPosition();
+ return Location.create(context.file, context.getContents(),
+ position.getStart(), position.getEnd());
+ }
+
+ @NonNull
+ @Override
+ public
+ Location.Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node) {
+ return new LocationHandle(context.file, node);
+ }
+
+ @Override
+ public void dispose(@NonNull JavaContext context,
+ @NonNull Node compilationUnit) {
+ if (mSourceUnits != null && mCompiled != null) {
+ ICompilationUnit sourceUnit = mSourceUnits.get(context.file);
+ if (sourceUnit != null) {
+ mSourceUnits.remove(context.file);
+ mCompiled.remove(sourceUnit);
+ }
+ }
+ }
+
+ @Nullable
+ private static Object getNativeNode(@NonNull Node node) {
+ Object nativeNode = node.getNativeNode();
+ if (nativeNode != null) {
+ return nativeNode;
+ }
+
+ Node parent = node.getParent();
+ // The ECJ native nodes are sometimes spotty; for example, for a
+ // MethodInvocation node we can have a null native node, but its
+ // parent expression statement will point to the real MessageSend node
+ if (parent != null) {
+ nativeNode = parent.getNativeNode();
+ if (nativeNode != null) {
+ return nativeNode;
+ }
+ }
+
+ if (node instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration) node;
+ VariableDefinition definition = declaration.astDefinition();
+ if (definition != null) {
+ lombok.ast.TypeReference typeReference = definition.astTypeReference();
+ if (typeReference != null) {
+ return typeReference.getNativeNode();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node) {
+ Object nativeNode = getNativeNode(node);
+ if (nativeNode == null) {
+ return null;
+ }
+
+ if (nativeNode instanceof NameReference) {
+ return resolve(((NameReference) nativeNode).binding);
+ } else if (nativeNode instanceof TypeReference) {
+ return resolve(((TypeReference) nativeNode).resolvedType);
+ } else if (nativeNode instanceof MessageSend) {
+ return resolve(((MessageSend) nativeNode).binding);
+ } else if (nativeNode instanceof AllocationExpression) {
+ return resolve(((AllocationExpression) nativeNode).binding);
+ } else if (nativeNode instanceof TypeDeclaration) {
+ return resolve(((TypeDeclaration) nativeNode).binding);
+ } else if (nativeNode instanceof ExplicitConstructorCall) {
+ return resolve(((ExplicitConstructorCall) nativeNode).binding);
+ } else if (nativeNode instanceof Annotation) {
+ return resolve(((Annotation) nativeNode).resolvedType);
+ } else if (nativeNode instanceof AbstractMethodDeclaration) {
+ return resolve(((AbstractMethodDeclaration) nativeNode).binding);
+ }
+
+ // TODO: Handle org.eclipse.jdt.internal.compiler.ast.SuperReference. It
+ // doesn't contain an actual method binding; the parent node call should contain
+ // it, but is missing a native node reference; investigate the ECJ bridge's super
+ // handling.
+
+ return null;
+ }
+
+ private static ResolvedNode resolve(@Nullable Binding binding) {
+ if (binding == null || binding instanceof ProblemBinding) {
+ return null;
+ }
+
+ if (binding instanceof TypeBinding) {
+ TypeBinding tb = (TypeBinding) binding;
+ return new EcjResolvedClass(tb);
+ } else if (binding instanceof MethodBinding) {
+ MethodBinding mb = (MethodBinding) binding;
+ if (mb instanceof ProblemMethodBinding) {
+ return null;
+ }
+ //noinspection VariableNotUsedInsideIf
+ if (mb.declaringClass != null) {
+ return new EcjResolvedMethod(mb);
+ }
+ } else if (binding instanceof LocalVariableBinding) {
+ LocalVariableBinding lvb = (LocalVariableBinding) binding;
+ //noinspection VariableNotUsedInsideIf
+ if (lvb.type != null) {
+ return new EcjResolvedVariable(lvb);
+ }
+ } else if (binding instanceof FieldBinding) {
+ FieldBinding fb = (FieldBinding) binding;
+ if (fb instanceof ProblemFieldBinding) {
+ return null;
+ }
+ if (fb.type != null && fb.declaringClass != null) {
+ return new EcjResolvedField(fb);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node) {
+ Object nativeNode = getNativeNode(node);
+ if (nativeNode == null) {
+ return null;
+ }
+
+ if (nativeNode instanceof MessageSend) {
+ nativeNode = ((MessageSend)nativeNode).binding;
+ } else if (nativeNode instanceof AllocationExpression) {
+ nativeNode = ((AllocationExpression)nativeNode).resolvedType;
+ } else if (nativeNode instanceof NameReference) {
+ nativeNode = ((NameReference)nativeNode).resolvedType;
+ } else if (nativeNode instanceof Expression) {
+ if (nativeNode instanceof Literal) {
+ if (nativeNode instanceof StringLiteral) {
+ return getTypeDescriptor(TYPE_STRING);
+ } else if (nativeNode instanceof NumberLiteral) {
+ if (nativeNode instanceof IntLiteral) {
+ return getTypeDescriptor(TYPE_INT);
+ } else if (nativeNode instanceof LongLiteral) {
+ return getTypeDescriptor(TYPE_LONG);
+ } else if (nativeNode instanceof CharLiteral) {
+ return getTypeDescriptor(TYPE_CHAR);
+ } else if (nativeNode instanceof FloatLiteral) {
+ return getTypeDescriptor(TYPE_FLOAT);
+ } else if (nativeNode instanceof DoubleLiteral) {
+ return getTypeDescriptor(TYPE_DOUBLE);
+ }
+ } else if (nativeNode instanceof MagicLiteral) {
+ if (nativeNode instanceof TrueLiteral || nativeNode instanceof FalseLiteral) {
+ return getTypeDescriptor(TYPE_BOOLEAN);
+ } else if (nativeNode instanceof NullLiteral) {
+ return getTypeDescriptor(TYPE_NULL);
+ }
+ }
+ }
+ nativeNode = ((Expression)nativeNode).resolvedType;
+ } else if (nativeNode instanceof TypeDeclaration) {
+ nativeNode = ((TypeDeclaration) nativeNode).binding;
+ } else if (nativeNode instanceof AbstractMethodDeclaration) {
+ nativeNode = ((AbstractMethodDeclaration) nativeNode).binding;
+ }
+
+ if (nativeNode instanceof Binding) {
+ Binding binding = (Binding) nativeNode;
+ if (binding instanceof TypeBinding) {
+ TypeBinding tb = (TypeBinding) binding;
+ return getTypeDescriptor(tb);
+ } else if (binding instanceof LocalVariableBinding) {
+ LocalVariableBinding lvb = (LocalVariableBinding) binding;
+ if (lvb.type != null) {
+ return getTypeDescriptor(lvb.type);
+ }
+ } else if (binding instanceof FieldBinding) {
+ FieldBinding fb = (FieldBinding) binding;
+ if (fb.type != null) {
+ return getTypeDescriptor(fb.type);
+ }
+ } else if (binding instanceof MethodBinding) {
+ return getTypeDescriptor(((MethodBinding) binding).returnType);
+ } else if (binding instanceof ProblemBinding) {
+ // Unresolved type. We just don't know.
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static TypeDescriptor getTypeDescriptor(@Nullable TypeBinding resolvedType) {
+ if (resolvedType == null) {
+ return null;
+ }
+ return new EcjTypeDescriptor(resolvedType.readableName());
+ }
+
+ private static TypeDescriptor getTypeDescriptor(String fqn) {
+ return new DefaultTypeDescriptor(fqn);
+ }
+
+ /* Handle for creating positions cheaply and returning full fledged locations later */
+ private static class LocationHandle implements Location.Handle {
+ private File mFile;
+ private Node mNode;
+ private Object mClientData;
+
+ public LocationHandle(File file, Node node) {
+ mFile = file;
+ mNode = node;
+ }
+
+ @NonNull
+ @Override
+ public Location resolve() {
+ lombok.ast.Position pos = mNode.getPosition();
+ return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd());
+ }
+
+ @Override
+ public void setClientData(@Nullable Object clientData) {
+ mClientData = clientData;
+ }
+
+ @Override
+ @Nullable
+ public Object getClientData() {
+ return mClientData;
+ }
+ }
+
+ // Custom version of the compiler which skips code generation and records source units
+ private static class NonGeneratingCompiler extends Compiler {
+ private Map<ICompilationUnit, CompilationUnitDeclaration> mUnits;
+ private CompilationUnitDeclaration mCurrentUnit;
+
+ public NonGeneratingCompiler(INameEnvironment environment, IErrorHandlingPolicy policy,
+ CompilerOptions options, ICompilerRequestor requestor,
+ IProblemFactory problemFactory,
+ Map<ICompilationUnit, CompilationUnitDeclaration> units) {
+ super(environment, policy, options, requestor, problemFactory, null, null);
+ mUnits = units;
+ }
+
+ @Nullable
+ CompilationUnitDeclaration getCurrentUnit() {
+ // Can't use lookupEnvironment.unitBeingCompleted directly; it gets nulled out
+ // as part of the exception catch handling in the compiler before this method
+ // is called from lint -- therefore we stash a copy in our own mCurrentUnit field
+ return mCurrentUnit;
+ }
+
+ @Override
+ protected synchronized void addCompilationUnit(ICompilationUnit sourceUnit,
+ CompilationUnitDeclaration parsedUnit) {
+ super.addCompilationUnit(sourceUnit, parsedUnit);
+ mUnits.put(sourceUnit, parsedUnit);
+ }
+
+ @Override
+ public void process(CompilationUnitDeclaration unit, int unitNumber) {
+ mCurrentUnit = lookupEnvironment.unitBeingCompleted = unit;
+
+ parser.getMethodBodies(unit);
+ if (unit.scope != null) {
+ unit.scope.faultInTypes();
+ unit.scope.verifyMethods(lookupEnvironment.methodVerifier());
+ }
+ unit.resolve();
+ unit.analyseCode();
+
+ // This is where we differ from super: DON'T call generateCode().
+ // Sadly we can't just set ignoreMethodBodies=true to have the same effect,
+ // since that would also skip the analyseCode call, which we DO, want:
+ // unit.generateCode();
+
+ if (options.produceReferenceInfo && unit.scope != null) {
+ unit.scope.storeDependencyInfo();
+ }
+ unit.finalizeProblems();
+ unit.compilationResult.totalUnitsKnown = totalUnits;
+ lookupEnvironment.unitBeingCompleted = null;
+ }
+ }
+
+ private static class EcjTypeDescriptor extends TypeDescriptor {
+ private String mName;
+ private char[] mChars;
+
+ private EcjTypeDescriptor(char[] chars) {
+ mChars = chars;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ if (mName == null) {
+ mName = new String(mChars);
+ }
+ return mName;
+ }
+
+ @NonNull
+ @Override
+ public String getSignature() {
+ return getName();
+ }
+
+ @Override
+ public boolean matchesName(@NonNull String name) {
+ return sameChars(name, mChars);
+ }
+
+ @Override
+ public boolean matchesSignature(@NonNull String signature) {
+ return matchesName(signature);
+ }
+
+ @Override
+ public String toString() {
+ return getSignature();
+ }
+ }
+
+ private static class EcjResolvedMethod extends ResolvedMethod {
+ private MethodBinding mBinding;
+
+ private EcjResolvedMethod(MethodBinding binding) {
+ mBinding = binding;
+ assert mBinding.declaringClass != null;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ char[] c = isConstructor() ? mBinding.declaringClass.readableName() : mBinding.selector;
+ return new String(c);
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ char[] c = isConstructor() ? mBinding.declaringClass.readableName() : mBinding.selector;
+ return sameChars(name, c);
+ }
+
+ @NonNull
+ @Override
+ public ResolvedClass getContainingClass() {
+ return new EcjResolvedClass(mBinding.declaringClass);
+ }
+
+ @Override
+ public int getArgumentCount() {
+ return mBinding.parameters != null ? mBinding.parameters.length : 0;
+ }
+
+ @NonNull
+ @Override
+ public TypeDescriptor getArgumentType(int index) {
+ TypeBinding parameterType = mBinding.parameters[index];
+ TypeDescriptor typeDescriptor = getTypeDescriptor(parameterType);
+ assert typeDescriptor != null; // because parameter is not null
+ return typeDescriptor;
+ }
+
+ @Nullable
+ @Override
+ public TypeDescriptor getReturnType() {
+ return isConstructor() ? null : getTypeDescriptor(mBinding.returnType);
+ }
+
+ @Override
+ public boolean isConstructor() {
+ return mBinding.isConstructor();
+ }
+
+ @Override
+ public int getModifiers() {
+ return mBinding.getAccessFlags();
+ }
+
+ @Override
+ public String getSignature() {
+ return mBinding.toString();
+ }
+ }
+
+ private static class EcjResolvedClass extends ResolvedClass {
+ private TypeBinding mBinding;
+
+ private EcjResolvedClass(TypeBinding binding) {
+ mBinding = binding;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return new String(mBinding.readableName());
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ return sameChars(name, mBinding.readableName());
+ }
+
+ @Nullable
+ @Override
+ public ResolvedClass getSuperClass() {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding refBinding = (ReferenceBinding) mBinding;
+ ReferenceBinding superClass = refBinding.superclass();
+ if (superClass != null) {
+ return new EcjResolvedClass(superClass);
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ResolvedClass getContainingClass() {
+ if (mBinding instanceof NestedTypeBinding) {
+ NestedTypeBinding ntb = (NestedTypeBinding) mBinding;
+ if (ntb.enclosingType != null) {
+ return new EcjResolvedClass(ntb.enclosingType);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isSubclassOf(@NonNull String name, boolean strict) {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ if (strict) {
+ cls = cls.superclass();
+ }
+ for (; cls != null; cls = cls.superclass()) {
+ if (sameChars(name, cls.readableName())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ @NonNull
+ public Iterable<ResolvedMethod> getConstructors() {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ MethodBinding[] methods = cls.getMethods(TypeConstants.INIT);
+ if (methods != null) {
+ int count = methods.length;
+ List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(count);
+ for (MethodBinding method : methods) {
+ if (method.isConstructor()) {
+ result.add(new EcjResolvedMethod(method));
+ }
+ }
+ return result;
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ @Override
+ @NonNull
+ public Iterable<ResolvedMethod> getMethods(@NonNull String name) {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ MethodBinding[] methods = cls.getMethods(name.toCharArray());
+ if (methods != null) {
+ int count = methods.length;
+ List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(count);
+ for (MethodBinding method : methods) {
+ if (!method.isConstructor()) {
+ result.add(new EcjResolvedMethod(method));
+ }
+ }
+ return result;
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ @Override
+ @Nullable
+ public ResolvedField getField(@NonNull String name) {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ FieldBinding[] fields = cls.fields();
+ if (fields != null) {
+ for (FieldBinding field : fields) {
+ if (sameChars(name, field.name)) {
+ return new EcjResolvedField(field);
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getModifiers() {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ // These constants from ClassFileConstants luckily agree with the Modifier
+ // constants in the low bits we care about (public, abstract, static, etc)
+ return cls.getAccessFlags();
+ }
+ return 0;
+ }
+
+ @Override
+ public String getSignature() {
+ return getName();
+ }
+ }
+
+ private static class EcjResolvedField extends ResolvedField {
+ private FieldBinding mBinding;
+
+ private EcjResolvedField(FieldBinding binding) {
+ mBinding = binding;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return new String(mBinding.readableName());
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ return sameChars(name, mBinding.readableName());
+ }
+
+ @NonNull
+ @Override
+ public TypeDescriptor getType() {
+ TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.type);
+ assert typeDescriptor != null; // because mBinding.type is known not to be null
+ return typeDescriptor;
+ }
+
+ @NonNull
+ @Override
+ public ResolvedClass getContainingClass() {
+ return new EcjResolvedClass(mBinding.declaringClass);
+ }
+
+ @Nullable
+ @Override
+ public Object getValue() {
+ Constant constant = mBinding.constant();
+ if (constant != null) {
+ if (constant instanceof StringConstant) {
+ return constant.stringValue();
+ } else if (constant instanceof IntConstant) {
+ return constant.intValue();
+ } else if (constant instanceof BooleanConstant) {
+ return constant.booleanValue();
+ } else if (constant instanceof LongConstant) {
+ return constant.longValue();
+ } else if (constant instanceof DoubleConstant) {
+ return constant.doubleValue();
+ } else if (constant instanceof CharConstant) {
+ return constant.charValue();
+ } else if (constant instanceof FloatConstant) {
+ return constant.floatValue();
+ } else if (constant instanceof ShortConstant) {
+ return constant.shortValue();
+ } else if (constant instanceof ByteConstant) {
+ return constant.byteValue();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int getModifiers() {
+ return mBinding.getAccessFlags();
+ }
+
+ @Override
+ public String getSignature() {
+ return mBinding.toString();
+ }
+ }
+
+ private static class EcjResolvedVariable extends ResolvedVariable {
+ private LocalVariableBinding mBinding;
+
+ private EcjResolvedVariable(LocalVariableBinding binding) {
+ mBinding = binding;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return new String(mBinding.readableName());
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ return sameChars(name, mBinding.readableName());
+ }
+
+ @NonNull
+ @Override
+ public TypeDescriptor getType() {
+ TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.type);
+ assert typeDescriptor != null; // because mBinding.type is known not to be null
+ return typeDescriptor;
+ }
+
+ @Override
+ public int getModifiers() {
+ return mBinding.modifiers;
+ }
+
+ @Override
+ public String getSignature() {
+ return mBinding.toString();
+ }
+ }
+
+ private static boolean sameChars(String str, char[] chars) {
+ int length = str.length();
+ if (chars.length != length) {
+ return false;
+ }
+
+ for (int i = 0; i < length; i++) {
+ if (chars[i] != str.charAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
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 100796f..9610888 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
@@ -29,6 +29,7 @@
import com.android.tools.lint.detector.api.Position;
import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.SdkUtils;
import com.google.common.annotations.Beta;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
@@ -97,9 +98,10 @@
Map<Issue, String> missing = computeMissingIssues(issues);
mWriter.write(
- "<html>\n" + //$NON-NLS-1$
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + //$NON-NLS-1$
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" + //$NON-NLS-1$
"<head>\n" + //$NON-NLS-1$
- "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>" + //$NON-NLS-1$
+ "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />" + //$NON-NLS-1$
"<title>" + mTitle + "</title>\n"); //$NON-NLS-1$//$NON-NLS-2$
writeStyleSheet();
@@ -107,7 +109,7 @@
if (!mSimpleFormat) {
// JavaScript for collapsing/expanding long lists
mWriter.write(
- "<script language=\"javascript\"> \n" + //$NON-NLS-1$
+ "<script language=\"javascript\" type=\"text/javascript\"> \n" + //$NON-NLS-1$
"<!--\n" + //$NON-NLS-1$
"function reveal(id) {\n" + //$NON-NLS-1$
"if (document.getElementById) {\n" + //$NON-NLS-1$
@@ -124,15 +126,15 @@
"<body>\n" + //$NON-NLS-1$
"<h1>" + //$NON-NLS-1$
mTitle +
- "<div class=\"titleSeparator\"></div>\n" + //$NON-NLS-1$
- "</h1>\n"); //$NON-NLS-1$
+ "</h1>\n" + //$NON-NLS-1$
+ "<div class=\"titleSeparator\"></div>\n"); //$NON-NLS-1$
mWriter.write(String.format("Check performed at %1$s.",
new Date().toString()));
- mWriter.write("<br/>"); //$NON-NLS-1$
+ mWriter.write("<br/>\n"); //$NON-NLS-1$
mWriter.write(String.format("%1$d errors and %2$d warnings found:",
errorCount, warningCount));
- mWriter.write("<br/><br/>"); //$NON-NLS-1$
+ mWriter.write("<br/><br/>\n"); //$NON-NLS-1$
Issue previousIssue = null;
if (!issues.isEmpty()) {
@@ -264,7 +266,6 @@
}
mWriter.write("</ul>");
if (otherLocations > 0) {
-
String id = "Location" + count + "Div"; //$NON-NLS-1$
mWriter.write("<button id=\""); //$NON-NLS-1$
mWriter.write(id);
@@ -318,6 +319,8 @@
mWriter.write("</div>\n"); //$NON-NLS-1$
writeIssueMetadata(issue, first.severity, null);
+
+ mWriter.write("</div>\n"); //$NON-NLS-1$
}
if (!mClient.isCheckingSpecificIssues()) {
@@ -331,8 +334,10 @@
mWriter.write("\n</body>\n</html>"); //$NON-NLS-1$
mWriter.close();
- String path = mOutput.getAbsolutePath();
- System.out.println(String.format("Wrote HTML report to %1$s", path));
+ if (mDisplayEmpty || errorCount > 0 || warningCount > 0) {
+ String url = SdkUtils.fileToUrlString(mOutput.getAbsoluteFile());
+ System.out.println(String.format("Wrote HTML report to %1$s", url));
+ }
}
private void writeIssueMetadata(Issue issue, Severity severity, String disabledBy)
@@ -343,7 +348,7 @@
((BuiltinIssueRegistry) mClient.getRegistry()).hasAutoFix("adt", issue)) { //$NON-NLS-1$
mWriter.write("Note: This issue has an associated quickfix operation in Eclipse/ADT");
if (mFixUrl != null) {
- mWriter.write(" <img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
+ mWriter.write(" <img alt=\"Fix\" border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
mWriter.write(mFixUrl);
mWriter.write("\" />\n"); //$NON-NLS-1$
}
@@ -387,28 +392,27 @@
mWriter.write(explanationHtml);
mWriter.write("\n</div>\n"); //$NON-NLS-1$;
List<String> moreInfo = issue.getMoreInfo();
- if (moreInfo != null) {
- mWriter.write("<br/>"); //$NON-NLS-1$
- mWriter.write("<div class=\"moreinfo\">"); //$NON-NLS-1$
- mWriter.write("More info: ");
- int count = moreInfo.size();
- if (count > 1) {
- mWriter.write("<ul>"); //$NON-NLS-1$
- }
- for (String uri : moreInfo) {
- if (count > 1) {
- mWriter.write("<li>"); //$NON-NLS-1$
- }
- mWriter.write("<a href=\""); //$NON-NLS-1$
- mWriter.write(uri);
- mWriter.write("\">"); //$NON-NLS-1$
- mWriter.write(uri);
- mWriter.write("</a></div>\n"); //$NON-NLS-1$
- }
- if (count > 1) {
- mWriter.write("</ul>"); //$NON-NLS-1$
- }
+ mWriter.write("<br/>"); //$NON-NLS-1$
+ mWriter.write("<div class=\"moreinfo\">"); //$NON-NLS-1$
+ mWriter.write("More info: ");
+ int count = moreInfo.size();
+ if (count > 1) {
+ mWriter.write("<ul>"); //$NON-NLS-1$
}
+ for (String uri : moreInfo) {
+ if (count > 1) {
+ mWriter.write("<li>"); //$NON-NLS-1$
+ }
+ mWriter.write("<a href=\""); //$NON-NLS-1$
+ mWriter.write(uri);
+ mWriter.write("\">" ); //$NON-NLS-1$
+ mWriter.write(uri);
+ mWriter.write("</a>\n"); //$NON-NLS-1$
+ }
+ if (count > 1) {
+ mWriter.write("</ul>"); //$NON-NLS-1$
+ }
+ mWriter.write("</div>"); //$NON-NLS-1$
mWriter.write("<br/>"); //$NON-NLS-1$
mWriter.write(String.format(
@@ -417,8 +421,6 @@
issue.getId(),
"<a href=\"#SuppressInfo\">", "</a>")); //$NON-NLS-1$ //$NON-NLS-2$
mWriter.write("<br/>\n");
-
- mWriter.write("</div>"); //$NON-NLS-1$
}
private void writeSuppressInfo() throws IOException {
@@ -472,11 +474,11 @@
}
private void writeMissingIssues(Map<Issue, String> missing) throws IOException {
- mWriter.write("\n<a name=\"MissingIssues\"></a>\n"); //$NON-NLS-1$
- mWriter.write("<div class=\"category\">"); //$NON-NLS-1$
+ mWriter.write("\n<a name=\"MissingIssues\"></a>\n"); //$NON-NLS-1$
+ mWriter.write("<div class=\"category\">"); //$NON-NLS-1$
mWriter.write("Disabled Checks");
- mWriter.write("<div class=\"categorySeparator\"></div>\n");//$NON-NLS-1$
- mWriter.write("</div>\n");//$NON-NLS-1$
+ mWriter.write("<div class=\"categorySeparator\"></div>\n"); //$NON-NLS-1$
+ mWriter.write("</div>\n"); //$NON-NLS-1$
mWriter.write(
"The following issues were not run by lint, either " +
@@ -491,15 +493,16 @@
for (Issue issue : list) {
mWriter.write("<a name=\"" + issue.getId() + "\"></a>\n"); //$NON-NLS-1$ //$NON-NLS-2$
- mWriter.write("<div class=\"issue\">\n"); //$NON-NLS-1$
+ mWriter.write("<div class=\"issue\">\n"); //$NON-NLS-1$
// Explain this issue
- mWriter.write("<div class=\"id\">"); //$NON-NLS-1$
+ mWriter.write("<div class=\"id\">"); //$NON-NLS-1$
mWriter.write(issue.getId());
- mWriter.write("<div class=\"issueSeparator\"></div>\n"); //$NON-NLS-1$
- mWriter.write("</div>\n"); //$NON-NLS-1$
+ mWriter.write("<div class=\"issueSeparator\"></div>\n"); //$NON-NLS-1$
+ mWriter.write("</div>\n"); //$NON-NLS-1$
String disabledBy = missing.get(issue);
writeIssueMetadata(issue, issue.getDefaultSeverity(), disabledBy);
+ mWriter.write("</div>\n"); //$NON-NLS-1$
}
}
@@ -507,17 +510,20 @@
if (USE_HOLO_STYLE) {
mWriter.write(
"<link rel=\"stylesheet\" type=\"text/css\" " + //$NON-NLS-1$
- "href=\"http://fonts.googleapis.com/css?family=Roboto\">" );//$NON-NLS-1$
+ "href=\"http://fonts.googleapis.com/css?family=Roboto\" />\n" );//$NON-NLS-1$
}
URL cssUrl = HtmlReporter.class.getResource(CSS);
if (mSimpleFormat) {
// Inline the CSS
mWriter.write("<style>\n"); //$NON-NLS-1$
- @SuppressWarnings("resource") // Eclipse doesn't know about Closeables.closeQuietly
InputStream input = cssUrl.openStream();
byte[] bytes = ByteStreams.toByteArray(input);
- Closeables.closeQuietly(input);
+ try {
+ Closeables.close(input, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
String css = new String(bytes, Charsets.UTF_8);
mWriter.write(css);
mWriter.write("</style>\n"); //$NON-NLS-1$
@@ -526,7 +532,7 @@
if (ref != null) {
mWriter.write(
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" //$NON-NLS-1$
- + ref + "\">\n"); //$NON-NLS-1$
+ + ref + "\" />\n"); //$NON-NLS-1$
}
}
}
@@ -581,6 +587,8 @@
if (imageUrl != null) {
mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
mWriter.write(imageUrl);
+ mWriter.write("\" alt=\"");
+ mWriter.write(isError ? "Error" : "Warning");
mWriter.write("\" />\n"); //$NON-NLS-1$
}
@@ -620,7 +628,12 @@
mWriter.write(url);
mWriter.write("\">"); //$NON-NLS-1$
}
- mWriter.write(stripPath(path));
+
+ String displayPath = stripPath(path);
+ if (url != null && url.startsWith("../") && new File(displayPath).isAbsolute()) {
+ displayPath = url;
+ }
+ mWriter.write(displayPath);
if (url != null) {
mWriter.write("</a>"); //$NON-NLS-1$
}
@@ -754,7 +767,7 @@
}
}
- private void appendEscapedText(String textValue) throws IOException {
+ protected void appendEscapedText(String textValue) throws IOException {
for (int i = 0, n = textValue.length(); i < n; i++) {
char c = textValue.charAt(i);
if (c == '<') {
@@ -762,7 +775,7 @@
} else if (c == '&') {
mWriter.write("&"); //$NON-NLS-1$
} else if (c == '\n') {
- mWriter.write("<br/>");
+ mWriter.write("<br/>\n");
} else {
if (c > 255) {
mWriter.write("&#"); //$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 ae004a2..2759ea9 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
@@ -26,8 +26,8 @@
import com.android.annotations.VisibleForTesting;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.DefaultConfiguration;
-import com.android.tools.lint.client.api.IDomParser;
-import com.android.tools.lint.client.api.IJavaParser;
+import com.android.tools.lint.client.api.XmlParser;
+import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.client.api.LintDriver;
@@ -43,6 +43,7 @@
import com.google.common.annotations.Beta;
import com.google.common.base.Splitter;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import com.google.common.io.Closeables;
import java.io.File;
@@ -116,8 +117,17 @@
Collections.sort(mWarnings);
+ boolean hasConsoleOutput = false;
for (Reporter reporter : mFlags.getReporters()) {
reporter.write(mErrorCount, mWarningCount, mWarnings);
+ if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
+ hasConsoleOutput = true;
+ }
+ }
+
+ if (!mFlags.isQuiet() && !hasConsoleOutput) {
+ System.out.println(String.format(
+ "Lint found %1$d errors and %2$d warnings", mErrorCount, mWarningCount));
}
return mFlags.isSetExitCode() ? (mHasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;
@@ -150,13 +160,13 @@
}
@Override
- public IDomParser getDomParser() {
+ public XmlParser getXmlParser() {
return new LintCliXmlParser();
}
@Override
public Configuration getConfiguration(@NonNull Project project) {
- return new CliConfiguration(getConfiguration(), project);
+ return new CliConfiguration(getConfiguration(), project, mFlags.isFatalOnly());
}
/** File content cache */
@@ -174,8 +184,8 @@
}
@Override
- public IJavaParser getJavaParser() {
- return new LombokParser();
+ public JavaParser getJavaParser(@Nullable Project project) {
+ return new EcjParser(this, project);
}
@Override
@@ -374,12 +384,17 @@
* flags supplied on the command line
*/
class CliConfiguration extends DefaultConfiguration {
- CliConfiguration(@NonNull Configuration parent, @NonNull Project project) {
+ private boolean mFatalOnly;
+
+ CliConfiguration(@NonNull Configuration parent, @NonNull Project project,
+ boolean fatalOnly) {
super(LintCliClient.this, project, parent);
+ mFatalOnly = fatalOnly;
}
- CliConfiguration(File lintFile) {
+ CliConfiguration(File lintFile, boolean fatalOnly) {
super(LintCliClient.this, null /*project*/, null /*parent*/, lintFile);
+ mFatalOnly = fatalOnly;
}
@NonNull
@@ -387,7 +402,11 @@
public Severity getSeverity(@NonNull Issue issue) {
Severity severity = computeSeverity(issue);
- if (mFlags.isWarningsAsErrors() && severity != Severity.IGNORE) {
+ if (mFatalOnly && severity != Severity.FATAL) {
+ return Severity.IGNORE;
+ }
+
+ if (mFlags.isWarningsAsErrors() && severity == Severity.WARNING) {
severity = Severity.ERROR;
}
@@ -417,6 +436,11 @@
return Severity.IGNORE;
}
+ Severity manual = mFlags.getSeverityOverrides().get(id);
+ if (manual != null) {
+ return manual;
+ }
+
Set<String> enabled = mFlags.getEnabledIds();
Set<String> check = mFlags.getExactCheckedIds();
if (enabled.contains(id) || (check != null && check.contains(id))) {
@@ -576,14 +600,22 @@
return mDriver;
}
+ private static Set<File> sAlreadyWarned;
+
/** Returns the configuration used by this client */
Configuration getConfiguration() {
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);
+ if (sAlreadyWarned == null || !sAlreadyWarned.contains(configFile)) {
+ log(Severity.ERROR, null,
+ "Warning: Configuration file %1$s does not exist", configFile);
+ }
+ if (sAlreadyWarned == null) {
+ sAlreadyWarned = Sets.newHashSet();
+ }
+ sAlreadyWarned.add(configFile);
}
mConfiguration = createConfigurationFromFile(configFile);
}
@@ -598,10 +630,9 @@
}
public Configuration createConfigurationFromFile(File file) {
- return new CliConfiguration(file);
+ return new CliConfiguration(file, mFlags.isFatalOnly());
}
- @SuppressWarnings("resource") // Eclipse doesn't know about Closeables.closeQuietly
@Nullable
String getRevision() {
File file = findResource("tools" + File.separator + //$NON-NLS-1$
@@ -620,7 +651,11 @@
} catch (IOException e) {
// Couldn't find or read the version info: just print out unknown below
} finally {
- Closeables.closeQuietly(input);
+ try {
+ Closeables.close(input, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
}
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 caf74ce..0dcae04 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
@@ -18,15 +18,17 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Severity;
import com.google.common.annotations.Beta;
import com.google.common.collect.Lists;
import java.io.File;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -39,6 +41,7 @@
public class LintCliFlags {
private final Set<String> mSuppress = new HashSet<String>();
private final Set<String> mEnabled = new HashSet<String>();
+ private Map<String,Severity> mSeverities;
private Set<String> mCheck = null;
private boolean mSetExitCode;
private boolean mFullPath;
@@ -48,6 +51,8 @@
private boolean mWarnAll;
private boolean mNoWarnings;
private boolean mAllErrors;
+ private boolean mFatalOnly;
+ private boolean mExplainIssues;
private List<File> mSources;
private List<File> mClasses;
private List<File> mLibraries;
@@ -82,6 +87,15 @@
}
/**
+ * Returns a map of manually configured severities to use
+ * @return the severity to use for a given issue id
+ */
+ @NonNull
+ public Map<String,Severity> getSeverityOverrides() {
+ return mSeverities == null ? Collections.<String,Severity>emptyMap() : mSeverities;
+ }
+
+ /**
* 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 #getEnabledIds} and without issues disabled
* via {@link #getSuppressedIds}. If non-null, callers are allowed to modify this collection.
@@ -337,4 +351,48 @@
public void setResourcesOverride(@Nullable List<File> resources) {
mResources = resources;
}
+
+ /**
+ * Returns true if we should only check fatal issues
+ * @return true if we should only check fatal issues
+ */
+ public boolean isFatalOnly() {
+ return mFatalOnly;
+ }
+
+ /**
+ * Sets whether we should only check fatal issues
+ * @param fatalOnly if true, only check fatal issues
+ */
+ public void setFatalOnly(boolean fatalOnly) {
+ mFatalOnly = fatalOnly;
+ }
+
+ /**
+ * Sets a map of severities to use
+ * @param severities map from issue id to severity
+ */
+ public void setSeverityOverrides(@NonNull Map<String, Severity> severities) {
+ mSeverities = severities;
+ }
+
+ /**
+ * Whether text reports should include full explanation texts. (HTML and XML reports always
+ * do, unconditionally.)
+ *
+ * @return true if text reports should include explanation text
+ */
+ public boolean isExplainIssues() {
+ return mExplainIssues;
+ }
+
+ /**
+ * Sets whether text reports should include full explanation texts. (HTML and XML reports
+ * always do, unconditionally.)
+ *
+ * @param explainText true if text reports should include explanation text
+ */
+ public void setExplainIssues(boolean explainText) {
+ mExplainIssues = explainText;
+ }
}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/LintCliXmlParser.java b/lint/cli/src/main/java/com/android/tools/lint/LintCliXmlParser.java
index bfbe7cb..e5aa27a 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/LintCliXmlParser.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/LintCliXmlParser.java
@@ -18,7 +18,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.IDomParser;
+import com.android.tools.lint.client.api.XmlParser;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Location.Handle;
@@ -39,7 +39,15 @@
* <p>
* It also catches and reports parser errors as lint errors.
*/
-public class LintCliXmlParser extends PositionXmlParser implements IDomParser {
+public class LintCliXmlParser extends XmlParser {
+ private final PositionXmlParser mParser = new PositionXmlParser() {
+ @NonNull
+ @Override
+ protected OffsetPosition createPosition(int line, int column, int offset) {
+ return new OffsetPosition(line, column, offset);
+ }
+ };
+
@Override
public Document parseXml(@NonNull XmlContext context) {
String xml = null;
@@ -47,7 +55,7 @@
// Do we need to provide an input stream for encoding?
xml = context.getContents();
if (xml != null) {
- return super.parse(xml);
+ return mParser.parse(xml);
}
} catch (UnsupportedEncodingException e) {
context.report(
@@ -85,7 +93,7 @@
@NonNull
@Override
public Location getLocation(@NonNull XmlContext context, @NonNull Node node) {
- OffsetPosition pos = (OffsetPosition) getPosition(node, -1, -1);
+ OffsetPosition pos = (OffsetPosition) mParser.getPosition(node, -1, -1);
if (pos != null) {
return Location.create(context.file, pos, (OffsetPosition) pos.getEnd());
}
@@ -97,7 +105,7 @@
@Override
public Location getLocation(@NonNull XmlContext context, @NonNull Node node,
int start, int end) {
- OffsetPosition pos = (OffsetPosition) getPosition(node, start, end);
+ OffsetPosition pos = (OffsetPosition) mParser.getPosition(node, start, end);
if (pos != null) {
return Location.create(context.file, pos, (OffsetPosition) pos.getEnd());
}
@@ -111,12 +119,6 @@
return new LocationHandle(context.file, node);
}
- @NonNull
- @Override
- protected OffsetPosition createPosition(int line, int column, int offset) {
- return new OffsetPosition(line, column, offset);
- }
-
private static class OffsetPosition extends com.android.tools.lint.detector.api.Position
implements PositionXmlParser.Position {
/** The line number (0-based where the first line is line 0) */
@@ -183,7 +185,26 @@
}
@Override
- public void dispose(@NonNull XmlContext context, @NonNull Document document) {
+ public int getNodeStartOffset(@NonNull XmlContext context, @NonNull Node node) {
+ OffsetPosition pos = (OffsetPosition) mParser.getPosition(node, -1, -1);
+ if (pos != null) {
+ return pos.getOffset();
+ }
+
+ return -1;
+ }
+
+ @Override
+ public int getNodeEndOffset(@NonNull XmlContext context, @NonNull Node node) {
+ OffsetPosition pos = (OffsetPosition) mParser.getPosition(node, -1, -1);
+ if (pos != null) {
+ PositionXmlParser.Position end = pos.getEnd();
+ if (end != null) {
+ return end.getOffset();
+ }
+ }
+
+ return -1;
}
/* Handle for creating DOM positions cheaply and returning full fledged locations later */
@@ -200,7 +221,7 @@
@NonNull
@Override
public Location resolve() {
- OffsetPosition pos = (OffsetPosition) getPosition(mNode);
+ OffsetPosition pos = (OffsetPosition) mParser.getPosition(mNode);
if (pos != null) {
return Location.create(mFile, pos, (OffsetPosition) pos.getEnd());
}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/LombokParser.java b/lint/cli/src/main/java/com/android/tools/lint/LombokParser.java
deleted file mode 100644
index f699b59..0000000
--- a/lint/cli/src/main/java/com/android/tools/lint/LombokParser.java
+++ /dev/null
@@ -1,162 +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.tools.lint;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.IJavaParser;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-
-import java.io.File;
-import java.util.List;
-
-import lombok.ast.CompilationUnit;
-import lombok.ast.Node;
-import lombok.ast.Position;
-import lombok.ast.TypeReference;
-import lombok.ast.grammar.ParseProblem;
-import lombok.ast.grammar.Source;
-
-/**
- * Java parser which uses the Lombok parser directly. This is a pretty slow parser
- * (2.5 times slower than javac, which in turn is about 3 times slower than EJC for
- * some benchmarks).
- */
-public class LombokParser implements IJavaParser {
-
- @Override
- public Node parseJava(@NonNull JavaContext context) {
- try {
- Source source = new Source(context.getContents(), context.file.getName());
- List<Node> nodes = source.getNodes();
-
- // Don't analyze files containing errors
- List<ParseProblem> problems = source.getProblems();
- if (problems != null && !problems.isEmpty()) {
- context.getDriver().setHasParserErrors(true);
-
- /* Silently ignore the errors. There are still some bugs in Lombok/Parboiled
- * (triggered if you run lint on the AOSP framework directory for example),
- * and having these show up as fatal errors when it's really a tool bug
- * is bad. To make matters worse, the error messages aren't clear:
- * http://code.google.com/p/projectlombok/issues/detail?id=313
- for (ParseProblem problem : problems) {
- Position position = problem.getPosition();
- Location location = Location.create(context.file,
- context.getContents(), position.getStart(), position.getEnd());
- // Sanitize the message?
- // See http://code.google.com/p/projectlombok/issues/detail?id=313
- String message = problem.getMessage();
- context.report(
- com.android.tools.lint.client.api.IssueRegistry.PARSER_ERROR, location,
- message,
- null);
-
- }
- */
- return null;
- }
-
- // There could be more than one node when there are errors; pick out the
- // compilation unit node
- for (Node node : nodes) {
- if (node instanceof CompilationUnit) {
- return node;
- }
- }
- return null;
- } catch (Throwable e) {
- /* Silently ignore the errors. There are still some bugs in Lombok/Parboiled
- * (triggered if you run lint on the AOSP framework directory for example),
- * and having these show up as fatal errors when it's really a tool bug
- * is bad. To make matters worse, the error messages aren't clear:
- * http://code.google.com/p/projectlombok/issues/detail?id=313
- context.report(
- IssueRegistry.PARSER_ERROR, Location.create(context.file),
- e.getCause() != null ? e.getCause().getLocalizedMessage() :
- e.getLocalizedMessage(),
- null);
- */
-
- return null;
- }
- }
-
- @NonNull
- @Override
- public Location getLocation(
- @NonNull JavaContext context,
- @NonNull Node node) {
- Position position = node.getPosition();
- return Location.create(context.file, context.getContents(),
- position.getStart(), position.getEnd());
- }
-
- @NonNull
- @Override
- public Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node) {
- return new LocationHandle(context.file, node);
- }
-
- @Override
- public void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit) {
- }
-
- @Nullable
- @Override
- public Node resolve(@NonNull JavaContext context, @NonNull Node node) {
- return null;
- }
-
- @Nullable
- @Override
- public TypeReference getType(@NonNull JavaContext context, @NonNull Node node) {
- return null;
- }
-
- /* Handle for creating positions cheaply and returning full fledged locations later */
- private static class LocationHandle implements Handle {
- private final File mFile;
- private final Node mNode;
- private Object mClientData;
-
- public LocationHandle(File file, Node node) {
- mFile = file;
- mNode = node;
- }
-
- @NonNull
- @Override
- public Location resolve() {
- Position pos = mNode.getPosition();
- return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd());
- }
-
- @Override
- public void setClientData(@Nullable Object clientData) {
- mClientData = clientData;
- }
-
- @Override
- @Nullable
- public Object getClientData() {
- return mClientData;
- }
- }
-}
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 d5f9507..a4a8886 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
@@ -98,6 +98,7 @@
private static final String PROP_WORK_DIR = "com.android.tools.lint.workdir"; //$NON-NLS-1$
private LintCliFlags mFlags = new LintCliFlags();
+ private IssueRegistry mGlobalRegistry;
/** Creates a CLI driver */
public Main() {
@@ -124,7 +125,6 @@
System.exit(ERRNO_USAGE);
}
- IssueRegistry registry = new BuiltinIssueRegistry();
// 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
@@ -174,6 +174,7 @@
printUsage(System.out);
System.exit(ERRNO_HELP);
} else if (arg.equals(ARG_LIST_IDS)) {
+ IssueRegistry registry = getGlobalRegistry(client);
// Did the user provide a category list?
if (index < args.length - 1 && !args[index + 1].startsWith("-")) { //$NON-NLS-1$
String[] ids = args[++index].split(",");
@@ -200,6 +201,7 @@
}
System.exit(ERRNO_SUCCESS);
} else if (arg.equals(ARG_SHOW)) {
+ IssueRegistry registry = getGlobalRegistry(client);
// Show specific issues?
if (index < args.length - 1 && !args[index + 1].startsWith("-")) { //$NON-NLS-1$
String[] ids = args[++index].split(",");
@@ -355,6 +357,7 @@
boolean closeWriter;
String outputName = args[++index];
if (outputName.equals("stdout")) { //$NON-NLS-1$
+ //noinspection IOResourceOpenedButNotSafelyClosed
writer = new PrintWriter(System.out, true);
closeWriter = false;
} else {
@@ -376,6 +379,7 @@
System.exit(ERRNO_EXISTS);
}
try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
writer = new BufferedWriter(new FileWriter(output));
} catch (IOException e) {
log(e, null);
@@ -389,6 +393,7 @@
System.err.println("Missing categories or id's to disable");
System.exit(ERRNO_INVALID_ARGS);
}
+ IssueRegistry registry = getGlobalRegistry(client);
String[] ids = args[++index].split(",");
for (String id : ids) {
if (registry.isCategoryName(id)) {
@@ -415,6 +420,7 @@
System.err.println("Missing categories or id's to enable");
System.exit(ERRNO_INVALID_ARGS);
}
+ IssueRegistry registry = getGlobalRegistry(client);
String[] ids = args[++index].split(",");
for (String id : ids) {
if (registry.isCategoryName(id)) {
@@ -444,6 +450,7 @@
checkedIds = new HashSet<String>();
mFlags.setExactCheckedIds(checkedIds);
}
+ IssueRegistry registry = getGlobalRegistry(client);
String[] ids = args[++index].split(",");
for (String id : ids) {
if (registry.isCategoryName(id)) {
@@ -589,41 +596,40 @@
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
- // TODO: Find out if we need file:// on Windows.
- urlMap = "=file://"; //$NON-NLS-1$
- } else {
+ //noinspection VariableNotUsedInsideIf
+ if (urlMap != null) {
for (Reporter reporter : reporters) {
if (!reporter.isSimpleFormat()) {
reporter.setBundleResources(true);
}
}
- }
- if (!urlMap.equals(VALUE_NONE)) {
- Map<String, String> map = new HashMap<String, String>();
- String[] replace = urlMap.split(","); //$NON-NLS-1$
- for (String s : replace) {
- // Allow ='s in the suffix part
- int index = s.indexOf('=');
- if (index == -1) {
- System.err.println(
- "The URL map argument must be of the form 'path_prefix=url_prefix'");
- System.exit(ERRNO_INVALID_ARGS);
+ if (!urlMap.equals(VALUE_NONE)) {
+ Map<String, String> map = new HashMap<String, String>();
+ String[] replace = urlMap.split(","); //$NON-NLS-1$
+ for (String s : replace) {
+ // Allow ='s in the suffix part
+ int index = s.indexOf('=');
+ if (index == -1) {
+ System.err.println(
+ "The URL map argument must be of the form 'path_prefix=url_prefix'");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ String key = s.substring(0, index);
+ String value = s.substring(index + 1);
+ map.put(key, value);
}
- String key = s.substring(0, index);
- String value = s.substring(index + 1);
- map.put(key, value);
- }
- for (Reporter reporter : reporters) {
- reporter.setUrlMap(map);
+ for (Reporter reporter : reporters) {
+ reporter.setUrlMap(map);
+ }
}
}
}
try {
- int exitCode = client.run(registry, files);
+ // Not using mGlobalRegistry; LintClient will do its own registry merging
+ // also including project rules.
+ int exitCode = client.run(new BuiltinIssueRegistry(), files);
System.exit(exitCode);
} catch (IOException e) {
log(e, null);
@@ -631,6 +637,14 @@
}
}
+ private IssueRegistry getGlobalRegistry(LintCliClient client) {
+ if (mGlobalRegistry == null) {
+ mGlobalRegistry = client.addCustomLintRules(new BuiltinIssueRegistry());
+ }
+
+ return mGlobalRegistry;
+ }
+
/**
* Converts a relative or absolute command-line argument into an input file.
*
diff --git a/lint/cli/src/main/java/com/android/tools/lint/MultiProjectHtmlReporter.java b/lint/cli/src/main/java/com/android/tools/lint/MultiProjectHtmlReporter.java
index 5811fc2..43fdba5 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/MultiProjectHtmlReporter.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/MultiProjectHtmlReporter.java
@@ -18,9 +18,10 @@
import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.SdkUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-import com.google.common.io.Closeables;
+import com.google.common.io.Closer;
import java.io.File;
import java.io.IOException;
@@ -130,19 +131,29 @@
relative));
}
+ Closer closer = Closer.create();
// Write overview index?
- writeOverview(errorCount, warningCount, projects);
- Closeables.closeQuietly(mWriter);
+ try {
+ closer.register(mWriter);
+ writeOverview(errorCount, warningCount, projects);
+ } catch (Throwable e) {
+ throw closer.rethrow(e);
+ } finally {
+ closer.close();
+ }
- File index = new File(mDir, INDEX_NAME);
- System.out.println();
- System.out.println(String.format("Wrote overview index to %1$s", index));
+ if (mDisplayEmpty || errorCount > 0 || warningCount > 0) {
+ File index = new File(mDir, INDEX_NAME);
+ String url = SdkUtils.fileToUrlString(index.getAbsoluteFile());
+ System.out.println(String.format("Wrote overview index to %1$s", url));
+ }
}
private void writeOverview(int errorCount, int warningCount, List<ProjectEntry> projects)
throws IOException {
mWriter.write(
- "<html>\n" + //$NON-NLS-1$
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + //$NON-NLS-1$
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" + //$NON-NLS-1$
"<head>\n" + //$NON-NLS-1$
"<title>" + mTitle + "</title>\n"); //$NON-NLS-1$//$NON-NLS-2$
writeStyleSheet();
@@ -151,8 +162,8 @@
"<body>\n" + //$NON-NLS-1$
"<h1>" + //$NON-NLS-1$
mTitle +
- "<div class=\"titleSeparator\"></div>\n" + //$NON-NLS-1$
- "</h1>"); //$NON-NLS-1$
+ "</h1>\n" + //$NON-NLS-1$
+ "<div class=\"titleSeparator\"></div>\n"); //$NON-NLS-1$
// Sort project list in decreasing order of errors, warnings and names
@@ -160,11 +171,11 @@
mWriter.write(String.format("Check performed at %1$s.",
new Date().toString()));
- mWriter.write("<br/>"); //$NON-NLS-1$
+ mWriter.write("<br/>\n"); //$NON-NLS-1$
mWriter.write(String.format("%1$d errors and %2$d warnings found:\n",
errorCount, warningCount));
- mWriter.write("<br/><br/>"); //$NON-NLS-1$
+ mWriter.write("<br/><br/>\n"); //$NON-NLS-1$
if (errorCount == 0 && warningCount == 0) {
mWriter.write("Congratulations!");
@@ -186,7 +197,7 @@
if (errorUrl != null) {
mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
mWriter.write(errorUrl);
- mWriter.write("\" />\n"); //$NON-NLS-1$
+ mWriter.write("\" alt=\"Error\" />\n"); //$NON-NLS-1$
}
mWriter.write("Errors");
mWriter.write("</th><th class=\"countColumn\">"); //$NON-NLS-1$
@@ -194,7 +205,7 @@
if (warningUrl != null) {
mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
mWriter.write(warningUrl);
- mWriter.write("\" />\n"); //$NON-NLS-1$
+ mWriter.write("\" alt=\"Warning\" />\n"); //$NON-NLS-1$
}
mWriter.write("Warnings");
mWriter.write("</th></tr>\n"); //$NON-NLS-1$
@@ -202,7 +213,7 @@
for (ProjectEntry entry : projects) {
mWriter.write("<tr><td>"); //$NON-NLS-1$
mWriter.write("<a href=\"");
- mWriter.write(entry.fileName); // TODO: Escape?
+ appendEscapedText(entry.fileName);
mWriter.write("\">"); //$NON-NLS-1$
mWriter.write(entry.path);
mWriter.write("</a></td><td class=\"countColumn\">"); //$NON-NLS-1$
@@ -212,6 +223,8 @@
mWriter.write("</td></tr>\n"); //$NON-NLS-1$
}
mWriter.write("</table>\n"); //$NON-NLS-1$
+
+ mWriter.write("</body>\n</html>\n"); //$NON-NLS-1$
}
private static class ProjectEntry implements Comparable<ProjectEntry> {
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 663fa02..7f579de 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
@@ -16,13 +16,19 @@
package com.android.tools.lint;
+import static com.android.SdkConstants.CURRENT_PLATFORM;
import static com.android.SdkConstants.DOT_9PNG;
import static com.android.SdkConstants.DOT_PNG;
+import static com.android.SdkConstants.PLATFORM_LINUX;
+import static com.android.SdkConstants.UTF_8;
import static com.android.tools.lint.detector.api.LintUtils.endsWith;
+import static java.io.File.separatorChar;
+import com.android.annotations.Nullable;
+import com.android.utils.SdkUtils;
import com.google.common.annotations.Beta;
import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
+import com.google.common.io.Closer;
import com.google.common.io.Files;
import java.io.File;
@@ -30,10 +36,12 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
/** A reporter is an output generator for lint warnings
@@ -52,6 +60,7 @@
protected File mResources;
protected final Map<File, String> mResourceUrl = new HashMap<File, String>();
protected final Map<String, File> mNameToFile = new HashMap<String, File>();
+ protected boolean mDisplayEmpty = true;
/**
* Write the given warnings into the report
@@ -126,31 +135,38 @@
if (mUrlMap != null) {
String path = file.getAbsolutePath();
- try {
- // Perform the comparison using URLs such that we properly escape spaces etc.
- String pathUrl = URLEncoder.encode(path, "UTF-8"); //$NON-NLS-1$
- for (Map.Entry<String, String> entry : mUrlMap.entrySet()) {
- String prefix = entry.getKey();
- String prefixUrl = URLEncoder.encode(prefix, "UTF-8"); //$NON-NLS-1$
- if (pathUrl.startsWith(prefixUrl)) {
- String relative = pathUrl.substring(prefixUrl.length());
- return entry.getValue()
- + relative.replace("%2F", "/"); //$NON-NLS-1$ //$NON-NLS-2$
- }
+ // Perform the comparison using URLs such that we properly escape spaces etc.
+ String pathUrl = encodeUrl(path);
+ for (Map.Entry<String, String> entry : mUrlMap.entrySet()) {
+ String prefix = entry.getKey();
+ String prefixUrl = encodeUrl(prefix);
+ if (pathUrl.startsWith(prefixUrl)) {
+ String relative = pathUrl.substring(prefixUrl.length());
+ return entry.getValue() + relative;
}
- } catch (UnsupportedEncodingException e) {
- // This shouldn't happen for UTF-8
- System.err.println("Invalid URL map specification - " + e.getLocalizedMessage());
}
}
- return null;
+ if (file.isAbsolute()) {
+ String relativePath = getRelativePath(mOutput.getParentFile(), file);
+ if (relativePath != null) {
+ relativePath = relativePath.replace(separatorChar, '/');
+ return encodeUrl(relativePath);
+ }
+ }
+
+ try {
+ return SdkUtils.fileToUrlString(file);
+ } catch (MalformedURLException e) {
+ return null;
+ }
}
/** Encodes the given String as a safe URL substring, escaping spaces etc */
static String encodeUrl(String url) {
try {
- return URLEncoder.encode(url, "UTF-8"); //$NON-NLS-1$
+ url = url.replace('\\', '/');
+ return URLEncoder.encode(url, UTF_8).replace("%2F", "/"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
// This shouldn't happen for UTF-8
System.err.println("Invalid string " + e.getLocalizedMessage());
@@ -159,7 +175,7 @@
}
/** Set mapping of path prefixes to corresponding URLs in the HTML report */
- public void setUrlMap(Map<String, String> urlMap) {
+ public void setUrlMap(@Nullable Map<String, String> urlMap) {
mUrlMap = urlMap;
}
@@ -234,7 +250,7 @@
/** Returns a URL to a local copy of the given resource, or null. There is
* no filename conflict resolution. */
- protected String addLocalResources(URL url) {
+ protected String addLocalResources(URL url) throws IOException {
// Attempt to make local copy
File resourceDir = computeResourceDir();
if (resourceDir != null) {
@@ -243,17 +259,95 @@
mNameToFile.put(base, new File(url.toExternalForm()));
File target = new File(resourceDir, base);
+ Closer closer = Closer.create();
try {
- FileOutputStream output = new FileOutputStream(target);
- InputStream input = url.openStream();
+ FileOutputStream output = closer.register(new FileOutputStream(target));
+ InputStream input = closer.register(url.openStream());
ByteStreams.copy(input, output);
- Closeables.closeQuietly(output);
- Closeables.closeQuietly(input);
- } catch (IOException e) {
- return null;
+ } catch (Throwable e) {
+ closer.rethrow(e);
+ } finally {
+ closer.close();
}
return resourceDir.getName() + '/' + encodeUrl(base);
}
return null;
}
+
+ // Based on similar code in com.intellij.openapi.util.io.FileUtilRt
+ @Nullable
+ static String getRelativePath(File base, File file) {
+ if (base == null || file == null) {
+ return null;
+ }
+ if (!base.isDirectory()) {
+ base = base.getParentFile();
+ if (base == null) {
+ return null;
+ }
+ }
+ if (base.equals(file)) {
+ return ".";
+ }
+
+ final String filePath = file.getAbsolutePath();
+ String basePath = base.getAbsolutePath();
+
+ // TODO: Make this return null if we go all the way to the root!
+
+ basePath = !basePath.isEmpty() && basePath.charAt(basePath.length() - 1) == separatorChar
+ ? basePath : basePath + separatorChar;
+
+ // Whether filesystem is case sensitive. Technically on OSX you could create a
+ // sensitive one, but it's not the default.
+ boolean caseSensitive = CURRENT_PLATFORM == PLATFORM_LINUX;
+ Locale l = Locale.getDefault();
+ String basePathToCompare = caseSensitive ? basePath : basePath.toLowerCase(l);
+ String filePathToCompare = caseSensitive ? filePath : filePath.toLowerCase(l);
+ if (basePathToCompare.equals(!filePathToCompare.isEmpty()
+ && filePathToCompare.charAt(filePathToCompare.length() - 1) == separatorChar
+ ? filePathToCompare : filePathToCompare + separatorChar)) {
+ return ".";
+ }
+ int len = 0;
+ int lastSeparatorIndex = 0;
+ // bug in inspection; see http://youtrack.jetbrains.com/issue/IDEA-118971
+ //noinspection ConstantConditions
+ while (len < filePath.length() && len < basePath.length()
+ && filePathToCompare.charAt(len) == basePathToCompare.charAt(len)) {
+ if (basePath.charAt(len) == separatorChar) {
+ lastSeparatorIndex = len;
+ }
+ len++;
+ }
+ if (len == 0) {
+ return null;
+ }
+
+ StringBuilder relativePath = new StringBuilder();
+ for (int i = len; i < basePath.length(); i++) {
+ if (basePath.charAt(i) == separatorChar) {
+ relativePath.append("..");
+ relativePath.append(separatorChar);
+ }
+ }
+ relativePath.append(filePath.substring(lastSeparatorIndex + 1));
+ return relativePath.toString();
+ }
+
+ /**
+ * Returns whether this report should display info (such as a path to the report) if
+ * no issues were found
+ */
+ public boolean isDisplayEmpty() {
+ return mDisplayEmpty;
+ }
+
+ /**
+ * Sets whether this report should display info (such as a path to the report) if
+ * no issues were found
+ */
+ public void setDisplayEmpty(boolean displayEmpty) {
+ mDisplayEmpty = displayEmpty;
+ }
}
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 87ae189..5afab49 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
@@ -16,11 +16,16 @@
package com.android.tools.lint;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Position;
import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.SdkUtils;
import com.google.common.annotations.Beta;
import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
import java.io.File;
import java.io.IOException;
@@ -47,7 +52,8 @@
* @param writer the writer to write into
* @param close whether the writer should be closed when done
*/
- public TextReporter(LintCliClient client, LintCliFlags flags, Writer writer, boolean close) {
+ public TextReporter(@NonNull LintCliClient client, @NonNull LintCliFlags flags,
+ @NonNull Writer writer, boolean close) {
this(client, flags, null, writer, close);
}
@@ -60,8 +66,8 @@
* @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) {
+ public TextReporter(@NonNull LintCliClient client, @NonNull LintCliFlags flags,
+ @Nullable File file, @NonNull Writer writer, boolean close) {
super(client, file);
mFlags = flags;
mWriter = writer;
@@ -74,12 +80,18 @@
StringBuilder output = new StringBuilder(issues.size() * 200);
if (issues.isEmpty()) {
- mWriter.write('\n');
- mWriter.write("No issues found.");
- mWriter.write('\n');
- mWriter.flush();
+ if (mDisplayEmpty) {
+ mWriter.write("No issues found.");
+ mWriter.write('\n');
+ mWriter.flush();
+ }
} else {
+ Issue lastIssue = null;
for (Warning warning : issues) {
+ if (warning.issue != lastIssue) {
+ explainIssue(output, lastIssue);
+ lastIssue = warning.issue;
+ }
int startLength = output.length();
if (warning.path != null) {
@@ -120,6 +132,7 @@
if (warning.location != null && warning.location.getSecondary() != null) {
Location location = warning.location.getSecondary();
+ boolean omitted = false;
while (location != null) {
if (location.getMessage() != null
&& !location.getMessage().isEmpty()) {
@@ -145,19 +158,21 @@
}
output.append('\n');
+ } else {
+ omitted = true;
}
location = location.getSecondary();
}
- if (!abbreviate) {
+ if (!abbreviate && omitted) {
location = warning.location.getSecondary();
StringBuilder sb = new StringBuilder(100);
sb.append("Also affects: ");
int begin = sb.length();
while (location != null) {
if (location.getMessage() == null
- || !location.getMessage().isEmpty()) {
+ || location.getMessage().isEmpty()) {
if (sb.length() > begin) {
sb.append(", ");
}
@@ -196,6 +211,7 @@
output.append('\n');
}
}
+ explainIssue(output, lastIssue);
mWriter.write(output.toString());
@@ -213,4 +229,45 @@
}
}
}
+
+ private void explainIssue(@NonNull StringBuilder output, @Nullable Issue issue)
+ throws IOException {
+ if (issue == null || !mFlags.isExplainIssues()) {
+ return;
+ }
+
+ String explanation = issue.getExplanation(Issue.OutputFormat.TEXT);
+ if (explanation.trim().isEmpty()) {
+ return;
+ }
+
+ String indent = " ";
+ String formatted = SdkUtils.wrap(explanation, Main.MAX_LINE_WIDTH - indent.length(), null);
+ output.append('\n');
+ output.append(indent);
+ output.append("Explanation for issues of type \"").append(issue.getId()).append("\":\n");
+ for (String line : Splitter.on('\n').split(formatted)) {
+ if (!line.isEmpty()) {
+ output.append(indent);
+ output.append(line);
+ }
+ output.append('\n');
+ }
+ List<String> moreInfo = issue.getMoreInfo();
+ if (!moreInfo.isEmpty()) {
+ for (String url : moreInfo) {
+ if (formatted.contains(url)) {
+ continue;
+ }
+ output.append(indent);
+ output.append(url);
+ output.append('\n');
+ }
+ output.append('\n');
+ }
+ }
+
+ boolean isWriteToConsole() {
+ return mOutput == null;
+ }
}
\ No newline at end of file
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 c683ab8..9565b85 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
@@ -24,6 +24,7 @@
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.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -105,7 +106,39 @@
return line - other.line;
}
- return message.compareTo(other.message);
+ int delta = message.compareTo(other.message);
+ if (delta != 0) {
+ return delta;
+ }
+
+ if (file != null) {
+ if (other.file != null) {
+ delta = file.compareTo(other.file);
+ if (delta != 0) {
+ return delta;
+ }
+ } else {
+ return -1;
+ }
+ } else if (other.file != null) {
+ return 1;
+ }
+
+ Location secondary1 = location != null ? location.getSecondary() : null;
+ File secondaryFile1 = secondary1 != null ? secondary1.getFile() : null;
+ Location secondary2 = other.location != null ? other.location.getSecondary() : null;
+ File secondaryFile2 = secondary2 != null ? secondary2.getFile() : null;
+ if (secondaryFile1 != null) {
+ if (secondaryFile2 != null) {
+ return secondaryFile1.compareTo(secondaryFile2);
+ } else {
+ return -1;
+ }
+ } else if (secondaryFile2 != null) {
+ return 1;
+ }
+
+ return 0;
}
@Override
@@ -139,6 +172,21 @@
return false;
}
+ Location secondary1 = location != null ? location.getSecondary() : null;
+ Location secondary2 = warning.location != null ? warning.location.getSecondary() : null;
+ if (secondary1 != null) {
+ if (secondary2 != null) {
+ if (!Objects.equal(secondary1.getFile(), secondary2.getFile())) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else //noinspection VariableNotUsedInsideIf
+ if (secondary2 != null) {
+ return false;
+ }
+
return true;
}
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 a7b7be5..5b3b9ab 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
@@ -161,8 +161,10 @@
mWriter.write("\n</issues>\n"); //$NON-NLS-1$
mWriter.close();
- String path = mOutput.getAbsolutePath();
- System.out.println(String.format("Wrote XML report to %1$s", path));
+ if (mDisplayEmpty || errorCount > 0 || warningCount > 0) {
+ String path = mOutput.getAbsolutePath();
+ System.out.println(String.format("Wrote XML report to %1$s", path));
+ }
}
private static void writeAttribute(Writer writer, int indent, String name, String value)
diff --git a/lint/cli/src/main/java/com/android/tools/lint/hololike.css b/lint/cli/src/main/java/com/android/tools/lint/hololike.css
index b12611f..09645da 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/hololike.css
+++ b/lint/cli/src/main/java/com/android/tools/lint/hololike.css
@@ -1,8 +1,8 @@
body {
max-width: 800px;
background-color: #000000;
- background: -webkit-gradient(linear, left top, left bottom, from(#000000), to(#272d33));
- background: -moz-linear-gradient(top, #000000, #272d33);
+ background: -webkit-gradient(linear, left top, right bottom, from(#000000), to(#272d33));
+ background: -moz-linear-gradient(left top, #000000, #272d33);
color: #f3f3f3;
font-family: 'Roboto', Sans-Serif;
}
diff --git a/lint/cli/src/test/.classpath b/lint/cli/src/test/.classpath
index 4ba8119..31d7d29 100644
--- a/lint/cli/src/test/.classpath
+++ b/lint/cli/src/test/.classpath
@@ -9,7 +9,7 @@
<classpathentry combineaccessrules="false" kind="src" path="/lint-cli"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src-4.0.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src-4.0.zip"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/guava-tools/guava-13.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/guava-tools/src-4.0.zip"/>
+ <classpathentry exported="true" kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/lombok-ast/src-4.0.zip"/>
<classpathentry combineaccessrules="false" kind="src" path="/layoutlib-api"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/EcjParserTest.java b/lint/cli/src/test/java/com/android/tools/lint/EcjParserTest.java
new file mode 100644
index 0000000..28fa357
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/EcjParserTest.java
@@ -0,0 +1,693 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.checks.SdCardDetector;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.LintUtilsTest;
+
+import junit.framework.Assert;
+
+import java.io.File;
+
+import lombok.ast.DescribedNode;
+import lombok.ast.Node;
+import lombok.ast.printer.SourceFormatter;
+import lombok.ast.printer.SourcePrinter;
+import lombok.ast.printer.TextFormatter;
+
+public class EcjParserTest extends AbstractCheckTest {
+ public void testTryCatchHang() throws Exception {
+ // Ensure that we're really using this parser
+ JavaParser javaParser = createClient().getJavaParser(null);
+ assertNotNull(javaParser);
+ assertTrue(javaParser.getClass().getName(), javaParser instanceof EcjParser);
+
+ // See https://code.google.com/p/projectlombok/issues/detail?id=573#c6
+ // With lombok.ast 0.2.1 and the parboiled-based Java parser this test will hang forever.
+ assertEquals(
+ "No warnings.",
+
+ lintProject("src/test/pkg/TryCatchHang.java.txt=>src/test/pkg/TryCatchHang.java"));
+ }
+
+ public void testKitKatLanguageFeatures() throws Exception {
+ String testClass = "" +
+ "package test.pkg;\n" +
+ "\n" +
+ "import java.io.BufferedReader;\n" +
+ "import java.io.FileReader;\n" +
+ "import java.io.IOException;\n" +
+ "import java.lang.reflect.InvocationTargetException;\n" +
+ "import java.util.List;\n" +
+ "import java.util.Map;\n" +
+ "import java.util.TreeMap;\n" +
+ "\n" +
+ "public class Java7LanguageFeatureTest {\n" +
+ " public void testDiamondOperator() {\n" +
+ " Map<String, List<Integer>> map = new TreeMap<>();\n" +
+ " }\n" +
+ "\n" +
+ " public int testStringSwitches(String value) {\n" +
+ " final String first = \"first\";\n" +
+ " final String second = \"second\";\n" +
+ "\n" +
+ " switch (value) {\n" +
+ " case first:\n" +
+ " return 41;\n" +
+ " case second:\n" +
+ " return 42;\n" +
+ " default:\n" +
+ " return 0;\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " public String testTryWithResources(String path) throws IOException {\n" +
+ " try (BufferedReader br = new BufferedReader(new FileReader(path))) {\n" +
+ " return br.readLine();\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " public void testNumericLiterals() {\n" +
+ " int thousand = 1_000;\n" +
+ " int million = 1_000_000;\n" +
+ " int binary = 0B01010101;\n" +
+ " }\n" +
+ "\n" +
+ " public void testMultiCatch() {\n" +
+ "\n" +
+ " try {\n" +
+ " Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n" +
+ " } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {\n" +
+ " e.printStackTrace();\n" +
+ " } catch (ClassNotFoundException e) {\n" +
+ " // TODO: Logging here\n" +
+ " }\n" +
+ " }\n" +
+ "}\n";
+
+ Node unit = LintUtilsTest.getCompilationUnit(testClass);
+ assertNotNull(unit);
+
+ // Now print the AST back and make sure that it contains at least the essence of the AST
+ TextFormatter formatter = new TextFormatter();
+ unit.accept(new SourcePrinter(formatter));
+ String actual = formatter.finish();
+ assertEquals(""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import java.io.BufferedReader;\n"
+ + "import java.io.FileReader;\n"
+ + "import java.io.IOException;\n"
+ + "import java.lang.reflect.InvocationTargetException;\n"
+ + "import java.util.List;\n"
+ + "import java.util.Map;\n"
+ + "import java.util.TreeMap;\n"
+ + "\n"
+ + "public class Java7LanguageFeatureTest {\n"
+ + " public void testDiamondOperator() {\n"
+ + " Map<String, List<Integer>> map = new TreeMap();\n" // missing types on rhs
+ + " }\n"
+ + " \n"
+ + " public int testStringSwitches(String value) {\n"
+ + " final String first = \"first\";\n"
+ + " final String second = \"second\";\n"
+ + " switch (value) {\n"
+ + " case first:\n"
+ + " return 41;\n"
+ + " case second:\n"
+ + " return 42;\n"
+ + " default:\n"
+ + " return 0;\n"
+ + " }\n"
+ + " }\n"
+ + " \n"
+ + " public String testTryWithResources(String path) throws IOException {\n"
+ + " try {\n" // Note how the initialization clause is gone here
+ + " return br.readLine();\n"
+ + " }\n"
+ + " }\n"
+ + " \n"
+ + " public void testNumericLiterals() {\n"
+ + " int thousand = 1_000;\n"
+ + " int million = 1_000_000;\n"
+ + " int binary = 0B01010101;\n"
+ + " }\n"
+ + " \n"
+ + " public void testMultiCatch() {\n"
+ + " try {\n"
+ + " Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n"
+ + " } catch (IllegalAccessException e) {\n" // Note: missing other union types
+ + " e.printStackTrace();\n"
+ + " } catch (ClassNotFoundException e) {\n"
+ + " }\n"
+ + " }\n"
+ + "}",
+ actual);
+ }
+
+ public void testResolution() throws Exception {
+ String source =
+ "package test.pkg;\n" +
+ "\n" +
+ "import java.io.File;\n" +
+ "\n" +
+ "public class TypeResolutionTest {\n" +
+ " public static class Inner extends File {\n" +
+ " public float myField = 5f;\n" +
+ " public int[] myInts;\n" +
+ "\n" +
+ " public Inner(File dir, String name) {\n" +
+ " super(dir, name);\n" +
+ " }\n" +
+ "\n" +
+ " public void call(int arg1, double arg2) {\n" +
+ " boolean x = super.canRead();\n" +
+ " System.out.println(x);\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " @SuppressWarnings(\"all\")\n" +
+ " public static class Other {\n" +
+ " private void client(int z) {\n" +
+ " int x = z;\n" +
+ " int y = x + 5;\n" +
+ " Inner inner = new Inner(null, null);\n" +
+ " inner.myField = 6;\n" +
+ " System.out.println(inner.myInts);\n" +
+ " }\n" +
+ " }\n" +
+ "}\n";
+
+ Node unit = LintUtilsTest.getCompilationUnit(source,
+ new File("src/test/pkg/TypeResolutionTest.java"));
+
+ JavaParser parser = new EcjParser(new LintCliClient(), null);
+ AstPrettyPrinter astPrettyPrinter = new AstPrettyPrinter(parser);
+ unit.accept(new SourcePrinter(astPrettyPrinter));
+ String actual = astPrettyPrinter.finish();
+ assertEquals(
+ "[CompilationUnit]\n" +
+ " [PackageDeclaration]\n" +
+ " [Identifier test]\n" +
+ " PROPERTY: name = test\n" +
+ " [Identifier pkg]\n" +
+ " PROPERTY: name = pkg\n" +
+ " [ImportDeclaration]\n" +
+ " PROPERTY: static = false\n" +
+ " PROPERTY: star = false\n" +
+ " [Identifier java]\n" +
+ " PROPERTY: name = java\n" +
+ " [Identifier io]\n" +
+ " PROPERTY: name = io\n" +
+ " [Identifier File]\n" +
+ " PROPERTY: name = File\n" +
+ " [ClassDeclaration TypeResolutionTest], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
+ " [Modifiers], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " typeName: [Identifier TypeResolutionTest], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
+ " PROPERTY: name = TypeResolutionTest\n" +
+ " [NormalTypeBody], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
+ " [ClassDeclaration Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [Modifiers], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " [KeywordModifier static]\n" +
+ " PROPERTY: modifier = static\n" +
+ " typeName: [Identifier Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " PROPERTY: name = Inner\n" +
+ " extends: [TypeReference File], type: java.io.File, resolved class: java.io.File \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: java.io.File, resolved class: java.io.File \n" +
+ " [Identifier File]\n" +
+ " PROPERTY: name = File\n" +
+ " [NormalTypeBody], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [VariableDeclaration], type: float, resolved class: float \n" +
+ " [VariableDefinition]\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " type: [TypeReference float], type: float, resolved class: float \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: float, resolved class: float \n" +
+ " [Identifier float]\n" +
+ " PROPERTY: name = float\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier myField]\n" +
+ " PROPERTY: name = myField\n" +
+ " [FloatingPointLiteral 5.0], type: float\n" +
+ " PROPERTY: value = 5f\n" +
+ " [VariableDeclaration], type: int[], resolved class: int[] \n" +
+ " [VariableDefinition]\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " type: [TypeReference int[]], type: int[], resolved class: int[] \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 1\n" +
+ " [TypeReferencePart], type: int[], resolved class: int[] \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier myInts]\n" +
+ " PROPERTY: name = myInts\n" +
+ " [ConstructorDeclaration], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [Modifiers], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " typeName: [Identifier Inner], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = Inner\n" +
+ " parameter: [VariableDefinition], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference File], type: java.io.File, resolved class: java.io.File \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: java.io.File, resolved class: java.io.File \n" +
+ " [Identifier File]\n" +
+ " PROPERTY: name = File\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier dir]\n" +
+ " PROPERTY: name = dir\n" +
+ " parameter: [VariableDefinition], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference String], type: java.lang.String, resolved class: java.lang.String \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: java.lang.String, resolved class: java.lang.String \n" +
+ " [Identifier String]\n" +
+ " PROPERTY: name = String\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier name]\n" +
+ " PROPERTY: name = name\n" +
+ " [Block], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [SuperConstructorInvocation], resolved method: java.io.File java.io.File\n" +
+ " [VariableReference], type: java.io.File, resolved variable: dir java.io.File\n" +
+ " [Identifier dir], type: java.io.File, resolved variable: dir java.io.File\n" +
+ " PROPERTY: name = dir\n" +
+ " [VariableReference], type: java.lang.String, resolved variable: name java.lang.String\n" +
+ " [Identifier name], type: java.lang.String, resolved variable: name java.lang.String\n" +
+ " PROPERTY: name = name\n" +
+ " [MethodDeclaration call], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " [Modifiers], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " returnType: [TypeReference void], type: void, resolved class: void \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: void, resolved class: void \n" +
+ " [Identifier void]\n" +
+ " PROPERTY: name = void\n" +
+ " methodName: [Identifier call], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = call\n" +
+ " parameter: [VariableDefinition], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference int], type: int, resolved class: int \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: int, resolved class: int \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier arg1]\n" +
+ " PROPERTY: name = arg1\n" +
+ " parameter: [VariableDefinition], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference double], type: double, resolved class: double \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: double, resolved class: double \n" +
+ " [Identifier double]\n" +
+ " PROPERTY: name = double\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier arg2]\n" +
+ " PROPERTY: name = arg2\n" +
+ " [Block], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " [VariableDeclaration], type: boolean, resolved class: boolean \n" +
+ " [VariableDefinition]\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference boolean], type: boolean, resolved class: boolean \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: boolean, resolved class: boolean \n" +
+ " [Identifier boolean]\n" +
+ " PROPERTY: name = boolean\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier x]\n" +
+ " PROPERTY: name = x\n" +
+ " [MethodInvocation canRead], type: boolean, resolved method: canRead java.io.File\n" +
+ " operand: [Super], type: java.io.File\n" +
+ " methodName: [Identifier canRead], type: boolean, resolved method: canRead java.io.File\n" +
+ " PROPERTY: name = canRead\n" +
+ " [ExpressionStatement], type: void, resolved method: println java.io.PrintStream\n" +
+ " [MethodInvocation println], type: void, resolved method: println java.io.PrintStream\n" +
+ " operand: [Select], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " operand: [VariableReference], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " [Identifier System]\n" +
+ " PROPERTY: name = System\n" +
+ " selected: [Identifier out], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " PROPERTY: name = out\n" +
+ " methodName: [Identifier println]\n" +
+ " PROPERTY: name = println\n" +
+ " [VariableReference], type: boolean, resolved variable: x boolean\n" +
+ " [Identifier x], type: boolean, resolved variable: x boolean\n" +
+ " PROPERTY: name = x\n" +
+ " [ClassDeclaration Other], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
+ " [Modifiers], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
+ " [Annotation SuppressWarnings], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
+ " [TypeReference SuppressWarnings], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
+ " [Identifier SuppressWarnings]\n" +
+ " PROPERTY: name = SuppressWarnings\n" +
+ " [AnnotationElement null], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
+ " [StringLiteral all], type: java.lang.String\n" +
+ " PROPERTY: value = \"all\"\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " [KeywordModifier static]\n" +
+ " PROPERTY: modifier = static\n" +
+ " typeName: [Identifier Other], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
+ " PROPERTY: name = Other\n" +
+ " [NormalTypeBody], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
+ " [MethodDeclaration client], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+ " [Modifiers], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+ " [KeywordModifier private]\n" +
+ " PROPERTY: modifier = private\n" +
+ " returnType: [TypeReference void], type: void, resolved class: void \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: void, resolved class: void \n" +
+ " [Identifier void]\n" +
+ " PROPERTY: name = void\n" +
+ " methodName: [Identifier client], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+ " PROPERTY: name = client\n" +
+ " parameter: [VariableDefinition], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference int], type: int, resolved class: int \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: int, resolved class: int \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier z]\n" +
+ " PROPERTY: name = z\n" +
+ " [Block], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+ " [VariableDeclaration], type: int, resolved class: int \n" +
+ " [VariableDefinition]\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference int], type: int, resolved class: int \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: int, resolved class: int \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier x]\n" +
+ " PROPERTY: name = x\n" +
+ " [VariableReference], type: int, resolved variable: z int\n" +
+ " [Identifier z], type: int, resolved variable: z int\n" +
+ " PROPERTY: name = z\n" +
+ " [VariableDeclaration], type: int, resolved class: int \n" +
+ " [VariableDefinition]\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference int], type: int, resolved class: int \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: int, resolved class: int \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier y]\n" +
+ " PROPERTY: name = y\n" +
+ " [BinaryExpression +], type: int\n" +
+ " PROPERTY: operator = +\n" +
+ " left: [VariableReference], type: int, resolved variable: x int\n" +
+ " [Identifier x], type: int, resolved variable: x int\n" +
+ " PROPERTY: name = x\n" +
+ " right: [IntegralLiteral 5], type: int\n" +
+ " PROPERTY: value = 5\n" +
+ " [VariableDeclaration], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [VariableDefinition]\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [Identifier Inner]\n" +
+ " PROPERTY: name = Inner\n" +
+ " [VariableDefinitionEntry]\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier inner]\n" +
+ " PROPERTY: name = inner\n" +
+ " [ConstructorInvocation Inner], type: test.pkg.TypeResolutionTest.Inner, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [Identifier Inner]\n" +
+ " PROPERTY: name = Inner\n" +
+ " [NullLiteral], type: null\n" +
+ " [NullLiteral], type: null\n" +
+ " [ExpressionStatement], type: float\n" +
+ " [BinaryExpression =], type: float\n" +
+ " PROPERTY: operator = =\n" +
+ " left: [Select], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " operand: [VariableReference], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [Identifier inner]\n" +
+ " PROPERTY: name = inner\n" +
+ " selected: [Identifier myField], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = myField\n" +
+ " right: [IntegralLiteral 6], type: int\n" +
+ " PROPERTY: value = 6\n" +
+ " [ExpressionStatement], type: void, resolved method: println java.io.PrintStream\n" +
+ " [MethodInvocation println], type: void, resolved method: println java.io.PrintStream\n" +
+ " operand: [Select], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " operand: [VariableReference], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " [Identifier System]\n" +
+ " PROPERTY: name = System\n" +
+ " selected: [Identifier out], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " PROPERTY: name = out\n" +
+ " methodName: [Identifier println]\n" +
+ " PROPERTY: name = println\n" +
+ " [Select], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " operand: [VariableReference], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [Identifier inner]\n" +
+ " PROPERTY: name = inner\n" +
+ " selected: [Identifier myInts], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = myInts\n",
+ actual);
+ }
+
+ @Override
+ protected Detector getDetector() {
+ return new SdCardDetector();
+ }
+
+ public static class AstPrettyPrinter implements SourceFormatter {
+
+ private final StringBuilder mOutput = new StringBuilder(1000);
+
+ private final JavaParser mResolver;
+
+ private int mIndent;
+
+ private String mName;
+
+ public AstPrettyPrinter(JavaParser resolver) {
+ mResolver = resolver;
+ }
+
+ private void add(String in, Object... args) {
+ for (int i = 0; i < mIndent; i++) {
+ mOutput.append(" ");
+ }
+ if (mName != null) {
+ mOutput.append(mName).append(": ");
+ mName = null;
+ }
+ if (args.length == 0) {
+ mOutput.append(in);
+ } else {
+ mOutput.append(String.format(in, args));
+ }
+ }
+
+ @Override
+ public void buildInline(Node node) {
+ buildNode(node);
+ }
+
+ @Override
+ public void buildBlock(Node node) {
+ buildNode(node);
+ }
+
+ private void buildNode(Node node) {
+ if (node == null) {
+ mIndent++;
+ return;
+ }
+ String name = node.getClass().getSimpleName();
+ String description = "";
+ if (node instanceof DescribedNode) {
+ description = " " + ((DescribedNode) node).getDescription();
+ }
+
+ String typeDescription = "";
+ String resolutionDescription = "";
+ JavaParser.TypeDescriptor t = mResolver.getType(null, node);
+ if (t != null) {
+ typeDescription = ", type: " + t.getName();
+ }
+ ResolvedNode resolved = mResolver.resolve(null, node);
+ if (resolved != null) {
+ String c = "unknown";
+ String extra = "";
+ if (resolved instanceof ResolvedClass) {
+ c = "class";
+ } else if (resolved instanceof ResolvedMethod) {
+ c = "method";
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ extra = method.getContainingClass().getName();
+ } else if (resolved instanceof ResolvedField) {
+ c = "field";
+ ResolvedField field = (ResolvedField) resolved;
+ extra = field.getContainingClass().getName();
+ } else if (resolved instanceof ResolvedVariable) {
+ c = "variable";
+ ResolvedVariable variable = (ResolvedVariable) resolved;
+ extra = variable.getType().getName();
+ }
+ resolutionDescription = String.format(", resolved %1$s: %2$s %3$s",
+ c, resolved.getName(), extra);
+ }
+
+ add("[%1$s%2$s]%3$s%4$s\n", name, description, typeDescription, resolutionDescription);
+
+ mIndent++;
+ }
+
+ @Override
+ public void fail(String fail) {
+ Assert.fail(fail);
+ }
+
+ @Override
+ public void property(String name, Object value) {
+ add("PROPERTY: %s = %s\n", name, value);
+ }
+
+ @Override
+ public void keyword(String text) {
+ }
+
+ @Override
+ public void operator(String text) {
+ }
+
+ @Override
+ public void verticalSpace() {
+ }
+
+ @Override
+ public void space() {
+ }
+
+ @Override
+ public void append(String text) {
+ }
+
+ @Override
+ public void startSuppressBlock() {
+ }
+
+ @Override
+ public void endSuppressBlock() {
+ }
+
+ @Override
+ public void startSuppressIndent() {
+ }
+
+ @Override
+ public void endSuppressIndent() {
+ }
+
+ @Override
+ public void closeInline() {
+ mIndent--;
+ }
+
+ @Override
+ public void closeBlock() {
+ mIndent--;
+ }
+
+ @Override
+ public void addError(int start, int end, String message) {
+ fail(message);
+ }
+
+ @Override
+ public String finish() {
+ return mOutput.toString();
+ }
+
+ @Override
+ public void setTimeTaken(long taken) {
+ }
+
+ @Override
+ public void nameNextElement(String name) {
+ mName = name;
+ }
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/HtmlReporterTest.java b/lint/cli/src/test/java/com/android/tools/lint/HtmlReporterTest.java
new file mode 100644
index 0000000..3ab4470
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/HtmlReporterTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotations.NonNull;
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.checks.HardcodedValuesDetector;
+import com.android.tools.lint.checks.ManifestDetector;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.detector.api.DefaultPosition;
+import com.android.tools.lint.detector.api.Detector;
+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.base.Charsets;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SuppressWarnings("javadoc")
+public class HtmlReporterTest extends AbstractCheckTest {
+ public void test() throws Exception {
+ //noinspection ResultOfMethodCallIgnored
+ File projectDir = Files.createTempDir();
+ File buildDir = new File(projectDir, "build");
+ File reportFile = new File(buildDir, "report");
+ //noinspection ResultOfMethodCallIgnored
+ buildDir.mkdirs();
+
+ try {
+ LintCliClient client = new LintCliClient() {
+ @Override
+ IssueRegistry getRegistry() {
+ if (mRegistry == null) {
+ mRegistry = new IssueRegistry() {
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return Arrays.asList(
+ ManifestDetector.USES_SDK,
+ HardcodedValuesDetector.ISSUE,
+ // Not reported, but for the disabled-list
+ ManifestDetector.MOCK_LOCATION);
+ }
+ };
+ }
+ return mRegistry;
+ }
+ };
+
+ HtmlReporter reporter = new HtmlReporter(client, reportFile);
+ File res = new File(projectDir, "res");
+ File layout = new File(res, "layout");
+ File main = new File(layout, "main.xml");
+ File manifest = new File(projectDir, "AndroidManifest.xml");
+ Project project = Project.create(client, projectDir, projectDir);
+ Warning warning1 = new Warning(ManifestDetector.USES_SDK,
+ "<uses-sdk> tag should specify a target API level (the highest verified " +
+ "version; when running on later versions, compatibility behaviors may " +
+ "be enabled) with android:targetSdkVersion=\"?\"",
+ Severity.WARNING, project, null);
+ warning1.line = 6;
+ warning1.file = manifest;
+ warning1.errorLine = " <uses-sdk android:minSdkVersion=\"8\" />\n ^\n";
+ warning1.path = "AndroidManifest.xml";
+ warning1.location = Location.create(warning1.file,
+ new DefaultPosition(6, 4, 198), new DefaultPosition(6, 42, 236));
+
+ Warning warning2 = new Warning(HardcodedValuesDetector.ISSUE,
+ "[I18N] Hardcoded string \"Fooo\", should use @string resource",
+ Severity.WARNING, project, null);
+ warning2.line = 11;
+ warning2.file = main;
+ warning2.errorLine = " (java.lang.String) android:text=\"Fooo\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n";
+ warning2.path = "res/layout/main.xml";
+ warning2.location = Location.create(warning2.file,
+ new DefaultPosition(11, 8, 377), new DefaultPosition(11, 27, 396));
+
+ List<Warning> warnings = new ArrayList<Warning>();
+ warnings.add(warning1);
+ warnings.add(warning2);
+
+ reporter.write(0, 2, warnings);
+
+ String report = Files.toString(reportFile, Charsets.UTF_8);
+
+ // Replace the timestamp to make golden file comparison work
+ String timestampPrefix = "Check performed at ";
+ int begin = report.indexOf(timestampPrefix);
+ assertTrue(begin != -1);
+ begin += timestampPrefix.length();
+ int end = report.indexOf(".<br/>", begin);
+ assertTrue(end != -1);
+ report = report.substring(0, begin) + "$DATE" + report.substring(end);
+
+ // NOTE: If you change the output, please validate it manually in
+ // http://validator.w3.org/#validate_by_input
+ // before updating the following
+ assertEquals(""
+ + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ + "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ + "<head>\n"
+ + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /><title>Lint Report</title>\n"
+ + "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://fonts.googleapis.com/css?family=Roboto\" />\n"
+ + "<link rel=\"stylesheet\" type=\"text/css\" href=\"report_files/hololike.css\" />\n"
+ + "<script language=\"javascript\" type=\"text/javascript\"> \n"
+ + "<!--\n"
+ + "function reveal(id) {\n"
+ + "if (document.getElementById) {\n"
+ + "document.getElementById(id).style.display = 'block';\n"
+ + "document.getElementById(id+'Link').style.display = 'none';\n"
+ + "}\n"
+ + "}\n"
+ + "//--> \n"
+ + "</script>\n"
+ + "</head>\n"
+ + "<body>\n"
+ + "<h1>Lint Report</h1>\n"
+ + "<div class=\"titleSeparator\"></div>\n"
+ + "Check performed at $DATE.<br/>\n"
+ + "0 errors and 2 warnings found:<br/><br/>\n"
+ + "<table class=\"overview\">\n"
+ + "<tr><td></td><td class=\"categoryColumn\"><a href=\"#Correctness\">Correctness</a>\n"
+ + "</td></tr>\n"
+ + "<tr>\n"
+ + "<td class=\"countColumn\">1</td><td class=\"issueColumn\"><img border=\"0\" align=\"top\" src=\"report_files/lint-warning.png\" alt=\"Warning\" />\n"
+ + "<a href=\"#UsesMinSdkAttributes\">UsesMinSdkAttributes: Minimum SDK and target SDK attributes not defined</a>\n"
+ + "</td></tr>\n"
+ + "<tr><td></td><td class=\"categoryColumn\"><a href=\"#Internationalization\">Internationalization</a>\n"
+ + "</td></tr>\n"
+ + "<tr>\n"
+ + "<td class=\"countColumn\">1</td><td class=\"issueColumn\"><img border=\"0\" align=\"top\" src=\"report_files/lint-warning.png\" alt=\"Warning\" />\n"
+ + "<a href=\"#HardcodedText\">HardcodedText: Hardcoded text</a>\n"
+ + "</td></tr>\n"
+ + "</table>\n"
+ + "<br/>\n"
+ + "<a name=\"Correctness\"></a>\n"
+ + "<div class=\"category\"><a href=\"#\" title=\"Return to top\">Correctness</a><div class=\"categorySeparator\"></div>\n"
+ + "</div>\n"
+ + "<a name=\"UsesMinSdkAttributes\"></a>\n"
+ + "<div class=\"issue\">\n"
+ + "<div class=\"id\"><a href=\"#\" title=\"Return to top\">UsesMinSdkAttributes: Minimum SDK and target SDK attributes not defined</a><div class=\"issueSeparator\"></div>\n"
+ + "</div>\n"
+ + "<div class=\"warningslist\">\n"
+ + "<span class=\"location\"><a href=\"../AndroidManifest.xml\">AndroidManifest.xml</a>:7</span>: <span class=\"message\"><uses-sdk> tag should specify a target API level (the highest verified version; when running on later versions, compatibility behaviors may be enabled) with android:targetSdkVersion=\"?\"</span><br />\n"
+ + "</div>\n"
+ + "<div class=\"metadata\">Priority: 9 / 10<br/>\n"
+ + "Category: Correctness</div>\n"
+ + "Severity: <span class=\"warning\">Warning</span><div class=\"summary\">\n"
+ + "Explanation: Checks that the minimum SDK and target SDK attributes are defined.</div>\n"
+ + "<div class=\"explanation\">\n"
+ + "The manifest should contain a <code><uses-sdk></code> element which defines the minimum API Level required for the application to run, as well as the target version (the highest API level you have tested the version for.)\n"
+ + "</div>\n"
+ + "<br/><div class=\"moreinfo\">More info: <a href=\"http://developer.android.com/guide/topics/manifest/uses-sdk-element.html\">http://developer.android.com/guide/topics/manifest/uses-sdk-element.html</a>\n"
+ + "</div><br/>To suppress this error, use the issue id \"UsesMinSdkAttributes\" as explained in the <a href=\"#SuppressInfo\">Suppressing Warnings and Errors</a> section.<br/>\n"
+ + "</div>\n"
+ + "\n"
+ + "<a name=\"Internationalization\"></a>\n"
+ + "<div class=\"category\"><a href=\"#\" title=\"Return to top\">Internationalization</a><div class=\"categorySeparator\"></div>\n"
+ + "</div>\n"
+ + "<a name=\"HardcodedText\"></a>\n"
+ + "<div class=\"issue\">\n"
+ + "<div class=\"id\"><a href=\"#\" title=\"Return to top\">HardcodedText: Hardcoded text</a><div class=\"issueSeparator\"></div>\n"
+ + "</div>\n"
+ + "<div class=\"warningslist\">\n"
+ + "<span class=\"location\"><a href=\"../res/layout/main.xml\">res/layout/main.xml</a>:12</span>: <span class=\"message\">[I18N] Hardcoded string \"Fooo\", should use @string resource</span><br />\n"
+ + "</div>\n"
+ + "<div class=\"metadata\">Priority: 5 / 10<br/>\n"
+ + "Category: Internationalization</div>\n"
+ + "Severity: <span class=\"warning\">Warning</span><div class=\"summary\">\n"
+ + "Explanation: Looks for hardcoded text attributes which should be converted to resource lookup.</div>\n"
+ + "<div class=\"explanation\">\n"
+ + "Hardcoding text attributes directly in layout files is bad for several reasons:<br/>\n"
+ + "<br/>\n"
+ + "* When creating configuration variations (for example for landscape or portrait)you have to repeat the actual text (and keep it up to date when making changes)<br/>\n"
+ + "<br/>\n"
+ + "* The application cannot be translated to other languages by just adding new translations for existing string resources.<br/>\n"
+ + "<br/>\n"
+ + "In Android Studio and Eclipse there are quickfixes to automatically extract this hardcoded string into a resource lookup.\n"
+ + "</div>\n"
+ + "<br/><div class=\"moreinfo\">More info: </div><br/>To suppress this error, use the issue id \"HardcodedText\" as explained in the <a href=\"#SuppressInfo\">Suppressing Warnings and Errors</a> section.<br/>\n"
+ + "</div>\n"
+ + "\n"
+ + "<a name=\"MissingIssues\"></a>\n"
+ + "<div class=\"category\">Disabled Checks<div class=\"categorySeparator\"></div>\n"
+ + "</div>\n"
+ + "The following issues were not run by lint, either because the check is not enabled by default, or because it was disabled with a command line flag or via one or more lint.xml configuration files in the project directories.\n"
+ + "<br/><br/>\n"
+ + "\n"
+ + "<a name=\"SuppressInfo\"></a>\n"
+ + "<div class=\"category\">Suppressing Warnings and Errors<div class=\"categorySeparator\"></div>\n"
+ + "</div>\n"
+ + "Lint errors can be suppressed in a variety of ways:<br/>\n"
+ + "<br/>\n"
+ + "1. With a @SuppressLint annotation in the Java code<br/>\n"
+ + "2. With a tools:ignore attribute in the XML file<br/>\n"
+ + "3. With a lint.xml configuration file in the project<br/>\n"
+ + "4. With a lint.xml configuration file passed to lint via the --config flag<br/>\n"
+ + "5. With the --ignore flag passed to lint.<br/>\n"
+ + "<br/>\n"
+ + "To suppress a lint warning with an annotation, add a @SuppressLint(\"id\") annotation on the class, method or variable declaration closest to the warning instance you want to disable. The id can be one or more issue id's, such as \"UnusedResources\" or {\"UnusedResources\",\"UnusedIds\"}, or it can be \"all\" to suppress all lint warnings in the given scope.<br/>\n"
+ + "<br/>\n"
+ + "To suppress a lint warning in an XML file, add a tools:ignore=\"id\" attribute on the element containing the error, or one of its surrounding elements. You also need to define the namespace for the tools prefix on the root element in your document, next to the xmlns:android declaration:<br/>\n"
+ + "* xmlns:tools=\"http://schemas.android.com/tools\"<br/>\n"
+ + "<br/>\n"
+ + "To suppress lint warnings with a configuration XML file, create a file named lint.xml and place it at the root directory of the project in which it applies. (If you use the Eclipse plugin's Lint view, you can suppress errors there via the toolbar and Eclipse will create the lint.xml file for you.).<br/>\n"
+ + "<br/>\n"
+ + "The format of the lint.xml file is something like the following:<br/>\n"
+ + "<br/>\n"
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><br/>\n"
+ + "<lint><br/>\n"
+ + " <!-- Disable this given check in this project --><br/>\n"
+ + " <issue id=\"IconMissingDensityFolder\" severity=\"ignore\" /><br/>\n"
+ + "<br/>\n"
+ + " <!-- Ignore the ObsoleteLayoutParam issue in the given files --><br/>\n"
+ + " <issue id=\"ObsoleteLayoutParam\"><br/>\n"
+ + " <ignore path=\"res/layout/activation.xml\" /><br/>\n"
+ + " <ignore path=\"res/layout-xlarge/activation.xml\" /><br/>\n"
+ + " </issue><br/>\n"
+ + "<br/>\n"
+ + " <!-- Ignore the UselessLeaf issue in the given file --><br/>\n"
+ + " <issue id=\"UselessLeaf\"><br/>\n"
+ + " <ignore path=\"res/layout/main.xml\" /><br/>\n"
+ + " </issue><br/>\n"
+ + "<br/>\n"
+ + " <!-- Change the severity of hardcoded strings to \"error\" --><br/>\n"
+ + " <issue id=\"HardcodedText\" severity=\"error\" /><br/>\n"
+ + "</lint><br/>\n"
+ + "<br/>\n"
+ + "To suppress lint checks from the command line, pass the --ignore flag with a comma separated list of ids to be suppressed, such as:<br/>\n"
+ + "\"lint --ignore UnusedResources,UselessLeaf /my/project/path\"<br/>\n"
+ + "\n"
+ + "\n"
+ + "</body>\n"
+ + "</html>",
+ report);
+ } finally {
+ deleteFile(projectDir);
+ }
+ }
+
+ @Override
+ protected Detector getDetector() {
+ fail("Not used in this test");
+ return null;
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/LintCliXmlParserTest.java b/lint/cli/src/test/java/com/android/tools/lint/LintCliXmlParserTest.java
index ae8e8f6..cc14316 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/LintCliXmlParserTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/LintCliXmlParserTest.java
@@ -73,7 +73,7 @@
LintClient client = new TestClient();
LintDriver driver = new LintDriver(new BuiltinIssueRegistry(), client);
Project project = Project.create(client, file.getParentFile(), file.getParentFile());
- XmlContext context = new XmlContext(driver, project, null, file, null);
+ XmlContext context = new XmlContext(driver, project, null, file, null, parser);
Document document = parser.parseXml(context);
assertNotNull(document);
@@ -147,7 +147,7 @@
LintClient client = new TestClient();
LintDriver driver = new LintDriver(new BuiltinIssueRegistry(), client);
Project project = Project.create(client, file.getParentFile(), file.getParentFile());
- XmlContext context = new XmlContext(driver, project, null, file, null);
+ XmlContext context = new XmlContext(driver, project, null, file, null, parser);
Document document = parser.parseXml(context);
assertNotNull(document);
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 8737d51..33f4fa2 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
@@ -379,7 +379,6 @@
checkDriver(
"\n" +
"Scanning MainTest_testLibraries: \n" +
- "\n" +
"No issues found.\n",
"",
diff --git a/lint/cli/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java b/lint/cli/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java
new file mode 100644
index 0000000..8837fe2
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotations.NonNull;
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.checks.HardcodedValuesDetector;
+import com.android.tools.lint.checks.ManifestDetector;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.detector.api.DefaultPosition;
+import com.android.tools.lint.detector.api.Detector;
+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.base.Charsets;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class MultiProjectHtmlReporterTest extends AbstractCheckTest {
+ public void test() throws Exception {
+ File dir = new File(getTargetDir(), "report");
+ try {
+ LintCliClient client = new LintCliClient() {
+ @Override
+ IssueRegistry getRegistry() {
+ if (mRegistry == null) {
+ mRegistry = new IssueRegistry() {
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return Arrays.asList(
+ ManifestDetector.USES_SDK,
+ HardcodedValuesDetector.ISSUE,
+ // Not reported, but for the disabled-list
+ ManifestDetector.MOCK_LOCATION);
+ }
+ };
+ }
+ return mRegistry;
+ }
+ };
+
+ //noinspection ResultOfMethodCallIgnored
+ dir.mkdirs();
+ MultiProjectHtmlReporter reporter = new MultiProjectHtmlReporter(client, dir);
+ Project project = Project.create(client, new File("/foo/bar/Foo"),
+ new File("/foo/bar/Foo"));
+
+ Warning warning1 = new Warning(ManifestDetector.USES_SDK,
+ "<uses-sdk> tag should specify a target API level (the highest verified " +
+ "version; when running on later versions, compatibility behaviors may " +
+ "be enabled) with android:targetSdkVersion=\"?\"",
+ Severity.WARNING, project, null);
+ warning1.line = 6;
+ warning1.file = new File("/foo/bar/Foo/AndroidManifest.xml");
+ warning1.errorLine = " <uses-sdk android:minSdkVersion=\"8\" />\n ^\n";
+ warning1.path = "AndroidManifest.xml";
+ warning1.location = Location.create(warning1.file,
+ new DefaultPosition(6, 4, 198), new DefaultPosition(6, 42, 236));
+
+ Warning warning2 = new Warning(HardcodedValuesDetector.ISSUE,
+ "[I18N] Hardcoded string \"Fooo\", should use @string resource",
+ Severity.WARNING, project, null);
+ warning2.line = 11;
+ warning2.file = new File("/foo/bar/Foo/res/layout/main.xml");
+ warning2.errorLine = " (java.lang.String) android:text=\"Fooo\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n";
+ warning2.path = "res/layout/main.xml";
+ warning2.location = Location.create(warning2.file,
+ new DefaultPosition(11, 8, 377), new DefaultPosition(11, 27, 396));
+
+ List<Warning> warnings = new ArrayList<Warning>();
+ warnings.add(warning1);
+ warnings.add(warning2);
+
+ reporter.write(0, 2, warnings);
+
+ String report = Files.toString(new File(dir, "index.html"), Charsets.UTF_8);
+
+ // Replace the timestamp to make golden file comparison work
+ String timestampPrefix = "Check performed at ";
+ int begin = report.indexOf(timestampPrefix);
+ assertTrue(begin != -1);
+ begin += timestampPrefix.length();
+ int end = report.indexOf(".<br/>", begin);
+ assertTrue(end != -1);
+ report = report.substring(0, begin) + "$DATE" + report.substring(end);
+
+ // NOTE: If you change the output, please validate it manually in
+ // http://validator.w3.org/#validate_by_input
+ // before updating the following
+ assertEquals(""
+ + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ + "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ + "<head>\n"
+ + "<title>Lint Report</title>\n"
+ + "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://fonts.googleapis.com/css?family=Roboto\" />\n"
+ + "<link rel=\"stylesheet\" type=\"text/css\" href=\"index_files/hololike.css\" />\n"
+ + "</head>\n"
+ + "<body>\n"
+ + "<h1>Lint Report</h1>\n"
+ + "<div class=\"titleSeparator\"></div>\n"
+ + "Check performed at $DATE.<br/>\n"
+ + "0 errors and 2 warnings found:\n"
+ + "<br/><br/>\n"
+ + "<table class=\"overview\">\n"
+ + "<tr><th>Project</th><th class=\"countColumn\"><img border=\"0\" align=\"top\" src=\"index_files/lint-error.png\" alt=\"Error\" />\n"
+ + "Errors</th><th class=\"countColumn\"><img border=\"0\" align=\"top\" src=\"index_files/lint-warning.png\" alt=\"Warning\" />\n"
+ + "Warnings</th></tr>\n"
+ + "<tr><td><a href=\"Foo.html\">Foo</a></td><td class=\"countColumn\">0</td><td class=\"countColumn\">2</td></tr>\n"
+ + "</table>\n"
+ + "</body>\n"
+ + "</html>\n",
+ report);
+
+ assertTrue(new File(dir, "index_files" + File.separator + "hololike.css").exists());
+ assertTrue(new File(dir, "index_files" + File.separator + "lint-warning.png").exists());
+ assertTrue(new File(dir, "index_files" + File.separator + "lint-error.png").exists());
+ assertTrue(new File(dir, "Foo.html").exists());
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ dir.delete();
+ }
+ }
+
+ @Override
+ protected Detector getDetector() {
+ fail("Not used in this test");
+ return null;
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/ReporterTest.java b/lint/cli/src/test/java/com/android/tools/lint/ReporterTest.java
new file mode 100644
index 0000000..f1bc492
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/ReporterTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.tools.lint.Reporter.encodeUrl;
+import static com.android.tools.lint.Reporter.getRelativePath;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class ReporterTest extends TestCase {
+ private static File file(String path) {
+ return new File(path.replace('/', File.separatorChar));
+ }
+
+ public void testEncodeUrl() {
+ assertEquals("a/b/c", encodeUrl("a/b/c"));
+ assertEquals("a/b/c", encodeUrl("a\\b\\c"));
+ assertEquals("a/b/c/%24%26%2B%2C%3A%3B%3D%3F%40/foo+bar%25/d",
+ encodeUrl("a/b/c/$&+,:;=?@/foo bar%/d"));
+ assertEquals("a/%28b%29/d", encodeUrl("a/(b)/d"));
+ assertEquals("a/b+c/d", encodeUrl("a/b c/d")); // + or %20
+ }
+
+ public void testRelative() {
+ assertEquals(file("../../d/e/f").getPath(),
+ getRelativePath(file("a/b/c"), file("d/e/f")));
+ assertEquals(file("../d/e/f").getPath(),
+ getRelativePath(file("a/b/c"), file("a/d/e/f")));
+ assertEquals(file("../d/e/f").getPath(),
+ getRelativePath(file("1/2/3/a/b/c"), file("1/2/3/a/d/e/f")));
+ assertEquals(file("c").getPath(),
+ getRelativePath(file("a/b/c"), file("a/b/c")));
+ assertEquals(file("../../e").getPath(),
+ getRelativePath(file("a/b/c/d/e/f"), file("a/b/c/e")));
+ assertEquals(file("d/e/f").getPath(),
+ getRelativePath(file("a/b/c/e"), file("a/b/c/d/e/f")));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/TextReporterTest.java b/lint/cli/src/test/java/com/android/tools/lint/TextReporterTest.java
new file mode 100644
index 0000000..9726908
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/TextReporterTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.HardcodedValuesDetector;
+import com.android.tools.lint.checks.ManifestDetector;
+import com.android.tools.lint.detector.api.DefaultPosition;
+import com.android.tools.lint.detector.api.Detector;
+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.base.Charsets;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TextReporterTest extends AbstractCheckTest {
+ public void test() throws Exception {
+ File file = new File(getTargetDir(), "report");
+ try {
+ LintCliClient client = new LintCliClient() {
+ @Override
+ String getRevision() {
+ return "unittest"; // Hardcode version to keep unit test output stable
+ }
+ };
+ //noinspection ResultOfMethodCallIgnored
+ file.getParentFile().mkdirs();
+ FileWriter writer = new FileWriter(file);
+ TextReporter reporter = new TextReporter(client, client.mFlags, file, writer, true);
+ Project project = Project.create(client, new File("/foo/bar/Foo"),
+ new File("/foo/bar/Foo"));
+ client.mFlags.setShowEverything(true);
+
+ Warning warning1 = new Warning(ManifestDetector.USES_SDK,
+ "<uses-sdk> tag should specify a target API level (the highest verified " +
+ "version; when running on later versions, compatibility behaviors may " +
+ "be enabled) with android:targetSdkVersion=\"?\"",
+ Severity.WARNING, project, null);
+ warning1.line = 6;
+ warning1.file = new File("/foo/bar/Foo/AndroidManifest.xml");
+ warning1.errorLine = " <uses-sdk android:minSdkVersion=\"8\" />\n ^\n";
+ warning1.path = "AndroidManifest.xml";
+ warning1.location = Location.create(warning1.file,
+ new DefaultPosition(6, 4, 198), new DefaultPosition(6, 42, 236));
+ Location secondary = Location.create(warning1.file,
+ new DefaultPosition(7, 4, 198), new DefaultPosition(7, 42, 236));
+ secondary.setMessage("Secondary location");
+ warning1.location.setSecondary(secondary);
+
+ Warning warning2 = new Warning(HardcodedValuesDetector.ISSUE,
+ "[I18N] Hardcoded string \"Fooo\", should use @string resource",
+ Severity.WARNING, project, null);
+ warning2.line = 11;
+ warning2.file = new File("/foo/bar/Foo/res/layout/main.xml");
+ warning2.errorLine = " android:text=\"Fooo\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n";
+ warning2.path = "res/layout/main.xml";
+ warning2.location = Location.create(warning2.file,
+ new DefaultPosition(11, 8, 377), new DefaultPosition(11, 27, 396));
+ secondary = Location.create(warning1.file,
+ new DefaultPosition(7, 4, 198), new DefaultPosition(7, 42, 236));
+ secondary.setMessage("Secondary location");
+ warning2.location.setSecondary(secondary);
+ Location tertiary = Location.create(warning2.file,
+ new DefaultPosition(5, 4, 198), new DefaultPosition(5, 42, 236));
+ secondary.setSecondary(tertiary);
+
+ List<Warning> warnings = new ArrayList<Warning>();
+ warnings.add(warning1);
+ warnings.add(warning2);
+ Collections.sort(warnings);
+
+ reporter.write(0, 2, warnings);
+
+ String report = Files.toString(file, Charsets.UTF_8);
+ assertEquals(""
+ + "AndroidManifest.xml:7: Warning: <uses-sdk> tag should specify a target API level (the highest verified version; when running on later versions, compatibility behaviors may be enabled) with android:targetSdkVersion=\"?\" [UsesMinSdkAttributes]\n"
+ + " <uses-sdk android:minSdkVersion=\"8\" />\n"
+ + " ^\n"
+ + " AndroidManifest.xml:8: Secondary location\n"
+ + "res/layout/main.xml:12: Warning: [I18N] Hardcoded string \"Fooo\", should use @string resource [HardcodedText]\n"
+ + " android:text=\"Fooo\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + " AndroidManifest.xml:8: Secondary location\n"
+ + "Also affects: res/layout/main.xml:6\n"
+ + "0 errors, 2 warnings\n",
+ report);
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ file.delete();
+ }
+ }
+
+ public void testWithExplanations() throws Exception {
+ File file = new File(getTargetDir(), "report");
+ try {
+ LintCliClient client = new LintCliClient() {
+ @Override
+ String getRevision() {
+ return "unittest"; // Hardcode version to keep unit test output stable
+ }
+ };
+ //noinspection ResultOfMethodCallIgnored
+ file.getParentFile().mkdirs();
+ FileWriter writer = new FileWriter(file);
+ TextReporter reporter = new TextReporter(client, client.mFlags, file, writer, true);
+ client.mFlags.setExplainIssues(true);
+ Project project = Project.create(client, new File("/foo/bar/Foo"),
+ new File("/foo/bar/Foo"));
+ client.mFlags.setShowEverything(true);
+
+ Warning warning1 = new Warning(ManifestDetector.USES_SDK,
+ "<uses-sdk> tag should specify a target API level (the highest verified " +
+ "version; when running on later versions, compatibility behaviors may " +
+ "be enabled) with android:targetSdkVersion=\"?\"",
+ Severity.WARNING, project, null);
+ warning1.line = 6;
+ warning1.file = new File("/foo/bar/Foo/AndroidManifest.xml");
+ warning1.errorLine = " <uses-sdk android:minSdkVersion=\"8\" />\n ^\n";
+ warning1.path = "AndroidManifest.xml";
+ warning1.location = Location.create(warning1.file,
+ new DefaultPosition(6, 4, 198), new DefaultPosition(6, 42, 236));
+ Location secondary = Location.create(warning1.file,
+ new DefaultPosition(7, 4, 198), new DefaultPosition(7, 42, 236));
+ secondary.setMessage("Secondary location");
+ warning1.location.setSecondary(secondary);
+
+ Warning warning2 = new Warning(HardcodedValuesDetector.ISSUE,
+ "[I18N] Hardcoded string \"Fooo\", should use @string resource",
+ Severity.WARNING, project, null);
+ warning2.line = 11;
+ warning2.file = new File("/foo/bar/Foo/res/layout/main.xml");
+ warning2.errorLine = " android:text=\"Fooo\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n";
+ warning2.path = "res/layout/main.xml";
+ warning2.location = Location.create(warning2.file,
+ new DefaultPosition(11, 8, 377), new DefaultPosition(11, 27, 396));
+ secondary = Location.create(warning1.file,
+ new DefaultPosition(7, 4, 198), new DefaultPosition(7, 42, 236));
+ secondary.setMessage("Secondary location");
+ warning2.location.setSecondary(secondary);
+ Location tertiary = Location.create(warning2.file,
+ new DefaultPosition(5, 4, 198), new DefaultPosition(5, 42, 236));
+ secondary.setSecondary(tertiary);
+
+ // Add another warning of the same type as warning 1 to make sure we
+ // (1) sort the warnings of the same issue together and (2) only print
+ // the explanation twice1
+ Warning warning3 = new Warning(ManifestDetector.USES_SDK,
+ "<uses-sdk> tag should specify a target API level (the highest verified " +
+ "version; when running on later versions, compatibility behaviors may " +
+ "be enabled) with android:targetSdkVersion=\"?\"",
+ Severity.WARNING, project, null);
+ warning3.line = 8;
+ warning3.file = new File("/foo/bar/Foo/AndroidManifest.xml");
+ warning3.errorLine = " <uses-sdk android:minSdkVersion=\"8\" />\n ^\n";
+ warning3.path = "AndroidManifest.xml";
+ warning3.location = Location.create(warning3.file,
+ new DefaultPosition(8, 4, 198), new DefaultPosition(8, 42, 236));
+
+ List<Warning> warnings = new ArrayList<Warning>();
+ warnings.add(warning1);
+ warnings.add(warning2);
+ warnings.add(warning3);
+ Collections.sort(warnings);
+
+ reporter.write(0, 3, warnings);
+
+ String report = Files.toString(file, Charsets.UTF_8);
+ assertEquals(""
+ + "AndroidManifest.xml:7: Warning: <uses-sdk> tag should specify a target API level (the highest verified version; when running on later versions, compatibility behaviors may be enabled) with android:targetSdkVersion=\"?\" [UsesMinSdkAttributes]\n"
+ + " <uses-sdk android:minSdkVersion=\"8\" />\n"
+ + " ^\n"
+ + " AndroidManifest.xml:8: Secondary location\n"
+ + "AndroidManifest.xml:9: Warning: <uses-sdk> tag should specify a target API level (the highest verified version; when running on later versions, compatibility behaviors may be enabled) with android:targetSdkVersion=\"?\" [UsesMinSdkAttributes]\n"
+ + " <uses-sdk android:minSdkVersion=\"8\" />\n"
+ + " ^\n"
+ + "\n"
+ + " Explanation for issues of type \"UsesMinSdkAttributes\":\n"
+ + " The manifest should contain a <uses-sdk> element which defines the minimum\n"
+ + " API Level required for the application to run, as well as the target\n"
+ + " version (the highest API level you have tested the version for.)\n"
+ + "\n"
+ + " http://developer.android.com/guide/topics/manifest/uses-sdk-element.html\n"
+ + "\n"
+ + "res/layout/main.xml:12: Warning: [I18N] Hardcoded string \"Fooo\", should use @string resource [HardcodedText]\n"
+ + " android:text=\"Fooo\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + " AndroidManifest.xml:8: Secondary location\n"
+ + "Also affects: res/layout/main.xml:6\n"
+ + "\n"
+ + " Explanation for issues of type \"HardcodedText\":\n"
+ + " Hardcoding text attributes directly in layout files is bad for several\n"
+ + " reasons:\n"
+ + "\n"
+ + " * When creating configuration variations (for example for landscape or\n"
+ + " portrait)you have to repeat the actual text (and keep it up to date when\n"
+ + " making changes)\n"
+ + "\n"
+ + " * The application cannot be translated to other languages by just adding\n"
+ + " new translations for existing string resources.\n"
+ + "\n"
+ + " In Android Studio and Eclipse there are quickfixes to automatically extract\n"
+ + " this hardcoded string into a resource lookup.\n"
+ + "\n"
+ + "0 errors, 3 warnings\n",
+ report);
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ file.delete();
+ }
+ }
+
+ @Override
+ protected Detector getDetector() {
+ fail("Not used in this test");
+ return null;
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/XmlReporterTest.java b/lint/cli/src/test/java/com/android/tools/lint/XmlReporterTest.java
index b743fa1..99d01c6 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/XmlReporterTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/XmlReporterTest.java
@@ -48,6 +48,7 @@
return "unittest"; // Hardcode version to keep unit test output stable
}
};
+ //noinspection ResultOfMethodCallIgnored
file.getParentFile().mkdirs();
XmlReporter reporter = new XmlReporter(client, file);
Project project = Project.create(client, new File("/foo/bar/Foo"),
@@ -70,7 +71,7 @@
Severity.WARNING, project, null);
warning2.line = 11;
warning2.file = new File("/foo/bar/Foo/res/layout/main.xml");
- warning2.errorLine = " (java.lang.String) android:text=\"Fooo\" />\n" +
+ warning2.errorLine = " android:text=\"Fooo\" />\n" +
" ~~~~~~~~~~~~~~~~~~~\n";
warning2.path = "res/layout/main.xml";
warning2.location = Location.create(warning2.file,
@@ -118,8 +119,8 @@
"\n" +
"* The application cannot be translated to other languages by just adding new translations for existing string resources.\n" +
"\n" +
- "In Eclipse there is a quickfix to automatically extract this hardcoded string into a resource lookup.\"\n" +
- " errorLine1=\" (java.lang.String) android:text="Fooo" />\"\n" +
+ "In Android Studio and Eclipse there are quickfixes to automatically extract this hardcoded string into a resource lookup.\"\n" +
+ " errorLine1=\" android:text="Fooo" />\"\n" +
" errorLine2=\" ~~~~~~~~~~~~~~~~~~~\">\n" +
" <location\n" +
" file=\"res/layout/main.xml\"\n" +
@@ -135,6 +136,7 @@
assertNotNull(document);
assertEquals(2, document.getElementsByTagName("issue").getLength());
} finally {
+ //noinspection ResultOfMethodCallIgnored
file.delete();
}
}
@@ -150,6 +152,7 @@
};
client.mFlags.setFullPath(true);
+ //noinspection ResultOfMethodCallIgnored
file.getParentFile().mkdirs();
XmlReporter reporter = new XmlReporter(client, file);
Project project = Project.create(client, new File("/foo/bar/Foo"),
@@ -172,7 +175,7 @@
Severity.WARNING, project, null);
warning2.line = 11;
warning2.file = new File("/foo/bar/Foo/res/layout/main.xml");
- warning2.errorLine = " (java.lang.String) android:text=\"Fooo\" />\n" +
+ warning2.errorLine = " android:text=\"Fooo\" />\n" +
" ~~~~~~~~~~~~~~~~~~~\n";
warning2.path = "res/layout/main.xml";
warning2.location = Location.create(warning2.file,
@@ -220,8 +223,8 @@
"\n" +
"* The application cannot be translated to other languages by just adding new translations for existing string resources.\n" +
"\n" +
- "In Eclipse there is a quickfix to automatically extract this hardcoded string into a resource lookup.\"\n" +
- " errorLine1=\" (java.lang.String) android:text="Fooo" />\"\n" +
+ "In Android Studio and Eclipse there are quickfixes to automatically extract this hardcoded string into a resource lookup.\"\n" +
+ " errorLine1=\" android:text="Fooo" />\"\n" +
" errorLine2=\" ~~~~~~~~~~~~~~~~~~~\">\n" +
" <location\n" +
" file=\"/foo/bar/Foo/res/layout/main.xml\"\n" +
@@ -237,6 +240,7 @@
assertNotNull(document);
assertEquals(2, document.getElementsByTagName("issue").getLength());
} finally {
+ //noinspection ResultOfMethodCallIgnored
file.delete();
}
}
@@ -251,6 +255,7 @@
return "unittest"; // Hardcode version to keep unit test output stable
}
};
+ //noinspection ResultOfMethodCallIgnored
file.getParentFile().mkdirs();
XmlReporter reporter = new XmlReporter(client, file);
Project project = Project.create(client, new File("/foo/bar/Foo"),
@@ -306,6 +311,7 @@
assertEquals(TypographyDetector.FRACTIONS.getExplanation(Issue.OutputFormat.RAW),
explanation);
} finally {
+ //noinspection ResultOfMethodCallIgnored
file.delete();
}
}
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 b575061..5bb7cf0 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
@@ -16,20 +16,31 @@
package com.android.tools.lint.checks;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.DuplicateDataException;
+import com.android.ide.common.res2.MergingException;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.ide.common.res2.ResourceMerger;
+import com.android.ide.common.res2.ResourceRepository;
+import com.android.ide.common.res2.ResourceSet;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.sdklib.IAndroidTarget;
import com.android.testutils.SdkTestCase;
import com.android.tools.lint.LintCliClient;
import com.android.tools.lint.LintCliFlags;
-import com.android.tools.lint.LintCliXmlParser;
-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;
-import com.android.tools.lint.client.api.IJavaParser;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.client.api.LintDriver;
@@ -37,11 +48,27 @@
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
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.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.ILogger;
import com.android.utils.SdkUtils;
+import com.android.utils.StdLogger;
+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.Files;
+
+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.BufferedInputStream;
import java.io.File;
@@ -56,8 +83,11 @@
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/** Common utility methods for the various lint check tests */
@SuppressWarnings("javadoc")
@@ -112,14 +142,32 @@
files.add(file);
}
+ Collections.sort(files, new Comparator<File>() {
+ @Override
+ public int compare(File file1, File file2) {
+ ResourceFolderType folder1 = ResourceFolderType.getFolderType(
+ file1.getParentFile().getName());
+ ResourceFolderType folder2 = ResourceFolderType.getFolderType(
+ file2.getParentFile().getName());
+ if (folder1 != null && folder2 != null && folder1 != folder2) {
+ return folder1.compareTo(folder2);
+ }
+ return file1.compareTo(file2);
+ }
+ });
+
addManifestFile(targetDir);
return checkLint(files);
}
protected String checkLint(List<File> files) throws Exception {
- mOutput = new StringBuilder();
TestLintClient lintClient = createClient();
+ return checkLint(lintClient, files);
+ }
+
+ protected String checkLint(TestLintClient lintClient, List<File> files) throws Exception {
+ mOutput = new StringBuilder();
String result = lintClient.analyze(files);
// The output typically contains a few directory/filenames.
@@ -157,6 +205,16 @@
return checkLint(Collections.singletonList(projectDir));
}
+ protected String lintProjectIncrementally(String currentFile, String... relativePaths)
+ throws Exception {
+ File projectDir = getProjectDir(null, relativePaths);
+ File current = new File(projectDir, currentFile.replace('/', File.separatorChar));
+ assertTrue(current.exists());
+ TestLintClient client = createClient();
+ client.setIncremental(current);
+ return checkLint(client, Collections.singletonList(projectDir));
+ }
+
@Override
protected File getTargetDir() {
File targetDir = new File(getTempDir(), getClass().getSimpleName() + "_" + getName());
@@ -268,6 +326,7 @@
public class TestLintClient extends LintCliClient {
private StringWriter mWriter = new StringWriter();
+ private File mIncrementalCheck;
public TestLintClient() {
super(new LintCliFlags());
@@ -287,7 +346,18 @@
public String analyze(List<File> files) throws Exception {
mDriver = new LintDriver(new CustomIssueRegistry(), this);
configureDriver(mDriver);
- mDriver.analyze(new LintRequest(this, files).setScope(getLintScope(files)));
+ LintRequest request = new LintRequest(this, files);
+ if (mIncrementalCheck != null) {
+ assertEquals(1, files.size());
+ File projectDir = files.get(0);
+ assertTrue(isProjectDirectory(projectDir));
+ Project project = createProject(projectDir, projectDir);
+ project.addFile(mIncrementalCheck);
+ List<Project> projects = Collections.singletonList(project);
+ request.setProjects(projects);
+ }
+
+ mDriver.analyze(request.setScope(getLintScope(files)));
// Check compare contract
Warning prev = null;
@@ -332,7 +402,7 @@
}
String result = mOutput.toString();
- if (result.equals("\nNo issues found.\n")) {
+ if (result.equals("No issues found.\n")) {
result = "No warnings.";
}
@@ -366,10 +436,16 @@
// specifically included in the text report
if (location != null && location.getSecondary() != null) {
Location l = location.getSecondary();
+ if (l == location) {
+ fail("Location link cycle");
+ }
while (l != null) {
if (l.getMessage() == null) {
l.setMessage("<No location-specific message");
}
+ if (l == l.getSecondary()) {
+ fail("Location link cycle");
+ }
l = l.getSecondary();
}
}
@@ -379,6 +455,7 @@
// Make sure errors are unique!
Warning prev = null;
for (Warning warning : mWarnings) {
+ assertNotSame(warning, prev);
assert prev == null || !warning.equals(prev);
prev = warning;
}
@@ -404,16 +481,6 @@
}
@Override
- public IDomParser getDomParser() {
- return new LintCliXmlParser();
- }
-
- @Override
- public IJavaParser getJavaParser() {
- return new LombokParser();
- }
-
- @Override
public Configuration getConfiguration(@NonNull Project project) {
return AbstractCheckTest.this.getConfiguration(this, project);
}
@@ -467,6 +534,138 @@
// Don't pick up random custom rules in ~/.android/lint when running unit tests
return Collections.emptyList();
}
+
+ public void setIncremental(File currentFile) {
+ mIncrementalCheck = currentFile;
+ }
+
+ @Override
+ public boolean supportsProjectResources() {
+ return mIncrementalCheck != null;
+ }
+
+ @Nullable
+ @Override
+ public AbstractResourceRepository getProjectResources(Project project,
+ boolean includeDependencies) {
+ if (mIncrementalCheck == null) {
+ return null;
+ }
+
+ ResourceRepository repository = new ResourceRepository(false);
+ ILogger logger = new StdLogger(StdLogger.Level.INFO);
+ ResourceMerger merger = new ResourceMerger();
+
+ ResourceSet resourceSet = new ResourceSet(getName()) {
+ @Override
+ protected void checkItems() throws DuplicateDataException {
+ // No checking in ProjectResources; duplicates can happen, but
+ // the project resources shouldn't abort initialization
+ }
+ };
+ // Only support 1 resource folder in test setup right now
+ assertEquals(1, project.getResourceFolders().size());
+ resourceSet.addSource(project.getResourceFolders().get(0));
+ try {
+ resourceSet.loadFromFiles(logger);
+ merger.addDataSet(resourceSet);
+ merger.mergeData(repository.createMergeConsumer(), true);
+
+ // Workaround: The repository does not insert ids from layouts! We need
+ // to do that here.
+ Map<ResourceType,ListMultimap<String,ResourceItem>> items = repository.getItems();
+ ListMultimap<String, ResourceItem> layouts = items
+ .get(ResourceType.LAYOUT);
+ if (layouts != null) {
+ for (ResourceItem item : layouts.values()) {
+ ResourceFile source = item.getSource();
+ if (source == null) {
+ continue;
+ }
+ File file = source.getFile();
+ try {
+ String xml = Files.toString(file, Charsets.UTF_8);
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ assertNotNull(document);
+ Set<String> ids = Sets.newHashSet();
+ addIds(ids, document); // TODO: pull parser
+ if (!ids.isEmpty()) {
+ ListMultimap<String, ResourceItem> idMap =
+ items.get(ResourceType.ID);
+ if (idMap == null) {
+ idMap = ArrayListMultimap.create();
+ items.put(ResourceType.ID, idMap);
+ }
+ for (String id : ids) {
+ ResourceItem idItem = new ResourceItem(id, ResourceType.ID,
+ null);
+ String qualifiers = file.getParentFile().getName();
+ if (qualifiers.startsWith("layout-")) {
+ qualifiers = qualifiers.substring("layout-".length());
+ } else if (qualifiers.equals("layout")) {
+ qualifiers = "";
+ }
+ idItem.setSource(new ResourceFile(file, item, qualifiers));
+ idMap.put(id, idItem);
+ }
+ }
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ }
+ }
+ }
+ catch (DuplicateDataException e) {
+ fail(e.getMessage());
+ }
+ catch (MergingException e) {
+ fail(e.getMessage());
+ }
+
+ return repository;
+ }
+
+ private void addIds(Set<String> ids, Node node) {
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if (id != null && !id.isEmpty()) {
+ ids.add(LintUtils.stripIdPrefix(id));
+ }
+
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ String value = attribute.getValue();
+ if (value.startsWith(NEW_ID_PREFIX)) {
+ ids.add(value.substring(NEW_ID_PREFIX.length()));
+ }
+ }
+ }
+
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ addIds(ids, child);
+ }
+ }
+
+ @Nullable
+ @Override
+ public IAndroidTarget getCompileTarget(@NonNull Project project) {
+ IAndroidTarget compileTarget = super.getCompileTarget(project);
+ if (compileTarget == null) {
+ IAndroidTarget[] targets = getTargets();
+ for (int i = targets.length - 1; i >= 0; i--) {
+ IAndroidTarget target = targets[i];
+ if (target.isPlatform()) {
+ return target;
+ }
+ }
+ }
+
+ return compileTarget;
+ }
}
/**
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetectorTest.java
new file mode 100644
index 0000000..f644823
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetectorTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 AddJavascriptInterfaceDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new AddJavascriptInterfaceDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/AddJavascriptInterfaceTest.java:16: Warning: WebView.addJavascriptInterface should not be called [AddJavascriptInterface]\n"
+ + " webView.addJavascriptInterface(object, string);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AddJavascriptInterfaceTest.java:23: Warning: WebView.addJavascriptInterface should not be called [AddJavascriptInterface]\n"
+ + " webView.addJavascriptInterface(object, string);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/AddJavascriptInterfaceTest.java.txt=>src/test/pkg/AddJavascriptInterfaceTest.java",
+ "bytecode/AddJavascriptInterfaceTest.class.data=>bin/classes/test/pkg/AddJavascriptInterfaceTest.class",
+ "bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class.data=>"
+ + "bin/classes/test/pkg/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class",
+ "bytecode/AddJavascriptInterfaceTest$WebViewChild.class.data=>"
+ + "bin/classes/test/pkg/AddJavascriptInterfaceTest$WebViewChild.class",
+ "bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebViewChild.class.data=>"
+ + "bin/classes/test/pkg/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebViewChild.class",
+ "bytecode/AddJavascriptInterfaceTest$NonWebView.class.data=>"
+ + "bin/classes/test/pkg/AddJavascriptInterfaceTest$NonWebView.class",
+ "bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnNonWebView.class.data=>"
+ + "bin/classes/test/pkg/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnNonWebView.class"
+ ));
+ }
+
+ public void testNoWarningWhenMinSdkAt17() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifestMinSdk17.xml=>AndroidManifest.xml",
+ "bytecode/AddJavascriptInterfaceTest.java.txt=>src/test/pkg/AddJavascriptInterfaceTest.java",
+ "bytecode/AddJavascriptInterfaceTest.class.data=>bin/classes/test/pkg/AddJavascriptInterfaceTest.class",
+ "bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class.data=>"
+ + "bin/classes/test/pkg/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class"
+ ));
+ }
+}
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 9a765a2..22d17bb 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
@@ -16,9 +16,12 @@
package com.android.tools.lint.checks;
+import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Project;
+import java.io.File;
+
@SuppressWarnings("javadoc")
public class ApiDetectorTest extends AbstractCheckTest {
@Override
@@ -62,17 +65,71 @@
public void testAttrWithoutSlash() throws Exception {
assertEquals(""
- + "res/layout/divider.xml:7: Error: ?android:dividerHorizontal requires API level 11 (current min is 1) [NewApi]\n"
- + " android:divider=\"?android:dividerHorizontal\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/attribute.xml:4: Error: ?android:indicatorStart requires API level 18 (current min is 1) [NewApi]\n"
+ + " android:enabled=\"?android:indicatorStart\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
lintProject(
"apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/attribute.xml=>res/layout/attribute.xml"
+ ));
+ }
+
+ public void testUnusedShowDividers() throws Exception {
+ assertEquals(""
+ + "res/layout/divider.xml:9: Warning: Attribute \"showDividers\" is only used in API level 11 and higher (current min is 1) [UnusedAttribute]\n"
+ + " android:showDividers=\"middle\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
"apicheck/divider.xml=>res/layout/divider.xml"
));
}
+ public void testUnusedOnSomeVersions1() throws Exception {
+ assertEquals(""
+ + "res/layout/attribute2.xml:4: Error: switchTextAppearance requires API level 14 (current min is 1), but note that attribute editTextColor is only used in API level 11 and higher [NewApi]\n"
+ + " android:editTextColor=\"?android:switchTextAppearance\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/attribute2.xml:4: Warning: Attribute \"editTextColor\" is only used in API level 11 and higher (current min is 1) [UnusedAttribute]\n"
+ + " android:editTextColor=\"?android:switchTextAppearance\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/attribute2.xml=>res/layout/attribute2.xml"
+ ));
+ }
+
+ public void testXmlApi() throws Exception {
+ assertEquals(""
+ + "res/layout/attribute2.xml:4: Error: ?android:switchTextAppearance requires API level 14 (current min is 11) [NewApi]\n"
+ + " android:editTextColor=\"?android:switchTextAppearance\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk11.xml=>AndroidManifest.xml",
+ "apicheck/attribute2.xml=>res/layout/attribute2.xml"
+ ));
+ }
+
+ public void testReportAttributeName() throws Exception {
+ assertEquals("res/layout/layout.xml:13: Warning: Attribute \"layout_row\" is only used in API level 14 and higher (current min is 4) [UnusedAttribute]\n"
+ + " android:layout_row=\"2\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/layoutattr.xml=>res/layout/layout.xml"
+ ));
+ }
+
public void testXmlApi14() throws Exception {
assertEquals(
"No warnings.",
@@ -147,6 +204,18 @@
));
}
+ public void testThemeVersion() throws Exception {
+ assertEquals(""
+ + "res/values/themes3.xml:3: Error: android:Theme.Holo.Light.DarkActionBar requires API level 14 (current min is 4) [NewApi]\n"
+ + " <style name=\"AppTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/values/themes3.xml"
+ ));
+ }
+
public void testApi1() throws Exception {
assertEquals(
"src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar [NewApi]\n" +
@@ -946,4 +1015,63 @@
"apicheck/ApiCallTest14$3.class.data=>bin/classes/test/pkg/ApiCallTest14$3.class"
));
}
+
+ public void testTryWithResources() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/TryWithResources.java:13: Error: Try-with-resources requires API level 19 (current min is 1) [NewApi]\n"
+ + " try (BufferedReader br = new BufferedReader(new FileReader(path))) {\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "src/test/pkg/TryWithResources.java.txt=>src/test/pkg/TryWithResources.java"
+ ));
+ }
+
+ public void testTryWithResourcesOk() throws Exception {
+ assertEquals(""
+ + "No warnings.",
+ lintProject(
+ "apicheck/minsdk19.xml=>AndroidManifest.xml",
+ "src/test/pkg/TryWithResources.java.txt=>src/test/pkg/TryWithResources.java"
+ ));
+ }
+
+ public void testMissingApiDatabase() throws Exception {
+ ApiLookup.dispose();
+ assertEquals(""
+ + "ApiDetectorTest_testMissingApiDatabase: Error: Can't find API database; API check not performed [LintError]\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "apicheck/classpath=>.classpath",
+ "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
+ ));
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ if (getName().equals("testMissingApiDatabase")) {
+ // Simulate an environment where there is no API database
+ return new TestLintClient() {
+ @Override
+ public File findResource(@NonNull String relativePath) {
+ return null;
+ }
+ };
+ }
+ return super.createClient();
+ }
+
+ @Override
+ protected boolean ignoreSystemErrors() {
+ if (getName().equals("testMissingApiDatabase")) {
+ return false;
+ }
+ return super.ignoreSystemErrors();
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
index eaba6e6..7a7fa28 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
@@ -106,8 +106,11 @@
}
private File mCacheDir;
+ @SuppressWarnings("StringBufferField")
private StringBuilder mLogBuffer = new StringBuilder();
+ @SuppressWarnings({"ConstantConditions", "IOResourceOpenedButNotSafelyClosed",
+ "ResultOfMethodCallIgnored"})
public void testCorruptedCacheHandling() throws Exception {
ApiLookup lookup;
@@ -132,7 +135,8 @@
// Now truncate cache file
File cacheFile = new File(mCacheDir,
- ApiLookup.getCacheFileName("api-versions.xml")); //$NON-NLS-1$
+ ApiLookup.getCacheFileName("api-versions.xml",
+ ApiLookup.getPlatformVersion(new LookupTestClient()))); //$NON-NLS-1$
mLogBuffer.setLength(0);
assertTrue(cacheFile.exists());
RandomAccessFile raf = new RandomAccessFile(cacheFile, "rw");
@@ -181,6 +185,7 @@
}
private final class LookupTestClient extends TestLintClient {
+ @SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public File getCacheDir(boolean create) {
assertNotNull(mCacheDir);
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/AppCompatCallDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/AppCompatCallDetectorTest.java
new file mode 100644
index 0000000..8953344
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/AppCompatCallDetectorTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 AppCompatCallDetectorTest extends AbstractCheckTest {
+ public void testArguments() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/AppCompatTest.java:5: Warning: Should use getSupportActionBar instead of getActionBar name [AppCompatMethod]\n"
+ + " getActionBar(); // ERROR\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:8: Warning: Should use startSupportActionMode instead of startActionMode name [AppCompatMethod]\n"
+ + " startActionMode(null); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:11: Warning: Should use supportRequestWindowFeature instead of requestWindowFeature name [AppCompatMethod]\n"
+ + " requestWindowFeature(0); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:14: Warning: Should use setSupportProgressBarVisibility instead of setProgressBarVisibility name [AppCompatMethod]\n"
+ + " setProgressBarVisibility(true); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:15: Warning: Should use setSupportProgressBarIndeterminate instead of setProgressBarIndeterminate name [AppCompatMethod]\n"
+ + " setProgressBarIndeterminate(true); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:16: Warning: Should use setSupportProgressBarIndeterminateVisibility instead of setProgressBarIndeterminateVisibility name [AppCompatMethod]\n"
+ + " setProgressBarIndeterminateVisibility(true); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 6 warnings\n",
+ lintProject(
+ "bytecode/classes.jar=>libs/appcompat-v7-18.0.0.jar",
+ "appcompat/AppCompatTest.java.txt=>src/test/pkg/AppCompatTest.java",
+ "appcompat/IntermediateActivity.java.txt=>src/test/pkg/IntermediateActivity.java",
+ // Stubs just to be able to do type resolution without needing the full appcompat jar
+ "appcompat/ActionBarActivity.java.txt=>src/android/support/v7/app/ActionBarActivity.java",
+ "appcompat/ActionMode.java.txt=>src/android/support/v7/view/ActionMode.java"
+ ));
+ }
+
+ public void testNoWarningsWithoutAppCompat() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "appcompat/AppCompatTest.java.txt=>src/test/pkg/AppCompatTest.java",
+ "appcompat/IntermediateActivity.java.txt=>src/test/pkg/IntermediateActivity.java",
+ "appcompat/ActionBarActivity.java.txt=>src/android/support/v7/app/ActionBarActivity.java",
+ "appcompat/ActionMode.java.txt=>src/android/support/v7/view/ActionMode.java"
+ ));
+ }
+
+ @Override
+ protected Detector getDetector() {
+ return new AppCompatCallDetector();
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/AppCompatResourceDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/AppCompatResourceDetectorTest.java
new file mode 100644
index 0000000..e7f8613
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/AppCompatResourceDetectorTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 AppCompatResourceDetectorTest extends AbstractCheckTest {
+ public void testNotGradleProject() throws Exception {
+ assertEquals("No warnings.",
+ lintProject("res/menu/showAction1.xml"));
+ }
+
+ public void testNoAppCompat() throws Exception {
+ assertEquals(""
+ + "res/menu/showAction1.xml:6: Error: Should use android:showAsAction when not using the appcompat library [AppCompatResource]\n"
+ + " app:showAsAction=\"never\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "res/menu/showAction1.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ public void testCorrectAppCompat() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "res/menu/showAction1.xml",
+ "bytecode/classes.jar=>libs/appcompat-v7-18.0.0.jar",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ public void testWrongAppCompat() throws Exception {
+ assertEquals(""
+ + "res/menu/showAction2.xml:5: Error: Should use app:showAsAction with the appcompat library with xmlns:app=\"http://schemas.android.com/apk/res-auto\" [AppCompatResource]\n"
+ + " android:showAsAction=\"never\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "res/menu/showAction2.xml",
+ "bytecode/classes.jar=>libs/appcompat-v7-18.0.0.jar",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ public void testAppCompatV14() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "res/menu/showAction2.xml=>res/menu-v14/showAction2.xml",
+ "bytecode/classes.jar=>libs/appcompat-v7-18.0.0.jar",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ @Override
+ protected Detector getDetector() {
+ return new AppCompatResourceDetector();
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ArraySizeDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ArraySizeDetectorTest.java
index 127e7f8..3692699 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ArraySizeDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ArraySizeDetectorTest.java
@@ -68,4 +68,23 @@
"res/values/arrays.xml",
"res/values-land/arrays_ignore.xml=>res/values-land/arrays.xml"));
}
+
+ public void testArraySizesIncremental() throws Exception {
+ assertEquals(""
+ + "res/values/arrays.xml:3: Warning: Array security_questions has an inconsistent number of items (4 in values/arrays.xml, 3 in values-nl-rNL/arrays.xml) [InconsistentArrays]\n"
+ + " <string-array name=\"security_questions\">\n"
+ + " ^\n"
+ + "res/values/arrays.xml:10: Warning: Array signal_strength has an inconsistent number of items (5 in values/arrays.xml, 6 in values-land/arrays.xml) [InconsistentArrays]\n"
+ + " <array name=\"signal_strength\">\n"
+ + " ^\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProjectIncrementally("res/values/arrays.xml",
+ "res/values/arrays.xml",
+ "res/values-cs/arrays.xml",
+ "res/values-land/arrays.xml",
+ "res/values-nl-rNL/arrays.xml",
+ "res/values-es/strings.xml"));
+ }
+
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/AssertDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/AssertDetectorTest.java
new file mode 100644
index 0000000..9b64e00
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/AssertDetectorTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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("javadoc")
+public class AssertDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new AssertDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/Assert.java:7: Warning: Assertions are unreliable. Use BuildConfig.DEBUG conditional checks instead. [Assert]\n"
+ + " assert false; // ERROR\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/Assert.java:8: Warning: Assertions are unreliable. Use BuildConfig.DEBUG conditional checks instead. [Assert]\n"
+ + " assert param > 5 : \"My description\"; // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/Assert.java:9: Warning: Assertions are unreliable. Use BuildConfig.DEBUG conditional checks instead. [Assert]\n"
+ + " assert param2 == param3; // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/Assert.java:10: Warning: Assertions are unreliable. Use BuildConfig.DEBUG conditional checks instead. [Assert]\n"
+ + " assert param2 != null && param3 == param2; // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 4 warnings\n",
+
+ lintProject("src/test/pkg/Assert.java.txt=>src/test/pkg/Assert.java"));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java
new file mode 100644
index 0000000..aba38f4
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Issue;
+import com.android.tools.lint.detector.api.Scope;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Field;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class BuiltinIssueRegistryTest extends TestCase {
+ public void testNoListResize() {
+ BuiltinIssueRegistry registry = new BuiltinIssueRegistry();
+ List<Issue> issues = registry.getIssues();
+ int issueCount = issues.size();
+ assertTrue(Integer.toString(issueCount),
+ BuiltinIssueRegistry.INITIAL_CAPACITY >= issueCount);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testCapacities() throws IllegalAccessException {
+ TestIssueRegistry registry = new TestIssueRegistry();
+ for (Scope scope : Scope.values()) {
+ EnumSet<Scope> scopeSet = EnumSet.of(scope);
+ checkCapacity(registry, scopeSet);
+ }
+
+ // Also check the commonly used combinations
+ for (Field field : Scope.class.getDeclaredFields()) {
+ if (field.getType().isAssignableFrom(EnumSet.class)) {
+ checkCapacity(registry, (EnumSet<Scope>) field.get(null));
+ }
+ }
+ }
+
+ public void testUnique() {
+ // Check that ids are unique
+ Set<String> ids = new HashSet<String>();
+ for (Issue issue : new BuiltinIssueRegistry().getIssues()) {
+ String id = issue.getId();
+ assertTrue("Duplicate id " + id, !ids.contains(id));
+ ids.add(id);
+ }
+ }
+
+ private static void checkCapacity(TestIssueRegistry registry,
+ EnumSet<Scope> scopeSet) {
+ List<Issue> issuesForScope = registry.getIssuesForScope(scopeSet);
+ int requiredSize = issuesForScope.size();
+ int capacity = registry.getIssueCapacity(scopeSet);
+ if (requiredSize > capacity) {
+ fail("For Scope set " + scopeSet + ": capacity " + capacity
+ + " < actual " + requiredSize);
+ }
+ }
+
+ private static class TestIssueRegistry extends BuiltinIssueRegistry {
+ // Override to make method accessible outside package
+ @NonNull
+ @Override
+ public List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) {
+ return super.getIssuesForScope(scope);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ButtonDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ButtonDetectorTest.java
index 21153aa..72b9946 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ButtonDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ButtonDetectorTest.java
@@ -232,7 +232,7 @@
"res/values/buttonbar-values.xml:9: Warning: The standard Android way to capitalize Ok is \"OK\" (tip: use @android:string/ok instead) [ButtonCase]\n" +
" <string name=\"resume2\"> Ok </string>\n" +
" ^\n" +
- "res/values/buttonbar-values.xml:10: Warning: The standard Android way to capitalize CANCEL is \"Cancel\" (tip: use @android:string/ok instead) [ButtonCase]\n" +
+ "res/values/buttonbar-values.xml:10: Warning: The standard Android way to capitalize CANCEL is \"Cancel\" (tip: use @android:string/cancel instead) [ButtonCase]\n" +
" <string name=\"giveup2\">\"CANCEL\"</string>\n" +
" ^\n" +
"0 errors, 2 warnings\n" +
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ByteOrderMarkDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ByteOrderMarkDetectorTest.java
new file mode 100644
index 0000000..eeb16a0
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ByteOrderMarkDetectorTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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("javadoc")
+public class ByteOrderMarkDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ByteOrderMarkDetector();
+ }
+
+ public void test() throws Exception {
+ // See issue b.android.com/65103
+ assertEquals(""
+ + "res/values-zh-rCN/bom.xml:3: Error: Found byte-order-mark in the middle of a file [ByteOrderMark]\n"
+ + " <string name=\"hanping_chinese\uFEFF_lite\uFEFF_app_name\">(Translated name)</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject("res/values-zh-rCN/bom.xml"));
+ }
+}
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
index 81b46f8..2cb175b 100644
--- 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
@@ -27,10 +27,10 @@
assertEquals(""
+ "src/test/pkg/DetachedFromWindow.java:7: Warning: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
+ " protected void onDetachedFromWindow() {\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/DetachedFromWindow.java:26: Warning: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
+ " protected void onDetachedFromWindow() {\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 2 warnings\n",
lintProject("src/test/pkg/DetachedFromWindow.java.txt=>" +
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/CipherGetInstanceDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/CipherGetInstanceDetectorTest.java
new file mode 100644
index 0000000..2f8bae1
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/CipherGetInstanceDetectorTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 CipherGetInstanceDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new CipherGetInstanceDetector();
+ }
+
+ public void testCipherGetInstanceAES() throws Exception {
+ assertEquals(
+ "src/test/pkg/CipherGetInstanceAES.java:7: Warning: Cipher.getInstance should not be called without setting the encryption mode and padding [GetInstance]\n"
+ + " Cipher.getInstance(\"AES\");\n"
+ + " ~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/test/pkg/CipherGetInstanceAES.java.txt=>src/test/pkg/CipherGetInstanceAES.java"
+ )
+ );
+ }
+
+ public void testCipherGetInstanceDES() throws Exception {
+ assertEquals(
+ "src/test/pkg/CipherGetInstanceDES.java:7: Warning: Cipher.getInstance should not be called without setting the encryption mode and padding [GetInstance]\n"
+ + " Cipher.getInstance(\"DES\");\n"
+ + " ~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/test/pkg/CipherGetInstanceDES.java.txt=>src/test/pkg/CipherGetInstanceDES.java"
+ )
+ );
+ }
+
+ public void testCipherGetInstanceAESECB() throws Exception {
+ assertEquals(
+ "src/test/pkg/CipherGetInstanceAESECB.java:7: Warning: ECB encryption mode should not be used [GetInstance]\n"
+ + " Cipher.getInstance(\"AES/ECB/NoPadding\");\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/test/pkg/CipherGetInstanceAESECB.java.txt=>src/test/pkg/CipherGetInstanceAESECB.java"
+ )
+ );
+ }
+
+ public void testCipherGetInstanceAESCBC() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "src/test/pkg/CipherGetInstanceAESCBC.java.txt=>src/test/pkg/CipherGetInstanceAESCBC.java"
+ )
+ );
+ }
+
+ public void testResolveConstants() throws Exception {
+ assertEquals(
+ "src/test/pkg/CipherGetInstanceTest.java:10: Warning: ECB encryption mode should not be used (was \"DES/ECB/NoPadding\") [GetInstance]\n"
+ + " Cipher des = Cipher.getInstance(Constants.DES);\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/test/pkg/CipherGetInstanceTest.java.txt=>src/test/pkg/CipherGetInstanceTest.java"
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetectorTest.java
new file mode 100644
index 0000000..569e51b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetectorTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 ClickableViewAccessibilityDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new ClickableViewAccessibilityDetector();
+ }
+
+ public void testWarningWhenViewOverridesOnTouchEventButNotPerformClick() throws Exception {
+ assertEquals(
+ "src/test/pkg/ClickableViewAccessibilityTest.java:16: Warning: Custom view test/pkg/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick overrides onTouchEvent but not performClick [ClickableViewAccessibility]\n"
+ + " public boolean onTouchEvent(MotionEvent event) {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick.class"
+ ));
+ }
+
+ public void testWarningWhenOnTouchEventDoesNotCallPerformClick() throws Exception {
+ assertEquals(
+ "src/test/pkg/ClickableViewAccessibilityTest.java:28: Warning: test/pkg/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick#onTouchEvent should call test/pkg/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick#performClick when a click is detected [ClickableViewAccessibility]\n"
+ + " public boolean onTouchEvent(MotionEvent event) {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick.class"
+ ));
+ }
+
+ public void testWarningWhenPerformClickDoesNotCallSuper() throws Exception {
+ assertEquals(
+ "src/test/pkg/ClickableViewAccessibilityTest.java:44: Warning: test/pkg/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper#performClick should call super#performClick [ClickableViewAccessibility]\n"
+ + " public boolean performClick() {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper.class"
+ ));
+ }
+
+ public void testNoWarningOnValidView() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$ValidView.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$ValidView.class"
+ ));
+ }
+
+ public void testNoWarningOnNonViewSubclass() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$NotAView.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$NotAView.class"
+ ));
+ }
+
+ public void testWarningOnViewSubclass() throws Exception {
+ // ViewSubclass is actually a subclass of ValidView. This tests that we can detect
+ // tests further down in the inheritance hierarchy than direct children of View.
+ assertEquals(
+ "src/test/pkg/ClickableViewAccessibilityTest.java:84: Warning: test/pkg/ClickableViewAccessibilityTest$ViewSubclass#performClick should call super#performClick [ClickableViewAccessibility]\n"
+ + " public boolean performClick() {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$ValidView.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$ValidView.class",
+ "bytecode/ClickableViewAccessibilityTest$ViewSubclass.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$ViewSubclass.class"
+ ));
+ }
+
+ public void testNoWarningOnOnTouchEventWithDifferentSignature() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$ViewWithDifferentOnTouchEvent.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$ViewWithDifferentOnTouchEvent.class"
+ ));
+ }
+
+ public void testNoWarningOnPerformClickWithDifferentSignature() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$ViewWithDifferentPerformClick.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$ViewWithDifferentPerformClick.class"
+ ));
+ }
+
+ public void testWarningWhenSetOnTouchListenerCalledOnViewWithNoPerformClick() throws Exception {
+ assertEquals(
+ "src/test/pkg/ClickableViewAccessibilityTest.java:124: Warning: Custom view test/pkg/ClickableViewAccessibilityTest$NoPerformClick has setOnTouchListener called on it but does not override performClick [ClickableViewAccessibility]\n"
+ + " view.setOnTouchListener(new ValidOnTouchListener());\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$NoPerformClick.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$NoPerformClick.class",
+ "bytecode/ClickableViewAccessibilityTest$NoPerformClickOnTouchListenerSetter.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$NoPerformClickOnTouchListenerSetter.class",
+ "bytecode/ClickableViewAccessibilityTest$ValidOnTouchListener.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$ValidOnTouchListener.class"
+ ));
+ }
+
+ public void testNoWarningWhenSetOnTouchListenerNotCalledOnViewWithNoPerformClick() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$NoPerformClick.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$NoPerformClick.class"
+ ));
+ }
+
+ public void testNoWarningWhenSetOnTouchListenerCalledOnViewWithPerformClick() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest$HasPerformClick.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$HasPerformClick.class",
+ "bytecode/ClickableViewAccessibilityTest$HasPerformClickOnTouchListenerSetter.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$HasPerformClickOnTouchListenerSetter.class"
+ ));
+ }
+
+ public void testNoWarningWhenOnTouchListenerCalledOnNonViewSubclass() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$NotAView.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$NotAView.class",
+ "bytecode/ClickableViewAccessibilityTest$NotAViewOnTouchListenerSetter.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$NotAViewOnTouchListenerSetter.class"
+ ));
+ }
+
+ public void testNoWarningWhenOnTouchCallsPerformClick() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$ValidOnTouchListener.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$ValidOnTouchListener.class"
+ ));
+ }
+
+ public void testWarningWhenOnTouchDoesNotCallPerformClick() throws Exception {
+ assertEquals(
+ "src/test/pkg/ClickableViewAccessibilityTest.java:162: Warning: test/pkg/ClickableViewAccessibilityTest$InvalidOnTouchListener#onTouch should call View#performClick when a click is detected [ClickableViewAccessibility]\n"
+ + " public boolean onTouch(View v, MotionEvent event) {\n"
+ + " ~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$InvalidOnTouchListener.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$InvalidOnTouchListener.class"
+ ));
+ }
+
+ public void testNoWarningWhenAnonymousOnTouchListenerCallsPerformClick() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener.class",
+ "bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener$1.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener$1.class"
+ ));
+ }
+
+
+ public void testWarningWhenAnonymousOnTouchListenerDoesNotCallPerformClick() throws Exception {
+ assertEquals(
+ "src/test/pkg/ClickableViewAccessibilityTest.java:182: Warning: test/pkg/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1#onTouch should call View#performClick when a click is detected [ClickableViewAccessibility]\n"
+ + " public boolean onTouch(View v, MotionEvent event) {\n"
+ + " ~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/ClickableViewAccessibilityTest.java.txt=>src/test/pkg/ClickableViewAccessibilityTest.java",
+ "bytecode/ClickableViewAccessibilityTest.class.data=>bin/classes/test/pkg/ClickableViewAccessibilityTest.class",
+ "bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener.class",
+ "bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1.class.data=>"
+ + "bin/classes/test/pkg/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1.class"
+ ));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java
index f4f7898..2d3e2b1 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java
@@ -84,8 +84,28 @@
public void testUnusedNamespace() throws Exception {
assertEquals(
- "No warnings.",
+ "No warnings.",
- lintProject("res/layout/message_edit_detail.xml"));
+ lintProject("res/layout/message_edit_detail.xml"));
+ }
+
+ public void testMissingLayoutAttribute() throws Exception {
+ assertEquals(
+ "res/layout/rtl.xml:7: Error: Attribute is missing the Android namespace prefix [MissingPrefix]\n" +
+ " layout_gravity=\"left\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/rtl.xml:8: Error: Attribute is missing the Android namespace prefix [MissingPrefix]\n" +
+ " layout_alignParentLeft=\"true\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/rtl.xml:9: Error: Attribute is missing the Android namespace prefix [MissingPrefix]\n" +
+ " editable=\"false\"\n" +
+ " ~~~~~~~~~~~~~~~~\n" +
+ "3 errors, 0 warnings\n",
+
+ lintProject(
+ "overdraw/project.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/rtl_noprefix.xml=>res/layout/rtl.xml"
+ ));
}
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/DuplicateIdDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
index 72a9035..3215787 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
@@ -27,11 +27,11 @@
public void testDuplicate() throws Exception {
assertEquals(
- "res/layout/duplicate.xml:5: Warning: Duplicate id @+id/android_logo, already defined earlier in this layout [DuplicateIds]\n" +
+ "res/layout/duplicate.xml:5: Error: Duplicate id @+id/android_logo, already defined earlier in this layout [DuplicateIds]\n" +
" <ImageButton android:id=\"@+id/android_logo\" 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" +
" res/layout/duplicate.xml:4: @+id/android_logo originally defined here\n" +
- "0 errors, 1 warnings\n" +
+ "1 errors, 0 warnings\n" +
"",
lintFiles("res/layout/duplicate.xml"));
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
index a4a5a68..7b49992 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
@@ -45,6 +45,34 @@
"res/values/customattr.xml=>res/values/customattr2.xml"));
}
+ public void testSameFile() throws Exception {
+ assertEquals(""
+ + "res/values/duplicate-strings.xml:6: Error: app_name has already been defined in this folder [DuplicateDefinition]\n"
+ + " <string name=\"app_name\">App Name 1</string>\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + " res/values/duplicate-strings.xml:4: Previously defined here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/duplicate-strings.xml"));
+ }
+
+ public void testStyleItems() throws Exception {
+ assertEquals(""
+ + "res/values/duplicate-items.xml:7: Error: android:textColor has already been defined in this <style> [DuplicateDefinition]\n"
+ + " <item name=\"android:textColor\">#ff0000</item>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/duplicate-items.xml:5: Previously defined here\n"
+ + "res/values/duplicate-items.xml:13: Error: contentId has already been defined in this <declare-styleable> [DuplicateDefinition]\n"
+ + " <attr name=\"contentId\" format=\"integer\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/duplicate-items.xml:11: Previously defined here\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/duplicate-items.xml"));
+ }
+
public void testOk() throws Exception {
assertEquals(
"No warnings.",
@@ -60,4 +88,23 @@
"res/values-es/donottranslate.xml",
"res/values-nl-rNL/strings.xml"));
}
+
+ public void testResourceAliases() throws Exception {
+ assertEquals(""
+ + "res/values/refs.xml:3: Error: Unexpected resource reference type; expected value of type @string/ [ReferenceType]\n"
+ + " <item name=\"invalid1\" type=\"string\">@layout/other</item>\n"
+ + " ^\n"
+ + "res/values/refs.xml:5: Error: Unexpected resource reference type; expected value of type @drawable/ [ReferenceType]\n"
+ + " @layout/other\n"
+ + " ^\n"
+ + "res/values/refs.xml:10: Error: Unexpected resource reference type; expected value of type @string/ [ReferenceType]\n"
+ + " <string name=\"invalid4\">@layout/indirect</string>\n"
+ + " ^\n"
+ + "res/values/refs.xml:15: Error: Unexpected resource reference type; expected value of type @color/ [ReferenceType]\n"
+ + " <item name=\"drawableAsColor\" type=\"color\">@drawable/my_drawable</item>\n"
+ + " ^\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject("res/values/refs.xml"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
index 0654ddc..150b772 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
@@ -26,26 +26,26 @@
}
public void test() throws Exception {
- assertEquals(
- "src/test/pkg/FragmentTest.java:10: Error: This fragment class should be public (test.pkg.FragmentTest.Fragment1) [ValidFragment]\n" +
- " private static class Fragment1 extends Fragment {\n" +
- " ^\n" +
- "src/test/pkg/FragmentTest.java:15: Error: This fragment inner class should be static (test.pkg.FragmentTest.Fragment2) [ValidFragment]\n" +
- " public class Fragment2 extends Fragment {\n" +
- " ^\n" +
- "src/test/pkg/FragmentTest.java:21: Error: The default constructor must be public [ValidFragment]\n" +
- " private Fragment3() {\n" +
- " ~~~~~~~~~\n" +
- "src/test/pkg/FragmentTest.java:26: Error: This fragment should provide a default constructor (a public constructor with no arguments) (test.pkg.FragmentTest.Fragment4) [ValidFragment]\n" +
- " public static class Fragment4 extends Fragment {\n" +
- " ~~~~~~~~~\n" +
- "src/test/pkg/FragmentTest.java:27: Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]\n" +
- " private Fragment4(int dummy) {\n" +
- " ~~~~~~~~~\n" +
- "src/test/pkg/FragmentTest.java:36: Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]\n" +
- " public Fragment5(int dummy) {\n" +
- " ~~~~~~~~~\n" +
- "6 errors, 0 warnings\n",
+ assertEquals(""
+ + "src/test/pkg/FragmentTest.java:10: Error: This fragment class should be public (test.pkg.FragmentTest.Fragment1) [ValidFragment]\n"
+ + " private static class Fragment1 extends Fragment {\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:15: Error: This fragment inner class should be static (test.pkg.FragmentTest.Fragment2) [ValidFragment]\n"
+ + " public class Fragment2 extends Fragment {\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:21: Error: The default constructor must be public [ValidFragment]\n"
+ + " private Fragment3() {\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:26: Error: This fragment should provide a default constructor (a public constructor with no arguments) (test.pkg.FragmentTest.Fragment4) [ValidFragment]\n"
+ + " public static class Fragment4 extends Fragment {\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:27: Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]\n"
+ + " private Fragment4(int dummy) {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:36: Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]\n"
+ + " public Fragment5(int dummy) {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "6 errors, 0 warnings\n",
lintProject(
"bytecode/FragmentTest$Fragment1.class.data=>bin/classes/test/pkg/FragmentTest$Fragment1.class",
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/GridLayoutDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/GridLayoutDetectorTest.java
index dee52e1..33400f6 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/GridLayoutDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/GridLayoutDetectorTest.java
@@ -34,4 +34,26 @@
"",
lintFiles("res/layout/gridlayout.xml"));
}
+
+ public void testGridLayout2() throws Exception {
+ assertEquals(""
+ + "res/layout/layout.xml:9: Error: Wrong namespace; with v7 GridLayout you should use myns:orientation [GridLayout]\n"
+ + " android:orientation=\"horizontal\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/layout.xml:14: Error: Wrong namespace; with v7 GridLayout you should use myns:layout_row [GridLayout]\n"
+ + " android:layout_row=\"2\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+ lintFiles("res/layout/gridlayout2.xml=>res/layout/layout.xml"));
+ }
+
+ public void testGridLayout3() throws Exception {
+ assertEquals(""
+ + "res/layout/layout.xml:12: Error: Wrong namespace; with v7 GridLayout you should use app:layout_row "
+ + "(and add xmlns:app=\"http://schemas.android.com/apk/res-auto\" to your root element.) [GridLayout]\n"
+ + " android:layout_row=\"2\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintFiles("res/layout/gridlayout3.xml=>res/layout/layout.xml"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/HardcodedDebugModeDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/HardcodedDebugModeDetectorTest.java
index edb6c50..c17d180 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/HardcodedDebugModeDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/HardcodedDebugModeDetectorTest.java
@@ -27,10 +27,10 @@
public void test() throws Exception {
assertEquals(
- "AndroidManifest.xml:10: Warning: Avoid hardcoding the debug mode; leaving it out allows debug and release builds to automatically assign one [HardcodedDebugMode]\n" +
+ "AndroidManifest.xml:10: Error: Avoid hardcoding the debug mode; leaving it out allows debug and release builds to automatically assign one [HardcodedDebugMode]\n" +
" android:debuggable=\"true\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
+ "1 errors, 0 warnings\n" +
"",
lintProject("debuggable.xml=>AndroidManifest.xml"));
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
index b326fd4..cdd246c 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
@@ -70,4 +70,13 @@
lintFiles("res/layout/ignores.xml"));
}
+ public void testSuppressViaComment() throws Exception {
+ assertEquals(""
+ + "res/layout/ignores2.xml:51: Warning: [I18N] Hardcoded string \"Hardcoded\", should use @string resource [HardcodedText]\n"
+ + " android:text=\"Hardcoded\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintFiles("res/layout/ignores2.xml"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
index 0cdb0a5..36e57d3 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
@@ -37,7 +37,7 @@
private Set<Issue> mEnabled = new HashSet<Issue>();
private boolean mAbbreviate;
- private static Set<Issue> ALL = new HashSet<Issue>();
+ private static final Set<Issue> ALL = new HashSet<Issue>();
static {
ALL.add(IconDetector.DUPLICATES_CONFIGURATIONS);
ALL.add(IconDetector.DUPLICATES_NAMES);
@@ -168,7 +168,8 @@
" res/drawable-hdpi/frame.png: <No location-specific message\n" +
" res/drawable-mdpi/frame.png: <No location-specific message\n" +
" res/drawable-ldpi/frame.png: <No location-specific message\n" +
- "res/drawable-xhdpi/frame.png: Warning: The following unrelated icon files have identical contents: frame.png, frame.png, frame.png, file1.png, file2.png, frame.png [IconDuplicates]\n" +
+ "res/drawable-xxhdpi/frame.png: Warning: The following unrelated icon files have identical contents: frame.png, frame.png, frame.png, file1.png, file2.png, frame.png, frame.png [IconDuplicates]\n" +
+ " res/drawable-xhdpi/frame.png: <No location-specific message\n" +
" res/drawable-nodpi/file2.png: <No location-specific message\n" +
" res/drawable-nodpi/file1.png: <No location-specific message\n" +
" res/drawable-mdpi/frame.png: <No location-specific message\n" +
@@ -182,6 +183,7 @@
"res/drawable-mdpi/frame.png=>res/drawable-hdpi/frame.png",
"res/drawable-mdpi/frame.png=>res/drawable-ldpi/frame.png",
"res/drawable-mdpi/frame.png=>res/drawable-xhdpi/frame.png",
+ "res/drawable-mdpi/frame.png=>res/drawable-xxhdpi/frame.png",
"res/drawable-mdpi/frame.png=>res/drawable-nodpi/file1.png",
"res/drawable-mdpi/frame.png=>res/drawable-nodpi/file2.png"));
}
@@ -477,4 +479,15 @@
"res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/ic_launcher_2.gif"
));
}
+
+ public void test67486() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=67486
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals("No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/drawable-xhdpi/ic_stat_notify.png=>res/drawable-xhdpi/ic_stat_notify.png"
+ ));
+ }
}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/IncludeDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/IncludeDetectorTest.java
new file mode 100644
index 0000000..9d2e1e4
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/IncludeDetectorTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 IncludeDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new IncludeDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "res/layout/include_params.xml:43: Error: Layout parameter layout_margin ignored unless both layout_width and layout_height are also specified on <include> tag [IncludeLayoutParam]\n"
+ + " android:layout_margin=\"20dp\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/include_params.xml:44: Error: Layout parameter layout_weight ignored unless both layout_width and layout_height are also specified on <include> tag [IncludeLayoutParam]\n"
+ + " android:layout_weight=\"1.5\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/include_params.xml:51: Error: Layout parameter layout_weight ignored unless layout_width is also specified on <include> tag [IncludeLayoutParam]\n"
+ + " android:layout_weight=\"1.5\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/include_params.xml:58: Error: Layout parameter layout_weight ignored unless layout_height is also specified on <include> tag [IncludeLayoutParam]\n"
+ + " android:layout_weight=\"1.5\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/include_params.xml:65: Error: Layout parameter layout_width ignored unless layout_height is also specified on <include> tag [IncludeLayoutParam]\n"
+ + " android:layout_width=\"fill_parent\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/include_params.xml:72: Error: Layout parameter layout_height ignored unless layout_width is also specified on <include> tag [IncludeLayoutParam]\n"
+ + " android:layout_height=\"fill_parent\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "6 errors, 0 warnings\n",
+
+ lintProject(
+ "res/layout/include_params.xml"));
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/InefficientWeightDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
index a9325c7..bf3c3d3 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
@@ -30,10 +30,10 @@
"res/layout/inefficient_weight.xml:3: Error: Wrong orientation? No orientation specified, and the default is horizontal, yet this layout has multiple children where at least one has layout_width=\"match_parent\" [Orientation]\n" +
"<LinearLayout\n" +
"^\n" +
- "res/layout/inefficient_weight.xml:10: Warning: Use a layout_width of 0dip instead of match_parent for better performance [InefficientWeight]\n" +
+ "res/layout/inefficient_weight.xml:10: Warning: Use a layout_width of 0dp instead of match_parent for better performance [InefficientWeight]\n" +
" android:layout_width=\"match_parent\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/inefficient_weight.xml:24: Warning: Use a layout_height of 0dip instead of wrap_content for better performance [InefficientWeight]\n" +
+ "res/layout/inefficient_weight.xml:24: Warning: Use a layout_height of 0dp instead of wrap_content for better performance [InefficientWeight]\n" +
" android:layout_height=\"wrap_content\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 2 warnings\n",
@@ -134,4 +134,61 @@
lintFiles("res/layout/orientation.xml"));
}
+
+ public void testIncremental1() throws Exception {
+ assertEquals(""
+ + "res/layout/orientation2.xml:5: Error: No orientation specified, and the default is horizontal. This is a common source of bugs when children are added dynamically. [Orientation]\n"
+ + " <LinearLayout\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProjectIncrementally("res/layout/orientation2.xml",
+ "res/layout/orientation2.xml"));
+ }
+
+ public void testIncremental2() throws Exception {
+ assertEquals("No warnings.",
+
+ lintProjectIncrementally("res/layout/orientation2.xml",
+ "res/layout/orientation2.xml",
+ "res/values/styles-inherited-orientation.xml"));
+ }
+
+ public void testIncremental3() throws Exception {
+ assertEquals("No warnings.",
+ lintProjectIncrementally("res/layout/orientation2.xml",
+ "res/layout/orientation2.xml",
+ "res/values/styles-orientation.xml"));
+ }
+
+ public void testIncremental4() throws Exception {
+ assertEquals(""
+ + "res/layout/inefficient_weight3.xml:9: Warning: Use a layout_height of 0dp instead of (undefined) for better performance [InefficientWeight]\n"
+ + " <Button\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProjectIncrementally(
+ "res/layout/inefficient_weight3.xml",
+ "res/layout/inefficient_weight3.xml"));
+ }
+
+ public void testIncremental5() throws Exception {
+ assertEquals("No warnings.",
+ lintProjectIncrementally(
+ "res/layout/inefficient_weight3.xml",
+ "res/layout/inefficient_weight3.xml",
+ "res/values/styles-orientation.xml"));
+ }
+
+ public void testIncremental6() throws Exception {
+ assertEquals(""
+ + "res/layout/inefficient_weight3.xml:9: Warning: Use a layout_height of 0dp instead of wrap_content for better performance [InefficientWeight]\n"
+ + " <Button\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProjectIncrementally(
+ "res/layout/inefficient_weight3.xml",
+ "res/layout/inefficient_weight3.xml",
+ "res/values/styles-inherited-orientation.xml"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
index b634255..02cddae 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
@@ -53,4 +53,45 @@
"bytecode/classes.jar=>libs/classes.jar"
));
}
+
+ public void testLibraryInJavax() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "bytecode/javax.jar.data=>libs/javax.jar"
+ ));
+ }
+
+ public void testAnnotationProcessors1() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=64014
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "bytecode/butterknife-2.0.1.jar.data=>libs/butterknife-2.0.1.jar"
+ ));
+ }
+
+ public void testAnnotationProcessors2() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=64014
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "bytecode/dagger-compiler-1.2.1-subset.jar.data=>libs/dagger-compiler-1.2.1.jar"
+ ));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
index e2f0ce4..513ef9e 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
@@ -76,6 +76,9 @@
"src/test/pkg/JavaPerformanceTest.java:192: Warning: Use new SparseBooleanArray(...) instead for better performance [UseSparseArrays]\n" +
" new SparseArray<Boolean>(); // Use SparseBooleanArray instead\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:201: Warning: Use new SparseArray<String>(...) instead for better performance [UseSparseArrays]\n" +
+ " Map<Byte, String> myByteMap = new HashMap<Byte, String>();\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"src/test/pkg/JavaPerformanceTest.java:33: Warning: Use Integer.valueOf(5) instead [UseValueOf]\n" +
" Integer i = new Integer(5);\n" +
" ~~~~~~~~~~~~~~\n" +
@@ -98,7 +101,7 @@
" Double d1 = new Double(1.0);\n" +
" ~~~~~~~~~~~~~~~\n" +
(isInAospEnvironment ?
- "0 errors, 22 warnings\n" : "0 errors, 21 warnings\n"),
+ "0 errors, 23 warnings\n" : "0 errors, 22 warnings\n"),
lintProject("src/test/pkg/JavaPerformanceTest.java.txt=>" +
"src/test/pkg/JavaPerformanceTest.java"));
@@ -116,6 +119,18 @@
"src/test/pkg/LongSparseArray.java.txt=>src/test/pkg/LongSparseArray.java"));
}
+ public void testLongSparseSupportLibArray() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/LongSparseArray.java:10: Warning: Use new android.support.v4.util.LongSparseArray(...) instead for better performance [UseSparseArrays]\n"
+ + " Map<Long, String> myStringMap = new HashMap<Long, String>();\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "src/test/pkg/LongSparseArray.java.txt=>src/test/pkg/LongSparseArray.java",
+ "bytecode/classes.jar=>libs/android-support-v4.jar"));
+ }
+
public void testNoLongSparseArray() throws Exception {
assertEquals(
"No warnings.",
@@ -124,4 +139,29 @@
"apicheck/minsdk1.xml=>AndroidManifest.xml",
"src/test/pkg/LongSparseArray.java.txt=>src/test/pkg/LongSparseArray.java"));
}
+
+ public void testSparseLongArray1() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/SparseLongArray.java:10: Warning: Use new SparseLongArray(...) instead for better performance [UseSparseArrays]\n"
+ + " Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk19.xml=>AndroidManifest.xml",
+ "src/test/pkg/SparseLongArray.java.txt=>src/test/pkg/SparseLongArray.java"));
+ }
+
+ public void testSparseLongArray2() throws Exception {
+ // Note -- it's offering a SparseArray, not a SparseLongArray!
+ assertEquals(""
+ + "src/test/pkg/SparseLongArray.java:10: Warning: Use new SparseArray<Long>(...) instead for better performance [UseSparseArrays]\n"
+ + " Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "src/test/pkg/SparseLongArray.java.txt=>src/test/pkg/SparseLongArray.java"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java
new file mode 100644
index 0000000..ca1f290
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+public class LayoutInflationDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new LayoutInflationDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/LayoutInflationTest.java:13: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
+ + " convertView = mInflater.inflate(R.layout.your_layout, null);\n"
+ + " ~~~~\n"
+ + "src/test/pkg/LayoutInflationTest.java:14: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
+ + " convertView = mInflater.inflate(R.layout.your_layout, null, true);\n"
+ + " ~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "src/test/pkg/LayoutInflationTest.java.txt=>src/test/pkg/LayoutInflationTest.java",
+ "res/layout/textsize.xml=>res/layout/your_layout.xml",
+ "res/layout/listseparator.xml=>res/layout-port/your_layout.xml"));
+ }
+
+ public void testNoLayoutParams() throws Exception {
+ assertEquals("No warnings.",
+
+ lintProject(
+ "src/test/pkg/LayoutInflationTest.java.txt=>src/test/pkg/LayoutInflationTest.java",
+ "res/layout/listseparator.xml=>res/layout/your_layout.xml"));
+ }
+
+ public void testHasLayoutParams() throws IOException, XmlPullParserException {
+ assertFalse(LayoutInflationDetector.hasLayoutParams(new StringReader("")));
+ assertFalse(LayoutInflationDetector.hasLayoutParams(new StringReader("<LinearLayout/>")));
+ assertFalse(LayoutInflationDetector.hasLayoutParams(new StringReader(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:orientation=\"vertical\" >\n"
+ + "\n"
+ + " <include\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " layout=\"@layout/layoutcycle1\" />\n"
+ + "\n"
+ + "</LinearLayout>")));
+
+
+ assertTrue(LayoutInflationDetector.hasLayoutParams(new StringReader(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\"\n"
+ + " android:orientation=\"vertical\" >\n"
+ + "\n"
+ + " <include\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " layout=\"@layout/layoutcycle1\" />\n"
+ + "\n"
+ + "</LinearLayout>")));
+ }
+
+ public void testSuppressed() throws Exception {
+ assertEquals("No warnings.",
+
+ lintProject(
+ "src/test/pkg/LayoutInflationTest_ignored.java.txt=>src/test/pkg/LayoutInflationTest.java",
+ "res/layout/textsize.xml=>res/layout/your_layout.xml",
+ "res/layout/listseparator.xml=>res/layout-port/your_layout.xml"));
+ }
+}
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/LocaleFolderDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/LocaleFolderDetectorTest.java
new file mode 100644
index 0000000..cd591b6
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/LocaleFolderDetectorTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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("javadoc")
+public class LocaleFolderDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new LocaleFolderDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "res/values-he: Warning: The locale folder \"he\" should be called \"iw\" instead; see the java.util.Locale documentation [LocaleFolder]\n"
+ + "res/values-id: Warning: The locale folder \"id\" should be called \"in\" instead; see the java.util.Locale documentation [LocaleFolder]\n"
+ + "res/values-yi: Warning: The locale folder \"yi\" should be called \"ji\" instead; see the java.util.Locale documentation [LocaleFolder]\n"
+ + "0 errors, 3 warnings\n",
+
+ lintProject(
+ "res/values/strings.xml",
+ "res/values/strings.xml=>res/values-no/strings.xml",
+ "res/values/strings.xml=>res/values-he/strings.xml",
+ "res/values/strings.xml=>res/values-id/strings.xml",
+ "res/values/strings.xml=>res/values-yi/strings.xml")
+ );
+ }
+}
\ No newline at end of file
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 ca7aadf..5ae23fd 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
@@ -17,8 +17,15 @@
package com.android.tools.lint.checks;
import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.Variant;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
@@ -327,16 +334,13 @@
+ "AndroidManifest.xml:2: Warning: The android:versionCode cannot be a resource url, it must be a literal integer [IllegalResourceRef]\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ "^\n"
- + "AndroidManifest.xml:2: Warning: The android:versionName cannot be a resource url, it must be a literal string [IllegalResourceRef]\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + "^\n"
+ "AndroidManifest.xml:7: Warning: The android:minSdkVersion cannot be a resource url, it must be a literal integer (or string if a preview codename) [IllegalResourceRef]\n"
+ " <uses-sdk android:minSdkVersion=\"@dimen/minSdkVersion\" android:targetSdkVersion=\"@dimen/targetSdkVersion\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "AndroidManifest.xml:7: Warning: The android:targetSdkVersion cannot be a resource url, it must be a literal integer (or string if a preview codename) [IllegalResourceRef]\n"
+ " <uses-sdk android:minSdkVersion=\"@dimen/minSdkVersion\" android:targetSdkVersion=\"@dimen/targetSdkVersion\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 4 warnings\n",
+ + "0 errors, 3 warnings\n",
lintProject("illegal_version.xml=>AndroidManifest.xml"));
}
@@ -420,7 +424,7 @@
"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.
+ // that a manifest file in a debug build type does not get flagged.
}
public void testMockLocationsOk() throws Exception {
@@ -432,6 +436,38 @@
"mock_location.xml=>AndroidManifest.xml"));
}
+ public void testGradleOverrides() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
+ assertEquals(""
+ + "AndroidManifest.xml:4: Warning: This versionCode value (1) is not used; it is always overridden by the value specified in the Gradle build script (2) [GradleOverrides]\n"
+ + " android:versionCode=\"1\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:5: Warning: This versionName value (1.0) is not used; it is always overridden by the value specified in the Gradle build script (MyName) [GradleOverrides]\n"
+ + " android:versionName=\"1.0\" >\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:7: Warning: This minSdkVersion value (14) is not used; it is always overridden by the value specified in the Gradle build script (5) [GradleOverrides]\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"17\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:7: Warning: This targetSdkVersion value (17) is not used; it is always overridden by the value specified in the Gradle build script (16) [GradleOverrides]\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"17\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 4 warnings\n",
+ lintProject(
+ "gradle_override.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ public void testGradleOverridesOk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
+ // (See custom logic in #createClient which returns -1/null for the merged flavor
+ // from this test, and not from testGradleOverrides)
+ assertEquals(""
+ + "No warnings.",
+ lintProject(
+ "gradle_override.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
// 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 {
@@ -467,12 +503,64 @@
@Override
protected TestLintClient createClient() {
- return new TestLintClient() {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new MyProject(this, dir, referenceDir);
- }
- };
+ if (mEnabled.contains(ManifestDetector.MOCK_LOCATION)) {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new MyProject(this, dir, referenceDir);
+ }
+ };
+ } else if (mEnabled.contains(ManifestDetector.GRADLE_OVERRIDES)) {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ ProductFlavor flavor = createNiceMock(ProductFlavor.class);
+ if (getName().equals("ManifestDetectorTest_testGradleOverridesOk")) {
+ expect(flavor.getMinSdkVersion()).andReturn(null).anyTimes();
+ expect(flavor.getTargetSdkVersion()).andReturn(null).anyTimes();
+ expect(flavor.getVersionCode()).andReturn(-1).anyTimes();
+ expect(flavor.getVersionName()).andReturn(null).anyTimes();
+ } else {
+ assertEquals(getName(),
+ "ManifestDetectorTest_testGradleOverrides");
+
+ ApiVersion apiMock = createNiceMock(ApiVersion.class);
+ expect(apiMock.getApiLevel()).andReturn(5);
+ expect(apiMock.getApiString()).andReturn("5");
+ expect(flavor.getMinSdkVersion()).andReturn(apiMock).anyTimes();
+ replay(apiMock);
+
+ apiMock = createNiceMock(ApiVersion.class);
+ expect(apiMock.getApiLevel()).andReturn(16);
+ expect(apiMock.getApiString()).andReturn("16");
+ expect(flavor.getTargetSdkVersion()).andReturn(apiMock).anyTimes();
+ replay(apiMock);
+
+ expect(flavor.getVersionCode()).andReturn(2).anyTimes();
+ expect(flavor.getVersionName()).andReturn("MyName").anyTimes();
+ }
+ replay(flavor);
+ Variant mock = createNiceMock(Variant.class);
+ expect(mock.getMergedFlavor()).andReturn(flavor).anyTimes();
+ replay(mock);
+ return mock;
+ }
+ };
+ }
+ };
+
+ }
+ return super.createClient();
}
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ManifestTypoDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ManifestTypoDetectorTest.java
index 169511b..8f53d98 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ManifestTypoDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ManifestTypoDetectorTest.java
@@ -35,10 +35,10 @@
public void testTypoUsesSdk() throws Exception {
assertEquals(""
- + "AndroidManifest.xml:7: Warning: Misspelled tag <use-sdk>: Did you mean <uses-sdk> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:7: Error: Misspelled tag <use-sdk>: Did you mean <uses-sdk> ? [ManifestTypo]\n"
+ " <use-sdk android:minSdkVersion=\"14\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"typo_uses_sdk.xml=>AndroidManifest.xml",
@@ -47,10 +47,10 @@
public void testTypoUsesSdk2() throws Exception {
assertEquals(""
- + "AndroidManifest.xml:7: Warning: Misspelled tag <user-sdk>: Did you mean <uses-sdk> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:7: Error: Misspelled tag <user-sdk>: Did you mean <uses-sdk> ? [ManifestTypo]\n"
+ " <user-sdk android:minSdkVersion=\"14\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"typo_uses_sdk2.xml=>AndroidManifest.xml",
@@ -59,10 +59,10 @@
public void testTypoUsesPermission() throws Exception {
assertEquals(""
- + "AndroidManifest.xml:9: Warning: Misspelled tag <use-permission>: Did you mean <uses-permission> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:9: Error: Misspelled tag <use-permission>: Did you mean <uses-permission> ? [ManifestTypo]\n"
+ " <use-permission android:name=\"com.example.helloworld.permission\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"typo_uses_permission.xml=>AndroidManifest.xml",
@@ -71,10 +71,10 @@
public void testTypoUsesPermission2() throws Exception {
assertEquals(""
- + "AndroidManifest.xml:9: Warning: Misspelled tag <user-permission>: Did you mean <uses-permission> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:9: Error: Misspelled tag <user-permission>: Did you mean <uses-permission> ? [ManifestTypo]\n"
+ " <user-permission android:name=\"com.example.helloworld.permission\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"typo_uses_permission2.xml=>AndroidManifest.xml",
@@ -83,10 +83,10 @@
public void testTypoUsesFeature() throws Exception {
assertEquals(""
- + "AndroidManifest.xml:11: Warning: Misspelled tag <use-feature>: Did you mean <uses-feature> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:11: Error: Misspelled tag <use-feature>: Did you mean <uses-feature> ? [ManifestTypo]\n"
+ " <use-feature android:name=\"android.hardware.wifi\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"typo_uses_feature.xml=>AndroidManifest.xml",
@@ -95,10 +95,10 @@
public void testTypoUsesFeature2() throws Exception {
assertEquals(""
- + "AndroidManifest.xml:11: Warning: Misspelled tag <user-feature>: Did you mean <uses-feature> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:11: Error: Misspelled tag <user-feature>: Did you mean <uses-feature> ? [ManifestTypo]\n"
+ " <user-feature android:name=\"android.hardware.wifi\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"typo_uses_feature2.xml=>AndroidManifest.xml",
@@ -107,10 +107,10 @@
public void testTypoUsesLibrary() throws Exception {
assertEquals(""
- + "AndroidManifest.xml:16: Warning: Misspelled tag <use-library>: Did you mean <uses-library> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:16: Error: Misspelled tag <use-library>: Did you mean <uses-library> ? [ManifestTypo]\n"
+ " <use-library android:name=\"com.example.helloworld\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"typo_uses_library.xml=>AndroidManifest.xml",
@@ -119,10 +119,10 @@
public void testTypoUsesLibrary2() throws Exception {
assertEquals(""
- + "AndroidManifest.xml:16: Warning: Misspelled tag <user-library>: Did you mean <uses-library> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:16: Error: Misspelled tag <user-library>: Did you mean <uses-library> ? [ManifestTypo]\n"
+ " <user-library android:name=\"com.example.helloworld\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"typo_uses_library2.xml=>AndroidManifest.xml",
@@ -131,28 +131,28 @@
public void testOtherTypos() throws Exception {
assertEquals(""
- + "AndroidManifest.xml:2: Warning: Misspelled tag <mannifest>: Did you mean <manifest> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:2: Error: Misspelled tag <mannifest>: Did you mean <manifest> ? [ManifestTypo]\n"
+ "<mannifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ "^\n"
- + "AndroidManifest.xml:7: Warning: Misspelled tag <uses-sd>: Did you mean <uses-sdk> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:7: Error: Misspelled tag <uses-sd>: Did you mean <uses-sdk> ? [ManifestTypo]\n"
+ " <uses-sd android:minSdkVersion=\"14\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:9: Warning: Misspelled tag <spplication>: Did you mean <application> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:9: Error: Misspelled tag <spplication>: Did you mean <application> ? [ManifestTypo]\n"
+ " <spplication\n"
+ " ^\n"
- + "AndroidManifest.xml:12: Warning: Misspelled tag <acctivity>: Did you mean <activity> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:12: Error: Misspelled tag <acctivity>: Did you mean <activity> ? [ManifestTypo]\n"
+ " <acctivity\n"
+ " ^\n"
- + "AndroidManifest.xml:15: Warning: Misspelled tag <inten-filter>: Did you mean <intent-filter> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:15: Error: Misspelled tag <inten-filter>: Did you mean <intent-filter> ? [ManifestTypo]\n"
+ " <inten-filter >\n"
+ " ^\n"
- + "AndroidManifest.xml:16: Warning: Misspelled tag <aktion>: Did you mean <action> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:16: Error: Misspelled tag <aktion>: Did you mean <action> ? [ManifestTypo]\n"
+ " <aktion android:name=\"android.intent.action.MAIN\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:18: Warning: Misspelled tag <caaategory>: Did you mean <category> ? [ManifestTypo]\n"
+ + "AndroidManifest.xml:18: Error: Misspelled tag <caaategory>: Did you mean <category> ? [ManifestTypo]\n"
+ " <caaategory android:name=\"android.intent.category.LAUNCHER\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 7 warnings\n",
+ + "7 errors, 0 warnings\n",
lintProject(
"typo_manifest.xml=>AndroidManifest.xml",
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
index 4d98959..8604401 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
@@ -179,14 +179,51 @@
checkLint(Arrays.asList(master, library)));
}
+ public void testIndirectLibraryProjects() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
+ "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
+ "bytecode/.classpath=>.classpath"
+ );
+ File library2 = getProjectDir("LibraryProject",
+ // Library project
+ "multiproject/library-manifest2.xml=>AndroidManifest.xml",
+ "multiproject/library2.properties=>project.properties"
+ );
+ File library = getProjectDir("RealLibrary",
+ // Library project
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
+ "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
+ "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
+ "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
+ "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
+ "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class"
+ // Missing TestReceiver: Test should complain about just that class
+ );
+ assertEquals(""
+ + "MasterProject/AndroidManifest.xml:32: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <receiver android:name=\"TestReceiver\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ checkLint(Arrays.asList(master, library2, library)));
+ }
+
public void testInnerClassStatic() throws Exception {
mScopes = null;
mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
assertEquals(
- "src/test/pkg/Foo.java:8: Warning: This inner class should be static (test.pkg.Foo.Baz) [Instantiatable]\n" +
+ "src/test/pkg/Foo.java:8: Error: This inner class should be static (test.pkg.Foo.Baz) [Instantiatable]\n" +
" public class Baz extends Activity {\n" +
" ^\n" +
- "0 errors, 1 warnings\n",
+ "1 errors, 0 warnings\n",
lintProject(
"registration/AndroidManifest.xml=>AndroidManifest.xml",
@@ -202,10 +239,10 @@
mScopes = null;
mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
assertEquals(
- "src/test/pkg/Foo/Bar.java:6: Warning: The default constructor must be public [Instantiatable]\n" +
+ "src/test/pkg/Foo/Bar.java:6: Error: The default constructor must be public [Instantiatable]\n" +
" private Bar() {\n" +
" ^\n" +
- "0 errors, 1 warnings\n",
+ "1 errors, 0 warnings\n",
lintProject(
"registration/AndroidManifestInner.xml=>AndroidManifest.xml",
@@ -315,10 +352,10 @@
+ "res/layout/fragment2.xml:17: Error: Class referenced in the layout file, my.app.Fragment2, was not found in the project or the libraries [MissingRegistered]\n"
+ " <fragment\n"
+ " ^\n"
- + "src/test/pkg/Foo/Bar.java:6: Warning: The default constructor must be public [Instantiatable]\n"
+ + "src/test/pkg/Foo/Bar.java:6: Error: The default constructor must be public [Instantiatable]\n"
+ " private Bar() {\n"
+ " ^\n"
- + "3 errors, 1 warnings\n",
+ + "4 errors, 0 warnings\n",
lintProject(
"bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
@@ -379,6 +416,47 @@
));
}
+ public void testCustomViewInCapitalizedPackage() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "res/layout/customview3.xml",
+ "bytecode/CustomView3.java.txt=>src/test/bytecode/CustomView3.java",
+ "bytecode/CustomView3.class.data=>bin/classes/test/bytecode/CustomView3.class"
+ ));
+ }
+
+ public void testCustomViewNotReferenced() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/CustomView3.java.txt=>src/test/bytecode/CustomView3.java",
+ "bytecode/CustomView3.class.data=>bin/classes/test/bytecode/CustomView3.class"
+ ));
+ }
+
+
+ public void testMissingClass() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/user_prefs_fragment.xml=>res/layout/user_prefs_fragment.xml",
+ "bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data=>bin/classes/course/examples/DataManagement/PreferenceActivity/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class"
+ ));
+ }
+
public void testFragments() throws Exception {
mScopes = Scope.MANIFEST_SCOPE;
mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
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 7f8cd07..e27d93d 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
@@ -81,49 +81,46 @@
public void testTypo() throws Exception {
assertEquals(
- "res/layout/wrong_namespace.xml:2: Warning: Unexpected namespace URI bound to the \"android\" prefix, was http://schemas.android.com/apk/res/andriod, expected http://schemas.android.com/apk/res/android [NamespaceTypo]\n" +
+ "res/layout/wrong_namespace.xml:2: Error: Unexpected namespace URI bound to the \"android\" prefix, was http://schemas.android.com/apk/res/andriod, expected http://schemas.android.com/apk/res/android [NamespaceTypo]\n" +
"<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/andriod\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
+ "1 errors, 0 warnings\n",
lintProject("res/layout/wrong_namespace.xml"));
}
public void testTypo2() throws Exception {
assertEquals(
- "res/layout/wrong_namespace2.xml:2: Warning: URI is case sensitive: was \"http://schemas.android.com/apk/res/Android\", expected \"http://schemas.android.com/apk/res/android\" [NamespaceTypo]\n" +
+ "res/layout/wrong_namespace2.xml:2: Error: URI is case sensitive: was \"http://schemas.android.com/apk/res/Android\", expected \"http://schemas.android.com/apk/res/android\" [NamespaceTypo]\n" +
"<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/Android\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
+ "1 errors, 0 warnings\n",
lintProject("res/layout/wrong_namespace2.xml"));
}
public void testTypo3() throws Exception {
assertEquals(
- "res/layout/wrong_namespace3.xml:2: Warning: Unexpected namespace URI bound to the \"android\" prefix, was http://schemas.android.com/apk/res/androi, expected http://schemas.android.com/apk/res/android [NamespaceTypo]\n" +
+ "res/layout/wrong_namespace3.xml:2: Error: Unexpected namespace URI bound to the \"android\" prefix, was http://schemas.android.com/apk/res/androi, expected http://schemas.android.com/apk/res/android [NamespaceTypo]\n" +
"<LinearLayout xmlns:a=\"http://schemas.android.com/apk/res/androi\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
+ "1 errors, 0 warnings\n",
lintProject("res/layout/wrong_namespace3.xml"));
}
public void testTypo4() throws Exception {
assertEquals(
- "res/layout/wrong_namespace5.xml:2: Warning: Suspicious namespace: should start with http:// [NamespaceTypo]\n" +
+ "res/layout/wrong_namespace5.xml:2: Error: Suspicious namespace: should start with http:// [NamespaceTypo]\n" +
" xmlns:noturi=\"tp://schems.android.com/apk/res/com.my.package\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/wrong_namespace5.xml:3: Warning: Possible typo in URL: was \"http://schems.android.com/apk/res/com.my.package\", should probably be \"http://schemas.android.com/apk/res/com.my.package\" [NamespaceTypo]\n" +
+ "res/layout/wrong_namespace5.xml:3: Error: Possible typo in URL: was \"http://schems.android.com/apk/res/com.my.package\", should probably be \"http://schemas.android.com/apk/res/com.my.package\" [NamespaceTypo]\n" +
" xmlns:typo1=\"http://schems.android.com/apk/res/com.my.package\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/wrong_namespace5.xml:4: Warning: Possible typo in URL: was \"http://schems.android.comm/apk/res/com.my.package\", should probably be \"http://schemas.android.com/apk/res/com.my.package\" [NamespaceTypo]\n" +
+ "res/layout/wrong_namespace5.xml:4: Error: Possible typo in URL: was \"http://schems.android.comm/apk/res/com.my.package\", should probably be \"http://schemas.android.com/apk/res/com.my.package\" [NamespaceTypo]\n" +
" xmlns:typo2=\"http://schems.android.comm/apk/res/com.my.package\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 3 warnings\n",
+ "3 errors, 0 warnings\n",
lintProject("res/layout/wrong_namespace5.xml"));
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/NfcTechListDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/NfcTechListDetectorTest.java
new file mode 100644
index 0000000..e5b535a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/NfcTechListDetectorTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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("javadoc")
+public class NfcTechListDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new NfcTechListDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "res/xml/nfc_tech_list_formatted.xml:6: Error: There should not be any whitespace inside <tech> elements [NfcTechWhitespace]\n"
+ + "android.nfc.tech.NfcA\n"
+ + "^\n"
+ + "res/xml/nfc_tech_list_formatted.xml:12: Error: There should not be any whitespace inside <tech> elements [NfcTechWhitespace]\n"
+ + "android.nfc.tech.MifareUltralight\n"
+ + "^\n"
+ + "res/xml/nfc_tech_list_formatted.xml:18: Error: There should not be any whitespace inside <tech> elements [NfcTechWhitespace]\n"
+ + "android.nfc.tech.ndefformatable\n"
+ + "^\n"
+ + "3 errors, 0 warnings\n",
+
+ lintProject(
+ "res/xml/nfc_tech_list_formatted.xml"));
+ }
+
+ public void testOk() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/xml/nfc_tech_list.xml"));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetectorTest.java
index 87211a2..c597ba2 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetectorTest.java
@@ -84,8 +84,13 @@
"res/layout/wrongparams5.xml:15: Warning: Invalid layout param 'layout_alignParentLeft' (included from within a LinearLayout in layout/wrongparams6.xml) [ObsoleteLayoutParam]\n" +
" android:layout_alignParentLeft=\"true\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 2 warnings\n" +
- "",
+ "res/layout/wrongparams6.xml:16: Warning: Invalid layout param in a LinearLayout: layout_alignStart [ObsoleteLayoutParam]\n" +
+ " android:layout_alignStart=\"@+id/include1\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/wrongparams6.xml:17: Warning: Invalid layout param in a LinearLayout: layout_toEndOf [ObsoleteLayoutParam]\n" +
+ " android:layout_toEndOf=\"@+id/include1\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 4 warnings\n",
lintProject("res/layout/wrongparams5.xml", "res/layout/wrongparams6.xml"));
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java
new file mode 100644
index 0000000..cfff113
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tools.lint.checks.PluralsDatabase.Quantity;
+
+import junit.framework.TestCase;
+
+import java.util.EnumSet;
+
+public class PluralsDatabaseTest extends TestCase {
+ public void testGetRelevant() {
+ PluralsDatabase db = PluralsDatabase.get();
+ assertNull(db.getRelevant("unknown"));
+ EnumSet<Quantity> relevant = db.getRelevant("en");
+ assertNotNull(relevant);
+ assertEquals(1, relevant.size());
+ assertSame(Quantity.one, relevant.iterator().next());
+
+ relevant = db.getRelevant("cs");
+ assertNotNull(relevant);
+ assertEquals(EnumSet.of(Quantity.few, Quantity.one), relevant);
+ }
+
+ public void testHasMultiValue() {
+ PluralsDatabase db = PluralsDatabase.get();
+
+ assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.one));
+ assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.two));
+ assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.few));
+ assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.many));
+
+ assertTrue(db.hasMultipleValuesForQuantity("br", Quantity.two));
+ assertTrue(db.hasMultipleValuesForQuantity("mk", Quantity.one));
+ assertTrue(db.hasMultipleValuesForQuantity("lv", Quantity.zero));
+ }
+}
\ No newline at end of file
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 0582d3b..98a3260 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
@@ -16,7 +16,16 @@
package com.android.tools.lint.checks;
+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.Sets;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
@SuppressWarnings("javadoc")
public class PluralsDetectorTest extends AbstractCheckTest {
@@ -26,11 +35,12 @@
}
public void test1() throws Exception {
+ mEnabled = Sets.newHashSet(PluralsDetector.MISSING, PluralsDetector.EXTRA);
assertEquals(""
- + "res/values-pl/plurals2.xml:3: Warning: For locale \"pl\" the following quantities should also be defined: many [MissingQuantity]\n"
+ + "res/values-pl/plurals2.xml:3: Error: For locale \"pl\" (Polish) the following quantities should also be defined: many [MissingQuantity]\n"
+ " <plurals name=\"numberOfSongsAvailable\">\n"
+ " ^\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"res/values/plurals.xml",
@@ -39,17 +49,18 @@
}
public void test2() throws Exception {
+ mEnabled = Sets.newHashSet(PluralsDetector.MISSING, PluralsDetector.EXTRA);
assertEquals(""
- + "res/values-cs/plurals3.xml:3: Warning: For locale \"cs\" the following quantities should also be defined: few [MissingQuantity]\n" +
+ + "res/values-cs/plurals3.xml:3: Error: For locale \"cs\" (Czech) 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" +
+ "res/values-zh-rCN/plurals3.xml:3: Warning: For language \"zh\" (Chinese) 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" +
+ "res/values-zh-rCN/plurals3.xml:7: Warning: For language \"zh\" (Chinese) the following quantities are not relevant: one [UnusedQuantity]\n" +
" <plurals name=\"title_day_dialog_content\">\n" +
" ^\n" +
- "0 errors, 3 warnings\n",
+ "1 errors, 2 warnings\n",
lintProject(
"res/values-zh-rCN/plurals3.xml",
@@ -57,13 +68,58 @@
}
public void testEmptyPlural() throws Exception {
+ mEnabled = Sets.newHashSet(PluralsDetector.MISSING, PluralsDetector.EXTRA);
assertEquals(""
- + "res/values/plurals4.xml:3: Warning: There should be at least one quantity string in this <plural> definition [MissingQuantity]\n"
+ + "res/values/plurals4.xml:3: Error: There should be at least one quantity string in this <plural> definition [MissingQuantity]\n"
+ " <plurals name=\"minutes_until_num\">\n"
+ " ^\n"
- + "0 errors, 1 warnings\n",
+ + "1 errors, 0 warnings\n",
lintProject(
"res/values/plurals4.xml"));
}
+
+ public void testPolish() throws Exception {
+ // Test for https://code.google.com/p/android/issues/detail?id=67803
+ mEnabled = Sets.newHashSet(PluralsDetector.MISSING, PluralsDetector.EXTRA);
+ assertEquals(""
+ + "res/values-pl/plurals5.xml:3: Error: For locale \"pl\" (Polish) the following quantities should also be defined: many [MissingQuantity]\n"
+ + " <plurals name=\"my_plural\">\n"
+ + " ^\n"
+ + "res/values-pl/plurals5.xml:3: Warning: For language \"pl\" (Polish) the following quantities are not relevant: zero [UnusedQuantity]\n"
+ + " <plurals name=\"my_plural\">\n"
+ + " ^\n"
+ + "1 errors, 1 warnings\n",
+
+ lintProject(
+ "res/values/plurals5.xml=>res/values-pl/plurals5.xml"));
+ }
+
+ public void testImpliedQuantity() throws Exception {
+ mEnabled = Collections.singleton(PluralsDetector.IMPLIED_QUANTITY);
+ assertEquals(""
+ + "res/values-sl/plurals2.xml:4: Error: The quantity 'one' matches more than one specific number in this locale, but the message did not include a formatting argument (such as %d). This is usually an internationalization error. See full issue explanation for more. [ImpliedQuantity]\n"
+ + " <item quantity=\"one\">Znaleziono jednÄ… piosenkÄ™.</item>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/plurals.xml",
+ "res/values/plurals2.xml",
+ "res/values-pl/plurals2.xml",
+ // Simulate locale message for locale which has multiple values for one
+ "res/values-pl/plurals2.xml=>res/values-sl/plurals2.xml"));
+ }
+
+ private Set<Issue> mEnabled = new HashSet<Issue>();
+
+ @Override
+ protected TestConfiguration getConfiguration(LintClient client, Project project) {
+ return new TestConfiguration(client, project, null) {
+ @Override
+ public boolean isEnabled(@NonNull Issue issue) {
+ return super.isEnabled(issue) && mEnabled.contains(issue);
+ }
+ };
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/PreferenceActivityDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/PreferenceActivityDetectorTest.java
new file mode 100644
index 0000000..90225f6
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/PreferenceActivityDetectorTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 PreferenceActivityDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new PreferenceActivityDetector();
+ }
+
+ public void testWarningWhenImplicitlyExportingPreferenceActivity() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:28: Warning: PreferenceActivity should not be exported [ExportedPreferenceActivity]\n"
+ + " <activity\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "export_preference_activity_implicit.xml=>AndroidManifest.xml"
+ ));
+ }
+
+ public void testWarningWhenExplicitlyExportingPreferenceActivity() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:28: Warning: PreferenceActivity should not be exported [ExportedPreferenceActivity]\n"
+ + " <activity\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "export_preference_activity_explicit.xml=>AndroidManifest.xml"
+ ));
+ }
+
+ public void testNoWarningWhenExportingNonPreferenceActivity() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "AndroidManifest.xml=>AndroidManifest.xml"
+ ));
+ }
+
+ public void testNoWarningWhenSuppressed() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "export_preference_activity_suppressed.xml=>AndroidManifest.xml"
+ ));
+ }
+
+ public void testWarningWhenImplicitlyExportingPreferenceActivitySubclass() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:28: Warning: PreferenceActivity subclass test.pkg.PreferenceActivitySubclass should not be exported [ExportedPreferenceActivity]\n"
+ + " <activity\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "android/PreferenceActivity.java.txt=>src/android/app/PreferenceActivity.java",
+ "src/test/pkg/PreferenceActivitySubclass.java.txt=>src/test/pkg/PreferenceActivitySubclass.java",
+ "export_preference_activity_subclass_implicit.xml=>AndroidManifest.xml"
+ ));
+ }
+
+ public void testWarningWhenExplicitlyExportingPreferenceActivitySubclass() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:28: Warning: PreferenceActivity subclass test.pkg.PreferenceActivitySubclass should not be exported [ExportedPreferenceActivity]\n"
+ + " <activity\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "android/PreferenceActivity.java.txt=>src/android/app/PreferenceActivity.java",
+ "src/test/pkg/PreferenceActivitySubclass.java.txt=>src/test/pkg/PreferenceActivitySubclass.java",
+ "export_preference_activity_subclass_explicit.xml=>AndroidManifest.xml"
+ ));
+ }
+
+ public void testNoWarningWhenActivityNotExported() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "export_preference_activity_no_export.xml=>AndroidManifest.xml"
+ ));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/PrivateKeyDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/PrivateKeyDetectorTest.java
index ff72dcb..541293f 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/PrivateKeyDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/PrivateKeyDetectorTest.java
@@ -32,8 +32,8 @@
public void testPrivateKey() throws Exception {
assertEquals(
- "res/private_key.pem: Warning: The res/private_key.pem file seems to be a private key file. Please make sure not to embed this in your APK file. [PackagedPrivateKey]\n" +
- "0 errors, 1 warnings\n",
+ "res/private_key.pem: Error: The res/private_key.pem file seems to be a private key file. Please make sure not to embed this in your APK file. [PackagedPrivateKey]\n" +
+ "1 errors, 0 warnings\n",
lintProject(
// Not a private key file
"res/values/strings.xml=>res/values/strings/xml",
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/PropertyFileDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/PropertyFileDetectorTest.java
new file mode 100644
index 0000000..88992e9
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/PropertyFileDetectorTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 PropertyFileDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new PropertyFileDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "local.properties:11: Error: Colon (:) must be escaped in .property files [PropertyEscape]\n"
+ + "windows.dir=C:\\my\\path\\to\\sdk\n"
+ + " ~\n"
+ + "local.properties:11: Error: Windows file separators (\\) must be escaped (\\\\); use C:\\\\my\\\\path\\\\to\\\\sdk [PropertyEscape]\n"
+ + "windows.dir=C:\\my\\path\\to\\sdk\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+ lintProject("local.properties=>local.properties"));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/PxUsageDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/PxUsageDetectorTest.java
index 71bec76..f9d01d6 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/PxUsageDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/PxUsageDetectorTest.java
@@ -86,4 +86,25 @@
lintFiles("res/values/pxsp.xml"));
}
+
+ public void testIncrementalDimensions() throws Exception {
+ assertEquals(""
+ + "res/layout/textsize2.xml:9: Warning: Should use \"sp\" instead of \"dp\" for text sizes (@dimen/bottom_bar_portrait_button_font_size is defined as 16dp in /TESTROOT/res/values/dimens.xml [SpUsage]\n"
+ + " android:textSize=\"@dimen/bottom_bar_portrait_button_font_size\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProjectIncrementally(
+ "res/layout/textsize2.xml",
+ "res/values/dimens.xml", "res/layout/textsize2.xml"));
+ }
+
+ public void testBatchDimensions() throws Exception {
+ assertEquals(""
+ + "res/values/dimens.xml:2: Warning: This dimension is used as a text size: Should use \"sp\" instead of \"dp\" [SpUsage]\n"
+ + " <dimen name=\"bottom_bar_portrait_button_font_size\">16dp</dimen>\n"
+ + " ^\n"
+ + " res/layout/textsize2.xml:9: Dimension used as a text size here\n"
+ + "0 errors, 1 warnings\n",
+ lintFiles("res/values/dimens.xml", "res/layout/textsize2.xml"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ResourceCycleDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ResourceCycleDetectorTest.java
new file mode 100644
index 0000000..238234d
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ResourceCycleDetectorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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("javadoc")
+public class ResourceCycleDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ResourceCycleDetector();
+ }
+
+ public void testStyles() throws Exception {
+ assertEquals(""
+ + "res/values/styles.xml:9: Error: Style DetailsPage_EditorialBuyButton should not extend itself [ResourceCycle]\n"
+ + "<style name=\"DetailsPage_EditorialBuyButton\" parent=\"@style/DetailsPage_EditorialBuyButton\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject("res/values/styles.xml"));
+ }
+
+ public void testStyleImpliedParent() throws Exception {
+ assertEquals(""
+ + "res/values/stylecycle.xml:3: Error: Potential cycle: PropertyToggle is the implied parent of PropertyToggle.Base and this defines the opposite [ResourceCycle]\n"
+ + " <style name=\"PropertyToggle\" parent=\"@style/PropertyToggle.Base\"></style>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject("res/values/stylecycle.xml"));
+ }
+
+ public void testLayouts() throws Exception {
+ assertEquals(""
+ + "res/layout/layoutcycle1.xml:10: Error: Layout layoutcycle1 should not include itself [ResourceCycle]\n"
+ + " layout=\"@layout/layoutcycle1\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject("res/layout/layoutcycle1.xml"));
+ }
+
+ public void testColors() throws Exception {
+ assertEquals(""
+ + "res/values/colorcycle1.xml:2: Error: Color test should not reference itself [ResourceCycle]\n"
+ + " <color name=\"test\">@color/test</color>\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject("res/values/colorcycle1.xml"));
+ }
+
+ public void testAaptCrash() throws Exception {
+ assertEquals(""
+ + "res/values/aaptcrash.xml:5: Error: This construct can potentially crash aapt during a build. Change @+id/titlebar to @id/titlebar and define the id explicitly using <item type=\"id\" name=\"titlebar\"/> instead. [AaptCrash]\n"
+ + " <item name=\"android:id\">@+id/titlebar</item>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject("res/values/aaptcrash.xml"));
+ }
+
+ public void testDeepColorCycle1() throws Exception {
+ assertEquals(""
+ + "res/values/colorcycle2.xml:2: Error: Color Resource definition cycle: test1 => test2 => test3 => test1 [ResourceCycle]\n"
+ + " <color name=\"test1\">@color/test2</color>\n"
+ + " ^\n"
+ + " res/values/colorcycle4.xml:2: Reference from @color/test3 to color/test1 here\n"
+ + " res/values/colorcycle3.xml:2: Reference from @color/test2 to color/test3 here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/colorcycle2.xml",
+ "res/values/colorcycle3.xml",
+ "res/values/colorcycle4.xml"
+ ));
+ }
+
+ public void testDeepColorCycle2() throws Exception {
+ assertEquals(""
+ + "res/values/colorcycle5.xml:2: Error: Color Resource definition cycle: test1 => test2 => test1 [ResourceCycle]\n"
+ + " <color name=\"test1\">@color/test2</color>\n"
+ + " ^\n"
+ + " res/values/colorcycle5.xml:3: Reference from @color/test2 to color/test1 here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/colorcycle5.xml"
+ ));
+ }
+
+ public void testDeepStyleCycle1() throws Exception {
+ assertEquals(""
+ + "res/values/stylecycle1.xml:6: Error: Style Resource definition cycle: ButtonStyle => ButtonStyle.Base => ButtonStyle [ResourceCycle]\n"
+ + " <style name=\"ButtonStyle\" parent=\"ButtonStyle.Base\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/stylecycle1.xml:3: Reference from @style/ButtonStyle.Base to style/ButtonStyle here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/stylecycle1.xml"
+ ));
+ }
+
+ public void testDeepStyleCycle2() throws Exception {
+ assertEquals(""
+ + "res/values/stylecycle2.xml:3: Error: Style Resource definition cycle: mystyle1 => mystyle2 => mystyle3 => mystyle1 [ResourceCycle]\n"
+ + " <style name=\"mystyle1\" parent=\"@style/mystyle2\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/stylecycle2.xml:9: Reference from @style/mystyle3 to style/mystyle1 here\n"
+ + " res/values/stylecycle2.xml:6: Reference from @style/mystyle2 to style/mystyle3 here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/stylecycle2.xml"
+ ));
+ }
+
+ public void testDeepIncludeOk() throws Exception {
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "res/layout/layout1.xml",
+ "res/layout/layout2.xml",
+ "res/layout/layout3.xml",
+ "res/layout/layout4.xml"
+ ));
+ }
+
+ public void testDeepIncludeCycle() throws Exception {
+ assertEquals(""
+ + "res/layout/layout1.xml:10: Error: Layout Resource definition cycle: layout1 => layout2 => layout4 => layout1 [ResourceCycle]\n"
+ + " layout=\"@layout/layout2\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/layout/layout4.xml:16: Reference from @layout/layout4 to layout/layout1 here\n"
+ + " res/layout/layout2.xml:16: Reference from @layout/layout2 to layout/layout4 here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/layout/layout1.xml",
+ "res/layout/layout2.xml",
+ "res/layout/layout3.xml",
+ "res/layout/layout4cycle.xml=>res/layout/layout4.xml"
+ ));
+ }
+
+ public void testDeepAliasCycle() throws Exception {
+ assertEquals(""
+ + "res/values/aliases.xml:2: Error: Layout Resource definition cycle: layout10 => layout20 => layout30 => layout10 [ResourceCycle]\n"
+ + " <item name=\"layout10\" type=\"layout\">@layout/layout20</item>\n"
+ + " ^\n"
+ + " res/values/aliases.xml:4: Reference from @layout/layout30 to layout/layout10 here\n"
+ + " res/values/aliases.xml:3: Reference from @layout/layout20 to layout/layout30 here\n"
+ + "res/values/colorcycle2.xml:2: Error: Color Resource definition cycle: test1 => test2 => test1 [ResourceCycle]\n"
+ + " <color name=\"test1\">@color/test2</color>\n"
+ + " ^\n"
+ + " res/values/aliases.xml:5: Reference from @color/test2 to color/test1 here\n"
+ + "res/layout/layout1.xml:10: Error: Layout Resource definition cycle: layout1 => layout2 => layout4 => layout1 [ResourceCycle]\n"
+ + " layout=\"@layout/layout2\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/aliases.xml:6: Reference from @layout/layout4 to layout/layout1 here\n"
+ + " res/layout/layout2.xml:16: Reference from @layout/layout2 to layout/layout4 here\n"
+ + "3 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/aliases.xml",
+ "res/layout/layout1.xml",
+ "res/layout/layout2.xml",
+ "res/layout/layout3.xml",
+ "res/values/colorcycle2.xml"
+ ));
+ }
+
+ public void testColorStateListCycle() throws Exception {
+ assertEquals(""
+ + "res/values/aliases2.xml:2: Error: Color Resource definition cycle: bright_foreground_dark => color1 => bright_foreground_dark [ResourceCycle]\n"
+ + " <item name=\"bright_foreground_dark\" type=\"color\">@color/color1</item>\n"
+ + " ^\n"
+ + " res/color/color1.xml:3: Reference from @color/color1 to color/bright_foreground_dark here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/color/color1.xml",
+ "res/values/aliases2.xml"
+ ));
+ }
+
+ public void testDrawableStateListCycle() throws Exception {
+ assertEquals(""
+ + "res/drawable/drawable1.xml:4: Error: Drawable Resource definition cycle: drawable1 => textfield_search_pressed => drawable2 => drawable1 [ResourceCycle]\n"
+ + " <item android:state_window_focused=\"false\" android:state_enabled=\"true\"\n"
+ + " ^\n"
+ + " res/values/aliases2.xml:4: Reference from @drawable/drawable2 to drawable/drawable1 here\n"
+ + " res/values/aliases2.xml:3: Reference from @drawable/textfield_search_pressed to drawable/drawable2 here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/drawable/drawable1.xml",
+ "res/values/aliases2.xml"
+ ));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java
new file mode 100644
index 0000000..95ec9fe
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Project;
+
+import java.io.File;
+import java.util.Arrays;
+
+public class ResourcePrefixDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new ResourcePrefixDetector();
+ }
+
+ public void testResourceFiles() throws Exception {
+ assertEquals(""
+ + "res/drawable-mdpi/frame.png: Error: Resource named 'frame' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_frame' ? [ResourceName]\n"
+ + "res/layout/layout1.xml:2: Error: Resource named 'layout1' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_layout1' ? [ResourceName]\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "res/menu/menu.xml:2: Error: Resource named 'menu' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_menu' ? [ResourceName]\n"
+ + "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
+ + "^\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ "res/layout/layout1.xml",
+ "res/menu/menu.xml",
+ "res/layout/layout1.xml=>res/layout/unit_test_prefix_ok.xml",
+ "res/drawable-mdpi/frame.png",
+ "res/drawable-mdpi/frame.png=>res/drawable/unit_test_prefix_ok1.png",
+ "res/drawable-mdpi/frame.png=>res/drawable/unit_test_prefix_ok2.9.png"
+ ));
+ }
+
+ public void testValues() throws Exception {
+ assertEquals(""
+ + "res/values/customattr.xml:2: Error: Resource named 'ContentFrame' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_ContentFrame' ? [ResourceName]\n"
+ + " <declare-styleable name=\"ContentFrame\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/customattr.xml:3: Error: Resource named 'content' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_content' ? [ResourceName]\n"
+ + " <attr name=\"content\" format=\"reference\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/customattr.xml:4: Error: Resource named 'contentId' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_contentId' ? [ResourceName]\n"
+ + " <attr name=\"contentId\" format=\"reference\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/customattrlayout.xml:2: Error: Resource named 'customattrlayout' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_customattrlayout' ? [ResourceName]\n"
+ + "<foo.bar.ContentFrame\n"
+ + "^\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/customattr.xml",
+ "res/layout/customattrlayout.xml",
+ "unusedR.java.txt=>gen/my/pkg/R.java",
+ "AndroidManifest.xml"));
+ }
+
+ public void testMultiProject() throws Exception {
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
+ );
+ File library = getProjectDir("LibraryProject",
+ // Library project
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>res/values/strings.xml"
+ );
+ assertEquals(""
+ + "LibraryProject/res/values/strings.xml:4: Error: Resource named 'app_name' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_app_name' ? [ResourceName]\n"
+ + " <string name=\"app_name\">LibraryProject</string>\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "LibraryProject/res/values/strings.xml:5: Error: Resource named 'string1' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_string1' ? [ResourceName]\n"
+ + " <string name=\"string1\">String 1</string>\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "LibraryProject/res/values/strings.xml:6: Error: Resource named 'string2' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_string2' ? [ResourceName]\n"
+ + " <string name=\"string2\">String 2</string>\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "LibraryProject/res/values/strings.xml:7: Error: Resource named 'string3' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_string3' ? [ResourceName]\n"
+ + " <string name=\"string3\">String 3</string>\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "4 errors, 0 warnings\n",
+
+ checkLint(Arrays.asList(master, library)).replace("/TESTROOT/",""));
+ }
+
+ // TODO: Test suppressing root level tag
+
+ @Override
+ protected TestLintClient createClient() {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ AndroidProject project = createNiceMock(AndroidProject.class);
+ expect(project.getResourcePrefix()).andReturn("unit_test_prefix_").anyTimes();
+ replay(project);
+ return project;
+ }
+ };
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
index bf34e94..3528781 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
@@ -16,6 +16,19 @@
package com.android.tools.lint.checks;
+import static com.android.SdkConstants.ATTR_DRAWABLE_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_END;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_START;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_END;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.tools.lint.checks.RtlDetector.ATTRIBUTES;
+import static com.android.tools.lint.checks.RtlDetector.convertNewToOld;
+import static com.android.tools.lint.checks.RtlDetector.convertOldToNew;
+import static com.android.tools.lint.checks.RtlDetector.convertToOppositeDirection;
+import static com.android.tools.lint.checks.RtlDetector.isRtlAttributeName;
+
import com.android.annotations.NonNull;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Detector;
@@ -39,6 +52,17 @@
ALL.add(RtlDetector.USE_START);
ALL.add(RtlDetector.ENABLED);
ALL.add(RtlDetector.COMPAT);
+ ALL.add(RtlDetector.SYMMETRY);
+ }
+
+ public void testIsRtlAttributeName() {
+ assertTrue(isRtlAttributeName(ATTR_LAYOUT_ALIGN_PARENT_START));
+ assertTrue(isRtlAttributeName(ATTR_LAYOUT_MARGIN_END));
+ assertTrue(isRtlAttributeName(ATTR_LAYOUT_ALIGN_END));
+ assertFalse(isRtlAttributeName(ATTR_LAYOUT_ALIGN_PARENT_LEFT));
+ assertFalse(isRtlAttributeName(ATTR_DRAWABLE_RIGHT));
+ assertFalse(isRtlAttributeName(ATTR_LAYOUT_ALIGN_RIGHT));
+ assertFalse(isRtlAttributeName(ATTR_NAME));
}
@Override
@@ -332,4 +356,61 @@
));
}
+ public void testNullLocalName() throws Exception {
+ // Regression test for attribute with null local name
+ mEnabled = ALL;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "overdraw/project.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/rtl_noprefix.xml=>res/layout/rtl.xml"
+ ));
+ }
+
+ public void testSymmetry() throws Exception {
+ mEnabled = Collections.singleton(RtlDetector.SYMMETRY);
+ assertEquals(""
+ + "res/layout/relative.xml:29: Error: When you define %1$s you should probably also define %2$s for right-to-left symmetry [RtlSymmetry]\n"
+ + " android:paddingRight=\"120dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/relative.xml=>res/layout/relative.xml"
+ ));
+ }
+
+ public void testConvertBetweenAttributes() {
+ assertEquals("alignParentStart", convertOldToNew("alignParentLeft"));
+ assertEquals("alignParentEnd", convertOldToNew("alignParentRight"));
+ assertEquals("paddingEnd", convertOldToNew("paddingRight"));
+ assertEquals("paddingStart", convertOldToNew("paddingLeft"));
+
+ assertEquals("alignParentLeft", convertNewToOld("alignParentStart"));
+ assertEquals("alignParentRight", convertNewToOld("alignParentEnd"));
+ assertEquals("paddingRight", convertNewToOld("paddingEnd"));
+ assertEquals("paddingLeft", convertNewToOld("paddingStart"));
+
+ for (int i = 0, n = ATTRIBUTES.length; i < n; i += 2) {
+ String oldAttribute = ATTRIBUTES[i];
+ String newAttribute = ATTRIBUTES[i + 1];
+ assertEquals(newAttribute, convertOldToNew(oldAttribute));
+ assertEquals(oldAttribute, convertNewToOld(newAttribute));
+ }
+ }
+
+ public void testConvertToOppositeDirection() {
+ assertEquals("alignParentRight", convertToOppositeDirection("alignParentLeft"));
+ assertEquals("alignParentLeft", convertToOppositeDirection("alignParentRight"));
+ assertEquals("alignParentStart", convertToOppositeDirection("alignParentEnd"));
+ assertEquals("alignParentEnd", convertToOppositeDirection("alignParentStart"));
+ assertEquals("paddingLeft", convertToOppositeDirection("paddingRight"));
+ assertEquals("paddingRight", convertToOppositeDirection("paddingLeft"));
+ assertEquals("paddingStart", convertToOppositeDirection("paddingEnd"));
+ assertEquals("paddingEnd", convertToOppositeDirection("paddingStart"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
index 7035c21..4fb95fa 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
@@ -69,8 +69,11 @@
public void test4() throws Exception {
// Regression test 3 for http://code.google.com/p/android/issues/detail?id=34322
- assertEquals(
- "No warnings.",
+ assertEquals(""
+ + "src/test/pkg/SharedPrefsTest4.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n"
+ + " Editor editor = preferences.edit();\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
lintProject("src/test/pkg/SharedPrefsTest4.java.txt=>" +
"src/test/pkg/SharedPrefsTest4.java"));
@@ -106,4 +109,34 @@
"src/test/pkg/SharedPrefsTest5.java"));
}
+ public void test6() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=68692
+ assertEquals(""
+ + "src/test/pkg/SharedPrefsTest7.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n"
+ + " settings.edit().putString(MY_PREF_KEY, myPrefValue);\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("src/test/pkg/SharedPrefsTest7.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest7.java"));
+ }
+
+ public void test7() throws Exception {
+ assertEquals("No warnings.", // minSdk < 9: no warnings
+
+ lintProject("src/test/pkg/SharedPrefsTest8.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest8.java"));
+ }
+
+ public void test8() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/SharedPrefsTest8.java:11: Warning: Consider using apply() instead; commit writes its data to persistent storage immediately, whereas apply will handle it in the background [CommitPrefEdits]\n"
+ + " editor.commit();\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk11.xml=>AndroidManifest.xml",
+ "src/test/pkg/SharedPrefsTest8.java.txt=>src/test/pkg/SharedPrefsTest8.java"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/SignatureOrSystemDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/SignatureOrSystemDetectorTest.java
new file mode 100644
index 0000000..0a9393f
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/SignatureOrSystemDetectorTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 SignatureOrSystemDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new SignatureOrSystemDetector();
+ }
+
+ public void testNoWarningOnProtectionLevelsOtherThanSignatureOrSystem() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "protection_level_ok.xml=>AndroidManifest.xml")
+ );
+ }
+
+ public void testWarningOnSignatureOrSystemProtectionLevel() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:13: Warning: protectionLevel should probably not be set to signatureOrSystem [SignatureOrSystemPermissions]\n"
+ + " android:protectionLevel=\"signatureOrSystem\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "protection_level_fail.xml=>AndroidManifest.xml")
+ );
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
index 65b2058..c00c4a1 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
@@ -32,7 +32,7 @@
public void testAll() throws Exception {
assertEquals(
- "src/test/pkg/StringFormatActivity.java:13: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String [StringFormatMatches]\n" +
+ "src/test/pkg/StringFormatActivity.java:13: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
" String output1 = String.format(hello, target);\n" +
" ~~~~~~\n" +
" res/values-es/formatstrings.xml:3: Conflicting argument declaration here\n" +
@@ -40,6 +40,14 @@
" String output2 = String.format(hello2, target, \"How are you\");\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
" res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:21: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output4 = String.format(score, true); // wrong\n" +
+ " ~~~~\n" +
+ " res/values/formatstrings.xml:6: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:22: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output4 = String.format(score, won); // wrong\n" +
+ " ~~~\n" +
+ " res/values/formatstrings.xml:6: Conflicting argument declaration here\n" +
"src/test/pkg/StringFormatActivity.java:24: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
" String.format(getResources().getString(R.string.hello2), target, \"How are you\");\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
@@ -52,13 +60,13 @@
" getResources().getString(R.string.hello2, target, \"How are you\");\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
" res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
- "src/test/pkg/StringFormatActivity.java:33: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String [StringFormatMatches]\n" +
+ "src/test/pkg/StringFormatActivity.java:33: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
" String output1 = String.format(hello, target);\n" +
" ~~~~~~\n" +
" res/values-es/formatstrings.xml:3: Conflicting argument declaration here\n" +
"res/values-es/formatstrings.xml:3: Error: Inconsistent formatting types for argument #1 in format string hello ('%1$d'): Found both 's' and 'd' (in values/formatstrings.xml) [StringFormatMatches]\n" +
" <string name=\"hello\">%1$d</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " ~~~~\n" +
" res/values/formatstrings.xml:3: Conflicting argument type here\n" +
"res/values-es/formatstrings.xml:4: Warning: Inconsistent number of arguments in formatting string hello2; found both 2 and 3 [StringFormatCount]\n" +
" <string name=\"hello2\">%3$d: %1$s, %2$s?</string>\n" +
@@ -67,8 +75,7 @@
"res/values/formatstrings.xml:5: Warning: Formatting string 'missing' is not referencing numbered arguments [1, 2] [StringFormatCount]\n" +
" <string name=\"missing\">Hello %3$s World</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "7 errors, 2 warnings\n" +
- "",
+ "9 errors, 2 warnings\n",
lintProject(
"res/values/formatstrings.xml",
@@ -79,6 +86,10 @@
}
public void testArgCount() {
+ assertEquals(0, StringFormatDetector.getFormatArgumentCount(
+ "%n%% ", null));
+ assertEquals(1, StringFormatDetector.getFormatArgumentCount(
+ "%n%% %s", null));
assertEquals(3, StringFormatDetector.getFormatArgumentCount(
"First: %1$s, Second %2$s, Third %3$s", null));
assertEquals(11, StringFormatDetector.getFormatArgumentCount(
@@ -98,6 +109,8 @@
public void testArgType() {
assertEquals("s", StringFormatDetector.getFormatArgumentType(
+ "First: %n%% %1$s, Second %2$s, Third %3$s", 1));
+ assertEquals("s", StringFormatDetector.getFormatArgumentType(
"First: %1$s, Second %2$s, Third %3$s", 1));
assertEquals("d", StringFormatDetector.getFormatArgumentType(
"First: %1$s, Second %2$-5d, Third %3$s", 2));
@@ -166,20 +179,24 @@
public void testIssue42798() throws Exception {
// http://code.google.com/p/android/issues/detail?id=42798
// String playsCount = String.format(Locale.FRANCE, this.context.getString(R.string.gridview_views_count), article.playsCount);
- assertEquals(""
- + "src/test/pkg/StringFormat3.java:16: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String [StringFormatMatches]\n"
- + " context.getString(R.string.gridview_views_count), \"wrong\");\n"
- + " ~~~~~~~\n"
- + " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n"
- + "src/test/pkg/StringFormat3.java:17: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String [StringFormatMatches]\n"
- + " String s4 = String.format(context.getString(R.string.gridview_views_count), \"wrong\");\n"
- + " ~~~~~~~\n"
- + " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n"
- + "src/test/pkg/StringFormat3.java:22: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String [StringFormatMatches]\n"
- + " context.getString(R.string.gridview_views_count), \"string\");\n"
- + " ~~~~~~~~\n"
- + " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n"
- + "3 errors, 0 warnings\n",
+ assertEquals(
+ "src/test/pkg/StringFormat3.java:12: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #3 in method call) [StringFormatMatches]\n" +
+ " context.getString(R.string.gridview_views_count), article.playsCount);\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormat3.java:16: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #3 in method call) [StringFormatMatches]\n" +
+ " context.getString(R.string.gridview_views_count), \"wrong\");\n" +
+ " ~~~~~~~\n" +
+ " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormat3.java:17: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
+ " String s4 = String.format(context.getString(R.string.gridview_views_count), \"wrong\");\n" +
+ " ~~~~~~~\n" +
+ " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormat3.java:22: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #3 in method call) [StringFormatMatches]\n" +
+ " context.getString(R.string.gridview_views_count), \"string\");\n" +
+ " ~~~~~~~~\n" +
+ " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
+ "4 errors, 0 warnings\n",
lintProject(
"res/values/formatstrings5.xml",
@@ -218,4 +235,135 @@
"res/values/formatstrings7.xml",
"src/test/pkg/StringFormat5.java.txt=>src/test/pkg/StringFormat5.java"));
}
+
+ public void testNewlineChar() throws Exception {
+ // https://code.google.com/p/android/issues/detail?id=65692
+ assertEquals(""
+ + "src/test/pkg/StringFormat8.java:12: Error: Wrong argument count, format string amount_string requires 1 but format call supplies 0 [StringFormatMatches]\n"
+ + " String amount4 = String.format(getResources().getString(R.string.amount_string)); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/formatstrings8.xml:2: This definition requires 1 arguments\n"
+ + "src/test/pkg/StringFormat8.java:13: Error: Wrong argument count, format string amount_string requires 1 but format call supplies 2 [StringFormatMatches]\n"
+ + " String amount5 = getResources().getString(R.string.amount_string, amount, amount); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/formatstrings8.xml:2: This definition requires 1 arguments\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/formatstrings8.xml",
+ "src/test/pkg/StringFormat8.java.txt=>src/test/pkg/StringFormat8.java"));
+ }
+
+ public void testIncremental() throws Exception {
+ assertEquals(
+ "src/test/pkg/StringFormatActivity.java:13: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output1 = String.format(hello, target);\n" +
+ " ~~~~~~\n" +
+ " res/values-es/formatstrings.xml: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:15: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " String output2 = String.format(hello2, target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:21: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output4 = String.format(score, true); // wrong\n" +
+ " ~~~~\n" +
+ " res/values/formatstrings.xml: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:22: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output4 = String.format(score, won); // wrong\n" +
+ " ~~~\n" +
+ " res/values/formatstrings.xml: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:24: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " String.format(getResources().getString(R.string.hello2), target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:25: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " getResources().getString(hello2, target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:26: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " getResources().getString(R.string.hello2, target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:33: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output1 = String.format(hello, target);\n" +
+ " ~~~~~~\n" +
+ " res/values-es/formatstrings.xml: Conflicting argument declaration here\n" +
+ "res/values-es/formatstrings.xml: Error: Inconsistent formatting types for argument #1 in format string hello ('%1$d'): Found both 's' and 'd' (in values/formatstrings.xml) [StringFormatMatches]\n" +
+ " res/values/formatstrings.xml: Conflicting argument type here\n" +
+ "res/values/formatstrings.xml: Warning: Inconsistent number of arguments in formatting string hello2; found both 3 and 2 [StringFormatCount]\n" +
+ " res/values-es/formatstrings.xml: Conflicting number of arguments here\n" +
+ "9 errors, 1 warnings\n",
+
+ lintProjectIncrementally(
+ "src/test/pkg/StringFormatActivity.java",
+ "res/values/formatstrings.xml",
+ "res/values-es/formatstrings.xml",
+ // Java files must be renamed in source tree
+ "src/test/pkg/StringFormatActivity.java.txt=>src/test/pkg/StringFormatActivity.java"
+ ));
+ }
+
+ public void testNotStringFormat() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=67597
+ assertEquals("No warnings.",
+
+ lintProject(
+ "res/values/formatstrings3.xml",//"res/values/formatstrings.xml",
+ "res/values/shared_prefs_keys.xml",
+ "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsTest6.java"));
+ }
+
+ public void testNotStringFormatIncrementally() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=67597
+ assertEquals("No warnings.",
+
+ lintProjectIncrementally(
+ "src/test/pkg/SharedPrefsTest6.java",
+
+ "res/values/formatstrings3.xml",//"res/values/formatstrings.xml",
+ "res/values/shared_prefs_keys.xml",
+ "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsTest6.java"));
+ }
+
+ public void testXliff() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/formatstrings9.xml",
+ "src/test/pkg/StringFormat9.java.txt=>src/test/pkg/StringFormat9.java"
+ ));
+ }
+
+ public void testXliffIncremental() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProjectIncrementally(
+ "src/test/pkg/StringFormat9.java",
+ "res/values/formatstrings9.xml",
+ "src/test/pkg/StringFormat9.java.txt=>src/test/pkg/StringFormat9.java"
+ ));
+ }
+
+ public void testBigDecimal() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=69527
+ assertEquals("No warnings.",
+
+ lintProject(
+ "res/values/formatstrings10.xml",
+ "src/test/pkg/StringFormat10.java.txt=>src/test/pkg/StringFormat10.java"
+ ));
+
+ }
+
+ public void testWrapperClasses() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=70496
+ assertEquals("No warnings.",
+
+ lintProject(
+ "res/values/formatstrings10.xml",
+ "src/test/pkg/StringFormat11.java.txt=>src/test/pkg/StringFormat11.java"
+ ));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/StyleCycleDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/StyleCycleDetectorTest.java
deleted file mode 100644
index d4823d7..0000000
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/StyleCycleDetectorTest.java
+++ /dev/null
@@ -1,49 +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.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
-@SuppressWarnings("javadoc")
-public class StyleCycleDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new StyleCycleDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "res/values/styles.xml:9: Error: Style DetailsPage_EditorialBuyButton should not extend itself [StyleCycle]\n" +
- "<style name=\"DetailsPage_EditorialBuyButton\" parent=\"@style/DetailsPage_EditorialBuyButton\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n" +
- "",
-
- lintProject("res/values/styles.xml"));
- }
-
- public void test2() throws Exception {
- assertEquals(
- "res/values/stylecycle.xml:3: Error: Potential cycle: PropertyToggle is the implied parent of PropertyToggle.Base and this defines the opposite [StyleCycle]\n" +
- " <style name=\"PropertyToggle\" parent=\"@style/PropertyToggle.Base\"></style>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n" +
- "",
-
- lintProject("res/values/stylecycle.xml"));
- }
-}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/TextFieldDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/TextFieldDetectorTest.java
index 795bc85..4e00db0 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/TextFieldDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/TextFieldDetectorTest.java
@@ -124,4 +124,26 @@
private static boolean containsWord(String name, String word) {
return TextFieldDetector.containsWord(name, word);
}
+
+ public void testIncremental1() throws Exception {
+ assertEquals(""
+ + "res/layout/note_edit2.xml:7: Warning: This text field does not specify an inputType or a hint [TextFields]\n"
+ + " <EditText\n"
+ + " ^\n"
+ + "res/layout/note_edit2.xml:12: Warning: This text field does not specify an inputType or a hint [TextFields]\n"
+ + " <EditText\n"
+ + " ^\n"
+ + "0 errors, 2 warnings\n",
+ lintProjectIncrementally(
+ "res/layout/note_edit2.xml",
+ "res/layout/note_edit2.xml"));
+ }
+
+ public void testIncremental2() throws Exception {
+ assertEquals("No warnings.",
+ lintProjectIncrementally(
+ "res/layout/note_edit2.xml",
+ "res/layout/note_edit2.xml",
+ "res/values/styles-orientation.xml"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/TitleDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/TitleDetectorTest.java
index 7259cde..3928918 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/TitleDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/TitleDetectorTest.java
@@ -27,13 +27,13 @@
public void test() throws Exception {
assertEquals(
- "res/menu/titles.xml:3: Warning: Menu items should specify a title [MenuTitle]\n" +
+ "res/menu/titles.xml:3: Error: Menu items should specify a title [MenuTitle]\n" +
" <item android:id=\"@+id/action_bar_progress_spinner\"\n" +
" ^\n" +
- "res/menu/titles.xml:12: Warning: Menu items should specify a title [MenuTitle]\n" +
+ "res/menu/titles.xml:12: Error: Menu items should specify a title [MenuTitle]\n" +
" <item android:id=\"@+id/menu_plus_one\"\n" +
" ^\n" +
- "0 errors, 2 warnings\n",
+ "2 errors, 0 warnings\n",
lintProject(
"apicheck/minsdk14.xml=>AndroidManifest.xml",
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
index e0358ef..b7f80ae 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
@@ -34,13 +34,13 @@
TranslationDetector.sCompleteRegions = false;
assertEquals(
// Sample files from the Home app
- "res/values/strings.xml:20: Error: \"show_all_apps\" is not translated in nl-rNL [MissingTranslation]\n" +
+ "res/values/strings.xml:20: Error: \"show_all_apps\" is not translated in \"nl-rNL\" (Dutch: Netherlands) [MissingTranslation]\n" +
" <string name=\"show_all_apps\">All</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in nl-rNL [MissingTranslation]\n" +
+ "res/values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in \"nl-rNL\" (Dutch: Netherlands) [MissingTranslation]\n" +
" <string name=\"menu_wallpaper\">Wallpaper</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in cs, de-rDE, es, es-rUS, nl-rNL [MissingTranslation]\n" +
+ "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"cs\" (Czech), \"de-rDE\" (German: Germany), \"es\" (Spanish), \"es-rUS\" (Spanish: United States), \"nl-rNL\" (Dutch: Netherlands) [MissingTranslation]\n" +
" <string name=\"menu_settings\">Settings</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~\n" +
"res/values-cs/arrays.xml:3: Error: \"security_questions\" is translated here but not found in default locale [ExtraTranslation]\n" +
@@ -50,8 +50,7 @@
"res/values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale [ExtraTranslation]\n" +
" <string name=\"continue_skip_label\">\"Weiter\"</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "5 errors, 0 warnings\n" +
- "",
+ "5 errors, 0 warnings\n",
lintProject(
"res/values/strings.xml",
@@ -69,27 +68,26 @@
TranslationDetector.sCompleteRegions = true;
assertEquals(
// Sample files from the Home app
- "res/values/strings.xml:19: Error: \"home_title\" is not translated in es-rUS [MissingTranslation]\n" +
+ "res/values/strings.xml:19: Error: \"home_title\" is not translated in \"es-rUS\" (Spanish: United States) [MissingTranslation]\n" +
" <string name=\"home_title\">Home Sample</string>\n" +
" ~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:20: Error: \"show_all_apps\" is not translated in es-rUS, nl-rNL [MissingTranslation]\n" +
+ "res/values/strings.xml:20: Error: \"show_all_apps\" is not translated in \"es-rUS\" (Spanish: United States), \"nl-rNL\" (Dutch: Netherlands) [MissingTranslation]\n" +
" <string name=\"show_all_apps\">All</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in es-rUS, nl-rNL [MissingTranslation]\n" +
+ "res/values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in \"es-rUS\" (Spanish: United States), \"nl-rNL\" (Dutch: Netherlands) [MissingTranslation]\n" +
" <string name=\"menu_wallpaper\">Wallpaper</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in cs, de-rDE, es-rUS, nl-rNL [MissingTranslation]\n" +
+ "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"cs\" (Czech), \"de-rDE\" (German: Germany), \"es-rUS\" (Spanish: United States), \"nl-rNL\" (Dutch: Netherlands) [MissingTranslation]\n" +
" <string name=\"menu_settings\">Settings</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:29: Error: \"wallpaper_instructions\" is not translated in es-rUS [MissingTranslation]\n" +
+ "res/values/strings.xml:29: Error: \"wallpaper_instructions\" is not translated in \"es-rUS\" (Spanish: United States) [MissingTranslation]\n" +
" <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
" res/values-land/strings.xml:19: <No location-specific message\n" +
"res/values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale [ExtraTranslation]\n" +
" <string name=\"continue_skip_label\">\"Weiter\"</string>\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "6 errors, 0 warnings\n" +
- "",
+ "6 errors, 0 warnings\n",
lintProject(
"res/values/strings.xml",
@@ -224,7 +222,7 @@
public void testIssue33845() throws Exception {
// See http://code.google.com/p/android/issues/detail?id=33845
assertEquals(""
- + "res/values/strings.xml:5: Error: \"dateTimeFormat\" is not translated in de [MissingTranslation]\n"
+ + "res/values/strings.xml:5: Error: \"dateTimeFormat\" is not translated in \"de\" (German) [MissingTranslation]\n"
+ " <string name=\"dateTimeFormat\">MM/dd/yyyy - HH:mm</string>\n"
+ " ~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
@@ -238,4 +236,19 @@
"locale33845/res/values-en-rGB/strings.xml=>res/values-en-rGB/strings.xml"
));
}
+
+ public void testIssue33845b() throws Exception {
+ // Similar to issue 33845, but with some variations to the test data
+ // See http://code.google.com/p/android/issues/detail?id=33845
+ assertEquals("No warnings.",
+
+ lintProject(
+ "locale33845/.classpath=>.classpath",
+ "locale33845/AndroidManifest.xml=>AndroidManifest.xml",
+ "locale33845/project.properties=>project.properties",
+ "locale33845/res/values/styles.xml=>res/values/styles.xml",
+ "locale33845/res/values/strings2.xml=>res/values/strings.xml",
+ "locale33845/res/values-en-rGB/strings2.xml=>res/values-en-rGB/strings.xml"
+ ));
+ }
}
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 a564860..8d75e08 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
@@ -258,7 +258,6 @@
"No warnings.",
lintProject(
- "res/layout/accessibility.xml",
"src/test/pkg/Foo.java.txt=>src/test/pkg/Foo.java",
"AndroidManifest.xml"));
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/UselessViewDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/UselessViewDetectorTest.java
index c34b987..80862c2 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/UselessViewDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/UselessViewDetectorTest.java
@@ -64,4 +64,12 @@
lintFiles("res/layout/breadcrumbs_in_fragment.xml"));
}
+
+ public void testUseless65519() throws Exception {
+ // https://code.google.com/p/android/issues/detail?id=65519
+ assertEquals(
+ "No warnings.",
+
+ lintFiles("res/layout/useless4.xml"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/Utf8DetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/Utf8DetectorTest.java
index d1a118d..0a03025 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/Utf8DetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/Utf8DetectorTest.java
@@ -25,23 +25,21 @@
return new Utf8Detector();
}
- public void test() throws Exception {
+ public void testIsoLatin() throws Exception {
assertEquals(
- "res/layout/encoding.xml:1: Warning: iso-latin-1: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n" +
+ "res/layout/encoding.xml:1: Error: iso-latin-1: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n" +
"<?xml version=\"1.0\" encoding=\"iso-latin-1\"?>\n" +
" ~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
+ "1 errors, 0 warnings\n",
lintProject("res/layout/encoding.xml"));
}
- public void testWithR() throws Exception {
+ public void testWithWindowsCarriageReturn() throws Exception {
assertEquals(
- "res/layout/encoding2.xml:1: Warning: iso-latin-1: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n" +
+ "res/layout/encoding2.xml:1: Error: iso-latin-1: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n" +
"<?xml version=\"1.0\" encoding=\"iso-latin-1\"?>\n" +
" ~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
+ "1 errors, 0 warnings\n",
// encoding2.xml = encoding.xml but with \n => \r
lintProject("res/layout/encoding2.xml"));
}
@@ -53,4 +51,78 @@
lintProject("res/layout/layout1.xml"));
}
+ public void testNoProlog() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject("res/layout/activity_item_two_pane.xml"));
+ }
+
+ public void testImplicitUtf16() throws Exception {
+ // Implicit encoding: Not currently checked
+ assertEquals("No warnings.",
+ lintProject("encoding/UTF-16-bom-implicit.xml=>res/layout/layout.xml"));
+ }
+
+ public void testUtf16WithByteOrderMark() throws Exception {
+ assertEquals(""
+ + "res/layout/layout.xml:1: Error: UTF-16: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n"
+ + "<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n"
+ + " ~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject("encoding/UTF-16-bom.xml=>res/layout/layout.xml"));
+ }
+
+ public void testUtf16WithoutByteOrderMark() throws Exception {
+ assertEquals(""
+ + "res/layout/layout.xml:1: Error: UTF-16: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n"
+ + "<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n"
+ + " ~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject("encoding/UTF-16-nobom.xml=>res/layout/layout.xml"));
+ }
+
+ public void testUtf32WithByteOrderMark() throws Exception {
+ assertEquals(""
+ + "res/layout/layout.xml:1: Error: UTF_32: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n"
+ + "<?xml version=\"1.0\" encoding=\"UTF_32\"?>\n"
+ + " ~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject("encoding/UTF_32-bom.xml=>res/layout/layout.xml"));
+ }
+
+ public void testUtf32WithoutByteOrderMark() throws Exception {
+ assertEquals(""
+ + "res/layout/layout.xml:1: Error: UTF_32: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n"
+ + "<?xml version=\"1.0\" encoding=\"UTF_32\"?>\n"
+ + " ~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject("encoding/UTF_32-nobom.xml=>res/layout/layout.xml"));
+ }
+
+ public void testUtf32LeWithoutByteOrderMark() throws Exception {
+ assertEquals(""
+ + "res/layout/layout.xml:1: Error: UTF_32LE: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n"
+ + "<?xml version=\"1.0\" encoding=\"UTF_32LE\"?>\n"
+ + " ~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject("encoding/UTF_32LE-nobom.xml=>res/layout/layout.xml"));
+ }
+
+ public void testMacRoman() throws Exception {
+ assertEquals(""
+ + "res/layout/layout.xml:1: Error: MacRoman: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n"
+ + "<?xml version=\"1.0\" encoding=\"MacRoman\"?>\n"
+ + " ~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject("encoding/MacRoman.xml=>res/layout/layout.xml"));
+ }
+
+ public void testWindows1252() throws Exception {
+ assertEquals(""
+ + "res/layout/layout.xml:1: Error: windows-1252: Not using UTF-8 as the file encoding. This can lead to subtle bugs with non-ascii characters [EnforceUTF8]\n"
+ + "<?xml version=\"1.0\" encoding=\"windows-1252\"?>\n"
+ + " ~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject("encoding/Windows-1252.xml=>res/layout/layout.xml"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java
index a84a74f..c9f8729 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java
@@ -26,11 +26,14 @@
}
public void test() throws Exception {
- assertEquals(
- "src/test/bytecode/CustomView1.java: Warning: Custom view test/pkg/CustomView1 is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int) [ViewConstructor]\n" +
- "src/test/bytecode/CustomView2.java: Warning: Custom view test/pkg/CustomView2 is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int) [ViewConstructor]\n" +
- "0 errors, 2 warnings\n" +
- "",
+ assertEquals(""
+ + "src/test/bytecode/CustomView1.java:5: Warning: Custom view CustomView1 is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int) [ViewConstructor]\n"
+ + "public class CustomView1 extends View {\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/bytecode/CustomView2.java:7: Warning: Custom view CustomView2 is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int) [ViewConstructor]\n"
+ + "public class CustomView2 extends Button {\n"
+ + " ~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
lintProject(
"bytecode/.classpath=>.classpath",
@@ -45,15 +48,16 @@
}
public void testInheritLocal() throws Exception {
- assertEquals(
- "src/test/pkg/CustomViewTest.java: Warning: Custom view test/pkg/CustomViewTest is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int) [ViewConstructor]\n" +
- "0 errors, 1 warnings\n" +
- "",
+ assertEquals(""
+ + "src/test/pkg/CustomViewTest.java:5: Warning: Custom view CustomViewTest is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int) [ViewConstructor]\n"
+ + "public class CustomViewTest extends IntermediateCustomV {\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
lintProject(
"bytecode/.classpath=>.classpath",
"bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "apicheck/Intermediate.java.txt=>src/test/pkg/Intermediate.java.txt",
+ "apicheck/Intermediate.java.txt=>src/test/pkg/Intermediate.java",
"src/test/pkg/CustomViewTest.java.txt=>src/test/pkg/CustomViewTest.java",
"bytecode/CustomViewTest.class.data=>bin/classes/test/pkg/CustomViewTest.class",
"apicheck/Intermediate.class.data=>bin/classes/test/pkg/Intermediate.class",
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ViewHolderDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewHolderDetectorTest.java
new file mode 100644
index 0000000..df8a0dd
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewHolderDetectorTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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("javadoc")
+public class ViewHolderDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ViewHolderDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/ViewHolderTest.java:42: Warning: Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling [ViewHolder]\n"
+ + " convertView = mInflater.inflate(R.layout.your_layout, null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "src/test/pkg/ViewHolderTest.java.txt=>src/test/pkg/ViewHolderTest.java"));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
index 8d9e2d5..cf21023 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
@@ -46,8 +46,6 @@
lintProject(
"bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
"bytecode/ViewTagTest.java.txt=>src/test/pkg/ViewTagTest.java",
"bytecode/ViewTagTest.class.data=>bin/classes/test/pkg/ViewTagTest.class"
));
@@ -60,7 +58,6 @@
lintProject(
"bytecode/.classpath=>.classpath",
"apicheck/minsdk14.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
"bytecode/ViewTagTest.java.txt=>src/test/pkg/ViewTagTest.java",
"bytecode/ViewTagTest.class.data=>bin/classes/test/pkg/ViewTagTest.class"
));
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ViewTypeDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewTypeDetectorTest.java
index cf38197..35848f0 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ViewTypeDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewTypeDetectorTest.java
@@ -82,4 +82,19 @@
"src/test/pkg/WrongCastActivity3.java.txt=>src/test/pkg/WrongCastActivity3.java"
));
}
+
+ public void testIncremental() throws Exception {
+ assertEquals(
+ "src/test/pkg/WrongCastActivity.java:13: Error: Unexpected cast to ToggleButton: layout tag was Button [WrongViewCast]\n" +
+ " ToggleButton toggleButton = (ToggleButton) findViewById(R.id.button);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProjectIncrementally(
+ "src/test/pkg/WrongCastActivity.java",
+ "res/layout/casts.xml",
+ "src/test/pkg/WrongCastActivity.java.txt=>src/test/pkg/WrongCastActivity.java"
+ ));
+ }
+
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
index 21434bb..9505b3a 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
@@ -158,6 +158,19 @@
));
}
+ public void test9() throws Exception {
+ // Regression test for 66040
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/WakelockActivity9.java.txt=>src/test/pkg/WakelockActivity9.java",
+ "bytecode/WakelockActivity9.class.data=>bin/classes/test/pkg/WakelockActivity9.class"
+ ));
+ }
+
public void testFlags() throws Exception {
assertEquals(""
+ "src/test/pkg/PowerManagerFlagTest.java:15: Warning: Should not set both PARTIAL_WAKE_LOCK and ACQUIRE_CAUSES_WAKEUP. If you do not want the screen to turn on, get rid of ACQUIRE_CAUSES_WAKEUP [Wakelock]\n"
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/WebViewDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/WebViewDetectorTest.java
new file mode 100644
index 0000000..ee1d6e5
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/WebViewDetectorTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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("javadoc")
+public class WebViewDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new WebViewDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals("res/layout/webview.xml:19: Error: Placing a <WebView> in a parent element that uses a wrap_content size can lead to subtle bugs; use match_parent [WebViewLayout]\n"
+ + " <WebView\n"
+ + " ^\n"
+ + " res/layout/webview.xml:16: <No location-specific message\n"
+ + "1 errors, 0 warnings\n",
+
+ lintFiles("res/layout/webview.xml"));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/WrongCaseDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/WrongCaseDetectorTest.java
index 8f7ed9c..685dc30 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/WrongCaseDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/WrongCaseDetectorTest.java
@@ -27,19 +27,19 @@
public void test() throws Exception {
assertEquals(""
- + "res/layout/case.xml:18: Warning: Invalid tag <Merge>; should be <merge> [WrongCase]\n"
+ + "res/layout/case.xml:18: Error: Invalid tag <Merge>; should be <merge> [WrongCase]\n"
+ "<Merge xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
+ "^\n"
- + "res/layout/case.xml:20: Warning: Invalid tag <Fragment>; should be <fragment> [WrongCase]\n"
+ + "res/layout/case.xml:20: Error: Invalid tag <Fragment>; should be <fragment> [WrongCase]\n"
+ " <Fragment android:name=\"foo.bar.Fragment\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/case.xml:21: Warning: Invalid tag <Include>; should be <include> [WrongCase]\n"
+ + "res/layout/case.xml:21: Error: Invalid tag <Include>; should be <include> [WrongCase]\n"
+ " <Include layout=\"@layout/foo\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/case.xml:22: Warning: Invalid tag <RequestFocus>; should be <requestFocus> [WrongCase]\n"
+ + "res/layout/case.xml:22: Error: Invalid tag <RequestFocus>; should be <requestFocus> [WrongCase]\n"
+ " <RequestFocus />\n"
+ " ~~~~~~~~~~~~~~~~\n"
- + "0 errors, 4 warnings\n",
+ + "4 errors, 0 warnings\n",
lintProject("res/layout/case.xml"));
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
index 537b845..2b409c8 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
@@ -114,7 +114,7 @@
lintFiles("res/layout/siblings.xml"));
}
- public void testInvalidIds() throws Exception {
+ public void testInvalidIds1() throws Exception {
// See https://code.google.com/p/android/issues/detail?id=56029
assertEquals(""
+ "res/layout/invalid_ids.xml:23: Error: ID definitions *must* be of the form @+id/name; try using @+id/menu_Reload [InvalidId]\n"
@@ -133,4 +133,40 @@
lintFiles("res/layout/invalid_ids.xml"));
}
+
+ public void testInvalidIds2() throws Exception {
+ // https://code.google.com/p/android/issues/detail?id=65244
+ assertEquals(""
+ + "res/layout/invalid_ids2.xml:8: Error: ID definitions *must* be of the form @+id/name; try using @+id/btn_skip [InvalidId]\n"
+ + " android:id=\"@+id/btn/skip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintFiles("res/layout/invalid_ids2.xml"));
+ }
+
+ public void testIncremental() throws Exception {
+ assertEquals(
+ "res/layout/layout1.xml:14: Error: The id \"button5\" is not defined anywhere. Did you mean one of {button1, button2, button3, button4} ? [UnknownId]\n" +
+ " android:layout_alignBottom=\"@+id/button5\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:17: Error: The id \"my_id3\" is not defined anywhere. Did you mean one of {my_id1, my_id2} ? [UnknownId]\n" +
+ " android:layout_alignRight=\"@+id/my_id3\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:18: Error: The id \"my_id1\" is defined but not assigned to any views. Did you mean one of {my_id2, my_id3} ? [UnknownId]\n" +
+ " android:layout_alignTop=\"@+id/my_id1\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:15: Warning: The id \"my_id2\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
+ " android:layout_alignLeft=\"@+id/my_id2\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "3 errors, 1 warnings\n",
+
+ lintProjectIncrementally(
+ "res/layout/layout1.xml",
+
+ "wrongid/layout1.xml=>res/layout/layout1.xml",
+ "wrongid/layout2.xml=>res/layout/layout2.xml",
+ "wrongid/ids.xml=>res/values/ids.xml"
+ ));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/android/PreferenceActivity.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/android/PreferenceActivity.java.txt
new file mode 100644
index 0000000..7a9c1fd
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/android/PreferenceActivity.java.txt
@@ -0,0 +1,9 @@
+package android.preference;
+
+import android.app.Activity;
+
+/**
+ * A mock PreferenceActivity for use in tests.
+ */
+public class PreferenceActivity extends Activity {
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute.xml
new file mode 100644
index 0000000..dc4cfeb
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute.xml
@@ -0,0 +1,9 @@
+<Button
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:text="Hello"
+ android:enabled="?android:indicatorStart"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute2.xml
new file mode 100644
index 0000000..98b9f91
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute2.xml
@@ -0,0 +1,7 @@
+<ExitText
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:text="Hello"
+ android:editTextColor="?android:switchTextAppearance"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute3.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute3.xml
new file mode 100644
index 0000000..f50cfeb
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute3.xml
@@ -0,0 +1,7 @@
+<ExitText
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:text="Hello"
+ android:baseline="?android:bottomOffset"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/layoutattr.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/layoutattr.xml
new file mode 100644
index 0000000..bcfcd3b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/layoutattr.xml
@@ -0,0 +1,16 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <android.support.v7.widget.GridLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:text="@string/hello_world"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_row="2"
+ app:layout_column="1" />
+ </android.support.v7.widget.GridLayout>
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk11.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk11.xml
new file mode 100644
index 0000000..ce7fef8
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk11.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.apicheck"
+ android:versionCode="1"
+ android:versionName="1.0" >
+ <uses-sdk android:minSdkVersion="11" />
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ </application>
+</manifest>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk19.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk19.xml
new file mode 100644
index 0000000..68d1efd
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk19.xml
@@ -0,0 +1,14 @@
+<?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="19" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ </application>
+
+</manifest>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionBarActivity.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionBarActivity.java.txt
new file mode 100644
index 0000000..dc03190
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionBarActivity.java.txt
@@ -0,0 +1,30 @@
+package android.support.v7.app;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.support.v7.view.ActionMode;
+/**
+ * Just a dumb stub for unit test
+ */
+public class ActionBarActivity extends Activity {
+ protected ActionBar getSupportActionBar() {
+ return null;
+ }
+
+ public ActionMode startSupportActionMode(ActionMode.Callback callback) {
+ return null;
+ }
+
+ public boolean supportRequestWindowFeature(int featureId) {
+ return true;
+ }
+
+ public void setSupportProgressBarVisibility(boolean visible) {
+ }
+
+ public void setSupportProgressBarIndeterminateVisibility(boolean visible) {
+ }
+
+ public void setSupportProgressBarIndeterminate(boolean indeterminate) {
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionMode.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionMode.java.txt
new file mode 100644
index 0000000..8702c25
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionMode.java.txt
@@ -0,0 +1,9 @@
+package android.support.v7.view;
+
+/**
+ * Just a unit testing stub
+ */
+public class ActionMode {
+ public interface Callback {
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatTest.java.txt
new file mode 100644
index 0000000..0a600a9
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatTest.java.txt
@@ -0,0 +1,22 @@
+package test.pkg;
+
+public class AppCompatTest extends IntermediateActivity {
+ public void test() {
+ getActionBar(); // ERROR
+ getSupportActionBar(); // OK
+
+ startActionMode(null); // ERROR
+ startSupportActionMode(null); // OK
+
+ requestWindowFeature(0); // ERROR
+ supportRequestWindowFeature(0); // OK
+
+ setProgressBarVisibility(true); // ERROR
+ setProgressBarIndeterminate(true); // ERROR
+ setProgressBarIndeterminateVisibility(true); // ERROR
+
+ setSupportProgressBarVisibility(true); // OK
+ setSupportProgressBarIndeterminate(true); // OK
+ setSupportProgressBarIndeterminateVisibility(true); // OK
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/IntermediateActivity.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/IntermediateActivity.java.txt
new file mode 100644
index 0000000..bcea1eb
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/appcompat/IntermediateActivity.java.txt
@@ -0,0 +1,25 @@
+package test.pkg;
+
+import android.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.view.ActionMode;
+
+public class IntermediateActivity extends ActionBarActivity {
+ protected ActionBar getSupportActionBar() {
+ return null;
+ }
+
+ public ActionMode startSupportActionMode(ActionMode.Callback callback) {
+ return null;
+ }
+
+ public boolean supportRequestWindowFeature(int featureId) {
+ return true;
+ }
+
+ public void setSupportProgressBarIndeterminateVisibility(boolean visible) {
+ }
+
+ public void setSupportProgressBarIndeterminate(boolean indeterminate) {
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnNonWebView.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnNonWebView.class.data
new file mode 100644
index 0000000..33a5c28
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnNonWebView.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class.data
new file mode 100644
index 0000000..73cdecb
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebViewChild.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebViewChild.class.data
new file mode 100644
index 0000000..f7ca55c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebViewChild.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$NonWebView.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$NonWebView.class.data
new file mode 100644
index 0000000..00d1c52
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$NonWebView.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$WebViewChild.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$WebViewChild.class.data
new file mode 100644
index 0000000..306f02c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$WebViewChild.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.class.data
new file mode 100644
index 0000000..4dd1548
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.java.txt
new file mode 100644
index 0000000..f8fb8b5
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.java.txt
@@ -0,0 +1,37 @@
+package test.pkg;
+
+import android.webkit.WebView;
+import android.content.Context;
+
+
+public class AddJavascriptInterfaceTest {
+ private static class WebViewChild extends WebView {
+ WebViewChild(Context context) {
+ super(context);
+ }
+ }
+
+ private static class CallAddJavascriptInterfaceOnWebView {
+ public void addJavascriptInterfaceToWebView(WebView webView, Object object, String string) {
+ webView.addJavascriptInterface(object, string);
+ }
+ }
+
+ private static class CallAddJavascriptInterfaceOnWebViewChild {
+ public void addJavascriptInterfaceToWebViewChild(
+ WebViewChild webView, Object object, String string) {
+ webView.addJavascriptInterface(object, string);
+ }
+ }
+
+ private static class NonWebView {
+ public void addJavascriptInterface(Object object, String string) { }
+ }
+
+ private static class CallAddJavascriptInterfaceOnNonWebView {
+ public void addJavascriptInterfaceToNonWebView(
+ NonWebView webView, Object object, String string) {
+ webView.addJavascriptInterface(object, string);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestMinSdk17.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestMinSdk17.xml
new file mode 100644
index 0000000..6568b6a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestMinSdk17.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="17" />
+
+ <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>
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$1.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$1.class.data
new file mode 100644
index 0000000..ceb0371
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$1.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1.class.data
new file mode 100644
index 0000000..45812a8
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener.class.data
new file mode 100644
index 0000000..ec8719a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener$1.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener$1.class.data
new file mode 100644
index 0000000..5310583
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener$1.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener.class.data
new file mode 100644
index 0000000..7c9ad59
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClick.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClick.class.data
new file mode 100644
index 0000000..11b6614
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClick.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClickOnTouchListenerSetter.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClickOnTouchListenerSetter.class.data
new file mode 100644
index 0000000..c4f10ff
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClickOnTouchListenerSetter.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$InvalidOnTouchListener.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$InvalidOnTouchListener.class.data
new file mode 100644
index 0000000..e73e337
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$InvalidOnTouchListener.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClick.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClick.class.data
new file mode 100644
index 0000000..b461212
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClick.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClickOnTouchListenerSetter.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClickOnTouchListenerSetter.class.data
new file mode 100644
index 0000000..eaca671
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClickOnTouchListenerSetter.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAView.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAView.class.data
new file mode 100644
index 0000000..43cf207
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAView.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAViewOnTouchListenerSetter.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAViewOnTouchListenerSetter.class.data
new file mode 100644
index 0000000..d2d9d18
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAViewOnTouchListenerSetter.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper.class.data
new file mode 100644
index 0000000..cb1aa1c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidOnTouchListener.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidOnTouchListener.class.data
new file mode 100644
index 0000000..84e78f4
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidOnTouchListener.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidView.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidView.class.data
new file mode 100644
index 0000000..721c4b5
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidView.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick.class.data
new file mode 100644
index 0000000..583a536
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick.class.data
new file mode 100644
index 0000000..5a1ad7f
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewSubclass.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewSubclass.class.data
new file mode 100644
index 0000000..54cd2d0
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewSubclass.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentOnTouchEvent.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentOnTouchEvent.class.data
new file mode 100644
index 0000000..1883900
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentOnTouchEvent.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentPerformClick.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentPerformClick.class.data
new file mode 100644
index 0000000..6933c0b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentPerformClick.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.class.data
new file mode 100644
index 0000000..1a07c45
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.java.txt
new file mode 100644
index 0000000..d82ace4
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.java.txt
@@ -0,0 +1,188 @@
+package test.pkg;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class ClickableViewAccessibilityTest {
+
+ // Fails because should also implement performClick.
+ private static class ViewOverridesOnTouchEventButNotPerformClick extends View {
+
+ public ViewOverridesOnTouchEventButNotPerformClick(Context context) {
+ super(context);
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ return false;
+ }
+ }
+
+ // Fails because should call performClick.
+ private static class ViewDoesNotCallPerformClick extends View {
+
+ public ViewDoesNotCallPerformClick(Context context) {
+ super(context);
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ public boolean performClick() {
+ return super.performClick();
+ }
+ }
+
+ // Fails because performClick should call super.performClick.
+ private static class PerformClickDoesNotCallSuper extends View {
+
+ public PerformClickDoesNotCallSuper(Context context) {
+ super(context);
+ }
+
+ public boolean performClick() {
+ return false;
+ }
+ }
+
+ // Valid view.
+ private static class ValidView extends View {
+
+ public ValidView(Context context) {
+ super(context);
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ performClick();
+ return false;
+ }
+
+ public boolean performClick() {
+ return super.performClick();
+ }
+ }
+
+ // Okay because it's not actually a view subclass.
+ private static class NotAView {
+
+ public boolean onTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ public void setOnTouchListener(View.OnTouchListener onTouchListener) { }
+ }
+
+ // Should fail because it's a view subclass. This tests that we can detect Views that are
+ // not just direct sub-children.
+ private static class ViewSubclass extends ValidView {
+
+ public ViewSubclass(Context context) {
+ super(context);
+ }
+
+ public boolean performClick() {
+ return false;
+ }
+ }
+
+ // Okay because it's declaring onTouchEvent with a different signature.
+ private static class ViewWithDifferentOnTouchEvent extends View {
+
+ public ViewWithDifferentOnTouchEvent(Context context) {
+ super(context);
+ }
+
+ public boolean onTouchEvent() {
+ return false;
+ }
+ }
+
+ // Okay because it's declaring performClick with a different signature.
+ private static class ViewWithDifferentPerformClick extends View {
+
+ public ViewWithDifferentPerformClick(Context context) {
+ super(context);
+ }
+
+ public boolean performClick(Context context) {
+ return false;
+ }
+ }
+
+ // Okay when NoPerformClickOnTouchListenerSetter in project.
+ // When NoPerformClickOnTouchListenerSetter is in the project, fails because no perform click
+ // and setOnTouchListener is called below.
+ private static class NoPerformClick extends View {
+ public NoPerformClick(Context context) {
+ super(context);
+ }
+ }
+
+ private static class NoPerformClickOnTouchListenerSetter {
+ private void callSetOnTouchListenerOnNoPerformClick(NoPerformClick view) {
+ view.setOnTouchListener(new ValidOnTouchListener());
+ }
+ }
+
+ // Succeeds because has performClick call.
+ private static class HasPerformClick extends View {
+ public HasPerformClick(Context context) {
+ super(context);
+ }
+
+ public boolean performClick() {
+ return super.performClick();
+ }
+ }
+
+ private static class HasPerformClickOnTouchListenerSetter {
+ private void callSetOnTouchListenerOnHasPerformClick(HasPerformClick view) {
+ view.setOnTouchListener(new ValidOnTouchListener());
+ }
+ }
+
+ // Okay because even though NotAView doesn't have a performClick call, it isn't
+ // a View subclass.
+ private static class NotAViewOnTouchListenerSetter {
+ private void callSetOnTouchListenerOnNotAView(NotAView notAView) {
+ notAView.setOnTouchListener(new ValidOnTouchListener());
+ }
+ }
+
+ // Okay because onTouch calls view.performClick().
+ private static class ValidOnTouchListener implements View.OnTouchListener {
+ public boolean onTouch(View v, MotionEvent event) {
+ return v.performClick();
+ }
+ }
+
+ // Fails because onTouch does not call view.performClick().
+ private static class InvalidOnTouchListener implements View.OnTouchListener {
+ public boolean onTouch(View v, MotionEvent event) {
+ return false;
+ }
+ }
+
+ // Okay because anonymous OnTouchListener calls view.performClick().
+ private static class AnonymousValidOnTouchListener {
+ private void callSetOnTouchListener(HasPerformClick view) {
+ view.setOnTouchListener(new View.OnTouchListener() {
+ public boolean onTouch(View v, MotionEvent event) {
+ return v.performClick();
+ }
+ });
+ }
+ }
+
+ // Fails because anonymous OnTouchListener does not call view.performClick().
+ private static class AnonymousInvalidOnTouchListener {
+ private void callSetOnTouchListener(HasPerformClick view) {
+ view.setOnTouchListener(new View.OnTouchListener() {
+ public boolean onTouch(View v, MotionEvent event) {
+ return false;
+ }
+ });
+ }
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.class.data
index a4b7c7d..e49ad85 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.class.data
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.java.txt
index 86d983f..0a7d50c 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.java.txt
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.java.txt
@@ -1,20 +1,13 @@
-package test.pkg;
+package test.Pkg;
import android.content.Context;
import android.util.AttributeSet;
-import android.widget.TextView;
+import android.view.View;
-public class CustomView3 extends TextView {
+public class CustomView3 extends View {
public CustomView3(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
- public CustomView3(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public CustomView3(Context context) {
- super(context);
- }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.java.txt
index d27c04a..e74d5cd 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.java.txt
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.java.txt
@@ -52,4 +52,8 @@
// (Not a fragment)
private class NotAFragment {
}
+
+ // Ok: Has implicit constructor
+ public static class Fragment7 extends Fragment {
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/META-INF/MANIFEST.MF b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..cb0a86e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Created-By: 1.6.0_33 (Apple Inc.)
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data
new file mode 100644
index 0000000..0c6926e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.class.data
new file mode 100644
index 0000000..e9d47a9
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.java.txt
new file mode 100644
index 0000000..9048e83
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.java.txt
@@ -0,0 +1,18 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+
+public class WakelockActivity9 extends Activity {
+ private WakeLock mWakeLock;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ PowerManager manager = (PowerManager) getSystemService(POWER_SERVICE);
+ mWakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Test");
+ mWakeLock.acquire(2000L);
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/butterknife-2.0.1.jar.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/butterknife-2.0.1.jar.data
new file mode 100644
index 0000000..4d997fb
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/butterknife-2.0.1.jar.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/dagger-compiler-1.2.1-subset.jar.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/dagger-compiler-1.2.1-subset.jar.data
new file mode 100644
index 0000000..3eca919
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/dagger-compiler-1.2.1-subset.jar.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/javax.jar.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/javax.jar.data
new file mode 100644
index 0000000..179637c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/javax.jar.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/user_prefs_fragment.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/user_prefs_fragment.xml
new file mode 100644
index 0000000..c2896f3
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/user_prefs_fragment.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ class="course.examples.DataManagement.PreferenceActivity.ViewAndUpdatePreferencesActivity$UserPreferenceFragment"
+ android:id="@+id/userPreferenceFragment">
+</fragment>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/MacRoman.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/MacRoman.xml
new file mode 100644
index 0000000..57035c9
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/MacRoman.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="MacRoman"?>
+<!-- This is a
+ multiline comment
+-->
+<foo attr="¾¯ÂŒ">
+
+<bar></bar>
+</foo>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom-implicit.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom-implicit.xml
new file mode 100644
index 0000000..d9198d0
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom-implicit.xml
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom.xml
new file mode 100644
index 0000000..12b75d4
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom.xml
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-nobom.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-nobom.xml
new file mode 100644
index 0000000..66dab29
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-nobom.xml
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-bom.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-bom.xml
new file mode 100644
index 0000000..6f5009e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-bom.xml
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-nobom.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-nobom.xml
new file mode 100644
index 0000000..e4f00ef
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-nobom.xml
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32LE-nobom.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32LE-nobom.xml
new file mode 100644
index 0000000..627d4d5
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32LE-nobom.xml
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/Windows-1252.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/Windows-1252.xml
new file mode 100644
index 0000000..5fa646d
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/encoding/Windows-1252.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="windows-1252"?>
+<!-- This is a
+ multiline comment
+-->
+<foo attr="æØå">
+
+<bar></bar>
+</foo>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_explicit.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_explicit.xml
new file mode 100644
index 0000000..2b7940a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_explicit.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="test.bytecode"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="10" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name="android.preference.PreferenceActivity"
+ android:label="@string/app_name"
+ android:exported="true">
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_implicit.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_implicit.xml
new file mode 100644
index 0000000..80fcf6d
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_implicit.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="test.bytecode"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="10" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name="android.preference.PreferenceActivity"
+ 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/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_no_export.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_no_export.xml
new file mode 100644
index 0000000..16e035c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_no_export.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="test.bytecode"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="10" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name="android.preference.PreferenceActivity"
+ android:label="@string/app_name" >
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_explicit.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_explicit.xml
new file mode 100644
index 0000000..32c69bd
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_explicit.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="test.pkg"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="10" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".PreferenceActivitySubclass"
+ android:label="@string/app_name"
+ android:exported="true">
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_implicit.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_implicit.xml
new file mode 100644
index 0000000..e5f1e11
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_implicit.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="test.pkg"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="10" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".PreferenceActivitySubclass"
+ 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/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_suppressed.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_suppressed.xml
new file mode 100644
index 0000000..2247254
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_suppressed.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="test.bytecode"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="10" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <!--suppress ExportedPreferenceActivity -->
+ <activity
+ android:name="android.preference.PreferenceActivity"
+ 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/lint/cli/src/test/java/com/android/tools/lint/checks/data/gradle_override.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/gradle_override.xml
new file mode 100644
index 0000000..e9bc26e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/gradle_override.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" android:targetSdkVersion="17" />
+ <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/local.properties b/lint/cli/src/test/java/com/android/tools/lint/checks/data/local.properties
new file mode 100644
index 0000000..e6723af
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/local.properties
@@ -0,0 +1,13 @@
+## 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=/Users/test/dev/sdks
+windows.dir=C:\my\path\to\sdk
+windows2.dir=C\:\\my\\path\\to\\sdk
+not.a.path.prop=Hello \my\path\to\sdk
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings2.xml
new file mode 100644
index 0000000..b6ca042
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings2.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<resources>
+
+ <string name="dateFormat">ukformat</string>
+ <string name="dummy">DeleteMeToGetRidOfOtherWarning</string>
+
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings2.xml
new file mode 100644
index 0000000..957b214
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings2.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:locale="en">
+
+ <string name="dateFormat">defaultformat</string>
+
+</resources>
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/styles.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/styles.xml
new file mode 100644
index 0000000..6ba9a7a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/styles.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- DeleteThisFileToGetRidOfOtherWarning -->
+
+</resources>
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest2.xml
new file mode 100644
index 0000000..b9d6bcb
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest2.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="foo.library2"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+</manifest>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/multiproject/library2.properties b/lint/cli/src/test/java/com/android/tools/lint/checks/data/multiproject/library2.properties
new file mode 100644
index 0000000..480b8ae
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/multiproject/library2.properties
@@ -0,0 +1,13 @@
+# 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 use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-14
+android.library=true
+android.library.reference.1=../RealLibrary
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/protection_level_fail.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/protection_level_fail.xml
new file mode 100644
index 0000000..69d624c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/protection_level_fail.xml
@@ -0,0 +1,20 @@
+<?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" />
+
+ <permission android:name="foo.permission.SIGNATURE_OR_SYSTEM"
+ android:label="@string/foo"
+ android:description="@string/foo"
+ android:protectionLevel="signatureOrSystem"/>
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/protection_level_ok.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/protection_level_ok.xml
new file mode 100644
index 0000000..6a700d2
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/protection_level_ok.xml
@@ -0,0 +1,27 @@
+<?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" />
+
+ <permission android:name="foo.permission.NORMAL"
+ android:label="@string/foo"
+ android:description="@string/foo"
+ android:protectionLevel="normal"/>
+ <permission android:name="foo.permission.DANGEROUS"
+ android:label="@string/foo"
+ android:description="@string/foo"
+ android:protectionLevel="dangerous"/>
+ <permission android:name="foo.permission.SIGNATURE"
+ android:label="@string/foo"
+ android:description="@string/foo"
+ android:protectionLevel="signature"/>
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/color/color1.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/color/color1.xml
new file mode 100644
index 0000000..d63b94a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/color/color1.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/bright_foreground_dark_disabled"/>
+ <item android:color="@color/bright_foreground_dark"/>
+</selector>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/drawable-xhdpi/ic_stat_notify.png b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/drawable-xhdpi/ic_stat_notify.png
new file mode 100644
index 0000000..b8d7509
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/drawable-xhdpi/ic_stat_notify.png
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/drawable/drawable1.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/drawable/drawable1.xml
new file mode 100644
index 0000000..3ac008e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/drawable/drawable1.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/textfield_search_default" />
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/textfield_search_pressed" />
+
+ <item android:state_enabled="true" android:state_focused="true"
+ android:drawable="@drawable/textfield_search_selected" />
+
+ <item android:drawable="@drawable/textfield_search_default" />
+</selector>
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/customview3.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/customview3.xml
new file mode 100644
index 0000000..606331d
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/customview3.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:id="@+id/newlinear"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <test.Pkg.CustomView3
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ tools:ignore="HardcodedText" />
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout2.xml
new file mode 100644
index 0000000..a450710
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout2.xml
@@ -0,0 +1,17 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:myns="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v7.widget.GridLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+ <TextView
+ android:text="@string/hello_world"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_row="2"
+ myns:layout_column="1" />
+ </android.support.v7.widget.GridLayout>
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout3.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout3.xml
new file mode 100644
index 0000000..87f9ba1
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout3.xml
@@ -0,0 +1,14 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v7.widget.GridLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:text="@string/hello_world"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_row="2" />
+ </android.support.v7.widget.GridLayout>
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores2.xml
new file mode 100644
index 0000000..224ab28
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores2.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/newlinear"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <!-- Ignored via comment, should be hidden -->
+
+ <!--suppress AndroidLintHardcodedText -->
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button1" >
+ </Button>
+
+ <!-- Inherited ignore from parent -->
+
+ <!--suppress AndroidLintHardcodedText-->
+ <LinearLayout
+ android:id="@+id/parent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" >
+
+ <Button
+ android:id="@+id/button2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button2" >
+ </Button>
+ </LinearLayout>
+
+ <!-- Ignored through item in ignore list -->
+
+ <!--suppress AndroidLintNewApi,AndroidLintHardcodedText -->
+
+ <Button
+ android:id="@+id/button4"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Hardcoded" >
+ </Button>
+
+ <!-- Not ignored: should show up as a warning -->
+ <!--suppress AndroidLintNewApi -->
+ <Button
+ android:id="@+id/button5"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Hardcoded"
+ >
+ </Button>
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/include_params.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/include_params.xml
new file mode 100644
index 0000000..67b0d4a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/include_params.xml
@@ -0,0 +1,75 @@
+<?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="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <!-- Ok: No layout params -->
+ <include layout="@layout/myincluded" />
+
+ <!-- Ok: No layout params -->
+ <include
+ android:id="@+id/myInclude"
+ layout="@layout/myincluded"
+ android:gravity="left"
+ android:visibility="visible" />
+
+ <!-- Ok: No layout params -->
+ <include
+ layout="@layout/myincluded"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+
+ <!-- Ok: Specifies both width and height -->
+ <include
+ layout="@layout/myincluded"
+ android:layout_width="fill_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1.5"
+ android:visibility="visible" />
+
+ <!-- Ok: ignored -->
+ <include
+ layout="@layout/myincluded"
+ android:layout_width="fill_parent"
+ android:layout_weight="1.5"
+ android:visibility="visible"
+ tools:ignore="IncludeLayoutParam" />
+
+ <!-- Wrong: Missing both -->
+ <include
+ layout="@layout/myincluded"
+ android:layout_margin="20dp"
+ android:layout_weight="1.5"
+ android:visibility="visible" />
+
+ <!-- Wrong: Missing width -->
+ <include
+ layout="@layout/myincluded"
+ android:layout_height="0dp"
+ android:layout_weight="1.5"
+ android:visibility="visible" />
+
+ <!-- Wrong: Missing height -->
+ <include
+ layout="@layout/myincluded"
+ android:layout_width="fill_parent"
+ android:layout_weight="1.5"
+ android:visibility="visible" />
+
+ <!-- Wrong: Specified only width -->
+ <include
+ android:id="@+id/myInclude"
+ layout="@layout/myincluded"
+ android:layout_width="fill_parent"
+ android:visibility="visible" />
+
+ <!-- Wrong: Specified only height -->
+ <include
+ android:id="@+id/myInclude"
+ layout="@layout/myincluded"
+ android:layout_height="fill_parent"
+ android:visibility="visible" />
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight3.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight3.xml
new file mode 100644
index 0000000..a15c25e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight3.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="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ style="@style/MyButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_weight="1.0"/>
+
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids2.xml
new file mode 100644
index 0000000..259903a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids2.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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" >
+
+ <Button
+ android:id="@+id/btn/skip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:text="Button" />
+
+</RelativeLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4cycle.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4cycle.xml
new file mode 100644
index 0000000..4397201
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4cycle.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="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <RadioButton
+ android:id="@+id/radioButton1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="RadioButton" />
+
+ <include
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ layout="@layout/layout1" />
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/layoutcycle1.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/layoutcycle1.xml
new file mode 100644
index 0000000..c106237
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/layoutcycle1.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <include
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ layout="@layout/layoutcycle1" />
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit2.xml
new file mode 100644
index 0000000..e3fa7a8
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit2.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">
+
+ <EditText
+ style="@style/TextWithHint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <EditText
+ style="@style/TextWithInput"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation2.xml
new file mode 100644
index 0000000..4ca976c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation2.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:id="@+id/linear"
+ style="@style/Layout.Horizontal" />
+
+</FrameLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize2.xml
new file mode 100644
index 0000000..f62cde4
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize2.xml
@@ -0,0 +1,13 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/LinearLayout1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:textSize="@dimen/bottom_bar_portrait_button_font_size"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/useless4.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/useless4.xml
new file mode 100644
index 0000000..56a7c8e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/useless4.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/detail_panel_counter_bg">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="5dp">
+
+ <View
+ android:layout_width="5dp"
+ android:layout_height="5dp" />
+
+ <View
+ android:layout_width="5dp"
+ android:layout_height="5dp" />
+ </LinearLayout>
+</FrameLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/webview.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/webview.xml
new file mode 100644
index 0000000..13fbf0c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/webview.xml
@@ -0,0 +1,30 @@
+<?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:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- OK -->
+ <WebView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <!-- Report error that parent height is wrap_content -->
+ <WebView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- Suppressed -->
+ <WebView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:ignore="WebViewLayout" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace5.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace5.xml
index ce0fc4b..a64c336 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace5.xml
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace5.xml
@@ -4,6 +4,7 @@
xmlns:typo2="http://schems.android.comm/apk/res/com.my.package"
xmlns:ok="http://foo.bar/res/unrelated"
xmlns:tools="http://schemas.android.com/tools"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
android:layout_width="match_parent"
android:layout_height="match_parent" >
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams6.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams6.xml
index 8691d7c..fb89ec7 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams6.xml
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams6.xml
@@ -10,4 +10,9 @@
android:layout_height="wrap_content"
layout="@layout/wrongparams5" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignStart="@+id/include1"
+ android:layout_toEndOf="@+id/include1" />
</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction1.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction1.xml
new file mode 100644
index 0000000..5df2c6a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction1.xml
@@ -0,0 +1,8 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:id="@+id/action_settings"
+ android:title="@string/action_settings"
+ android:orderInCategory="100"
+ app:showAsAction="never" />
+</menu>
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction2.xml
new file mode 100644
index 0000000..0a04222
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction2.xml
@@ -0,0 +1,7 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/action_settings"
+ android:title="@string/action_settings"
+ android:orderInCategory="100"
+ android:showAsAction="never" />
+</menu>
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/bom.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/bom.xml
new file mode 100644
index 0000000..58efff6
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/bom.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="hanping_chinese_lite_app_name">(Translated name)</string>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/aaptcrash.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/aaptcrash.xml
new file mode 100644
index 0000000..b5469e4
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/aaptcrash.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="TitleBar">
+ <item name="android:orientation">horizontal</item>
+ <item name="android:id">@+id/titlebar</item>
+ <item name="android:background">@drawable/bg_titlebar</item>
+ <item name="android:layout_width">fill_parent</item>
+ <item name="android:layout_height">@dimen/titlebar_height</item>
+ </style>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/aliases.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/aliases.xml
new file mode 100644
index 0000000..9de480b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/aliases.xml
@@ -0,0 +1,8 @@
+<resources>
+ <item name="layout10" type="layout">@layout/layout20</item>
+ <item name="layout20" type="layout">@layout/layout30</item>
+ <item name="layout30" type="layout">@layout/layout10</item>
+ <item name="test2" type="color">@color/test1</item>
+ <item name="layout4" type="layout">@layout/layout1</item>
+</resources>
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/aliases2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/aliases2.xml
new file mode 100644
index 0000000..9d68541
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/aliases2.xml
@@ -0,0 +1,6 @@
+<resources>
+ <item name="bright_foreground_dark" type="color">@color/color1</item>
+ <item name="textfield_search_pressed" type="drawable">@drawable/drawable2</item>
+ <item name="drawable2" type="drawable">@drawable/drawable1</item>
+</resources>
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle1.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle1.xml
new file mode 100644
index 0000000..c127844
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle1.xml
@@ -0,0 +1,3 @@
+<resources>
+ <color name="test">@color/test</color>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle2.xml
new file mode 100644
index 0000000..712ef9b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle2.xml
@@ -0,0 +1,5 @@
+<resources>
+ <color name="test1">@color/test2</color>
+ <color name="unrelated1">@color/test2b</color>
+ <color name="unrelated2">#ff0000</color>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle3.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle3.xml
new file mode 100644
index 0000000..c875f10
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle3.xml
@@ -0,0 +1,4 @@
+<resources>
+ <color name="test2">@color/test3</color>
+ <color name="test2b">#ff00ff00</color>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle4.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle4.xml
new file mode 100644
index 0000000..5eeacc5
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle4.xml
@@ -0,0 +1,3 @@
+<resources>
+ <color name="test3">@color/test1</color>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle5.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle5.xml
new file mode 100644
index 0000000..edbb54a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle5.xml
@@ -0,0 +1,4 @@
+<resources>
+ <color name="test1">@color/test2</color>
+ <color name="test2">@color/test1</color>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/dimens.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/dimens.xml
new file mode 100644
index 0000000..0ea5aad
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/dimens.xml
@@ -0,0 +1,3 @@
+<resources>
+ <dimen name="bottom_bar_portrait_button_font_size">16dp</dimen>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-items.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-items.xml
new file mode 100644
index 0000000..83ac291
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-items.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <style name="DetailsPage_BuyButton" parent="@style/DetailsPage_Button">
+ <item name="android:textColor">@color/buy_button</item>
+ <item name="android:background">@drawable/details_page_buy_button</item>
+ <item name="android:textColor">#ff0000</item>
+ </style>
+
+ <declare-styleable name="ContentFrame">
+ <attr name="content" format="reference" />
+ <attr name="contentId" format="reference" />
+ <attr name="contentId" format="integer" />
+ </declare-styleable>
+
+</resources>
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings.xml
new file mode 100644
index 0000000..aac0fd7
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">App Name</string>
+ <string name="hello_world">Hello world!</string>
+ <string name="app_name">App Name 1</string>
+ <string name="app_name2">App Name 2</string>
+
+</resources>
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings10.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings10.xml
new file mode 100644
index 0000000..b3ae44f
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings10.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="format_float">Formatted float value: %f</string>
+ <string name="format_integer">Formatted integer value: %d</string>
+ <string name="format_hex_float">Formatted float value: %A</string>
+ <string name="format_hex">Formatted integer value: %h</string>
+ <string name="decimal_format_string">Decimal string: %.2f</string>
+
+</resources>
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings8.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings8.xml
new file mode 100644
index 0000000..d22a48b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings8.xml
@@ -0,0 +1,4 @@
+<resources>
+ <string name="amount_string">Amount: $%1$.2f %n</string>
+ <string name="percent_newline">%%%n%n%n</string>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings9.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings9.xml
new file mode 100644
index 0000000..ee9dd92
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings9.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="toast_percent_copy_quota_used">
+ You\'ve used about <xliff:g id="copyQuotaUsed">%d</xliff:g>%% of your
+ quota</string>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/plurals5.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/plurals5.xml
new file mode 100644
index 0000000..af72316
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/plurals5.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <plurals name="my_plural">
+ <item quantity="zero">@string/hello</item>
+ <item quantity="one">@string/hello</item>
+ <item quantity="few">@string/hello</item>
+ <item quantity="other">@string/hello</item>
+ </plurals>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/refs.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/refs.xml
new file mode 100644
index 0000000..8f895cd
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/refs.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <item name="invalid1" type="string">@layout/other</item>
+ <item name="invalid1" type="drawable">
+ @layout/other
+ </item>
+ <item name="string1" type="string">Plain String</item>
+ <item name="string2" type="string">@string/indirect</item>
+ <string name="string3">@string/indirect</string>
+ <string name="invalid4">@layout/indirect</string>
+ <item name="other2" type="layout">@layout/indirect2</item>
+ <item name="indirect2" type="layout"> @layout/indirect1 </item>
+ <item name="indirect1" type="layout">@layout/to</item>
+ <item name="colorAsDrawable" type="drawable">@color/my_color</item>
+ <item name="drawableAsColor" type="color">@drawable/my_drawable</item>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/shared_prefs_keys.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/shared_prefs_keys.xml
new file mode 100644
index 0000000..ff0ac30
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/shared_prefs_keys.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="pref_key_assigned_bluetooth_device_name">Device Name</string>
+ <string name="pref_key_assigned_bluetooth_device_address">Device Address %1$s %2$s</string>
+</resources>
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle1.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle1.xml
new file mode 100644
index 0000000..058f91b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle1.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="ButtonStyle.Base">
+ <item name="android:textColor">#ff0000</item>
+ </style>
+ <style name="ButtonStyle" parent="ButtonStyle.Base">
+ <item name="android:layout_height">40dp</item>
+ </style>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle2.xml
new file mode 100644
index 0000000..a0570b0
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle2.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="mystyle1" parent="@style/mystyle2">
+ <item name="android:textColor">#ff0000</item>
+ </style>
+ <style name="mystyle2" parent="@style/mystyle3">
+ <item name="android:textColor">#ff0ff</item>
+ </style>
+ <style name="mystyle3" parent="@style/mystyle1">
+ <item name="android:textColor">#ffff00</item>
+ </style>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/styles-inherited-orientation.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/styles-inherited-orientation.xml
new file mode 100644
index 0000000..b616391
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/styles-inherited-orientation.xml
@@ -0,0 +1,15 @@
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="Layout">
+ <item name="android:orientation">vertical</item>
+ </style>
+
+ <style name="Layout.Horizontal">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ </style>
+
+ <style name="MyButtonStyle" parent="@style/Layout">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ </style>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/styles-orientation.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/styles-orientation.xml
new file mode 100644
index 0000000..2e14f1e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/styles-orientation.xml
@@ -0,0 +1,20 @@
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="Layout.Horizontal" parent="@style/Layout">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:layout_height">wrap_content</item>
+ </style>
+
+ <style name="MyButtonStyle" parent="@style/Layout">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">0dp</item>
+ </style>
+
+ <style name="TextWithHint">
+ <item name="android:hint">Number</item>
+ </style>
+
+ <style name="TextWithInput">
+ <item name="android:hint">number</item>
+ </style>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/themes3.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/themes3.xml
new file mode 100644
index 0000000..e79e79b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/themes3.xml
@@ -0,0 +1,7 @@
+<resources>
+
+ <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ </style>
+
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list.xml
new file mode 100644
index 0000000..3e11b34
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list.xml
@@ -0,0 +1,14 @@
+ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- capture anything using NfcF -->
+ <tech-list>
+ <tech>android.nfc.tech.NfcA</tech>
+ </tech-list>
+ <!-- OR -->
+ <tech-list>
+ <tech>android.nfc.tech.MifareUltralight</tech>
+ </tech-list>
+ <!-- OR -->
+ <tech-list>
+ <tech>android.nfc.tech.ndefformatable</tech>
+ </tech-list>
+ </resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list_formatted.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list_formatted.xml
new file mode 100644
index 0000000..e788ae9
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list_formatted.xml
@@ -0,0 +1,22 @@
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" >
+
+ <!-- capture anything using NfcF -->
+ <tech-list>
+ <tech>
+android.nfc.tech.NfcA
+ </tech>
+ </tech-list>
+ <!-- OR -->
+ <tech-list>
+ <tech>
+android.nfc.tech.MifareUltralight
+ </tech>
+ </tech-list>
+ <!-- OR -->
+ <tech-list>
+ <tech>
+android.nfc.tech.ndefformatable
+ </tech>
+ </tech-list>
+
+</resources>
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/relative.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/relative.xml
index f6ae19b..aa3d674 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/relative.xml
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/relative.xml
@@ -49,4 +49,18 @@
android:scaleType="fitXY"
android:src="@drawable/menu_list_divider" />
+ <Button
+ android:id="@+id/cancel2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_alignEnd="@id/text"
+ android:layout_below="@id/text"
+ android:background="@null"
+ android:layout_marginLeft="40dip"
+ android:layout_marginRight="40dip"
+ android:paddingLeft="120dip"
+ android:paddingRight="120dip"
+ android:text="@string/cancel" />
+
</RelativeLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/relativeOk.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/relativeOk.xml
index 9dafb79..b0a3406 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/relativeOk.xml
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/relativeOk.xml
@@ -31,6 +31,8 @@
android:ellipsize="end"
android:maxLines="3"
android:paddingRight="120dip"
+ android:paddingLeft="60dip"
+ android:paddingStart="60dip"
android:paddingEnd="120dip"
android:text="@string/creating_instant_mix"
android:textAppearance="?android:attr/textAppearanceMedium" />
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/rtl_noprefix.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/rtl_noprefix.xml
new file mode 100644
index 0000000..f5b9604
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rtl/rtl_noprefix.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="HardcodedText" >
+
+ <Button
+ layout_gravity="left"
+ layout_alignParentLeft="true"
+ editable="false"
+ android:text="Button" />
+
+</LinearLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt
new file mode 100644
index 0000000..63a9ffd
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt
@@ -0,0 +1,27 @@
+package test.pkg;
+
+import android.annotations.tools.SuppressLint;
+
+public class Assert {
+ public Assert(int param, Object param2, Object param3) {
+ assert false; // ERROR
+ assert param > 5 : "My description"; // ERROR
+ assert param2 == param3; // ERROR
+ assert param2 != null && param3 == param2; // ERROR
+ assert true; // OK
+ assert param2 == null; // OK
+ assert param2 != null && param3 == null; // OK
+ assert param2 == null && param3 != null; // OK
+ assert param2 != null && param3 != null; // OK
+ assert null != param2; // OK
+ assert param2 != null; // OK
+ assert param2 != null : "My description"; // OK
+ assert checkSuppressed(5) != null; // OK
+ }
+
+ @SuppressLint("Assert")
+ public Object checkSuppressed(int param) {
+ assert param > 5 : "My description";
+ return null;
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAES.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAES.java.txt
new file mode 100644
index 0000000..1577207
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAES.java.txt
@@ -0,0 +1,9 @@
+package test.pkg;
+
+import javax.crypto.Cipher;
+
+public class CipherGetInstanceAES {
+ private void foo() throws Exception {
+ Cipher.getInstance("AES");
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESCBC.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESCBC.java.txt
new file mode 100644
index 0000000..cb663c8
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESCBC.java.txt
@@ -0,0 +1,9 @@
+package test.pkg;
+
+import javax.crypto.Cipher;
+
+public class CipherGetInstanceAESCBC {
+ private void foo() throws Exception {
+ Cipher.getInstance("AES/CBC/NoPadding");
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESECB.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESECB.java.txt
new file mode 100644
index 0000000..8e17923
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESECB.java.txt
@@ -0,0 +1,9 @@
+package test.pkg;
+
+import javax.crypto.Cipher;
+
+public class CipherGetInstanceAESECB {
+ private void foo() throws Exception {
+ Cipher.getInstance("AES/ECB/NoPadding");
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceDES.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceDES.java.txt
new file mode 100644
index 0000000..5c1defe
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceDES.java.txt
@@ -0,0 +1,9 @@
+package test.pkg;
+
+import javax.crypto.Cipher;
+
+public class CipherGetInstanceDES {
+ private void foo() throws Exception {
+ Cipher.getInstance("DES");
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceTest.java.txt
new file mode 100644
index 0000000..ce6d36c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceTest.java.txt
@@ -0,0 +1,16 @@
+package test.pkg;
+
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+
+public class CipherGetInstanceTest {
+ public void test() throws NoSuchPaddingException, NoSuchAlgorithmException {
+ Cipher des = Cipher.getInstance(Constants.DES);
+ }
+
+ public static class Constants {
+ public static final String DES = "DES/ECB/NoPadding";
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt
index 2428e61..adb806c 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt
@@ -193,7 +193,11 @@
new SparseArray<Object>(); // OK
}
- public void longSparseArray() { // but only minSdkVersion >= 17
+ public void longSparseArray() { // but only minSdkVersion >= 17 or if has v4 support lib
Map<Long, String> myStringMap = new HashMap<Long, String>();
}
+
+ public void byteSparseArray() { // bytes easily apply to ints
+ Map<Byte, String> myByteMap = new HashMap<Byte, String>();
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest.java.txt
new file mode 100644
index 0000000..00fa008
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest.java.txt
@@ -0,0 +1,21 @@
+package test.pkg;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+public abstract class LayoutInflationTest extends BaseAdapter {
+ public View getView(int position, View convertView, ViewGroup parent) {
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+ convertView = mInflater.inflate(R.layout.your_layout, null, true);
+ convertView = mInflater.inflate(R.layout.your_layout);
+ convertView = mInflater.inflate(R.layout.your_layout, parent);
+ convertView = WeirdInflater.inflate(convertView, null);
+
+ return convertView;
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest_ignored.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest_ignored.java.txt
new file mode 100644
index 0000000..14fa452
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest_ignored.java.txt
@@ -0,0 +1,30 @@
+package test.pkg;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+public abstract class LayoutInflationTest extends BaseAdapter {
+ public View getView(int position, View convertView, ViewGroup parent) {
+ //noinspection InflateParams
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+ //noinspection AndroidLintInflateParams
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+ return convertView;
+ }
+ @SuppressLint("InflateParams")
+ public View getView(int position, View convertView, ViewGroup parent) {
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+ convertView = mInflater.inflate(R.layout.your_layout, null, true);
+ convertView = mInflater.inflate(R.layout.your_layout);
+ convertView = mInflater.inflate(R.layout.your_layout, parent);
+ convertView = WeirdInflater.inflate(convertView, null);
+
+ return convertView;
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclass.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclass.java.txt
new file mode 100644
index 0000000..27191b3
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclass.java.txt
@@ -0,0 +1,7 @@
+package test.pkg;
+
+import android.preference.PreferenceActivity;
+
+public class PreferenceActivitySubclass extends PreferenceActivity {
+
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest6.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest6.java.txt
new file mode 100644
index 0000000..57da938
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest6.java.txt
@@ -0,0 +1,23 @@
+package test.pkg;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+@SuppressWarnings("UnusedDeclaration")
+public class SharedPrefsFormat {
+ public void test(Context sessionContext) {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(sessionContext);
+ final String nameKey = sessionContext.getString(R.string.pref_key_assigned_bluetooth_device_name);
+ final String addressKey = sessionContext.getString(R.string.pref_key_assigned_bluetooth_device_address);
+ final String name = prefs.getString(nameKey, null);
+ final String address = prefs.getString(addressKey, null);
+ }
+
+ public static final class R {
+ public static final class string {
+ public static final int pref_key_assigned_bluetooth_device_name = 0x7f0a000e;
+ public static final int pref_key_assigned_bluetooth_device_address = 0x7f0a000f;
+ }
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest7.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest7.java.txt
new file mode 100644
index 0000000..2ed1858
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest7.java.txt
@@ -0,0 +1,15 @@
+package test.pkg;
+
+import android.content.SharedPreferences;
+
+public class SharedPrefsTest7 {
+ private static final String PREF_NAME = "MyPrefName";
+ private static final String MY_PREF_KEY = "MyKey";
+ SharedPreferences getSharedPreferences(String key, int deflt) {
+ return null;
+ }
+ public void test(String myPrefValue) {
+ SharedPreferences settings = getSharedPreferences(PREF_NAME, 0);
+ settings.edit().putString(MY_PREF_KEY, myPrefValue);
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest8.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest8.java.txt
new file mode 100644
index 0000000..a0ab2d2
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest8.java.txt
@@ -0,0 +1,50 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+public class SharedPrefsTest8 extends Activity {
+ public void commitWarning1() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.commit();
+ }
+
+ public void commitWarning2() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ boolean b = editor.commit(); // OK: reading return value
+ }
+ public void commitWarning3() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ boolean c;
+ c = editor.commit(); // OK: reading return value
+ }
+
+ public void commitWarning4() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ if (editor.commit()) { // OK: reading return value
+ //noinspection UnnecessaryReturnStatement
+ return;
+ }
+ }
+
+ public void commitWarning5() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ boolean c = false;
+ c |= editor.commit(); // OK: reading return value
+ }
+
+ public void commitWarning6() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ foo(editor.commit()); // OK: reading return value
+ }
+
+ public void foo(boolean x) {
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt
new file mode 100644
index 0000000..862a59f
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt
@@ -0,0 +1,12 @@
+package test.pkg;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.content.Context;
+
+public class SparseLongArray extends Button {
+ public void test() { // but only minSdkVersion >= 18
+ Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat10.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat10.java.txt
new file mode 100644
index 0000000..0b6521b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat10.java.txt
@@ -0,0 +1,50 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.content.Context;
+import android.widget.TextView;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+@SuppressWarnings("UnusedDeclaration")
+public class StringFormat10 extends Activity {
+ protected void check() {
+ // BigDecimal is okay as a float: f, e, E, g, G, a, A
+ // BigInteger is okay as an integer: d, o, x, X
+ // http://developer.android.com/reference/java/util/Formatter.html
+ BigDecimal decimal = new BigDecimal("3.14159265358979323846264338327950288419716939");
+ BigInteger integer = new BigInteger("2089986280348253421170679821480865132823066470");
+ TextView view1 = (TextView) findViewById(R.id.my_hello);
+ TextView view2 = (TextView) findViewById(R.id.my_hello2);
+ view1.setText(getResources().getString(R.string.format_float, decimal));
+ view2.setText(getResources().getString(R.string.format_integer, integer));
+ view1.setText(getResources().getString(R.string.format_hex_float, decimal));
+ view2.setText(getResources().getString(R.string.format_hex, integer));
+ }
+
+ protected void check2(Context mContext) {
+ String formatted = mContext.getString(R.string.decimal_format_string,
+ fnReturningDouble());
+ }
+
+ private BigDecimal fnReturningDouble() {
+ return new BigDecimal("3.14159265358979323846264338327950288419716939");
+ }
+
+ public static final class R {
+ public static final class string {
+ public static final int format_float = 0x7f0a000f;
+ public static final int format_integer = 0x7f0a0010;
+ public static final int format_hex_float = 0x7f0a0009;
+ public static final int format_hex = 0x7f0a000d;
+ public static final int decimal_format_string = 0x7f0a000e;
+ }
+
+ public static final class id {
+ public static final int my_hello = 0x7f07003c;
+ public static final int my_hello2 = 0x7f07003d;
+ }
+ }
+}
+
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat11.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat11.java.txt
new file mode 100644
index 0000000..a6440ba
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat11.java.txt
@@ -0,0 +1,109 @@
+package test.pkg;
+
+import android.app.Activity;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+public class StringFormat11 extends Activity {
+
+ protected void check() {
+ // -------------------------
+ // Test integral types
+ // -------------------------
+
+ byte varByte = 1;
+ Byte varByteWrapper = 1;
+ short varShort = 1;
+ Short varShortWrapper = 1;
+ int varInt = 1;
+ Integer varIntWrapper = 1;
+ long varLong = 1L;
+ Long varLongWrapper = 1L;
+ BigInteger varBigInteger = new BigInteger("1");
+ float varFloat = 1.0f;
+ Float varFloatWrapper = 1.0f;
+ double varDouble = 1.0d;
+ double varDoubleWrapper = 1.0d;
+ BigDecimal varBigDecimal = new BigDecimal("1.0");
+
+ // Check variable references with known types
+ getResources().getString(R.string.format_integer, varByte);
+ getResources().getString(R.string.format_integer, varByteWrapper);
+ getResources().getString(R.string.format_integer, varShort);
+ getResources().getString(R.string.format_integer, varShortWrapper);
+ getResources().getString(R.string.format_integer, varInt);
+ getResources().getString(R.string.format_integer, varIntWrapper);
+ getResources().getString(R.string.format_integer, varLong);
+ getResources().getString(R.string.format_integer, varLongWrapper);
+ getResources().getString(R.string.format_integer, varBigInteger);
+
+ // Check resolved types
+ getResources().getString(R.string.format_integer, getResolvedByte());
+ getResources().getString(R.string.format_integer, getResolvedByteWrapper());
+ getResources().getString(R.string.format_integer, getResolvedShort());
+ getResources().getString(R.string.format_integer, getResolvedShortWrapper());
+ getResources().getString(R.string.format_integer, getResolvedInt());
+ getResources().getString(R.string.format_integer, getResolvedIntWrapper());
+ getResources().getString(R.string.format_integer, getResolvedLong());
+ getResources().getString(R.string.format_integer, getResolvedLongWrapper());
+ getResources().getString(R.string.format_integer, getResolvedBigInteger());
+
+ // -------------------------
+ // Test float types
+ // -------------------------
+
+ // Check variable references with known types
+ getResources().getString(R.string.format_float, varFloat);
+ getResources().getString(R.string.format_float, varFloatWrapper);
+ getResources().getString(R.string.format_float, varDouble);
+ getResources().getString(R.string.format_float, varDoubleWrapper);
+ getResources().getString(R.string.format_float, varBigDecimal);
+
+ // Check resolved types
+ getResources().getString(R.string.format_float, getResolvedFloat());
+ getResources().getString(R.string.format_float, getResolvedFloatWrapper());
+ getResources().getString(R.string.format_float, getResolvedDouble());
+ getResources().getString(R.string.format_float, getResolvedDoubleWrapper());
+ getResources().getString(R.string.format_float, getResolvedBigDecimal());
+
+ // -------------------------
+ // Test conversions
+ // -------------------------
+
+ // Conversion (integer as float)
+ getResources().getString(R.string.format_float, varInt);
+ getResources().getString(R.string.format_float, varIntWrapper);
+ getResources().getString(R.string.format_float, getResolvedInt());
+ getResources().getString(R.string.format_float, getResolvedIntWrapper());
+
+ // Conversion (float as integer)
+ getResources().getString(R.string.format_integer, varFloat);
+ getResources().getString(R.string.format_integer, varFloatWrapper);
+ getResources().getString(R.string.format_integer, getResolvedFloat());
+ getResources().getString(R.string.format_integer, getResolvedFloatWrapper());
+ }
+
+ private byte getResolvedByte() { return 0; }
+ private Byte getResolvedByteWrapper() { return 0; }
+ private short getResolvedShort() { return 0; }
+ private Short getResolvedShortWrapper() { return 0; }
+ private int getResolvedInt() { return 0; }
+ private Integer getResolvedIntWrapper() { return 0; }
+ private long getResolvedLong() { return 0L; }
+ private Long getResolvedLongWrapper() { return 0L; }
+ private float getResolvedFloat() { return 0f; }
+ private Float getResolvedFloatWrapper() { return 0f; }
+ private double getResolvedDouble() { return 0f; }
+ private Double getResolvedDoubleWrapper() { return 0d; }
+ private BigInteger getResolvedBigInteger() { return new BigInteger("0"); }
+ private BigDecimal getResolvedBigDecimal() { return new BigDecimal("0.0"); }
+
+ public static final class R {
+ public static final class string {
+ public static final int format_float = 0x7f0a000f;
+ public static final int format_integer = 0x7f0a0010;
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt
new file mode 100644
index 0000000..f2d0123
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt
@@ -0,0 +1,16 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.content.Context;
+
+public class StringFormat8 extends Activity {
+ public final void test(Context context, float amount) {
+ Resources resources = getResources();
+ String amount1 = resources.getString(R.string.amount_string, amount);
+ String amount2 = getResources().getString(R.string.amount_string, amount);
+ String amount3 = String.format(getResources().getString(R.string.amount_string), amount);
+ String amount4 = String.format(getResources().getString(R.string.amount_string)); // ERROR
+ String amount5 = getResources().getString(R.string.amount_string, amount, amount); // ERROR
+ String misc = String.format(resource.getString(R.string.percent_newline));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt
new file mode 100644
index 0000000..49bad01
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt
@@ -0,0 +1,9 @@
+package test.pkg;
+
+import android.content.res.Resources;
+
+public class StringFormat9 {
+ public String format(Resources resources, int percentUsed) {
+ return resources.getString(R.string.toast_percent_copy_quota_used, percentUsed);
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
index 1646216..d79e421 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
@@ -49,6 +49,12 @@
@SuppressWarnings("SdCardPath")
String string2 = "/sdcard/mypath7";
+ @SuppressWarnings("AndroidLintSdCardPath")
+ String string3 = "/sdcard/mypath9";
+
+ //noinspection AndroidLintSdCardPath
+ String string4 = "/sdcard/mypath9";
+
return string;
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryCatchHang.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryCatchHang.java.txt
new file mode 100644
index 0000000..e660dba
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryCatchHang.java.txt
@@ -0,0 +1,26 @@
+package test.pkg;
+
+public final class TryCatchHang {
+ public void foo() {
+ try {
+ getClass().getField("").getInt(null);
+ }
+ catch(IllegalAccessException | NoSuchFieldException xc) {
+ throw new RuntimeException( xc );
+ }
+
+ try {
+ getClass().getField("").getInt(null);
+ }
+ catch (NoSuchFieldException | IllegalAccessException xc) {
+ throw new RuntimeException( xc );
+ }
+
+ try {
+ getClass().getField("").getInt(null);
+ }
+ catch (IllegalAccessException | NoSuchFieldException xc) {
+ throw new RuntimeException(xc);
+ }
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryWithResources.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryWithResources.java.txt
new file mode 100644
index 0000000..6c9e98f
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryWithResources.java.txt
@@ -0,0 +1,27 @@
+package test.pkg;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class TryWithResources {
+ public String testTryWithResources(String path) throws IOException {
+ try (BufferedReader br = new BufferedReader(new FileReader(path))) {
+ return br.readLine();
+ }
+ }
+
+ public void testMultiCatch() {
+ try {
+ Class.forName("java.lang.Integer").getMethod("toString").invoke(null);
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt
new file mode 100644
index 0000000..60fe18c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt
@@ -0,0 +1,137 @@
+package test.pkg;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+@SuppressWarnings({"ConstantConditions", "UnusedDeclaration"})
+public abstract class ViewHolderTest extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return 0;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ public static class Adapter1 extends ViewHolderTest {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return null;
+ }
+ }
+
+ public static class Adapter2 extends ViewHolderTest {
+ LayoutInflater mInflater;
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Should use View Holder pattern here
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+
+ TextView text = (TextView) convertView.findViewById(R.id.text);
+ text.setText("Position " + position);
+
+ return convertView;
+ }
+ }
+
+ public static class Adapter3 extends ViewHolderTest {
+ LayoutInflater mInflater;
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Already using View Holder pattern
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+ }
+
+ TextView text = (TextView) convertView.findViewById(R.id.text);
+ text.setText("Position " + position);
+
+ return convertView;
+ }
+ }
+
+ public static class Adapter4 extends ViewHolderTest {
+ LayoutInflater mInflater;
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Already using View Holder pattern
+ //noinspection StatementWithEmptyBody
+ if (convertView != null) {
+ } else {
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+ }
+
+ TextView text = (TextView) convertView.findViewById(R.id.text);
+ text.setText("Position " + position);
+
+ return convertView;
+ }
+ }
+
+ public static class Adapter5 extends ViewHolderTest {
+ LayoutInflater mInflater;
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Already using View Holder pattern
+ convertView = convertView == null ? mInflater.inflate(R.layout.your_layout, null) : convertView;
+
+ TextView text = (TextView) convertView.findViewById(R.id.text);
+ text.setText("Position " + position);
+
+ return convertView;
+ }
+ }
+
+ public static class Adapter6 extends ViewHolderTest {
+ private Context mContext;
+ private LayoutInflater mLayoutInflator;
+ private ArrayList<Double> mLapTimes;
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (mLayoutInflator == null)
+ mLayoutInflator = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ View v = convertView;
+ if (v == null) v = mLayoutInflator.inflate(R.layout.your_layout, null);
+
+ LinearLayout listItemHolder = (LinearLayout) v.findViewById(R.id.laptimes_list_item_holder);
+ listItemHolder.removeAllViews();
+
+ for (int i = 0; i < mLapTimes.size(); i++) {
+ View lapItemView = mLayoutInflator.inflate(R.layout.laptime_item, null);
+ if (i == 0) {
+ TextView t = (TextView) lapItemView.findViewById(R.id.laptime_text);
+ //t.setText(TimeUtils.createStyledSpannableString(mContext, mLapTimes.get(i), true));
+ }
+
+ TextView t2 = (TextView) lapItemView.findViewById(R.id.laptime_text2);
+ if (i < mLapTimes.size() - 1 && mLapTimes.size() > 1) {
+ double laptime = mLapTimes.get(i) - mLapTimes.get(i + 1);
+ if (laptime < 0) laptime = mLapTimes.get(i);
+ //t2.setText(TimeUtils.createStyledSpannableString(mContext, laptime, true));
+ } else {
+ //t2.setText(TimeUtils.createStyledSpannableString(mContext, mLapTimes.get(i), true));
+ }
+
+ listItemHolder.addView(lapItemView);
+
+ }
+ return v;
+ }
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/client/api/DefaultConfigurationTest.java b/lint/cli/src/test/java/com/android/tools/lint/client/api/DefaultConfigurationTest.java
index dad177e..0f9297c 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/client/api/DefaultConfigurationTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/client/api/DefaultConfigurationTest.java
@@ -15,6 +15,8 @@
*/
package com.android.tools.lint.client.api;
+import static com.android.tools.lint.client.api.DefaultConfiguration.globToRegexp;
+
import com.android.tools.lint.checks.AbstractCheckTest;
import com.android.tools.lint.checks.AccessibilityDetector;
import com.android.tools.lint.checks.ApiDetector;
@@ -34,6 +36,7 @@
import java.io.File;
import java.io.IOException;
+import java.util.regex.Pattern;
public class DefaultConfigurationTest extends AbstractCheckTest {
@@ -119,6 +122,100 @@
"", null));
}
+ public void testPatternIgnore() throws Exception {
+ File projectDir = getProjectDir(null,
+ "res/layout/onclick.xml=>res/layout/onclick.xml",
+ "res/layout/onclick.xml=>res/layout-xlarge/onclick.xml",
+ "res/layout/onclick.xml=>res/layout-xlarge/activation.xml"
+ );
+ LintClient client = new TestLintClient();
+ Project project = Project.create(client, projectDir, projectDir);
+ LintDriver driver = new LintDriver(new BuiltinIssueRegistry(), client);
+ File plainFile = new File(projectDir,
+ "res" + File.separator + "layout" + File.separator + "onclick.xml");
+ assertTrue(plainFile.exists());
+ File largeFile = new File(projectDir,
+ "res" + File.separator + "layout-xlarge" + File.separator + "onclick.xml");
+ assertTrue(largeFile.exists());
+ File windowsFile = new File(projectDir,
+ "res" + File.separator + "layout-xlarge" + File.separator + "activation.xml");
+ assertTrue(windowsFile.exists());
+ Context plainContext = new Context(driver, project, project, plainFile);
+ Context largeContext = new Context(driver, project, project, largeFile);
+ Context windowsContext = new Context(driver, project, project, windowsFile);
+ Location plainLocation = Location.create(plainFile);
+ Location largeLocation = Location.create(largeFile);
+ Location windowsLocation = Location.create(windowsFile);
+
+ assertEquals(Severity.WARNING, ObsoleteLayoutParamsDetector.ISSUE.getDefaultSeverity());
+ assertEquals(Severity.ERROR, ApiDetector.UNSUPPORTED.getDefaultSeverity());
+
+ DefaultConfiguration configuration = getConfiguration(""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<lint>\n"
+ + " <issue id=\"ObsoleteLayoutParam\">\n"
+ + " <ignore regexp=\"x.*onclick\" />\n"
+ + " <ignore regexp=\"res/.*layout.*/activation.xml\" />\n"
+ + " </issue>\n"
+ + "</lint>");
+
+ assertFalse(configuration.isIgnored(plainContext, ApiDetector.UNSUPPORTED,
+ plainLocation, "", null));
+ assertFalse(configuration.isIgnored(plainContext, ObsoleteLayoutParamsDetector.ISSUE,
+ plainLocation, "", null));
+ assertTrue(configuration.isIgnored(windowsContext, ObsoleteLayoutParamsDetector.ISSUE,
+ windowsLocation, "", null));
+ assertTrue(configuration.isIgnored(largeContext, ObsoleteLayoutParamsDetector.ISSUE,
+ largeLocation, "", null));
+ }
+
+ public void testGlobbing() throws Exception {
+ File projectDir = getProjectDir(null,
+ "res/layout/onclick.xml=>res/layout/onclick.xml",
+ "res/layout/onclick.xml=>res/layout-xlarge/onclick.xml",
+ "res/layout/onclick.xml=>res/layout-xlarge/activation.xml"
+ );
+ LintClient client = new TestLintClient();
+ Project project = Project.create(client, projectDir, projectDir);
+ LintDriver driver = new LintDriver(new BuiltinIssueRegistry(), client);
+ File plainFile = new File(projectDir,
+ "res" + File.separator + "layout" + File.separator + "onclick.xml");
+ assertTrue(plainFile.exists());
+ File largeFile = new File(projectDir,
+ "res" + File.separator + "layout-xlarge" + File.separator + "onclick.xml");
+ assertTrue(largeFile.exists());
+ File windowsFile = new File(projectDir,
+ "res" + File.separator + "layout-xlarge" + File.separator + "activation.xml");
+ assertTrue(windowsFile.exists());
+ Context plainContext = new Context(driver, project, project, plainFile);
+ Context largeContext = new Context(driver, project, project, largeFile);
+ Context windowsContext = new Context(driver, project, project, windowsFile);
+ Location plainLocation = Location.create(plainFile);
+ Location largeLocation = Location.create(largeFile);
+ Location windowsLocation = Location.create(windowsFile);
+
+ assertEquals(Severity.WARNING, ObsoleteLayoutParamsDetector.ISSUE.getDefaultSeverity());
+ assertEquals(Severity.ERROR, ApiDetector.UNSUPPORTED.getDefaultSeverity());
+
+ DefaultConfiguration configuration = getConfiguration(""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<lint>\n"
+ + " <issue id=\"ObsoleteLayoutParam\">\n"
+ + " <ignore path=\"**/layout-x*/onclick.xml\" />\n"
+ + " <ignore path=\"res/**/activation.xml\" />\n"
+ + " </issue>\n"
+ + "</lint>");
+
+ assertFalse(configuration.isIgnored(plainContext, ApiDetector.UNSUPPORTED,
+ plainLocation, "", null));
+ assertFalse(configuration.isIgnored(plainContext, ObsoleteLayoutParamsDetector.ISSUE,
+ plainLocation, "", null));
+ assertTrue(configuration.isIgnored(windowsContext, ObsoleteLayoutParamsDetector.ISSUE,
+ windowsLocation, "", null));
+ assertTrue(configuration.isIgnored(largeContext, ObsoleteLayoutParamsDetector.ISSUE,
+ largeLocation, "", null));
+ }
+
public void testWriteLintXml() throws Exception {
DefaultConfiguration configuration = getConfiguration(""
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
@@ -126,6 +223,7 @@
+ " <issue id=\"ObsoleteLayoutParam\">\n"
+ " <ignore path=\"res/layout-xlarge/activation.xml\" />\n"
+ " <ignore path=\"res\\layout-xlarge\\activation2.xml\" />\n"
+ + " <ignore regexp=\"res/.*/activation2.xml\" />\n"
+ " </issue>\n"
+ " <issue id=\"FloatMath\" severity=\"ignore\" />\n"
+ " <issue id=\"SdCardPath\" severity=\"ignore\" />"
@@ -142,6 +240,7 @@
+ " <issue id=\"ObsoleteLayoutParam\">\n"
+ " <ignore path=\"res/layout-xlarge/activation.xml\" />\n"
+ " <ignore path=\"res/layout-xlarge/activation2.xml\" />\n"
+ + " <ignore regexp=\"res/.*/activation2.xml\" />\n"
+ " </issue>\n"
+ " <issue id=\"SdCardPath\" severity=\"ignore\" />\n"
+ " <issue id=\"Typos\" severity=\"error\">\n"
@@ -163,4 +262,37 @@
fail("Not used from this unit test");
return null;
}
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testGlobToRegexp() {
+ assertEquals("^foo$", globToRegexp("foo"));
+ assertEquals("^foo/bar$", globToRegexp("foo/bar"));
+ assertEquals("^\\Qfoo\\bar\\E$", globToRegexp("foo\\bar"));
+ assertEquals("^f.?oo$", globToRegexp("f?oo"));
+ assertEquals("^fo.*?o$", globToRegexp("fo*o"));
+ assertEquals("^fo.*?o.*?$", globToRegexp("fo*o*"));
+ assertEquals("^fo.*?o$", globToRegexp("fo**o"));
+
+ assertEquals("^\\Qfoo(|)bar\\E$", globToRegexp("foo(|)bar"));
+ assertEquals("^\\Qf(o\\E.*?\\Q)b\\E.*?\\Q(\\E$", globToRegexp("f(o*)b**("));
+
+ assertTrue(Pattern.compile(globToRegexp("foo")).matcher("foo").matches());
+ assertFalse(Pattern.compile(globToRegexp("foo")).matcher("afoo").matches());
+ assertFalse(Pattern.compile(globToRegexp("foo")).matcher("fooa").matches());
+ assertTrue(Pattern.compile(globToRegexp("foo/bar")).matcher("foo/bar").matches());
+ assertFalse(Pattern.compile(globToRegexp("foo/bar")).matcher("foo/barf").matches());
+ assertFalse(Pattern.compile(globToRegexp("foo/bar")).matcher("foo/baz").matches());
+ assertTrue(Pattern.compile(globToRegexp("foo\\bar")).matcher("foo\\bar").matches());
+ assertTrue(Pattern.compile(globToRegexp("f?oo")).matcher("fboo").matches());
+ assertFalse(Pattern.compile(globToRegexp("f?oo")).matcher("fbaoo").matches());
+ assertTrue(Pattern.compile(globToRegexp("fo*o")).matcher("foo").matches());
+ assertTrue(Pattern.compile(globToRegexp("fo*o")).matcher("fooooo").matches());
+ assertTrue(Pattern.compile(globToRegexp("fo*o*")).matcher("fo?oa").matches());
+ assertTrue(Pattern.compile(globToRegexp("fo**o")).matcher("foo").matches());
+ assertTrue(Pattern.compile(globToRegexp("fo**o")).matcher("foooooo").matches());
+ assertTrue(Pattern.compile(globToRegexp("fo**o")).matcher("fo/abc/o").matches());
+ assertFalse(Pattern.compile(globToRegexp("fo**o")).matcher("fo/abc/oa").matches());
+ assertTrue(Pattern.compile(globToRegexp("f(o*)b**(")).matcher("f(o)b(").matches());
+ assertTrue(Pattern.compile(globToRegexp("f(o*)b**(")).matcher("f(oaa)b/c/d(").matches());
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/client/api/LintDriverTest.java b/lint/cli/src/test/java/com/android/tools/lint/client/api/LintDriverTest.java
index f4dba93..d75f194 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/client/api/LintDriverTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/client/api/LintDriverTest.java
@@ -49,4 +49,11 @@
Collections.sort(list3);
assertEquals(expected, list3);
}
+
+ public void testClassEntryCompareContract() throws Exception {
+ ClassEntry c0 = new ClassEntry(new File("abcde"), null, null, null);
+ ClassEntry c1 = new ClassEntry(new File("abcde"), null, null, null);
+ assertTrue(c0.compareTo(c1) <= 0);
+ assertTrue(c1.compareTo(c0) <= 0);
+ }
}
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 c919321..b007993 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
@@ -147,6 +147,33 @@
}
}
+ public void testDependsOn1() throws Exception {
+ File dir = getProjectDir("MyProject",
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java",
+ "bytecode/classes.jar=>libs/android-support-v4.jar"
+ );
+ TestClient client = new TestClient();
+ TestProject project1 = new TestProject(client, dir);
+ client.registerProject(dir, project1);
+ assertNull(project1.dependsOn("unknown:library"));
+ assertTrue(project1.dependsOn("com.android.support:support-v4"));
+ }
+
+ public void testDependsOn2() throws Exception {
+ File dir = getProjectDir("MyProject",
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java",
+ "bytecode/classes.jar=>libs/support-v4-13.0.0-f5279ca6f213451a9dfb870f714ce6e6.jar"
+ );
+ TestClient client = new TestClient();
+ TestProject project1 = new TestProject(client, dir);
+ client.registerProject(dir, project1);
+ assertNull(project1.dependsOn("unknown:library"));
+ assertTrue(project1.dependsOn("com.android.support:support-v4"));
+ }
@Override
protected Detector getDetector() {
return new UnusedResourceDetector();
diff --git a/lint/cli/src/test/java/com/android/tools/lint/detector/api/ImplementationTest.java b/lint/cli/src/test/java/com/android/tools/lint/detector/api/ImplementationTest.java
new file mode 100644
index 0000000..f29261f
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/detector/api/ImplementationTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.detector.api;
+
+import static com.android.tools.lint.detector.api.Scope.ALL;
+import static com.android.tools.lint.detector.api.Scope.ALL_RESOURCES_SCOPE;
+import static com.android.tools.lint.detector.api.Scope.ALL_RESOURCE_FILES;
+import static com.android.tools.lint.detector.api.Scope.CLASS_FILE;
+import static com.android.tools.lint.detector.api.Scope.CLASS_FILE_SCOPE;
+import static com.android.tools.lint.detector.api.Scope.JAVA_FILE;
+import static com.android.tools.lint.detector.api.Scope.JAVA_FILE_SCOPE;
+import static com.android.tools.lint.detector.api.Scope.MANIFEST_SCOPE;
+import static com.android.tools.lint.detector.api.Scope.RESOURCE_FILE;
+import static com.android.tools.lint.detector.api.Scope.RESOURCE_FILE_SCOPE;
+
+import com.android.tools.lint.checks.ApiDetector;
+import com.android.tools.lint.checks.DetectMissingPrefix;
+import com.android.tools.lint.checks.DuplicateResourceDetector;
+import com.android.tools.lint.checks.RtlDetector;
+
+import junit.framework.TestCase;
+
+import java.util.EnumSet;
+
+public class ImplementationTest extends TestCase {
+ @SuppressWarnings("unchecked")
+ public void testIsAdequate() throws Exception {
+ Implementation implementation = new Implementation(Detector.class, ALL_RESOURCES_SCOPE);
+ assertTrue(implementation.isAdequate(ALL_RESOURCES_SCOPE));
+ assertTrue(implementation.isAdequate(ALL));
+ assertTrue(implementation.isAdequate(EnumSet.of(ALL_RESOURCE_FILES)));
+ assertFalse(implementation.isAdequate(JAVA_FILE_SCOPE));
+ assertFalse(implementation.isAdequate(RESOURCE_FILE_SCOPE));
+ assertTrue(implementation.isAdequate(EnumSet.of(ALL_RESOURCE_FILES, JAVA_FILE)));
+
+ implementation = new Implementation(Detector.class, ALL_RESOURCES_SCOPE,
+ RESOURCE_FILE_SCOPE);
+ assertTrue(implementation.isAdequate(ALL_RESOURCES_SCOPE));
+ assertTrue(implementation.isAdequate(EnumSet.of(ALL_RESOURCE_FILES)));
+ assertFalse(implementation.isAdequate(JAVA_FILE_SCOPE));
+ assertTrue(implementation.isAdequate(EnumSet.of(ALL_RESOURCE_FILES, JAVA_FILE)));
+ assertTrue(implementation.isAdequate(RESOURCE_FILE_SCOPE));
+
+ implementation = new Implementation(Detector.class, EnumSet.of(RESOURCE_FILE, JAVA_FILE));
+ assertTrue(implementation.isAdequate(EnumSet.of(RESOURCE_FILE, JAVA_FILE)));
+ assertTrue(implementation.isAdequate(EnumSet.of(RESOURCE_FILE, JAVA_FILE, CLASS_FILE)));
+ assertFalse(implementation.isAdequate(ALL_RESOURCES_SCOPE));
+ assertFalse(implementation.isAdequate(JAVA_FILE_SCOPE));
+ assertFalse(implementation.isAdequate(RESOURCE_FILE_SCOPE));
+ assertFalse(implementation.isAdequate(EnumSet.of(ALL_RESOURCE_FILES, JAVA_FILE)));
+ assertFalse(implementation.isAdequate(EnumSet.of(RESOURCE_FILE, CLASS_FILE)));
+ assertTrue(implementation.isAdequate(ALL));
+
+ implementation = new Implementation(Detector.class, EnumSet.of(RESOURCE_FILE, JAVA_FILE),
+ RESOURCE_FILE_SCOPE, JAVA_FILE_SCOPE);
+ assertTrue(implementation.isAdequate(JAVA_FILE_SCOPE));
+ assertTrue(implementation.isAdequate(RESOURCE_FILE_SCOPE));
+ assertTrue(implementation.isAdequate(ALL));
+
+ assertFalse(ApiDetector.UNSUPPORTED.getImplementation().isAdequate(JAVA_FILE_SCOPE));
+ assertTrue(ApiDetector.UNSUPPORTED.getImplementation().isAdequate(CLASS_FILE_SCOPE));
+ assertTrue(ApiDetector.UNSUPPORTED.getImplementation().isAdequate(RESOURCE_FILE_SCOPE));
+ assertTrue(ApiDetector.UNSUPPORTED.getImplementation().isAdequate(MANIFEST_SCOPE));
+ assertTrue(DetectMissingPrefix.MISSING_NAMESPACE.getImplementation().isAdequate(
+ RESOURCE_FILE_SCOPE));
+ assertTrue(DetectMissingPrefix.MISSING_NAMESPACE.getImplementation().isAdequate(
+ MANIFEST_SCOPE));
+ assertFalse(DetectMissingPrefix.MISSING_NAMESPACE.getImplementation().isAdequate(
+ JAVA_FILE_SCOPE));
+ assertTrue(RtlDetector.COMPAT.getImplementation().isAdequate(MANIFEST_SCOPE));
+ assertTrue(DuplicateResourceDetector.ISSUE.getImplementation().isAdequate(
+ RESOURCE_FILE_SCOPE));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/detector/api/IssueTest.java b/lint/cli/src/test/java/com/android/tools/lint/detector/api/IssueTest.java
index c8aaf12..4bf0b99 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/detector/api/IssueTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/detector/api/IssueTest.java
@@ -99,7 +99,7 @@
public void testConvertMarkup2() throws Exception {
// http at the end:
- // Explanation from ManifestOrderDetector#TARGET_NEWER
+ // Explanation from ManifestDetector#TARGET_NEWER
String explanation =
"When your application runs on a version of Android that is more recent than your " +
"targetSdkVersion specifies that it has been tested with, various compatibility " +
@@ -168,7 +168,7 @@
public void testConvertMarkup5() throws Exception {
// monospace and bold test
- // From ManifestOrderDetector#MULTIPLE_USES_SDK
+ // From ManifestDetector#MULTIPLE_USES_SDK
String explanation =
"The `<uses-sdk>` element should appear just once; the tools will *not* merge the " +
"contents of all the elements so if you split up the atttributes across multiple " +
diff --git a/lint/cli/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java b/lint/cli/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
index c88aa1a..f7f2cef 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
@@ -16,25 +16,39 @@
package com.android.tools.lint.detector.api;
+import static com.android.tools.lint.detector.api.LintUtils.computeResourceName;
+import static com.android.tools.lint.detector.api.LintUtils.convertVersion;
import static com.android.tools.lint.detector.api.LintUtils.getLocaleAndRegion;
import static com.android.tools.lint.detector.api.LintUtils.isImported;
import static com.android.tools.lint.detector.api.LintUtils.splitPath;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.ProductFlavor;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.tools.lint.EcjParser;
import com.android.tools.lint.LintCliClient;
-import com.android.tools.lint.LombokParser;
import com.android.tools.lint.checks.BuiltinIssueRegistry;
-import com.android.tools.lint.client.api.IJavaParser;
+import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.client.api.LintDriver;
import com.google.common.collect.Iterables;
+import junit.framework.TestCase;
+
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
-import junit.framework.TestCase;
import lombok.ast.Node;
@SuppressWarnings("javadoc")
@@ -207,6 +221,7 @@
private static void checkEncoding(String encoding, boolean writeBom, String lineEnding)
throws Exception {
+ @SuppressWarnings("StringBufferReplaceableByString")
StringBuilder sb = new StringBuilder();
// Norwegian extra vowel characters such as "latin small letter a with ring above"
@@ -220,7 +235,7 @@
OutputStreamWriter writer = new OutputStreamWriter(stream, encoding);
if (writeBom) {
- String normalized = encoding.toLowerCase().replace("-", "_");
+ String normalized = encoding.toLowerCase(Locale.US).replace("-", "_");
if (normalized.equals("utf_8")) {
stream.write(0xef);
stream.write(0xbb);
@@ -332,21 +347,138 @@
"android.app.Activity"));
}
- private Node getCompilationUnit(String javaSource) {
- IJavaParser parser = new LombokParser();
- TestContext context = new TestContext(javaSource, new File("test"));
+ public void testComputeResourceName() {
+ assertEquals("", computeResourceName("", ""));
+ assertEquals("foo", computeResourceName("", "foo"));
+ assertEquals("foo", computeResourceName("foo", ""));
+ assertEquals("prefix_name", computeResourceName("prefix_", "name"));
+ assertEquals("prefixName", computeResourceName("prefix", "name"));
+ }
+
+ public static Node getCompilationUnit(String javaSource) {
+ return getCompilationUnit(javaSource, new File("test"));
+ }
+
+ public static Node getCompilationUnit(final String javaSource, final File relativePath) {
+ File dir = new File("projectDir");
+ final File fullPath = new File(dir, relativePath.getPath());
+ LintCliClient client = new LintCliClient() {
+ @NonNull
+ @Override
+ public String readFile(@NonNull File file) {
+ if (file.getPath().equals(fullPath.getPath())) {
+ return javaSource;
+ }
+ return super.readFile(file);
+ }
+
+ @Nullable
+ @Override
+ public IAndroidTarget getCompileTarget(@NonNull Project project) {
+ IAndroidTarget[] targets = getTargets();
+ for (int i = targets.length - 1; i >= 0; i--) {
+ IAndroidTarget target = targets[i];
+ if (target.isPlatform()) {
+ return target;
+ }
+ }
+
+ return super.getCompileTarget(project);
+ }
+ };
+ Project project = client.getProject(dir, dir);
+
+ LintDriver driver = new LintDriver(new BuiltinIssueRegistry(),
+ new LintCliClient());
+ driver.setScope(Scope.JAVA_FILE_SCOPE);
+ TestContext context = new TestContext(driver, client, project, javaSource, fullPath);
+ JavaParser parser = new EcjParser(client, project);
+ parser.prepareJavaParse(Collections.<JavaContext>singletonList(context));
Node compilationUnit = parser.parseJava(context);
assertNotNull(javaSource, compilationUnit);
return compilationUnit;
}
- private class TestContext extends JavaContext {
+ public void testConvertVersion() {
+ assertEquals(new AndroidVersion(5, null), convertVersion(new DefaultApiVersion(5, null),
+ null));
+ assertEquals(new AndroidVersion(19, null), convertVersion(new DefaultApiVersion(19, null),
+ null));
+ //noinspection SpellCheckingInspection
+ assertEquals(new AndroidVersion(18, "KITKAT"), // a preview platform API level is not final
+ convertVersion(new DefaultApiVersion(0, "KITKAT"),
+ null));
+ }
+
+ public void testIsModelOlderThan() throws Exception {
+ AndroidProject project = createNiceMock(AndroidProject.class);
+ expect(project.getModelVersion()).andReturn("0.10.4").anyTimes();
+ replay(project);
+
+ assertTrue(LintUtils.isModelOlderThan(project, 0, 10, 5));
+ assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 0));
+ assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 4));
+ assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
+
+ project = createNiceMock(AndroidProject.class);
+ expect(project.getModelVersion()).andReturn("0.11.0").anyTimes();
+ replay(project);
+
+ assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
+ assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
+ assertFalse(LintUtils.isModelOlderThan(project, 0, 10, 4));
+
+ project = createNiceMock(AndroidProject.class);
+ expect(project.getModelVersion()).andReturn("0.11.5").anyTimes();
+ replay(project);
+
+ assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
+ assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
+
+ project = createNiceMock(AndroidProject.class);
+ expect(project.getModelVersion()).andReturn("1.0.0").anyTimes();
+ replay(project);
+
+ assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 1));
+ assertFalse(LintUtils.isModelOlderThan(project, 1, 0, 0));
+ assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
+ }
+
+ private static final class DefaultApiVersion implements ApiVersion {
+ private final int mApiLevel;
+ private final String mCodename;
+
+ public DefaultApiVersion(int apiLevel, @Nullable String codename) {
+ mApiLevel = apiLevel;
+ mCodename = codename;
+ }
+
+ @Override
+ public int getApiLevel() {
+ return mApiLevel;
+ }
+
+ @Nullable
+ @Override
+ public String getCodename() {
+ return mCodename;
+ }
+
+ @NonNull
+ @Override
+ public String getApiString() {
+ fail("Not needed in this test");
+ return "<invalid>";
+ }
+ }
+
+ private static class TestContext extends JavaContext {
private final String mJavaSource;
- public TestContext(String javaSource, File file) {
- super(new LintDriver(new BuiltinIssueRegistry(),
- new LintCliClient()), new LintCliClient().getProject(new File("dummy"),
- new File("dummy")),
- null, file);
+ public TestContext(LintDriver driver, LintCliClient client, Project project,
+ String javaSource, File file) {
+ //noinspection ConstantConditions
+ super(driver, project,
+ null, file, client.getJavaParser(null));
mJavaSource = javaSource;
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/detector/api/SeverityTest.java b/lint/cli/src/test/java/com/android/tools/lint/detector/api/SeverityTest.java
new file mode 100644
index 0000000..5baa65e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/detector/api/SeverityTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.detector.api;
+
+import junit.framework.TestCase;
+
+public class SeverityTest extends TestCase {
+ public void testGetName() {
+ assertEquals("ERROR", Severity.ERROR.getName());
+ assertEquals("WARNING", Severity.WARNING.getName());
+ }
+
+ public void testGetDescription() {
+ assertEquals("Error", Severity.ERROR.getDescription());
+ assertEquals("Warning", Severity.WARNING.getDescription());
+ }
+
+ public void testFromString() {
+ assertSame(Severity.ERROR, Severity.fromName("ERROR"));
+ assertSame(Severity.ERROR, Severity.fromName("error"));
+ assertSame(Severity.ERROR, Severity.fromName("Error"));
+ assertSame(Severity.WARNING, Severity.fromName("WARNING"));
+ assertSame(Severity.WARNING, Severity.fromName("warning"));
+ assertSame(Severity.FATAL, Severity.fromName("FATAL"));
+ assertSame(Severity.INFORMATIONAL, Severity.fromName("Informational"));
+ assertSame(Severity.IGNORE, Severity.fromName("ignore"));
+ assertSame(Severity.IGNORE, Severity.fromName("IGNORE"));
+ }
+
+ public void testCompare() {
+ assertTrue(Severity.IGNORE.compareTo(Severity.ERROR) > 0);
+ assertTrue(Severity.WARNING.compareTo(Severity.ERROR) > 0);
+ assertTrue(Severity.ERROR.compareTo(Severity.ERROR) == 0);
+ assertTrue(Severity.FATAL.compareTo(Severity.ERROR) < 0);
+ assertTrue(Severity.WARNING.compareTo(Severity.ERROR) > 0);
+ }
+}
diff --git a/lint/libs/lint-api/build.gradle b/lint/libs/lint-api/build.gradle
index 1d75042..9499097 100644
--- a/lint/libs/lint-api/build.gradle
+++ b/lint/libs/lint-api/build.gradle
@@ -1,14 +1,16 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
+
group = 'com.android.tools.lint'
archivesBaseName = 'lint-api'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':sdk-common')
- compile project(':builder-model')
+ compile project(':base:sdk-common')
+ compile project(':base:builder-model')
- compile 'com.android.tools.external.lombok:lombok-ast:0.2.1'
+ compile 'com.android.tools.external.lombok:lombok-ast:0.2.2'
compile 'org.ow2.asm:asm:4.0'
compile 'org.ow2.asm:asm-tree:4.0'
}
@@ -18,14 +20,9 @@
test.resources.srcDir 'src/test/java'
}
-jar {
- from 'NOTICE'
-}
-
project.ext.pomName = 'Android Tools Lint API'
project.ext.pomDesc = 'API to build lint checks'
-apply from: '../../../baseVersion.gradle'
-apply from: '../../../publish.gradle'
-apply from: '../../../javadoc.gradle'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java
index b03b6a0..5660b85 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java
@@ -16,6 +16,9 @@
package com.android.tools.lint.client.api;
+import static com.android.SdkConstants.CURRENT_PLATFORM;
+import static com.android.SdkConstants.PLATFORM_WINDOWS;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.detector.api.Context;
@@ -23,23 +26,20 @@
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.android.utils.XmlUtils;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
-import com.google.common.io.Closeables;
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 org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;
-import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
@@ -51,9 +51,9 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
/**
* Default implementation of a {@link Configuration} which reads and writes
@@ -65,7 +65,8 @@
@Beta
public class DefaultConfiguration extends Configuration {
private final LintClient mClient;
- private static final String CONFIG_FILE_NAME = "lint.xml"; //$NON-NLS-1$
+ /** Default name of the configuration file */
+ public static final String CONFIG_FILE_NAME = "lint.xml"; //$NON-NLS-1$
// Lint XML File
@NonNull
@@ -77,6 +78,8 @@
@NonNull
private static final String ATTR_PATH = "path"; //$NON-NLS-1$
@NonNull
+ private static final String ATTR_REGEXP = "regexp"; //$NON-NLS-1$
+ @NonNull
private static final String TAG_IGNORE = "ignore"; //$NON-NLS-1$
@NonNull
private static final String VALUE_ALL = "all"; //$NON-NLS-1$
@@ -89,6 +92,10 @@
/** Map from id to list of project-relative paths for suppressed warnings */
private Map<String, List<String>> mSuppressed;
+ /** Map from id to regular expressions. */
+ @Nullable
+ private Map<String, List<Pattern>> mRegexps;
+
/**
* Map from id to custom {@link Severity} override
*/
@@ -170,11 +177,37 @@
}
}
- if (mParent != null) {
- return mParent.isIgnored(context, issue, location, message, data);
+ if (mRegexps != null) {
+ List<Pattern> regexps = mRegexps.get(id);
+ if (regexps == null) {
+ regexps = mRegexps.get(VALUE_ALL);
+ }
+ if (regexps != null && location != null) {
+ File file = location.getFile();
+ String relativePath = context.getProject().getRelativePath(file);
+ boolean checkUnixPath = false;
+ for (Pattern regexp : regexps) {
+ Matcher matcher = regexp.matcher(relativePath);
+ if (matcher.find()) {
+ return true;
+ } else if (regexp.pattern().indexOf('/') != -1) {
+ checkUnixPath = true;
+ }
+ }
+
+ if (checkUnixPath && CURRENT_PLATFORM == PLATFORM_WINDOWS) {
+ relativePath = relativePath.replace('\\', '/');
+ for (Pattern regexp : regexps) {
+ Matcher matcher = regexp.matcher(relativePath);
+ if (matcher.find()) {
+ return true;
+ }
+ }
+ }
+ }
}
- return false;
+ return mParent != null && mParent.isIgnored(context, issue, location, message, data);
}
@NonNull
@@ -237,16 +270,8 @@
return;
}
- @SuppressWarnings("resource") // Eclipse doesn't know about Closeables.closeQuietly
- BufferedInputStream input = null;
try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- input = new BufferedInputStream(new FileInputStream(mConfigFile));
- InputSource source = new InputSource(input);
- factory.setNamespaceAware(false);
- factory.setValidating(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document document = builder.parse(source);
+ Document document = XmlUtils.parseUtfXmlFile(mConfigFile, false);
NodeList issues = document.getElementsByTagName(TAG_ISSUE);
Splitter splitter = Splitter.on(',').trimResults().omitEmptyStrings();
for (int i = 0, count = issues.getLength(); i < count; i++) {
@@ -289,8 +314,13 @@
Element ignore = (Element) child;
String path = ignore.getAttribute(ATTR_PATH);
if (path.isEmpty()) {
- formatError("Missing required %1$s attribute under %2$s",
- ATTR_PATH, idList);
+ String regexp = ignore.getAttribute(ATTR_REGEXP);
+ if (regexp.isEmpty()) {
+ formatError("Missing required attribute %1$s or %2$s under %3$s",
+ ATTR_PATH, ATTR_REGEXP, idList);
+ } else {
+ addRegexp(idList, ids, n, regexp, false);
+ }
} else {
// Normalize path format to File.separator. Also
// handle the file format containing / or \.
@@ -300,13 +330,18 @@
path = path.replace('/', File.separatorChar);
}
- for (String id : ids) {
- List<String> paths = mSuppressed.get(id);
- if (paths == null) {
- paths = new ArrayList<String>(n / 2 + 1);
- mSuppressed.put(id, paths);
+ if (path.indexOf('*') != -1) {
+ String regexp = globToRegexp(path);
+ addRegexp(idList, ids, n, regexp, false);
+ } else {
+ for (String id : ids) {
+ List<String> paths = mSuppressed.get(id);
+ if (paths == null) {
+ paths = new ArrayList<String>(n / 2 + 1);
+ mSuppressed.put(id, paths);
+ }
+ paths.add(path);
}
- paths.add(path);
}
}
}
@@ -317,8 +352,74 @@
formatError(e.getMessage());
} catch (Exception e) {
mClient.log(e, null);
- } finally {
- Closeables.closeQuietly(input);
+ }
+ }
+
+ @VisibleForTesting
+ static String globToRegexp(String glob) {
+ StringBuilder sb = new StringBuilder(glob.length() * 2);
+ int begin = 0;
+ sb.append('^');
+ for (int i = 0, n = glob.length(); i < n; i++) {
+ char c = glob.charAt(i);
+ if (c == '*') {
+ begin = appendQuoted(sb, glob, begin, i) + 1;
+ if (i < n - 1 && glob.charAt(i + 1) == '*') {
+ i++;
+ begin++;
+ }
+ sb.append(".*?");
+ } else if (c == '?') {
+ begin = appendQuoted(sb, glob, begin, i) + 1;
+ sb.append(".?");
+ }
+ }
+ appendQuoted(sb, glob, begin, glob.length());
+ sb.append('$');
+ return sb.toString();
+ }
+
+ private static int appendQuoted(StringBuilder sb, String s, int from, int to) {
+ if (to > from) {
+ boolean isSimple = true;
+ for (int i = from; i < to; i++) {
+ char c = s.charAt(i);
+ if (!Character.isLetterOrDigit(c) && c != '/' && c != ' ') {
+ isSimple = false;
+ break;
+ }
+ }
+ if (isSimple) {
+ for (int i = from; i < to; i++) {
+ sb.append(s.charAt(i));
+ }
+ return to;
+ }
+ sb.append(Pattern.quote(s.substring(from, to)));
+ }
+ return to;
+ }
+
+ private void addRegexp(@NonNull String idList, @NonNull Iterable<String> ids, int n,
+ @NonNull String regexp, boolean silent) {
+ try {
+ if (mRegexps == null) {
+ mRegexps = new HashMap<String, List<Pattern>>();
+ }
+ Pattern pattern = Pattern.compile(regexp);
+ for (String id : ids) {
+ List<Pattern> paths = mRegexps.get(id);
+ if (paths == null) {
+ paths = new ArrayList<Pattern>(n / 2 + 1);
+ mRegexps.put(id, paths);
+ }
+ paths.add(pattern);
+ }
+ } catch (PatternSyntaxException e) {
+ if (!silent) {
+ formatError("Invalid pattern %1$s under %2$s: %3$s",
+ regexp, idList, e.getDescription());
+ }
}
}
@@ -359,17 +460,29 @@
severity.name().toLowerCase(Locale.US));
}
+ List<Pattern> regexps = mRegexps != null ? mRegexps.get(id) : null;
List<String> paths = mSuppressed.get(id);
- if (paths != null && !paths.isEmpty()) {
+ if (paths != null && !paths.isEmpty()
+ || regexps != null && !regexps.isEmpty()) {
writer.write('>');
writer.write('\n');
// The paths are already kept in sorted order when they are modified
// by ignore(...)
- for (String path : paths) {
- writer.write(" <"); //$NON-NLS-1$
- writer.write(TAG_IGNORE);
- writeAttribute(writer, ATTR_PATH, path.replace('\\', '/'));
- writer.write(" />\n"); //$NON-NLS-1$
+ if (paths != null) {
+ for (String path : paths) {
+ writer.write(" <"); //$NON-NLS-1$
+ writer.write(TAG_IGNORE);
+ writeAttribute(writer, ATTR_PATH, path.replace('\\', '/'));
+ writer.write(" />\n"); //$NON-NLS-1$
+ }
+ }
+ if (regexps != null) {
+ for (Pattern regexp : regexps) {
+ writer.write(" <"); //$NON-NLS-1$
+ writer.write(TAG_IGNORE);
+ writeAttribute(writer, ATTR_REGEXP, regexp.pattern());
+ writer.write(" />\n"); //$NON-NLS-1$
+ }
}
writer.write(" </"); //$NON-NLS-1$
writer.write(TAG_ISSUE);
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IDomParser.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IDomParser.java
deleted file mode 100644
index 1a70fac..0000000
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IDomParser.java
+++ /dev/null
@@ -1,93 +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.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.annotations.Beta;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-/**
- * A wrapper for an XML parser. This allows tools integrating lint to map directly
- * to builtin services, such as already-parsed data structures in XML editors.
- * <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>
- */
-@Beta
-public interface IDomParser {
- /**
- * Parse the file pointed to by the given context and return as a Document
- *
- * @param context the context pointing to the file to be parsed, typically
- * via {@link Context#getContents()} but the file handle (
- * {@link Context#file} can also be used to map to an existing
- * editor buffer in the surrounding tool, etc)
- * @return the parsed DOM document, or null if parsing fails
- */
- @Nullable
- Document parseXml(@NonNull XmlContext context);
-
- /**
- * Returns a {@link Location} for the given DOM node
- *
- * @param context information about the file being parsed
- * @param node the node to create a location for
- * @return a location for the given node
- */
- @NonNull
- Location getLocation(@NonNull XmlContext context, @NonNull Node node);
-
- /**
- * Returns a {@link Location} for the given DOM node. Like
- * {@link #getLocation(XmlContext, Node)}, but allows a position range that
- * is a subset of the node range.
- *
- * @param context information about the file being parsed
- * @param node the node to create a location for
- * @param start the starting position within the node, inclusive
- * @param end the ending position within the node, exclusive
- * @return a location for the given node
- */
- @NonNull
- Location getLocation(@NonNull XmlContext context, @NonNull Node node, int start, int end);
-
- /**
- * Creates a light-weight handle to a location for the given node. It can be
- * turned into a full fledged location by
- * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
- *
- * @param context the context providing the node
- * @param node the node (element or attribute) to create a location handle
- * for
- * @return a location handle
- */
- @NonNull
- Location.Handle createLocationHandle(@NonNull XmlContext context, @NonNull Node node);
-
- /**
- * Dispose any data structures held for the given context.
- * @param context information about the file previously parsed
- * @param document the document that was parsed and is now being disposed
- */
- void dispose(@NonNull XmlContext context, @NonNull Document document);
-}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IJavaParser.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IJavaParser.java
deleted file mode 100644
index b596fc0..0000000
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IJavaParser.java
+++ /dev/null
@@ -1,97 +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.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-
-import lombok.ast.Node;
-import lombok.ast.TypeReference;
-
-/**
- * A wrapper for a Java parser. This allows tools integrating lint to map directly
- * to builtin services, such as already-parsed data structures in Java editors.
- * <p/>
- * <b>NOTE: This is not a or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-public interface IJavaParser {
- /**
- * Parse the file pointed to by the given context.
- *
- * @param context the context pointing to the file to be parsed, typically
- * via {@link Context#getContents()} but the file handle (
- * {@link Context#file} can also be used to map to an existing
- * editor buffer in the surrounding tool, etc)
- * @return the compilation unit node for the file
- */
- @Nullable
- Node parseJava(@NonNull JavaContext context);
-
- /**
- * Returns a {@link Location} for the given node
- *
- * @param context information about the file being parsed
- * @param node the node to create a location for
- * @return a location for the given node
- */
- @NonNull
- Location getLocation(@NonNull JavaContext context, @NonNull Node node);
-
- /**
- * Creates a light-weight handle to a location for the given node. It can be
- * turned into a full fledged location by
- * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
- *
- * @param context the context providing the node
- * @param node the node (element or attribute) to create a location handle
- * for
- * @return a location handle
- */
- @NonNull
- Location.Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node);
-
- /**
- * Dispose any data structures held for the given context.
- * @param context information about the file previously parsed
- * @param compilationUnit the compilation unit being disposed
- */
- void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit);
-
- /**
- * Resolves the given expression node
- *
- * @param context information about the file being parsed
- * @param node the node to resolve
- * @return a node representing the resolved fully type, class, field or method
- */
- @Nullable
- Node resolve(@NonNull JavaContext context, @NonNull Node node);
-
- /**
- * Gets the type of the given node
- *
- * @param context information about the file being parsed
- * @param node the node to look up the type for
- * @return the type of the node, if known
- */
- @Nullable
- TypeReference getType(@NonNull JavaContext context, @NonNull Node node);
-}
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 9e94758..aed9c09 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
@@ -119,6 +119,17 @@
public abstract List<Issue> getIssues();
/**
+ * Get an approximate issue count for a given scope. This is just an optimization,
+ * so the number does not have to be accurate.
+ *
+ * @param scope the scope set
+ * @return an approximate ceiling of the number of issues expected for a given scope set
+ */
+ protected int getIssueCapacity(@NonNull EnumSet<Scope> scope) {
+ return 20;
+ }
+
+ /**
* Returns all available issues of a given scope (regardless of whether
* they are actually enabled for a given configuration etc)
*
@@ -126,24 +137,14 @@
* @return a list of issues
*/
@NonNull
- private List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) {
+ protected List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) {
List<Issue> list = sScopeIssues.get(scope);
if (list == null) {
List<Issue> issues = getIssues();
if (scope.equals(Scope.ALL)) {
list = issues;
} else {
- int initialSize = 12;
- if (scope.contains(Scope.RESOURCE_FILE)) {
- initialSize += 50;
- }
- if (scope.contains(Scope.JAVA_FILE)) {
- initialSize += 12;
- }
- if (scope.contains(Scope.CLASS_FILE)) {
- initialSize += 12;
- }
- list = new ArrayList<Issue>(initialSize);
+ list = new ArrayList<Issue>(getIssueCapacity(scope));
for (Issue issue : issues) {
// Determine if the scope matches
if (issue.getImplementation().isAdequate(scope)) {
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java
new file mode 100644
index 0000000..2d1d45b
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java
@@ -0,0 +1,313 @@
+/*
+ * 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.tools.lint.client.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Splitter;
+
+import java.util.List;
+
+import lombok.ast.Identifier;
+import lombok.ast.Node;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.TypeReference;
+import lombok.ast.TypeReferencePart;
+
+/**
+ * A wrapper for a Java parser. This allows tools integrating lint to map directly
+ * to builtin services, such as already-parsed data structures in Java editors.
+ * <p/>
+ * <b>NOTE: This is not public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+@Beta
+public abstract class JavaParser {
+ public static final String TYPE_OBJECT = "java.lang.Object"; //$NON-NLS-1$
+ public static final String TYPE_STRING = "java.lang.String"; //$NON-NLS-1$
+ public static final String TYPE_INT = "int"; //$NON-NLS-1$
+ public static final String TYPE_LONG = "long"; //$NON-NLS-1$
+ public static final String TYPE_CHAR = "char"; //$NON-NLS-1$
+ public static final String TYPE_FLOAT = "float"; //$NON-NLS-1$
+ public static final String TYPE_DOUBLE = "double"; //$NON-NLS-1$
+ public static final String TYPE_BOOLEAN = "boolean"; //$NON-NLS-1$
+ public static final String TYPE_SHORT = "short"; //$NON-NLS-1$
+ public static final String TYPE_BYTE = "byte"; //$NON-NLS-1$
+ public static final String TYPE_NULL = "null"; //$NON-NLS-1$
+
+ /**
+ * Prepare to parse the given contexts. This method will be called before
+ * a series of {@link #parseJava(JavaContext)} calls, which allows some
+ * parsers to do up front global computation in case they want to more
+ * efficiently process multiple files at the same time. This allows a single
+ * type-attribution pass for example, which is a lot more efficient than
+ * performing global type analysis over and over again for each individual
+ * file
+ *
+ * @param contexts a list of contexts to be parsed
+ */
+ public abstract void prepareJavaParse(@NonNull List<JavaContext> contexts);
+
+ /**
+ * Parse the file pointed to by the given context.
+ *
+ * @param context the context pointing to the file to be parsed, typically
+ * via {@link Context#getContents()} but the file handle (
+ * {@link Context#file} can also be used to map to an existing
+ * editor buffer in the surrounding tool, etc)
+ * @return the compilation unit node for the file
+ */
+ @Nullable
+ public abstract Node parseJava(@NonNull JavaContext context);
+
+ /**
+ * Returns a {@link Location} for the given node
+ *
+ * @param context information about the file being parsed
+ * @param node the node to create a location for
+ * @return a location for the given node
+ */
+ @NonNull
+ public abstract Location getLocation(@NonNull JavaContext context, @NonNull Node node);
+
+ /**
+ * Creates a light-weight handle to a location for the given node. It can be
+ * turned into a full fledged location by
+ * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
+ *
+ * @param context the context providing the node
+ * @param node the node (element or attribute) to create a location handle
+ * for
+ * @return a location handle
+ */
+ @NonNull
+ public abstract Location.Handle createLocationHandle(@NonNull JavaContext context,
+ @NonNull Node node);
+
+ /**
+ * Dispose any data structures held for the given context.
+ * @param context information about the file previously parsed
+ * @param compilationUnit the compilation unit being disposed
+ */
+ public void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit) {
+ }
+
+ /**
+ * Resolves the given expression node: computes the declaration for the given symbol
+ *
+ * @param context information about the file being parsed
+ * @param node the node to resolve
+ * @return a node representing the resolved fully type: class/interface/annotation,
+ * field, method or variable
+ */
+ @Nullable
+ public abstract ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node);
+
+ /**
+ * Gets the type of the given node
+ *
+ * @param context information about the file being parsed
+ * @param node the node to look up the type for
+ * @return the type of the node, if known
+ */
+ @Nullable
+ public abstract TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node);
+
+ /** A description of a type, such as a primitive int or the android.app.Activity class */
+ public abstract static class TypeDescriptor {
+ /**
+ * Returns the fully qualified name of the type, such as "int" or "android.app.Activity"
+ * */
+ @NonNull public abstract String getName();
+
+ /**
+ * Returns the full signature of the type, which is normally the same as {@link #getName()}
+ * but for arrays can include []'s, for generic methods can include generics parameters
+ * etc
+ */
+ @NonNull public abstract String getSignature();
+
+ public abstract boolean matchesName(@NonNull String name);
+
+ public abstract boolean matchesSignature(@NonNull String signature);
+
+ @NonNull
+ public TypeReference getNode() {
+ TypeReference typeReference = new TypeReference();
+ StrictListAccessor<TypeReferencePart, TypeReference> parts = typeReference.astParts();
+ for (String part : Splitter.on('.').split(getName())) {
+ Identifier identifier = Identifier.of(part);
+ parts.addToEnd(new TypeReferencePart().astIdentifier(identifier));
+ }
+
+ return typeReference;
+ }
+ }
+
+ /** Convenience implementation of {@link TypeDescriptor} */
+ public static class DefaultTypeDescriptor extends TypeDescriptor {
+ private String mName;
+
+ public DefaultTypeDescriptor(String name) {
+ mName = name;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ @Override
+ public String getSignature() {
+ return getName();
+ }
+
+ @Override
+ public boolean matchesName(@NonNull String name) {
+ return mName.equals(name);
+ }
+
+ @Override
+ public boolean matchesSignature(@NonNull String signature) {
+ return matchesName(signature);
+ }
+
+ @Override
+ public String toString() {
+ return getSignature();
+ }
+ }
+
+ /** A resolved declaration from an AST Node reference */
+ public abstract static class ResolvedNode {
+ @NonNull
+ public abstract String getName();
+
+ /** Returns the signature of the resolved node */
+ public abstract String getSignature();
+
+ public abstract int getModifiers();
+
+ @Override
+ public String toString() {
+ return getSignature();
+ }
+ }
+
+ /** A resolved class declaration */
+ public abstract static class ResolvedClass extends ResolvedNode {
+ /** Returns the fully qualified name of this class */
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /** Returns whether this class' fully qualified name matches the given name */
+ public abstract boolean matches(@NonNull String name);
+
+ @Nullable
+ public abstract ResolvedClass getSuperClass();
+
+ @Nullable
+ public abstract ResolvedClass getContainingClass();
+
+ public TypeDescriptor getType() {
+ return new DefaultTypeDescriptor(getName());
+ }
+
+ /**
+ * Determines whether this class extends the given name. If strict is true,
+ * it will not consider C extends C true.
+ *
+ * @param name the fully qualified class name
+ * @param strict if true, do not consider a class to be extending itself
+ * @return true if this class extends the given class
+ */
+ public abstract boolean isSubclassOf(@NonNull String name, boolean strict);
+
+ @NonNull
+ public abstract Iterable<ResolvedMethod> getConstructors();
+
+ @NonNull
+ public abstract Iterable<ResolvedMethod> getMethods(@NonNull String name);
+
+ @Nullable
+ public abstract ResolvedField getField(@NonNull String name);
+ }
+
+ /** A method or constructor declaration */
+ public abstract static class ResolvedMethod extends ResolvedNode {
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /** Returns whether this method name matches the given name */
+ public abstract boolean matches(@NonNull String name);
+
+ @NonNull
+ public abstract ResolvedClass getContainingClass();
+
+ public abstract int getArgumentCount();
+
+ @NonNull
+ public abstract TypeDescriptor getArgumentType(int index);
+
+ @Nullable
+ public abstract TypeDescriptor getReturnType();
+
+ public boolean isConstructor() {
+ return getReturnType() == null;
+ }
+ }
+
+ /** A field declaration */
+ public abstract static class ResolvedField extends ResolvedNode {
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /** Returns whether this field name matches the given name */
+ public abstract boolean matches(@NonNull String name);
+
+ @NonNull
+ public abstract TypeDescriptor getType();
+
+ @NonNull
+ public abstract ResolvedClass getContainingClass();
+
+ @Nullable
+ public abstract Object getValue();
+ }
+
+ /** A local variable or parameter declaration */
+ public abstract static class ResolvedVariable extends ResolvedNode {
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /** Returns whether this variable name matches the given name */
+ public abstract boolean matches(@NonNull String name);
+
+ @NonNull
+ public abstract TypeDescriptor getType();
+ }
+}
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 96a973d..fd3a96b 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
@@ -26,7 +26,6 @@
import com.android.tools.lint.detector.api.JavaContext;
import com.google.common.collect.Maps;
-import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -141,9 +140,9 @@
private final List<VisitingDetector> mFullTreeDetectors;
private final Map<Class<? extends Node>, List<VisitingDetector>> mNodeTypeDetectors =
new HashMap<Class<? extends Node>, List<VisitingDetector>>();
- private final IJavaParser mParser;
+ private final JavaParser mParser;
- JavaVisitor(@NonNull IJavaParser parser, @NonNull List<Detector> detectors) {
+ JavaVisitor(@NonNull JavaParser parser, @NonNull List<Detector> detectors) {
mParser = parser;
mAllDetectors = new ArrayList<VisitingDetector>(detectors.size());
mFullTreeDetectors = new ArrayList<VisitingDetector>(detectors.size());
@@ -189,9 +188,7 @@
}
}
- void visitFile(@NonNull JavaContext context, @NonNull File file) {
- context.parser = mParser;
-
+ void visitFile(@NonNull JavaContext context) {
Node compilationUnit = null;
try {
compilationUnit = mParser.parseJava(context);
@@ -201,7 +198,7 @@
// with details, location, etc.
return;
}
- context.compilationUnit = compilationUnit;
+ context.setCompilationUnit(compilationUnit);
for (VisitingDetector v : mAllDetectors) {
v.setContext(context);
@@ -233,6 +230,10 @@
}
}
+ public void prepare(@NonNull List<JavaContext> contexts) {
+ mParser.prepareJavaParse(contexts);
+ }
+
private static class VisitingDetector {
private AstVisitor mVisitor; // construct lazily, and clear out on context switch!
private JavaContext mContext;
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 307706e..3714be0 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,8 +16,6 @@
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;
@@ -29,10 +27,12 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
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.sdklib.repository.local.LocalSdk;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
@@ -40,9 +40,9 @@
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.android.utils.StdLogger;
-import com.android.utils.StdLogger.Level;
+import com.android.utils.XmlUtils;
import com.google.common.annotations.Beta;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
@@ -50,12 +50,12 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
import java.io.File;
import java.io.IOException;
-import java.io.StringReader;
+import java.net.HttpURLConnection;
import java.net.URL;
+import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -63,9 +63,6 @@
import java.util.Map;
import java.util.Set;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
/**
* Information about the tool embedding the lint analyzer. IDEs and other tools
* implementing lint support will extend this to integrate logging, displaying errors,
@@ -150,22 +147,26 @@
@Nullable Object... args);
/**
- * Returns a {@link IDomParser} to use to parse XML
+ * Returns a {@link XmlParser} to use to parse XML
*
- * @return a new {@link IDomParser}, or null if this client does not support
+ * @return a new {@link XmlParser}, or null if this client does not support
* XML analysis
*/
@Nullable
- public abstract IDomParser getDomParser();
+ public abstract XmlParser getXmlParser();
/**
- * Returns a {@link IJavaParser} to use to parse Java
+ * Returns a {@link JavaParser} to use to parse Java
*
- * @return a new {@link IJavaParser}, or null if this client does not
+ * @param project the project to parse, if known (this can be used to look up
+ * the class path for type attribution etc, and it can also be used
+ * to more efficiently process a set of files, for example to
+ * perform type attribution for multiple units in a single pass)
+ * @return a new {@link JavaParser}, or null if this client does not
* support Java analysis
*/
@Nullable
- public abstract IJavaParser getJavaParser();
+ public abstract JavaParser getJavaParser(@Nullable Project project);
/**
* Returns an optimal detector, if applicable. By default, just returns the
@@ -448,13 +449,8 @@
File classpathFile = new File(projectDir, ".classpath"); //$NON-NLS-1$
if (classpathFile.exists()) {
String classpathXml = readFile(classpathFile);
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- InputSource is = new InputSource(new StringReader(classpathXml));
- factory.setNamespaceAware(false);
- factory.setValidating(false);
try {
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document document = builder.parse(is);
+ Document document = XmlUtils.parseDocument(classpathXml, false);
NodeList tags = document.getElementsByTagName("classpathentry"); //$NON-NLS-1$
for (int i = 0, n = tags.getLength(); i < n; i++) {
Element element = (Element) tags.item(i);
@@ -653,7 +649,7 @@
return project.getDir().getName();
}
- private IAndroidTarget[] mTargets;
+ protected IAndroidTarget[] mTargets;
/**
* Returns all the {@link IAndroidTarget} versions installed in the user's SDK install
@@ -664,15 +660,9 @@
@NonNull
public IAndroidTarget[] getTargets() {
if (mTargets == null) {
- File sdkHome = getSdkHome();
- if (sdkHome != null) {
- StdLogger log = new StdLogger(Level.WARNING);
- SdkManager manager = SdkManager.createManager(sdkHome.getPath(), log);
- if (manager != null) {
- mTargets = manager.getTargets();
- } else {
- mTargets = new IAndroidTarget[0];
- }
+ LocalSdk localSdk = getSdk();
+ if (localSdk != null) {
+ mTargets = localSdk.getTargets();
} else {
mTargets = new IAndroidTarget[0];
}
@@ -681,13 +671,53 @@
return mTargets;
}
+ protected LocalSdk mSdk;
+
+ /**
+ * Returns the SDK installation (used to look up platforms etc)
+ *
+ * @return the SDK if known
+ */
+ @Nullable
+ public LocalSdk getSdk() {
+ if (mSdk == null) {
+ File sdkHome = getSdkHome();
+ if (sdkHome != null) {
+ mSdk = new LocalSdk(sdkHome);
+ }
+ }
+
+ return mSdk;
+ }
+
+ /**
+ * Returns the compile target to use for the given project
+ *
+ * @param project the project in question
+ *
+ * @return the compile target to use to build the given project
+ */
+ @Nullable
+ public IAndroidTarget getCompileTarget(@NonNull Project project) {
+ int buildSdk = project.getBuildSdk();
+ IAndroidTarget[] targets = getTargets();
+ for (int i = targets.length - 1; i >= 0; i--) {
+ IAndroidTarget target = targets[i];
+ if (target.isPlatform() && target.getVersion().getApiLevel() == buildSdk) {
+ return target;
+ }
+ }
+
+ return null;
+ }
+
/**
* Returns the highest known API level.
*
* @return the highest known API level
*/
public int getHighestKnownApiLevel() {
- int max = SdkVersionInfo.HIGHEST_KNOWN_API;
+ int max = SdkVersionInfo.HIGHEST_KNOWN_STABLE_API;
for (IAndroidTarget target : getTargets()) {
if (target.isPlatform()) {
@@ -820,6 +850,28 @@
}
/**
+ * Opens a URL connection.
+ *
+ * Clients such as IDEs can override this to for example consider the user's IDE proxy
+ * settings.
+ *
+ * @param url the URL to read
+ * @return a {@link java.net.URLConnection} or null
+ * @throws IOException if any kind of IO exception occurs
+ */
+ @Nullable
+ public URLConnection openConnection(@NonNull URL url) throws IOException {
+ return url.openConnection();
+ }
+
+ /** Closes a connection previously returned by {@link #openConnection(java.net.URL)} */
+ public void closeConnection(@NonNull URLConnection connection) throws IOException {
+ if (connection instanceof HttpURLConnection) {
+ ((HttpURLConnection)connection).disconnect();
+ }
+ }
+
+ /**
* 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.
@@ -831,4 +883,75 @@
public boolean isProjectDirectory(@NonNull File dir) {
return LintUtils.isManifestFolder(dir) || Project.isAospFrameworksProject(dir);
}
+
+ /**
+ * Returns whether lint should look for suppress comments. Tools that already do
+ * this on their own can return false here to avoid doing unnecessary work.
+ */
+ public boolean checkForSuppressComments() {
+ return true;
+ }
+
+ /**
+ * Adds in any custom lint rules and returns the result as a new issue registry,
+ * or the same one if no custom rules were found
+ *
+ * @param registry the main registry to add rules to
+ * @return a new registry containing the passed in rules plus any custom rules,
+ * or the original registry if no custom rules were found
+ */
+ public IssueRegistry addCustomLintRules(@NonNull IssueRegistry registry) {
+ List<File> jarFiles = findGlobalRuleJars();
+
+ if (!jarFiles.isEmpty()) {
+ List<IssueRegistry> registries = Lists.newArrayListWithExpectedSize(jarFiles.size());
+ registries.add(registry);
+ for (File jarFile : jarFiles) {
+ try {
+ registries.add(JarFileIssueRegistry.get(this, jarFile));
+ } catch (Throwable e) {
+ log(e, "Could not load custom rule jar file %1$s", jarFile);
+ }
+ }
+ if (registries.size() > 1) { // the first item is the passed in registry itself
+ return new CompositeIssueRegistry(registries);
+ }
+ }
+
+ return registry;
+ }
+
+ /**
+ * Returns true if this client supports project resource repository lookup via
+ * {@link #getProjectResources(Project,boolean)}
+ *
+ * @return true if the client can provide project resources
+ */
+ public boolean supportsProjectResources() {
+ return false;
+ }
+
+ /**
+ * Returns the project resources, if available
+ *
+ * @param includeDependencies if true, include merged view of all dependencies
+ * @return the project resources, or null if not available
+ */
+ @Nullable
+ public AbstractResourceRepository getProjectResources(Project project,
+ boolean includeDependencies) {
+ return null;
+ }
+
+ /**
+ * For a lint client which supports resource items (via {@link #supportsProjectResources()})
+ * return a handle for a resource item
+ *
+ * @param item the resource item to look up a location handle for
+ * @return a corresponding handle
+ */
+ @NonNull
+ public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
+ return new Location.ResourceItemHandle(item);
+ }
}
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 e0a2588..7aa3876 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
@@ -16,16 +16,12 @@
package com.android.tools.lint.client.api;
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
import static com.android.SdkConstants.ATTR_IGNORE;
import static com.android.SdkConstants.CLASS_CONSTRUCTOR;
import static com.android.SdkConstants.CONSTRUCTOR_NAME;
import static com.android.SdkConstants.DOT_CLASS;
import static com.android.SdkConstants.DOT_JAR;
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 static com.android.SdkConstants.SUPPRESS_ALL;
import static com.android.SdkConstants.SUPPRESS_LINT;
@@ -33,12 +29,14 @@
import static com.android.tools.lint.detector.api.LintUtils.isAnonymousClass;
import static org.objectweb.asm.Opcodes.ASM4;
-import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
import com.android.resources.ResourceFolderType;
import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repository.local.LocalSdk;
import com.android.tools.lint.client.api.LintListener.EventType;
import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Context;
@@ -48,13 +46,13 @@
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.tools.lint.detector.api.ResourceContext;
import com.android.tools.lint.detector.api.ResourceXmlDetector;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.XmlContext;
import com.google.common.annotations.Beta;
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Splitter;
+import com.google.common.base.Objects;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -63,7 +61,6 @@
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
-import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
@@ -81,6 +78,8 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@@ -93,6 +92,7 @@
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -128,6 +128,8 @@
*/
private static final int MAX_PHASES = 3;
private static final String SUPPRESS_LINT_VMSIG = '/' + SUPPRESS_LINT + ';';
+ /** Prefix used by the comment suppress mechanism in Studio/IntelliJ */
+ private static final String STUDIO_ID_PREFIX = "AndroidLint";
private final LintClient mClient;
private LintRequest mRequest;
@@ -173,6 +175,15 @@
}
/**
+ * Sets the scope for the lint job
+ *
+ * @param scope the scope to use
+ */
+ public void setScope(@NonNull EnumSet<Scope> scope) {
+ mScope = scope;
+ }
+
+ /**
* Returns the lint client requesting the lint check. This may not be the same
* instance as the one passed in to this driver; lint uses a wrapper which performs
* additional validation to ensure that for example badly behaved detectors which report
@@ -370,6 +381,9 @@
private void analyze() {
mCanceled = false;
mScope = mRequest.getScope();
+ assert mScope == null || !mScope.contains(Scope.ALL_RESOURCE_FILES) ||
+ mScope.contains(Scope.RESOURCE_FILE);
+
Collection<Project> projects;
try {
projects = mRequest.getProjects();
@@ -379,7 +393,8 @@
} catch (CircularDependencyException e) {
mCurrentProject = e.getProject();
if (mCurrentProject != null) {
- File file = e.getLocation().getFile();
+ Location location = e.getLocation();
+ File file = location != null ? location.getFile() : mCurrentProject.getDir();
Context context = new Context(this, mCurrentProject, null, file);
context.report(IssueRegistry.LINT_ERROR, e.getLocation(), e.getMessage(), null);
mCurrentProject = null;
@@ -505,6 +520,8 @@
// Ensure that the current visitor is recomputed
mCurrentFolderType = null;
mCurrentVisitor = null;
+ mCurrentXmlDetectors = null;
+ mCurrentBinaryDetectors = null;
// Create map from detector class to issue such that we can
// compute applicable issues for each detector in the list of detectors
@@ -637,12 +654,33 @@
}
}
+ List<Detector> gradleDetectors = mScopeDetectors.get(Scope.GRADLE_FILE);
+ if (gradleDetectors != null) {
+ for (Detector detector : gradleDetectors) {
+ assert detector instanceof Detector.GradleScanner : detector;
+ }
+ }
+
List<Detector> otherDetectors = mScopeDetectors.get(Scope.OTHER);
if (otherDetectors != null) {
for (Detector detector : otherDetectors) {
assert detector instanceof Detector.OtherFileScanner : detector;
}
}
+
+ List<Detector> dirDetectors = mScopeDetectors.get(Scope.RESOURCE_FOLDER);
+ if (dirDetectors != null) {
+ for (Detector detector : dirDetectors) {
+ assert detector instanceof Detector.ResourceFolderScanner : detector;
+ }
+ }
+
+ List<Detector> binaryDetectors = mScopeDetectors.get(Scope.BINARY_RESOURCE_FILE);
+ if (binaryDetectors != null) {
+ for (Detector detector : binaryDetectors) {
+ assert detector instanceof Detector.BinaryResourceScanner : detector;
+ }
+ }
}
}
@@ -656,7 +694,7 @@
private Collection<Project> computeProjects(@NonNull List<File> files) {
// Compute list of projects
- Map<File, Project> fileToProject = new HashMap<File, Project>();
+ Map<File, Project> fileToProject = new LinkedHashMap<File, Project>();
File sharedRoot = null;
@@ -842,7 +880,7 @@
runFileDetectors(project, project);
if (!Scope.checkSingleFile(mScope)) {
- List<Project> libraries = project.getDirectLibraries();
+ List<Project> libraries = project.getAllLibraries();
for (Project library : libraries) {
Context libraryContext = new Context(this, library, project, projectDir);
fireEvent(EventType.SCANNING_LIBRARY_PROJECT, libraryContext);
@@ -896,64 +934,82 @@
private void runFileDetectors(@NonNull Project project, @Nullable Project main) {
// Look up manifest information (but not for library projects)
- for (File manifestFile : project.getManifestFiles()) {
- XmlContext context = new XmlContext(this, project, main, manifestFile, null);
- IDomParser parser = mClient.getDomParser();
- if (parser != null) {
- context.document = parser.parseXml(context);
- if (context.document != null) {
- try {
- project.readManifest(context.document);
+ if (project.isAndroidProject()) {
+ for (File manifestFile : project.getManifestFiles()) {
+ XmlParser parser = mClient.getXmlParser();
+ if (parser != null) {
+ XmlContext context = new XmlContext(this, project, main, manifestFile, null,
+ parser);
+ context.document = parser.parseXml(context);
+ if (context.document != null) {
+ try {
+ project.readManifest(context.document);
- if ((!project.isLibrary() || (main != null && main.isMergingManifests()))
- && mScope.contains(Scope.MANIFEST)) {
- List<Detector> detectors = mScopeDetectors.get(Scope.MANIFEST);
- if (detectors != null) {
- XmlVisitor v = new XmlVisitor(parser, detectors);
- fireEvent(EventType.SCANNING_FILE, context);
- v.visitFile(context, manifestFile);
+ if ((!project.isLibrary() || (main != null
+ && main.isMergingManifests()))
+ && mScope.contains(Scope.MANIFEST)) {
+ List<Detector> detectors = mScopeDetectors.get(Scope.MANIFEST);
+ if (detectors != null) {
+ ResourceVisitor v = new ResourceVisitor(parser, detectors,
+ null);
+ fireEvent(EventType.SCANNING_FILE, context);
+ v.visitFile(context, manifestFile);
+ }
}
+ } finally {
+ if (context.document != null) { // else: freed by XmlVisitor above
+ parser.dispose(context, context.document);
+ }
}
- } finally {
- if (context.document != null) { // else: freed by XmlVisitor above
- parser.dispose(context, context.document);
- }
}
}
}
- }
- // Process both Scope.RESOURCE_FILE and Scope.ALL_RESOURCE_FILES detectors together
- // in a single pass through the resource directories.
- if (mScope.contains(Scope.ALL_RESOURCE_FILES) || mScope.contains(Scope.RESOURCE_FILE)) {
- List<Detector> checks = union(mScopeDetectors.get(Scope.RESOURCE_FILE),
- mScopeDetectors.get(Scope.ALL_RESOURCE_FILES));
- if (checks != null && !checks.isEmpty()) {
- List<ResourceXmlDetector> xmlDetectors =
- new ArrayList<ResourceXmlDetector>(checks.size());
- for (Detector detector : checks) {
- if (detector instanceof ResourceXmlDetector) {
- xmlDetectors.add((ResourceXmlDetector) detector);
+ // Process both Scope.RESOURCE_FILE and Scope.ALL_RESOURCE_FILES detectors together
+ // in a single pass through the resource directories.
+ if (mScope.contains(Scope.ALL_RESOURCE_FILES)
+ || mScope.contains(Scope.RESOURCE_FILE)
+ || mScope.contains(Scope.RESOURCE_FOLDER)
+ || mScope.contains(Scope.BINARY_RESOURCE_FILE)) {
+ List<Detector> dirChecks = mScopeDetectors.get(Scope.RESOURCE_FOLDER);
+ List<Detector> binaryChecks = mScopeDetectors.get(Scope.BINARY_RESOURCE_FILE);
+ List<Detector> checks = union(mScopeDetectors.get(Scope.RESOURCE_FILE),
+ mScopeDetectors.get(Scope.ALL_RESOURCE_FILES));
+ boolean haveXmlChecks = checks != null && !checks.isEmpty();
+ List<ResourceXmlDetector> xmlDetectors;
+ if (haveXmlChecks) {
+ xmlDetectors = new ArrayList<ResourceXmlDetector>(checks.size());
+ for (Detector detector : checks) {
+ if (detector instanceof ResourceXmlDetector) {
+ xmlDetectors.add((ResourceXmlDetector) detector);
+ }
}
+ haveXmlChecks = !xmlDetectors.isEmpty();
+ } else {
+ xmlDetectors = Collections.emptyList();
}
- if (!xmlDetectors.isEmpty()) {
+ if (haveXmlChecks
+ || dirChecks != null && !dirChecks.isEmpty()
+ || binaryChecks != null && !binaryChecks.isEmpty()) {
List<File> files = project.getSubset();
if (files != null) {
- checkIndividualResources(project, main, xmlDetectors, files);
+ checkIndividualResources(project, main, xmlDetectors, dirChecks,
+ binaryChecks, files);
} else {
List<File> resourceFolders = project.getResourceFolders();
- if (!resourceFolders.isEmpty() && !xmlDetectors.isEmpty()) {
+ if (!resourceFolders.isEmpty()) {
for (File res : resourceFolders) {
- checkResFolder(project, main, res, xmlDetectors);
+ checkResFolder(project, main, res, xmlDetectors, dirChecks,
+ binaryChecks);
}
}
}
}
}
- }
- if (mCanceled) {
- return;
+ if (mCanceled) {
+ return;
+ }
}
if (mScope.contains(Scope.JAVA_FILE) || mScope.contains(Scope.ALL_JAVA_FILES)) {
@@ -980,6 +1036,18 @@
checkClasses(project, main);
}
+ if (mCanceled) {
+ return;
+ }
+
+ if (mScope.contains(Scope.GRADLE_FILE)) {
+ checkBuildScripts(project, main);
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
if (mScope.contains(Scope.OTHER)) {
List<Detector> checks = mScopeDetectors.get(Scope.OTHER);
if (checks != null) {
@@ -992,9 +1060,36 @@
return;
}
- if (project == main && mScope.contains(Scope.PROGUARD_FILE)) {
+ if (project == main && mScope.contains(Scope.PROGUARD_FILE) &&
+ project.isAndroidProject()) {
checkProGuard(project, main);
}
+
+ if (project == main && mScope.contains(Scope.PROPERTY_FILE) &&
+ project.isAndroidProject()) {
+ checkProperties(project, main);
+ }
+ }
+
+ private void checkBuildScripts(Project project, Project main) {
+ List<Detector> detectors = mScopeDetectors.get(Scope.GRADLE_FILE);
+ if (detectors != null) {
+ List<File> files = project.getSubset();
+ if (files == null) {
+ files = project.getGradleBuildScripts();
+ }
+ for (File file : files) {
+ Context context = new Context(this, project, main, file);
+ fireEvent(EventType.SCANNING_FILE, context);
+ for (Detector detector : detectors) {
+ if (detector.appliesTo(context, file)) {
+ detector.beforeCheckFile(context);
+ detector.visitBuildScript(context, Maps.<String, Object>newHashMap());
+ detector.afterCheckFile(context);
+ }
+ }
+ }
+ }
}
private void checkProGuard(Project project, Project main) {
@@ -1015,6 +1110,24 @@
}
}
+ private void checkProperties(Project project, Project main) {
+ List<Detector> detectors = mScopeDetectors.get(Scope.PROPERTY_FILE);
+ if (detectors != null) {
+ File file = new File(project.getDir(), "local.properties");
+ if (file.exists()) {
+ Context context = new Context(this, project, main, file);
+ fireEvent(EventType.SCANNING_FILE, context);
+ for (Detector detector : detectors) {
+ if (detector.appliesTo(context, file)) {
+ detector.beforeCheckFile(context);
+ detector.run(context);
+ detector.afterCheckFile(context);
+ }
+ }
+ }
+ }
+ }
+
/** True if execution has been canceled */
boolean isCanceled() {
return mCanceled;
@@ -1075,7 +1188,7 @@
if (mCurrentProject != null) {
Boolean isSub = mClient.isSubclassOf(mCurrentProject, classNode.name, superClassName);
if (isSub != null) {
- return isSub.booleanValue();
+ return isSub;
}
}
@@ -1102,12 +1215,8 @@
// e.g. the DuplicateIdDetector registers both a cross-resource issue and a
// single-file issue, so it shows up on both scope lists:
Set<Detector> set = new HashSet<Detector>(list1.size() + list2.size());
- if (list1 != null) {
- set.addAll(list1);
- }
- if (list2 != null) {
- set.addAll(list2);
- }
+ set.addAll(list1);
+ set.addAll(list2);
return new ArrayList<Detector>(set);
}
@@ -1182,13 +1291,11 @@
if (file.isFile() && path.endsWith(DOT_CLASS)) {
try {
byte[] bytes = mClient.readBytes(file);
- if (bytes != null) {
- for (File dir : classFolders) {
- if (path.startsWith(dir.getPath())) {
- entries.add(new ClassEntry(file, null /* jarFile*/, dir,
- bytes));
- break;
- }
+ for (File dir : classFolders) {
+ if (path.startsWith(dir.getPath())) {
+ entries.add(new ClassEntry(file, null /* jarFile*/, dir,
+ bytes));
+ break;
}
}
} catch (IOException e) {
@@ -1230,7 +1337,14 @@
String sourceContents = null;
String sourceName = "";
mOuterClasses = new ArrayDeque<ClassNode>();
+ ClassEntry prev = null;
for (ClassEntry entry : entries) {
+ if (prev != null && prev.compareTo(entry) == 0) {
+ // Duplicate entries for some reason: ignore
+ continue;
+ }
+ prev = entry;
+
ClassReader reader;
ClassNode classNode;
try {
@@ -1438,6 +1552,7 @@
@NonNull List<File> classPath) {
for (File classPathEntry : classPath) {
if (classPathEntry.getName().endsWith(DOT_JAR)) {
+ //noinspection UnnecessaryLocalVariable
File jarFile = classPathEntry;
if (!jarFile.exists()) {
continue;
@@ -1471,11 +1586,14 @@
} catch (IOException e) {
mClient.log(e, "Could not read jar file contents from %1$s", jarFile);
} finally {
- Closeables.closeQuietly(zis);
+ try {
+ Closeables.close(zis, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
-
- continue;
} else if (classPathEntry.isDirectory()) {
+ //noinspection UnnecessaryLocalVariable
File binDir = classPathEntry;
List<File> classFiles = new ArrayList<File>();
addClassFiles(binDir, classFiles);
@@ -1483,9 +1601,7 @@
for (File file : classFiles) {
try {
byte[] bytes = mClient.readBytes(file);
- if (bytes != null) {
- entries.add(new ClassEntry(file, null /* jarFile*/, binDir, bytes));
- }
+ entries.add(new ClassEntry(file, null /* jarFile*/, binDir, bytes));
} catch (IOException e) {
mClient.log(e, null);
continue;
@@ -1521,7 +1637,7 @@
@Nullable Project main,
@NonNull List<File> sourceFolders,
@NonNull List<Detector> checks) {
- IJavaParser javaParser = mClient.getJavaParser();
+ JavaParser javaParser = mClient.getJavaParser(project);
if (javaParser == null) {
mClient.log(null, "No java parser provided to lint: not running Java checks");
return;
@@ -1536,10 +1652,16 @@
}
if (!sources.isEmpty()) {
JavaVisitor visitor = new JavaVisitor(javaParser, checks);
+ List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(sources.size());
for (File file : sources) {
- JavaContext context = new JavaContext(this, project, main, file);
+ JavaContext context = new JavaContext(this, project, main, file, javaParser);
+ contexts.add(context);
+ }
+
+ visitor.prepare(contexts);
+ for (JavaContext context : contexts) {
fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context, file);
+ visitor.visitFile(context);
if (mCanceled) {
return;
}
@@ -1553,7 +1675,7 @@
@NonNull List<Detector> checks,
@NonNull List<File> files) {
- IJavaParser javaParser = mClient.getJavaParser();
+ JavaParser javaParser = mClient.getJavaParser(project);
if (javaParser == null) {
mClient.log(null, "No java parser provided to lint: not running Java checks");
return;
@@ -1561,14 +1683,28 @@
JavaVisitor visitor = new JavaVisitor(javaParser, checks);
+ List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(files.size());
for (File file : files) {
if (file.isFile() && file.getPath().endsWith(DOT_JAVA)) {
- JavaContext context = new JavaContext(this, project, main, file);
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context, file);
- if (mCanceled) {
- return;
- }
+ contexts.add(new JavaContext(this, project, main, file, javaParser));
+ }
+ }
+
+ if (contexts.isEmpty()) {
+ return;
+ }
+
+ visitor.prepare(contexts);
+
+ if (mCanceled) {
+ return;
+ }
+
+ for (JavaContext context : contexts) {
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitFile(context);
+ if (mCanceled) {
+ return;
}
}
}
@@ -1588,37 +1724,56 @@
private ResourceFolderType mCurrentFolderType;
private List<ResourceXmlDetector> mCurrentXmlDetectors;
- private XmlVisitor mCurrentVisitor;
+ private List<Detector> mCurrentBinaryDetectors;
+ private ResourceVisitor mCurrentVisitor;
@Nullable
- private XmlVisitor getVisitor(
+ private ResourceVisitor getVisitor(
@NonNull ResourceFolderType type,
- @NonNull List<ResourceXmlDetector> checks) {
+ @NonNull List<ResourceXmlDetector> checks,
+ @Nullable List<Detector> binaryChecks) {
if (type != mCurrentFolderType) {
mCurrentFolderType = type;
// Determine which XML resource detectors apply to the given folder type
- List<ResourceXmlDetector> applicableChecks =
+ List<ResourceXmlDetector> applicableXmlChecks =
new ArrayList<ResourceXmlDetector>(checks.size());
for (ResourceXmlDetector check : checks) {
if (check.appliesTo(type)) {
- applicableChecks.add(check);
+ applicableXmlChecks.add(check);
+ }
+ }
+ List<Detector> applicableBinaryChecks = null;
+ if (binaryChecks != null) {
+ applicableBinaryChecks = new ArrayList<Detector>(binaryChecks.size());
+ for (Detector check : binaryChecks) {
+ if (check.appliesTo(type)) {
+ applicableBinaryChecks.add(check);
+ }
}
}
// If the list of detectors hasn't changed, then just use the current visitor!
- if (mCurrentXmlDetectors != null && mCurrentXmlDetectors.equals(applicableChecks)) {
+ if (mCurrentXmlDetectors != null && mCurrentXmlDetectors.equals(applicableXmlChecks)
+ && Objects.equal(mCurrentBinaryDetectors, applicableBinaryChecks)) {
return mCurrentVisitor;
}
- if (applicableChecks.isEmpty()) {
+ mCurrentXmlDetectors = applicableXmlChecks;
+ mCurrentBinaryDetectors = applicableBinaryChecks;
+
+ if (applicableXmlChecks.isEmpty()
+ && (applicableBinaryChecks == null || applicableBinaryChecks.isEmpty())) {
mCurrentVisitor = null;
return null;
}
- IDomParser parser = mClient.getDomParser();
+ XmlParser parser = mClient.getXmlParser();
if (parser != null) {
- mCurrentVisitor = new XmlVisitor(parser, applicableChecks);
+ mCurrentVisitor = new ResourceVisitor(parser, applicableXmlChecks,
+ applicableBinaryChecks);
+ } else {
+ mCurrentVisitor = null;
}
}
@@ -1629,7 +1784,9 @@
@NonNull Project project,
@Nullable Project main,
@NonNull File res,
- @NonNull List<ResourceXmlDetector> checks) {
+ @NonNull List<ResourceXmlDetector> xmlChecks,
+ @Nullable List<Detector> dirChecks,
+ @Nullable List<Detector> binaryChecks) {
assert res.isDirectory() : res;
File[] resourceDirs = res.listFiles();
if (resourceDirs == null) {
@@ -1637,13 +1794,15 @@
}
// Sort alphabetically such that we can process related folder types at the
- // same time
+ // same time, and to have a defined behavior such that detectors can rely on
+ // predictable ordering, e.g. layouts are seen before menus are seen before
+ // values, etc (l < m < v).
Arrays.sort(resourceDirs);
for (File dir : resourceDirs) {
ResourceFolderType type = ResourceFolderType.getFolderType(dir.getName());
if (type != null) {
- checkResourceFolder(project, main, dir, type, checks);
+ checkResourceFolder(project, main, dir, type, xmlChecks, dirChecks, binaryChecks);
}
if (mCanceled) {
@@ -1657,24 +1816,51 @@
@Nullable Project main,
@NonNull File dir,
@NonNull ResourceFolderType type,
- @NonNull List<ResourceXmlDetector> checks) {
+ @NonNull List<ResourceXmlDetector> xmlChecks,
+ @Nullable List<Detector> dirChecks,
+ @Nullable List<Detector> binaryChecks) {
+
// Process the resource folder
- File[] xmlFiles = dir.listFiles();
- if (xmlFiles != null && xmlFiles.length > 0) {
- XmlVisitor visitor = getVisitor(type, checks);
- if (visitor != null) { // if not, there are no applicable rules in this folder
- // Process files in alphabetical order, to ensure stable output
- // (for example for the duplicate resource detector)
- Arrays.sort(xmlFiles);
- for (File file : xmlFiles) {
- if (LintUtils.isXmlFile(file)) {
- XmlContext context = new XmlContext(this, project, main, file, type);
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context, file);
- if (mCanceled) {
- return;
- }
- }
+
+ if (dirChecks != null && !dirChecks.isEmpty()) {
+ ResourceContext context = new ResourceContext(this, project, main, dir, type);
+ String folderName = dir.getName();
+ fireEvent(EventType.SCANNING_FILE, context);
+ for (Detector check : dirChecks) {
+ if (check.appliesTo(type)) {
+ check.beforeCheckFile(context);
+ check.checkFolder(context, folderName);
+ check.afterCheckFile(context);
+ }
+ }
+ if (binaryChecks == null && xmlChecks.isEmpty()) {
+ return;
+ }
+ }
+
+ File[] files = dir.listFiles();
+ if (files == null || files.length <= 0) {
+ return;
+ }
+
+ ResourceVisitor visitor = getVisitor(type, xmlChecks, binaryChecks);
+ if (visitor != null) { // if not, there are no applicable rules in this folder
+ // Process files in alphabetical order, to ensure stable output
+ // (for example for the duplicate resource detector)
+ Arrays.sort(files);
+ for (File file : files) {
+ if (LintUtils.isXmlFile(file)) {
+ XmlContext context = new XmlContext(this, project, main, file, type,
+ visitor.getParser());
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitFile(context, file);
+ } else if (binaryChecks != null && LintUtils.isBitmapFile(file)) {
+ ResourceContext context = new ResourceContext(this, project, main, file, type);
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitBinaryResource(context);
+ }
+ if (mCanceled) {
+ return;
}
}
}
@@ -1685,6 +1871,8 @@
@NonNull Project project,
@Nullable Project main,
@NonNull List<ResourceXmlDetector> xmlDetectors,
+ @Nullable List<Detector> dirChecks,
+ @Nullable List<Detector> binaryChecks,
@NonNull List<File> files) {
for (File file : files) {
if (file.isDirectory()) {
@@ -1692,27 +1880,44 @@
ResourceFolderType type = ResourceFolderType.getFolderType(file.getName());
if (type != null && new File(file.getParentFile(), RES_FOLDER).exists()) {
// Yes.
- checkResourceFolder(project, main, file, type, xmlDetectors);
+ checkResourceFolder(project, main, file, type, xmlDetectors, dirChecks,
+ binaryChecks);
} else if (file.getName().equals(RES_FOLDER)) { // Is it the res folder?
// Yes
- checkResFolder(project, main, file, xmlDetectors);
+ checkResFolder(project, main, file, xmlDetectors, dirChecks, binaryChecks);
} else {
mClient.log(null, "Unexpected folder %1$s; should be project, " +
"\"res\" folder or resource folder", file.getPath());
- continue;
}
} else if (file.isFile() && LintUtils.isXmlFile(file)) {
// Yes, find out its resource type
String folderName = file.getParentFile().getName();
ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
if (type != null) {
- XmlVisitor visitor = getVisitor(type, xmlDetectors);
+ ResourceVisitor visitor = getVisitor(type, xmlDetectors, binaryChecks);
if (visitor != null) {
- XmlContext context = new XmlContext(this, project, main, file, type);
+ XmlContext context = new XmlContext(this, project, main, file, type,
+ visitor.getParser());
fireEvent(EventType.SCANNING_FILE, context);
visitor.visitFile(context, file);
}
}
+ } else if (binaryChecks != null && file.isFile() && LintUtils.isBitmapFile(file)) {
+ // Yes, find out its resource type
+ String folderName = file.getParentFile().getName();
+ ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
+ if (type != null) {
+ ResourceVisitor visitor = getVisitor(type, xmlDetectors, binaryChecks);
+ if (visitor != null) {
+ ResourceContext context = new ResourceContext(this, project, main, file,
+ type);
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitBinaryResource(context);
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
}
}
}
@@ -1851,8 +2056,8 @@
@Override
@Nullable
- public IDomParser getDomParser() {
- return mDelegate.getDomParser();
+ public XmlParser getXmlParser() {
+ return mDelegate.getXmlParser();
}
@Override
@@ -1874,10 +2079,10 @@
return mDelegate.getProject(dir, referenceDir);
}
- @Override
@Nullable
- public IJavaParser getJavaParser() {
- return mDelegate.getJavaParser();
+ @Override
+ public JavaParser getJavaParser(@Nullable Project project) {
+ return mDelegate.getJavaParser(project);
}
@Override
@@ -1915,6 +2120,18 @@
return mDelegate.getTargets();
}
+ @Nullable
+ @Override
+ public LocalSdk getSdk() {
+ return mDelegate.getSdk();
+ }
+
+ @Nullable
+ @Override
+ public IAndroidTarget getCompileTarget(@NonNull Project project) {
+ return mDelegate.getCompileTarget(project);
+ }
+
@Override
public int getHighestKnownApiLevel() {
return mDelegate.getHighestKnownApiLevel();
@@ -1972,6 +2189,45 @@
log(Severity.WARNING, null, "Too late to register projects");
mDelegate.registerProject(dir, project);
}
+
+ @Override
+ public IssueRegistry addCustomLintRules(@NonNull IssueRegistry registry) {
+ return mDelegate.addCustomLintRules(registry);
+ }
+
+ @Override
+ public boolean checkForSuppressComments() {
+ return mDelegate.checkForSuppressComments();
+ }
+
+ @Override
+ public boolean supportsProjectResources() {
+ return mDelegate.supportsProjectResources();
+ }
+
+ @Nullable
+ @Override
+ public AbstractResourceRepository getProjectResources(Project project,
+ boolean includeDependencies) {
+ return mDelegate.getProjectResources(project, includeDependencies);
+ }
+
+ @NonNull
+ @Override
+ public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
+ return mDelegate.createResourceItemHandle(item);
+ }
+
+ @Nullable
+ @Override
+ public URLConnection openConnection(@NonNull URL url) throws IOException {
+ return mDelegate.openConnection(url);
+ }
+
+ @Override
+ public void closeConnection(@NonNull URLConnection connection) throws IOException {
+ mDelegate.closeConnection(connection);
+ }
}
/**
@@ -2057,7 +2313,7 @@
private static MethodInsnNode findConstructorInvocation(
@NonNull MethodNode method,
@NonNull String className) {
- InsnList nodes = ((MethodNode) method).instructions;
+ InsnList nodes = method.instructions;
for (int i = 0, n = nodes.size(); i < n; i++) {
AbstractInsnNode instruction = nodes.get(i);
if (instruction.getOpcode() == Opcodes.INVOKESPECIAL) {
@@ -2127,6 +2383,7 @@
* @return true if there is a suppress annotation covering the specific
* issue on this field
*/
+ @SuppressWarnings("MethodMayBeStatic") // API; reserve need to require driver state later
public boolean isSuppressed(@Nullable Issue issue, @NonNull FieldNode field) {
if (field.invisibleAnnotations != null) {
@SuppressWarnings("unchecked")
@@ -2195,8 +2452,7 @@
Object value = annotation.values.get(i + 1);
if (value instanceof String) {
String id = (String) value;
- if (id.equalsIgnoreCase(SUPPRESS_ALL) ||
- issue != null && id.equalsIgnoreCase(issue.getId())) {
+ if (matches(issue, id)) {
return true;
}
} else if (value instanceof List) {
@@ -2205,8 +2461,7 @@
for (Object v : list) {
if (v instanceof String) {
String id = (String) v;
- if (id.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null
- && id.equalsIgnoreCase(issue.getId()))) {
+ if (matches(issue, id)) {
return true;
}
}
@@ -2221,15 +2476,39 @@
return false;
}
+ private static boolean matches(@Nullable Issue issue, @NonNull String id) {
+ if (id.equalsIgnoreCase(SUPPRESS_ALL)) {
+ return true;
+ }
+
+ if (issue != null) {
+ String issueId = issue.getId();
+ if (id.equalsIgnoreCase(issueId)) {
+ return true;
+ }
+ if (id.startsWith(STUDIO_ID_PREFIX)
+ && id.regionMatches(true, STUDIO_ID_PREFIX.length(), issueId, 0, issueId.length())
+ && id.substring(STUDIO_ID_PREFIX.length()).equalsIgnoreCase(issueId)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Returns whether the given issue is suppressed in the given parse tree node.
*
+ * @param context the context for the source being scanned
* @param issue the issue to be checked, or null to just check for "all"
* @param scope the AST node containing the issue
* @return true if there is a suppress annotation covering the specific
* issue in this class
*/
- public boolean isSuppressed(@NonNull Issue issue, @Nullable Node scope) {
+ public boolean isSuppressed(@Nullable JavaContext context, @NonNull Issue issue,
+ @Nullable Node scope) {
+ boolean checkComments = mClient.checkForSuppressComments() &&
+ context != null && context.containsCommentSuppress();
while (scope != null) {
Class<? extends Node> type = scope.getClass();
// The Lombok AST uses a flat hierarchy of node type implementation classes
@@ -2262,6 +2541,10 @@
}
}
+ if (checkComments && context.isSuppressedWithComment(scope, issue)) {
+ return true;
+ }
+
scope = scope.getParent();
}
@@ -2286,9 +2569,7 @@
return false;
}
- Iterator<Annotation> iterator = annotations.iterator();
- while (iterator.hasNext()) {
- Annotation annotation = iterator.next();
+ for (Annotation annotation : annotations) {
TypeReference t = annotation.astAnnotationTypeReference();
String typeName = t.getTypeName();
if (typeName.endsWith(SUPPRESS_LINT)
@@ -2296,9 +2577,7 @@
StrictListAccessor<AnnotationElement, Annotation> values =
annotation.astElements();
if (values != null) {
- Iterator<AnnotationElement> valueIterator = values.iterator();
- while (valueIterator.hasNext()) {
- AnnotationElement element = valueIterator.next();
+ for (AnnotationElement element : values) {
AnnotationValue valueNode = element.astValue();
if (valueNode == null) {
continue;
@@ -2306,8 +2585,7 @@
if (valueNode instanceof StringLiteral) {
StringLiteral literal = (StringLiteral) valueNode;
String value = literal.astValue();
- if (value.equalsIgnoreCase(SUPPRESS_ALL) ||
- issue != null && issue.getId().equalsIgnoreCase(value)) {
+ if (matches(issue, value)) {
return true;
}
} else if (valueNode instanceof ArrayInitializer) {
@@ -2317,13 +2595,10 @@
if (expressions == null) {
continue;
}
- Iterator<Expression> arrayIterator = expressions.iterator();
- while (arrayIterator.hasNext()) {
- Expression arrayElement = arrayIterator.next();
+ for (Expression arrayElement : expressions) {
if (arrayElement instanceof StringLiteral) {
String value = ((StringLiteral) arrayElement).astValue();
- if (value.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null
- && issue.getId().equalsIgnoreCase(value))) {
+ if (matches(issue, value)) {
return true;
}
}
@@ -2345,28 +2620,31 @@
* @return true if there is a suppress annotation covering the specific
* issue in this class
*/
- public boolean isSuppressed(@NonNull Issue issue, @Nullable org.w3c.dom.Node node) {
+ public boolean isSuppressed(@Nullable XmlContext context, @NonNull Issue issue,
+ @Nullable org.w3c.dom.Node node) {
if (node instanceof Attr) {
node = ((Attr) node).getOwnerElement();
}
+ boolean checkComments = mClient.checkForSuppressComments()
+ && context != null && context.containsCommentSuppress();
while (node != null) {
if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
Element element = (Element) node;
if (element.hasAttributeNS(TOOLS_URI, ATTR_IGNORE)) {
String ignore = element.getAttributeNS(TOOLS_URI, ATTR_IGNORE);
if (ignore.indexOf(',') == -1) {
- if (ignore.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null
- && issue.getId().equalsIgnoreCase(ignore))) {
+ if (matches(issue, ignore)) {
return true;
}
} else {
for (String id : ignore.split(",")) { //$NON-NLS-1$
- if (id.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null
- && issue.getId().equalsIgnoreCase(id))) {
+ if (matches(issue, id)) {
return true;
}
}
}
+ } else if (checkComments && context.isSuppressedWithComment(node, issue)) {
+ return true;
}
}
@@ -2401,11 +2679,14 @@
}
@Override
- public int compareTo(ClassEntry other) {
+ public int compareTo(@NonNull ClassEntry other) {
String p1 = file.getPath();
String p2 = other.file.getPath();
int m1 = p1.length();
int m2 = p2.length();
+ if (m1 == m2 && p1.equals(p2)) {
+ return 0;
+ }
int m = Math.min(m1, m2);
for (int i = 0; i < m; i++) {
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 0816bd5..1e8d457 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
@@ -26,7 +26,6 @@
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
-import java.util.Set;
/**
* Information about a request to run lint
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ResourceVisitor.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ResourceVisitor.java
new file mode 100644
index 0000000..25f5e15
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ResourceVisitor.java
@@ -0,0 +1,245 @@
+/*
+ * 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.tools.lint.client.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.XmlScanner;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.ResourceContext;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.annotations.Beta;
+
+import org.w3c.dom.Attr;
+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.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.RandomAccess;
+
+/**
+ * Specialized visitor for running detectors on resources: typically XML documents,
+ * but also binary resources.
+ * <p>
+ * It operates in two phases:
+ * <ol>
+ * <li> First, it computes a set of maps where it generates a map from each
+ * significant element name, and each significant attribute name, to a list
+ * of detectors to consult for that element or attribute name.
+ * The set of element names or attribute names (or both) that a detector
+ * is interested in is provided by the detectors themselves.
+ * <li> Second, it iterates over the document a single time. For each element and
+ * attribute it looks up the list of interested detectors, and runs them.
+ * </ol>
+ * It also notifies all the detectors before and after the document is processed
+ * such that they can do pre- and post-processing.
+ * <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>
+ */
+@Beta
+class ResourceVisitor {
+ private final Map<String, List<Detector.XmlScanner>> mElementToCheck =
+ new HashMap<String, List<Detector.XmlScanner>>();
+ private final Map<String, List<Detector.XmlScanner>> mAttributeToCheck =
+ new HashMap<String, List<Detector.XmlScanner>>();
+ private final List<Detector.XmlScanner> mDocumentDetectors =
+ new ArrayList<Detector.XmlScanner>();
+ private final List<Detector.XmlScanner> mAllElementDetectors =
+ new ArrayList<Detector.XmlScanner>();
+ private final List<Detector.XmlScanner> mAllAttributeDetectors =
+ new ArrayList<Detector.XmlScanner>();
+ private final List<? extends Detector> mAllDetectors;
+ private final List<? extends Detector> mBinaryDetectors;
+ private final XmlParser mParser;
+
+ // Really want this:
+ //<T extends List<Detector> & Detector.XmlScanner> XmlVisitor(IDomParser parser,
+ // T xmlDetectors) {
+ // but it makes client code tricky and ugly.
+ ResourceVisitor(
+ @NonNull XmlParser parser,
+ @NonNull List<? extends Detector> xmlDetectors,
+ @Nullable List<Detector> binaryDetectors) {
+ mParser = parser;
+ mAllDetectors = xmlDetectors;
+ mBinaryDetectors = binaryDetectors;
+
+ // TODO: Check appliesTo() for files, and find a quick way to enable/disable
+ // rules when running through a full project!
+ for (Detector detector : xmlDetectors) {
+ Detector.XmlScanner xmlDetector = (XmlScanner) detector;
+ Collection<String> attributes = xmlDetector.getApplicableAttributes();
+ if (attributes == XmlScanner.ALL) {
+ mAllAttributeDetectors.add(xmlDetector);
+ } else if (attributes != null) {
+ for (String attribute : attributes) {
+ List<Detector.XmlScanner> list = mAttributeToCheck.get(attribute);
+ if (list == null) {
+ list = new ArrayList<Detector.XmlScanner>();
+ mAttributeToCheck.put(attribute, list);
+ }
+ list.add(xmlDetector);
+ }
+ }
+ Collection<String> elements = xmlDetector.getApplicableElements();
+ if (elements == XmlScanner.ALL) {
+ mAllElementDetectors.add(xmlDetector);
+ } else if (elements != null) {
+ for (String element : elements) {
+ List<Detector.XmlScanner> list = mElementToCheck.get(element);
+ if (list == null) {
+ list = new ArrayList<Detector.XmlScanner>();
+ mElementToCheck.put(element, list);
+ }
+ list.add(xmlDetector);
+ }
+ }
+
+ if ((attributes == null || (attributes.isEmpty()
+ && attributes != XmlScanner.ALL))
+ && (elements == null || (elements.isEmpty()
+ && elements != XmlScanner.ALL))) {
+ mDocumentDetectors.add(xmlDetector);
+ }
+ }
+ }
+
+ void visitFile(@NonNull XmlContext context, @NonNull File file) {
+ assert LintUtils.isXmlFile(file);
+
+ try {
+ if (context.document == null) {
+ context.document = mParser.parseXml(context);
+ if (context.document == null) {
+ // No need to log this; the parser should be reporting
+ // a full warning (such as IssueRegistry#PARSER_ERROR)
+ // with details, location, etc.
+ return;
+ }
+ if (context.document.getDocumentElement() == null) {
+ // Ignore empty documents
+ return;
+ }
+ }
+
+ for (Detector check : mAllDetectors) {
+ check.beforeCheckFile(context);
+ }
+
+ for (Detector.XmlScanner check : mDocumentDetectors) {
+ check.visitDocument(context, context.document);
+ }
+
+ if (!mElementToCheck.isEmpty() || !mAttributeToCheck.isEmpty()
+ || !mAllAttributeDetectors.isEmpty() || !mAllElementDetectors.isEmpty()) {
+ visitElement(context, context.document.getDocumentElement());
+ }
+
+ for (Detector check : mAllDetectors) {
+ check.afterCheckFile(context);
+ }
+ } finally {
+ if (context.document != null) {
+ mParser.dispose(context, context.document);
+ context.document = null;
+ }
+ }
+ }
+
+ private void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ List<Detector.XmlScanner> elementChecks = mElementToCheck.get(element.getTagName());
+ if (elementChecks != null) {
+ assert elementChecks instanceof RandomAccess;
+ for (XmlScanner check : elementChecks) {
+ check.visitElement(context, element);
+ }
+ }
+ if (!mAllElementDetectors.isEmpty()) {
+ for (XmlScanner check : mAllElementDetectors) {
+ check.visitElement(context, element);
+ }
+ }
+
+ if (!mAttributeToCheck.isEmpty() || !mAllAttributeDetectors.isEmpty()) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ String name = attribute.getLocalName();
+ if (name == null) {
+ name = attribute.getName();
+ }
+ List<Detector.XmlScanner> list = mAttributeToCheck.get(name);
+ if (list != null) {
+ for (XmlScanner check : list) {
+ check.visitAttribute(context, attribute);
+ }
+ }
+ if (!mAllAttributeDetectors.isEmpty()) {
+ for (XmlScanner check : mAllAttributeDetectors) {
+ check.visitAttribute(context, attribute);
+ }
+ }
+ }
+ }
+
+ // Visit children
+ NodeList childNodes = element.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ visitElement(context, (Element) child);
+ }
+ }
+
+ // Post hooks
+ if (elementChecks != null) {
+ for (XmlScanner check : elementChecks) {
+ check.visitElementAfter(context, element);
+ }
+ }
+ if (!mAllElementDetectors.isEmpty()) {
+ for (XmlScanner check : mAllElementDetectors) {
+ check.visitElementAfter(context, element);
+ }
+ }
+ }
+
+ @NonNull
+ public XmlParser getParser() {
+ return mParser;
+ }
+
+ public void visitBinaryResource(@NonNull ResourceContext context) {
+ if (mBinaryDetectors == null) {
+ return;
+ }
+ for (Detector check : mBinaryDetectors) {
+ check.beforeCheckFile(context);
+ check.checkBinaryResource(context);
+ check.afterCheckFile(context);
+ }
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlParser.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlParser.java
new file mode 100644
index 0000000..5d72209
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlParser.java
@@ -0,0 +1,116 @@
+/*
+ * 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.tools.lint.client.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.annotations.Beta;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+/**
+ * A wrapper for an XML parser. This allows tools integrating lint to map directly
+ * to builtin services, such as already-parsed data structures in XML editors.
+ * <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>
+ */
+@Beta
+public abstract class XmlParser {
+ /**
+ * Parse the file pointed to by the given context and return as a Document
+ *
+ * @param context the context pointing to the file to be parsed, typically
+ * via {@link Context#getContents()} but the file handle (
+ * {@link Context#file} can also be used to map to an existing
+ * editor buffer in the surrounding tool, etc)
+ * @return the parsed DOM document, or null if parsing fails
+ */
+ @Nullable
+ public abstract Document parseXml(@NonNull XmlContext context);
+
+ /**
+ * Returns a {@link Location} for the given DOM node
+ *
+ * @param context information about the file being parsed
+ * @param node the node to create a location for
+ * @return a location for the given node
+ */
+ @NonNull
+ public abstract Location getLocation(@NonNull XmlContext context, @NonNull Node node);
+
+ /**
+ * Returns a {@link Location} for the given DOM node. Like
+ * {@link #getLocation(XmlContext, Node)}, but allows a position range that
+ * is a subset of the node range.
+ *
+ * @param context information about the file being parsed
+ * @param node the node to create a location for
+ * @param start the starting position within the node, inclusive
+ * @param end the ending position within the node, exclusive
+ * @return a location for the given node
+ */
+ @NonNull
+ public abstract Location getLocation(@NonNull XmlContext context, @NonNull Node node,
+ int start, int end);
+
+ /**
+ * Creates a light-weight handle to a location for the given node. It can be
+ * turned into a full fledged location by
+ * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
+ *
+ * @param context the context providing the node
+ * @param node the node (element or attribute) to create a location handle
+ * for
+ * @return a location handle
+ */
+ @NonNull
+ public abstract Location.Handle createLocationHandle(@NonNull XmlContext context,
+ @NonNull Node node);
+
+ /**
+ * Dispose any data structures held for the given context.
+ * @param context information about the file previously parsed
+ * @param document the document that was parsed and is now being disposed
+ */
+ public void dispose(@NonNull XmlContext context, @NonNull Document document) {
+ }
+
+ /**
+ * Returns the start offset of the given node, or -1 if not known
+ *
+ * @param context the context providing the node
+ * @param node the node (element or attribute) to create a location handle
+ * for
+ * @return the start offset, or -1 if not known
+ */
+ public abstract int getNodeStartOffset(@NonNull XmlContext context, @NonNull Node node);
+
+ /**
+ * Returns the end offset of the given node, or -1 if not known
+ *
+ * @param context the context providing the node
+ * @param node the node (element or attribute) to create a location handle
+ * for
+ * @return the end offset, or -1 if not known
+ */
+ public abstract int getNodeEndOffset(@NonNull XmlContext context, @NonNull Node node);
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlVisitor.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlVisitor.java
deleted file mode 100644
index 2e64118..0000000
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlVisitor.java
+++ /dev/null
@@ -1,221 +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.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.XmlScanner;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.annotations.Beta;
-
-import org.w3c.dom.Attr;
-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.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.RandomAccess;
-
-/**
- * Specialized visitor for running detectors on an XML document.
- * It operates in two phases:
- * <ol>
- * <li> First, it computes a set of maps where it generates a map from each
- * significant element name, and each significant attribute name, to a list
- * of detectors to consult for that element or attribute name.
- * The set of element names or attribute names (or both) that a detector
- * is interested in is provided by the detectors themselves.
- * <li> Second, it iterates over the document a single time. For each element and
- * attribute it looks up the list of interested detectors, and runs them.
- * </ol>
- * It also notifies all the detectors before and after the document is processed
- * such that they can do pre- and post-processing.
- * <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>
- */
-@Beta
-class XmlVisitor {
- private final Map<String, List<Detector.XmlScanner>> mElementToCheck =
- new HashMap<String, List<Detector.XmlScanner>>();
- private final Map<String, List<Detector.XmlScanner>> mAttributeToCheck =
- new HashMap<String, List<Detector.XmlScanner>>();
- private final List<Detector.XmlScanner> mDocumentDetectors =
- new ArrayList<Detector.XmlScanner>();
- private final List<Detector.XmlScanner> mAllElementDetectors =
- new ArrayList<Detector.XmlScanner>();
- private final List<Detector.XmlScanner> mAllAttributeDetectors =
- new ArrayList<Detector.XmlScanner>();
- private final List<? extends Detector> mAllDetectors;
- private final IDomParser mParser;
-
- // Really want this:
- //<T extends List<Detector> & Detector.XmlScanner> XmlVisitor(IDomParser parser,
- // T xmlDetectors) {
- // but it makes client code tricky and ugly.
- XmlVisitor(@NonNull IDomParser parser, @NonNull List<? extends Detector> xmlDetectors) {
- mParser = parser;
- mAllDetectors = xmlDetectors;
-
- // TODO: Check appliesTo() for files, and find a quick way to enable/disable
- // rules when running through a full project!
- for (Detector detector : xmlDetectors) {
- Detector.XmlScanner xmlDetector = (XmlScanner) detector;
- Collection<String> attributes = xmlDetector.getApplicableAttributes();
- if (attributes == XmlScanner.ALL) {
- mAllAttributeDetectors.add(xmlDetector);
- } else if (attributes != null) {
- for (String attribute : attributes) {
- List<Detector.XmlScanner> list = mAttributeToCheck.get(attribute);
- if (list == null) {
- list = new ArrayList<Detector.XmlScanner>();
- mAttributeToCheck.put(attribute, list);
- }
- list.add(xmlDetector);
- }
- }
- Collection<String> elements = xmlDetector.getApplicableElements();
- if (elements == XmlScanner.ALL) {
- mAllElementDetectors.add(xmlDetector);
- } else if (elements != null) {
- for (String element : elements) {
- List<Detector.XmlScanner> list = mElementToCheck.get(element);
- if (list == null) {
- list = new ArrayList<Detector.XmlScanner>();
- mElementToCheck.put(element, list);
- }
- list.add(xmlDetector);
- }
- }
-
- if ((attributes == null || (attributes.isEmpty()
- && attributes != XmlScanner.ALL))
- && (elements == null || (elements.isEmpty()
- && elements != XmlScanner.ALL))) {
- mDocumentDetectors.add(xmlDetector);
- }
- }
- }
-
- void visitFile(@NonNull XmlContext context, @NonNull File file) {
- assert LintUtils.isXmlFile(file);
- context.parser = mParser;
-
- try {
- if (context.document == null) {
- context.document = mParser.parseXml(context);
- if (context.document == null) {
- // No need to log this; the parser should be reporting
- // a full warning (such as IssueRegistry#PARSER_ERROR)
- // with details, location, etc.
- return;
- }
- if (context.document.getDocumentElement() == null) {
- // Ignore empty documents
- return;
- }
- }
-
- for (Detector check : mAllDetectors) {
- check.beforeCheckFile(context);
- }
-
- for (Detector.XmlScanner check : mDocumentDetectors) {
- check.visitDocument(context, context.document);
- }
-
- if (!mElementToCheck.isEmpty() || !mAttributeToCheck.isEmpty()
- || !mAllAttributeDetectors.isEmpty() || !mAllElementDetectors.isEmpty()) {
- visitElement(context, context.document.getDocumentElement());
- }
-
- for (Detector check : mAllDetectors) {
- check.afterCheckFile(context);
- }
- } finally {
- if (context.document != null) {
- mParser.dispose(context, context.document);
- context.document = null;
- }
- }
- }
-
- private void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- List<Detector.XmlScanner> elementChecks = mElementToCheck.get(element.getTagName());
- if (elementChecks != null) {
- assert elementChecks instanceof RandomAccess;
- for (XmlScanner check : elementChecks) {
- check.visitElement(context, element);
- }
- }
- if (!mAllElementDetectors.isEmpty()) {
- for (XmlScanner check : mAllElementDetectors) {
- check.visitElement(context, element);
- }
- }
-
- if (!mAttributeToCheck.isEmpty() || !mAllAttributeDetectors.isEmpty()) {
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attribute = (Attr) attributes.item(i);
- String name = attribute.getLocalName();
- if (name == null) {
- name = attribute.getName();
- }
- List<Detector.XmlScanner> list = mAttributeToCheck.get(name);
- if (list != null) {
- for (XmlScanner check : list) {
- check.visitAttribute(context, attribute);
- }
- }
- if (!mAllAttributeDetectors.isEmpty()) {
- for (XmlScanner check : mAllAttributeDetectors) {
- check.visitAttribute(context, attribute);
- }
- }
- }
- }
-
- // Visit children
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- visitElement(context, (Element) child);
- }
- }
-
- // Post hooks
- if (elementChecks != null) {
- for (XmlScanner check : elementChecks) {
- check.visitElementAfter(context, element);
- }
- }
- if (!mAllElementDetectors.isEmpty()) {
- for (XmlScanner check : mAllElementDetectors) {
- check.visitElementAfter(context, element);
- }
- }
- }
-}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Context.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Context.java
index 42c97e7..cad737f 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Context.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Context.java
@@ -16,6 +16,11 @@
package com.android.tools.lint.detector.api;
+import static com.android.SdkConstants.DOT_GRADLE;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.SUPPRESS_ALL;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.client.api.Configuration;
@@ -23,14 +28,11 @@
import com.android.tools.lint.client.api.LintDriver;
import com.android.tools.lint.client.api.SdkInfo;
import com.google.common.annotations.Beta;
-import com.google.common.base.Splitter;
import java.io.File;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Context passed to the detectors during an analysis run. It provides
@@ -77,6 +79,9 @@
/** Map of properties to share results between detectors */
private Map<String, Object> mProperties;
+ /** Whether this file contains any suppress markers (null means not yet determined) */
+ private Boolean mContainsCommentSuppress;
+
/**
* Construct a new {@link Context}
*
@@ -186,6 +191,7 @@
* @param name the name of the property
* @return the corresponding value, or null
*/
+ @SuppressWarnings("UnusedDeclaration") // Used in ADT
@Nullable
public Object getProperty(String name) {
if (mProperties == null) {
@@ -201,6 +207,7 @@
* @param name the name of the property
* @param value the corresponding value
*/
+ @SuppressWarnings("UnusedDeclaration") // Used in ADT
public void setProperty(@NonNull String name, @Nullable Object value) {
if (value == null) {
if (mProperties != null) {
@@ -322,46 +329,101 @@
mDriver.requestRepeat(detector, scope);
}
- /** Pattern for version qualifiers */
- private static final Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)$"); //$NON-NLS-1$
+ /** Returns the comment marker used in Studio to suppress statements for language, if any */
+ @Nullable
+ protected String getSuppressCommentPrefix() {
+ // Java and XML files are handled in sub classes (XmlContext, JavaContext)
- private static File sCachedFolder = null;
- private static int sCachedFolderVersion = -1;
-
- /**
- * Returns the folder version. For example, for the file values-v14/foo.xml,
- * it returns 14.
- *
- * @return the folder version, or -1 if no specific version was specified
- */
- public int getFolderVersion() {
- return getFolderVersion(file);
- }
-
- /**
- * Returns the folder version of the given file. For example, for the file values-v14/foo.xml,
- * it returns 14.
- *
- * @param file the file to be checked
- * @return the folder version, or -1 if no specific version was specified
- */
- public static int getFolderVersion(File file) {
- File parent = file.getParentFile();
- if (parent.equals(sCachedFolder)) {
- return sCachedFolderVersion;
+ String path = file.getPath();
+ if (path.endsWith(DOT_JAVA) || path.endsWith(DOT_GRADLE)) {
+ return JavaContext.SUPPRESS_COMMENT_PREFIX;
+ } else if (path.endsWith(DOT_XML)) {
+ return XmlContext.SUPPRESS_COMMENT_PREFIX;
+ } else if (path.endsWith(".cfg") || path.endsWith(".pro")) {
+ return "#suppress ";
}
- sCachedFolder = parent;
- sCachedFolderVersion = -1;
+ return null;
+ }
- for (String qualifier : Splitter.on('-').split(parent.getName())) {
- Matcher matcher = VERSION_PATTERN.matcher(qualifier);
- if (matcher.matches()) {
- sCachedFolderVersion = Integer.parseInt(matcher.group(1));
- break;
+ /** Returns whether this file contains any suppress comment markers */
+ public boolean containsCommentSuppress() {
+ if (mContainsCommentSuppress == null) {
+ mContainsCommentSuppress = false;
+ String prefix = getSuppressCommentPrefix();
+ if (prefix != null) {
+ String contents = getContents();
+ if (contents != null) {
+ mContainsCommentSuppress = contents.contains(prefix);
+ }
}
}
- return sCachedFolderVersion;
+ return mContainsCommentSuppress;
+ }
+
+ /**
+ * Returns true if the given issue is suppressed at the given character offset
+ * in the file's contents
+ */
+ public boolean isSuppressedWithComment(int startOffset, @NonNull Issue issue) {
+ String prefix = getSuppressCommentPrefix();
+ if (prefix == null) {
+ return false;
+ }
+
+ if (startOffset == -1) {
+ return false;
+ }
+
+ // Check whether there is a comment marker
+ String contents = getContents();
+ assert contents != null; // otherwise we wouldn't be here
+ if (startOffset >= contents.length()) {
+ return false;
+ }
+
+ // Scan backwards to the previous line and see if it contains the marker
+ int lineStart = contents.lastIndexOf('\n', startOffset) + 1;
+ if (lineStart <= 1) {
+ return false;
+ }
+ int prevLineStart = contents.lastIndexOf('\n', lineStart - 2) + 1;
+ if (prevLineStart == 0) {
+ return false;
+ }
+ int index = findPrefixOnPreviousLine(contents, lineStart, prefix);
+ if (index != -1 &&index+prefix.length() < lineStart) {
+ String line = contents.substring(index + prefix.length(), lineStart);
+ return line.contains(issue.getId())
+ || line.contains(SUPPRESS_ALL) && line.trim().startsWith(SUPPRESS_ALL);
+ }
+
+ return false;
+ }
+
+ private static int findPrefixOnPreviousLine(String contents, int lineStart, String prefix) {
+ // Search backwards on the previous line until you find the prefix start (also look
+ // back on previous lines if the previous line(s) contain just whitespace
+ char first = prefix.charAt(0);
+ int offset = lineStart - 2; // 0: first char on this line, -1: \n on previous line, -2 last
+ boolean seenNonWhitespace = false;
+ for (; offset >= 0; offset--) {
+ char c = contents.charAt(offset);
+ if (seenNonWhitespace && c == '\n') {
+ return -1;
+ }
+
+ if (!seenNonWhitespace && !Character.isWhitespace(c)) {
+ seenNonWhitespace = true;
+ }
+
+ if (c == first && contents.regionMatches(false, offset, prefix, 0,
+ prefix.length())) {
+ return offset;
+ }
+ }
+
+ return -1;
}
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
index a3412cf..da78977 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
@@ -21,9 +21,7 @@
import com.android.resources.ResourceFolderType;
import com.android.tools.lint.client.api.LintDriver;
import com.google.common.annotations.Beta;
-import lombok.ast.AstVisitor;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
+
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodInsnNode;
@@ -37,6 +35,11 @@
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
+import java.util.Map;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
/**
* A detector is able to find a particular problem. It might also be thought of as enforcing
@@ -301,6 +304,52 @@
@NonNull MethodNode method, @NonNull MethodInsnNode call);
}
+ /** Specialized interface for detectors that scan binary resource files */
+ public interface BinaryResourceScanner {
+ /**
+ * Called for each resource folder
+ *
+ * @param context the context for the resource file
+ */
+ void checkBinaryResource(@NonNull ResourceContext context);
+
+ /**
+ * Returns whether this detector applies to the given folder type. This
+ * allows the detectors to be pruned from iteration, so for example when we
+ * are analyzing a string value file we don't need to look up detectors
+ * related to layout.
+ *
+ * @param folderType the folder type to be visited
+ * @return true if this detector can apply to resources in folders of the
+ * given type
+ */
+ boolean appliesTo(@NonNull ResourceFolderType folderType);
+ }
+
+ /** Specialized interface for detectors that scan resource folders (the folder directory
+ * itself, not the individual files within it */
+ public interface ResourceFolderScanner {
+ /**
+ * Called for each resource folder
+ *
+ * @param context the context for the resource folder
+ * @param folderName the resource folder name
+ */
+ void checkFolder(@NonNull ResourceContext context, @NonNull String folderName);
+
+ /**
+ * Returns whether this detector applies to the given folder type. This
+ * allows the detectors to be pruned from iteration, so for example when we
+ * are analyzing a string value file we don't need to look up detectors
+ * related to layout.
+ *
+ * @param folderType the folder type to be visited
+ * @return true if this detector can apply to resources in folders of the
+ * given type
+ */
+ boolean appliesTo(@NonNull ResourceFolderType folderType);
+ }
+
/** Specialized interface for detectors that scan XML files */
public interface XmlScanner {
/**
@@ -372,6 +421,11 @@
// We want to distinguish this from just an *empty* list returned by the caller!
}
+ /** Specialized interface for detectors that scan Gradle files */
+ public interface GradleScanner {
+ void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData);
+ }
+
/** Specialized interface for detectors that scan other files */
public interface OtherFileScanner {
/**
@@ -491,6 +545,22 @@
return Speed.NORMAL;
}
+ /**
+ * Returns the expected speed of this detector.
+ * The issue parameter is made available for subclasses which analyze multiple issues
+ * and which need to distinguish implementation cost by issue. If the detector does
+ * not analyze multiple issues or does not vary in speed by issue type, just override
+ * {@link #getSpeed()} instead.
+ *
+ * @param issue the issue to look up the analysis speed for
+ * @return the expected speed of this detector
+ */
+ @NonNull
+ public Speed getSpeed(@SuppressWarnings("UnusedParameters") @NonNull Issue issue) {
+ // If not overridden, this detector does not distinguish speed by issue type
+ return getSpeed();
+ }
+
// ---- Dummy implementations to make implementing XmlScanner easier: ----
@SuppressWarnings("javadoc")
@@ -608,4 +678,23 @@
public EnumSet<Scope> getApplicableFiles() {
return Scope.OTHER_SCOPE;
}
+
+ // ---- Dummy implementations to make implementing an GradleScanner easier: ----
+
+ public void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData) {
+ }
+
+ // ---- Dummy implementations to make implementing a resource folder scanner easier: ----
+
+ public void checkFolder(@NonNull ResourceContext context, @NonNull String folderName) {
+ }
+
+ // ---- Dummy implementations to make implementing a binary resource scanner easier: ----
+
+ public void checkBinaryResource(@NonNull ResourceContext context) {
+ }
+
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return true;
+ }
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Implementation.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Implementation.java
index d2ffee4..9a4b703 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Implementation.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Implementation.java
@@ -34,6 +34,7 @@
private final EnumSet<Scope> mScope;
private EnumSet<Scope>[] mAnalysisScopes;
+ @SuppressWarnings("unchecked")
private static final EnumSet<Scope>[] EMPTY = new EnumSet[0];
/**
@@ -42,6 +43,7 @@
* @param detectorClass the class of the detector to find this issue
* @param scope the scope of files required to analyze this issue
*/
+ @SuppressWarnings("unchecked")
public Implementation(
@NonNull Class<? extends Detector> detectorClass,
@NonNull EnumSet<Scope> scope) {
@@ -176,7 +178,7 @@
if (mAnalysisScopes != null) {
for (EnumSet<Scope> analysisScope : mAnalysisScopes) {
- if (mScope.containsAll(analysisScope)) {
+ if (scope.containsAll(analysisScope)) {
return true;
}
}
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 623101f..c55b89b 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
@@ -16,9 +16,12 @@
package com.android.tools.lint.detector.api;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.IJavaParser;
+import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.client.api.LintDriver;
import java.io.File;
@@ -27,6 +30,7 @@
import lombok.ast.ConstructorDeclaration;
import lombok.ast.MethodDeclaration;
import lombok.ast.Node;
+import lombok.ast.Position;
/**
* A {@link Context} used when checking Java files.
@@ -35,10 +39,13 @@
* to adjust your code for the next tools release.</b>
*/
public class JavaContext extends Context {
+ static final String SUPPRESS_COMMENT_PREFIX = "//noinspection "; //$NON-NLS-1$
+
/** The parse tree */
- public Node compilationUnit;
+ private Node mCompilationUnit;
+
/** The parser which produced the parse tree */
- public IJavaParser parser;
+ private final JavaParser mParser;
/**
* Constructs a {@link JavaContext} for running lint on the given file, with
@@ -52,13 +59,16 @@
* the root project of all library projects, not necessarily the
* directly including project.
* @param file the file to be analyzed
+ * @param parser the parser to use
*/
public JavaContext(
@NonNull LintDriver driver,
@NonNull Project project,
@Nullable Project main,
- @NonNull File file) {
+ @NonNull File file,
+ @NonNull JavaParser parser) {
super(driver, project, main, file);
+ mParser = parser;
}
/**
@@ -69,17 +79,33 @@
*/
@NonNull
public Location getLocation(@NonNull Node node) {
- if (parser != null) {
- return parser.getLocation(this, node);
- }
+ return mParser.getLocation(this, node);
+ }
- return new Location(file, null, null);
+ @NonNull
+ public JavaParser getParser() {
+ return mParser;
+ }
+
+ @Nullable
+ public Node getCompilationUnit() {
+ return mCompilationUnit;
+ }
+
+ /**
+ * Sets the compilation result. Not intended for client usage; the lint infrastructure
+ * will set this when a context has been processed
+ *
+ * @param compilationUnit the parse tree
+ */
+ public void setCompilationUnit(@Nullable Node compilationUnit) {
+ mCompilationUnit = compilationUnit;
}
@Override
public void report(@NonNull Issue issue, @Nullable Location location,
@NonNull String message, @Nullable Object data) {
- if (mDriver.isSuppressed(issue, compilationUnit)) {
+ if (mDriver.isSuppressed(this, issue, mCompilationUnit)) {
return;
}
super.report(issue, location, message, data);
@@ -103,7 +129,7 @@
@Nullable Location location,
@NonNull String message,
@Nullable Object data) {
- if (scope != null && mDriver.isSuppressed(issue, scope)) {
+ if (scope != null && mDriver.isSuppressed(this, issue, scope)) {
return;
}
super.report(issue, location, message, data);
@@ -142,4 +168,37 @@
return null;
}
+ @Override
+ @Nullable
+ protected String getSuppressCommentPrefix() {
+ return SUPPRESS_COMMENT_PREFIX;
+ }
+
+ public boolean isSuppressedWithComment(@NonNull Node scope, @NonNull Issue issue) {
+ // Check whether there is a comment marker
+ String contents = getContents();
+ assert contents != null; // otherwise we wouldn't be here
+ Position position = scope.getPosition();
+ if (position == null) {
+ return false;
+ }
+
+ int start = position.getStart();
+ return isSuppressedWithComment(start, issue);
+ }
+
+ @NonNull
+ public Location.Handle createLocationHandle(@NonNull Node node) {
+ return mParser.createLocationHandle(this, node);
+ }
+
+ @Nullable
+ public ResolvedNode resolve(@NonNull Node node) {
+ return mParser.resolve(this, node);
+ }
+
+ @Nullable
+ public TypeDescriptor getType(@NonNull Node node) {
+ return mParser.getType(this, node);
+ }
}
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 608bf1f..1741276 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
@@ -17,21 +17,43 @@
package com.android.tools.lint.detector.api;
import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.BIN_FOLDER;
+import static com.android.SdkConstants.DOT_GIF;
+import static com.android.SdkConstants.DOT_JPEG;
+import static com.android.SdkConstants.DOT_JPG;
+import static com.android.SdkConstants.DOT_PNG;
import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.ID_PREFIX;
import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.UTF_8;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
+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.res2.ResourceItem;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.ide.common.sdk.SdkVersionInfo;
import com.android.resources.FolderTypeRelationship;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repository.FullRevision;
import com.android.tools.lint.client.api.LintClient;
import com.android.utils.PositionXmlParser;
+import com.android.utils.SdkUtils;
import com.google.common.annotations.Beta;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
@@ -44,8 +66,11 @@
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
+import java.util.Queue;
+import java.util.Set;
import lombok.ast.ImportDeclaration;
@@ -116,9 +141,22 @@
* @return true if the given file is an xml file
*/
public static boolean isXmlFile(@NonNull File file) {
- String string = file.getName();
- return string.regionMatches(true, string.length() - DOT_XML.length(),
- DOT_XML, 0, DOT_XML.length());
+ return SdkUtils.endsWithIgnoreCase(file.getPath(), DOT_XML);
+ }
+
+ /**
+ * Returns true if the given file represents a bitmap drawable file
+ *
+ * @param file the file to be checked
+ * @return true if the given file is an xml file
+ */
+ public static boolean isBitmapFile(@NonNull File file) {
+ String path = file.getPath();
+ // endsWith(name, DOT_PNG) is also true for endsWith(name, DOT_9PNG)
+ return endsWith(path, DOT_PNG)
+ || endsWith(path, DOT_JPG)
+ || endsWith(path, DOT_GIF)
+ || endsWith(path, DOT_JPEG);
}
/**
@@ -428,7 +466,6 @@
return null;
}
- private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
private static final String UTF_16 = "UTF_16"; //$NON-NLS-1$
private static final String UTF_16LE = "UTF_16LE"; //$NON-NLS-1$
@@ -743,8 +780,11 @@
* qualified name
*/
public static boolean isImported(
- @NonNull lombok.ast.Node compilationUnit,
+ @Nullable lombok.ast.Node compilationUnit,
@NonNull String fullyQualifiedName) {
+ if (compilationUnit == null) {
+ return false;
+ }
int dotIndex = fullyQualifiedName.lastIndexOf('.');
int dotLength = fullyQualifiedName.length() - dotIndex;
@@ -774,4 +814,265 @@
return imported;
}
+
+ /**
+ * Looks up the resource values for the given attribute given a style. Note that
+ * this only looks project-level style values, it does not resume into the framework
+ * styles.
+ */
+ @Nullable
+ public static List<ResourceValue> getStyleAttributes(
+ @NonNull Project project, @NonNull LintClient client,
+ @NonNull String styleUrl, @NonNull String namespace, @NonNull String attribute) {
+ if (!client.supportsProjectResources()) {
+ return null;
+ }
+
+ AbstractResourceRepository resources = client.getProjectResources(project, true);
+ if (resources == null) {
+ return null;
+ }
+
+ ResourceUrl style = ResourceUrl.parse(styleUrl);
+ if (style == null || style.framework) {
+ return null;
+ }
+
+ List<ResourceValue> result = null;
+
+ Queue<ResourceValue> queue = new ArrayDeque<ResourceValue>();
+ queue.add(new ResourceValue(style.type, style.name, false));
+ Set<String> seen = Sets.newHashSet();
+ int count = 0;
+ boolean isFrameworkAttribute = ANDROID_URI.equals(namespace);
+ while (count < 30 && !queue.isEmpty()) {
+ ResourceValue front = queue.remove();
+ String name = front.getName();
+ seen.add(name);
+ List<ResourceItem> items = resources.getResourceItem(front.getResourceType(), name);
+ if (items != null) {
+ for (ResourceItem item : items) {
+ ResourceValue rv = item.getResourceValue(false);
+ if (rv instanceof StyleResourceValue) {
+ StyleResourceValue srv = (StyleResourceValue) rv;
+ ResourceValue value = srv.findValue(attribute, isFrameworkAttribute);
+ if (value != null) {
+ if (result == null) {
+ result = Lists.newArrayList();
+ }
+ if (!result.contains(value)) {
+ result.add(value);
+ }
+ }
+
+ String parent = srv.getParentStyle();
+ if (parent != null && !parent.startsWith(ANDROID_PREFIX)) {
+ ResourceUrl p = ResourceUrl.parse(parent);
+ if (p != null && !p.framework && !seen.contains(p.name)) {
+ seen.add(p.name);
+ queue.add(new ResourceValue(ResourceType.STYLE, p.name,
+ false));
+ }
+ }
+
+ int index = name.lastIndexOf('.');
+ if (index > 0) {
+ String parentName = name.substring(0, index);
+ if (!seen.contains(parentName)) {
+ seen.add(parentName);
+ queue.add(new ResourceValue(ResourceType.STYLE, parentName,
+ false));
+ }
+ }
+ }
+ }
+ }
+
+ count++;
+ }
+
+ return result;
+ }
+
+ @Nullable
+ public static List<StyleResourceValue> getInheritedStyles(
+ @NonNull Project project, @NonNull LintClient client,
+ @NonNull String styleUrl) {
+ if (!client.supportsProjectResources()) {
+ return null;
+ }
+
+ AbstractResourceRepository resources = client.getProjectResources(project, true);
+ if (resources == null) {
+ return null;
+ }
+
+ ResourceUrl style = ResourceUrl.parse(styleUrl);
+ if (style == null || style.framework) {
+ return null;
+ }
+
+ List<StyleResourceValue> result = null;
+
+ Queue<ResourceValue> queue = new ArrayDeque<ResourceValue>();
+ queue.add(new ResourceValue(style.type, style.name, false));
+ Set<String> seen = Sets.newHashSet();
+ int count = 0;
+ while (count < 30 && !queue.isEmpty()) {
+ ResourceValue front = queue.remove();
+ String name = front.getName();
+ seen.add(name);
+ List<ResourceItem> items = resources.getResourceItem(front.getResourceType(), name);
+ if (items != null) {
+ for (ResourceItem item : items) {
+ ResourceValue rv = item.getResourceValue(false);
+ if (rv instanceof StyleResourceValue) {
+ StyleResourceValue srv = (StyleResourceValue) rv;
+ if (result == null) {
+ result = Lists.newArrayList();
+ }
+ result.add(srv);
+
+ String parent = srv.getParentStyle();
+ if (parent != null && !parent.startsWith(ANDROID_PREFIX)) {
+ ResourceUrl p = ResourceUrl.parse(parent);
+ if (p != null && !p.framework && !seen.contains(p.name)) {
+ seen.add(p.name);
+ queue.add(new ResourceValue(ResourceType.STYLE, p.name,
+ false));
+ }
+ }
+
+ int index = name.lastIndexOf('.');
+ if (index > 0) {
+ String parentName = name.substring(0, index);
+ if (!seen.contains(parentName)) {
+ seen.add(parentName);
+ queue.add(new ResourceValue(ResourceType.STYLE, parentName,
+ false));
+ }
+ }
+ }
+ }
+ }
+
+ count++;
+ }
+
+ return result;
+ }
+
+ /** Returns true if the given two paths point to the same logical resource file within
+ * a source set. This means that it only checks the parent folder name and individual
+ * file name, not the path outside the parent folder.
+ *
+ * @param file1 the first file to compare
+ * @param file2 the second file to compare
+ * @return true if the two files have the same parent and file names
+ */
+ public static boolean isSameResourceFile(@Nullable File file1, @Nullable File file2) {
+ if (file1 != null && file2 != null
+ && file1.getName().equals(file2.getName())) {
+ File parent1 = file1.getParentFile();
+ File parent2 = file2.getParentFile();
+ if (parent1 != null && parent2 != null &&
+ parent1.getName().equals(parent2.getName())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Whether we should attempt to look up the prefix from the model. Set to false
+ * if we encounter a model which is too old.
+ * <p>
+ * This is public such that code which for example syncs to a new gradle model
+ * can reset it.
+ */
+ public static boolean sTryPrefixLookup = true;
+
+ /** Looks up the resource prefix for the given Gradle project, if possible */
+ @Nullable
+ public static String computeResourcePrefix(@Nullable AndroidProject project) {
+ try {
+ if (sTryPrefixLookup && project != null) {
+ return project.getResourcePrefix();
+ }
+ } catch (Exception e) {
+ // This happens if we're talking to an older model than 0.10
+ // Ignore; fall through to normal handling and never try again.
+ //noinspection AssignmentToStaticFieldFromInstanceMethod
+ sTryPrefixLookup = false;
+ }
+
+ return null;
+ }
+
+ /** Computes a suggested name given a resource prefix and resource name */
+ public static String computeResourceName(@NonNull String prefix, @NonNull String name) {
+ if (prefix.isEmpty()) {
+ return name;
+ } else if (name.isEmpty()) {
+ return prefix;
+ } else if (prefix.endsWith("_")) {
+ return prefix + name;
+ } else {
+ return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ }
+ }
+
+
+ /**
+ * Convert an {@link com.android.builder.model.ApiVersion} to a {@link
+ * com.android.sdklib.AndroidVersion}. The chief problem here is that the {@link
+ * com.android.builder.model.ApiVersion}, when using a codename, will not encode the
+ * corresponding API level (it just reflects the string entered by the user in the gradle file)
+ * so we perform a search here (since lint really wants to know the actual numeric API level)
+ *
+ * @param api the api version to convert
+ * @param targets if known, the installed targets (used to resolve platform codenames, only
+ * needed to resolve platforms newer than the tools since {@link
+ * com.android.ide.common.sdk.SdkVersionInfo} knows the rest)
+ * @return the corresponding version
+ */
+ @NonNull
+ public static AndroidVersion convertVersion(
+ @NonNull ApiVersion api,
+ @Nullable IAndroidTarget[] targets) {
+ String codename = api.getCodename();
+ if (codename != null) {
+ AndroidVersion version = SdkVersionInfo.getVersion(codename, targets);
+ if (version != null) {
+ return version;
+ }
+ return new AndroidVersion(api.getApiLevel(), codename);
+ }
+ return new AndroidVersion(api.getApiLevel(), null);
+ }
+
+ /**
+ * Returns true if the given Gradle model is older than the given version number
+ */
+ public static boolean isModelOlderThan(@Nullable AndroidProject project,
+ int major, int minor, int micro) {
+ if (project != null) {
+ String modelVersion = project.getModelVersion();
+ try {
+ FullRevision version = FullRevision.parseRevision(modelVersion);
+ if (version.getMajor() != major) {
+ return version.getMajor() < major;
+ }
+ if (version.getMinor() != minor) {
+ return version.getMinor() < minor;
+ }
+ return version.getMicro() < micro;
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+ }
+
+ return false;
+ }
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
index c20f72b..6c4c268 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
@@ -18,6 +18,8 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
import com.google.common.annotations.Beta;
import java.io.File;
@@ -594,6 +596,33 @@
}
}
+ public static class ResourceItemHandle implements Handle {
+ private final ResourceItem mItem;
+
+ public ResourceItemHandle(@NonNull ResourceItem item) {
+ mItem = item;
+ }
+ @NonNull
+ @Override
+ public Location resolve() {
+ // TODO: Look up the exact item location more
+ // closely
+ ResourceFile source = mItem.getSource();
+ assert source != null : mItem;
+ return Location.create(source.getFile());
+ }
+
+ @Override
+ public void setClientData(@Nullable Object clientData) {
+ }
+
+ @Nullable
+ @Override
+ public Object getClientData() {
+ return null;
+ }
+ }
+
/**
* Whether to look forwards, or backwards, or in both directions, when
* searching for a pattern in the source code to determine the right
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 ca0c5ec..53c9ae2 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
@@ -20,6 +20,7 @@
import static com.android.SdkConstants.ANDROID_LIBRARY_REFERENCE_FORMAT;
import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT;
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;
@@ -28,16 +29,20 @@
import static com.android.SdkConstants.PROGUARD_CONFIG;
import static com.android.SdkConstants.PROJECT_PROPERTIES;
import static com.android.SdkConstants.RES_FOLDER;
+import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT;
import static com.android.SdkConstants.TAG_USES_SDK;
import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.ide.common.sdk.SdkVersionInfo.HIGHEST_KNOWN_API;
+import com.android.SdkConstants;
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.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
import com.android.tools.lint.client.api.CircularDependencyException;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.LintClient;
@@ -46,6 +51,7 @@
import com.google.common.base.CharMatcher;
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.io.Closeables;
import com.google.common.io.Files;
@@ -81,9 +87,12 @@
protected final File mReferenceDir;
protected Configuration mConfiguration;
protected String mPackage;
- protected int mMinSdk = 1;
- protected int mTargetSdk = -1;
protected int mBuildSdk = -1;
+ protected IAndroidTarget mTarget;
+
+ protected AndroidVersion mManifestMinSdk = AndroidVersion.DEFAULT;
+ protected AndroidVersion mManifestTargetSdk = AndroidVersion.DEFAULT;
+
protected boolean mLibrary;
protected String mName;
protected String mProguardPath;
@@ -98,6 +107,7 @@
*/
protected List<File> mFiles;
protected List<File> mProguardFiles;
+ protected List<File> mGradleFiles;
protected List<File> mManifestFiles;
protected List<File> mJavaSourceFolders;
protected List<File> mJavaClassFolders;
@@ -107,6 +117,8 @@
protected List<Project> mAllLibraries;
protected boolean mReportIssues = true;
protected Boolean mGradleProject;
+ protected Boolean mSupportLib;
+ protected Boolean mAppCompat;
/**
* Creates a new {@link Project} for the given directory.
@@ -138,6 +150,16 @@
}
/**
+ * Returns true if this project is an Android project.
+ *
+ * @return true if this project is an Android project.
+ */
+ @SuppressWarnings("MethodMayBeStatic")
+ public boolean isAndroidProject() {
+ return true;
+ }
+
+ /**
* 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.
@@ -192,7 +214,6 @@
Properties properties = new 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));
try {
properties.load(is);
@@ -269,7 +290,11 @@
}
}
} finally {
- Closeables.closeQuietly(is);
+ try {
+ Closeables.close(is, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
}
} catch (IOException ioe) {
@@ -424,7 +449,7 @@
if (folders.size() == 1 && isAospFrameworksProject(mDir)) {
// No manifest file for this project: just init the manifest values here
- mMinSdk = mTargetSdk = SdkVersionInfo.HIGHEST_KNOWN_API;
+ mManifestMinSdk = mManifestTargetSdk = new AndroidVersion(HIGHEST_KNOWN_API, null);
File folder = new File(folders.get(0), RES_FOLDER);
if (!folder.exists()) {
folders = Collections.emptyList();
@@ -535,29 +560,48 @@
}
/**
- * Returns the minimum API level requested by the manifest, or -1 if not
- * specified
+ * Returns the minimum API level for the project
+ *
+ * @return the minimum API level or {@link AndroidVersion#DEFAULT} if unknown
+ */
+ @NonNull
+ public AndroidVersion getMinSdkVersion() {
+ return mManifestMinSdk == null ? AndroidVersion.DEFAULT : mManifestMinSdk;
+ }
+
+ /**
+ * Returns the minimum API <b>level</b> requested by the manifest, or -1 if not
+ * specified. Use {@link #getMinSdkVersion()} to get a full version if you need
+ * to check if the platform is a preview platform etc.
*
* @return the minimum API level or -1 if unknown
*/
public int getMinSdk() {
- //assert !mLibrary; // Should call getMinSdk on the master project, not the library
- // Assertion disabled because you might be running lint on a standalone library project.
-
- return mMinSdk;
+ AndroidVersion version = getMinSdkVersion();
+ return version == AndroidVersion.DEFAULT ? -1 : version.getApiLevel();
}
/**
- * Returns the target API level specified by the manifest, or -1 if not
- * specified
+ * Returns the target API level for the project
+ *
+ * @return the target API level or {@link AndroidVersion#DEFAULT} if unknown
+ */
+ @NonNull
+ public AndroidVersion getTargetSdkVersion() {
+ return mManifestTargetSdk == AndroidVersion.DEFAULT
+ ? getMinSdkVersion() : mManifestTargetSdk;
+ }
+
+ /**
+ * Returns the target API <b>level</b> specified by the manifest, or -1 if not
+ * specified. Use {@link #getTargetSdkVersion()} to get a full version if you need
+ * to check if the platform is a preview platform etc.
*
* @return the target API level or -1 if unknown
*/
public int getTargetSdk() {
- //assert !mLibrary; // Should call getTargetSdk on the master project, not the library
- // Assertion disabled because you might be running lint on a standalone library project.
-
- return mTargetSdk;
+ AndroidVersion version = getTargetSdkVersion();
+ return version == AndroidVersion.DEFAULT ? -1 : version.getApiLevel();
}
/**
@@ -570,6 +614,20 @@
}
/**
+ * Returns the target used to build the project, or null if not known
+ *
+ * @return the build target, or null
+ */
+ @Nullable
+ public IAndroidTarget getBuildTarget() {
+ if (mTarget == null) {
+ mTarget = mClient.getCompileTarget(this);
+ }
+
+ return mTarget;
+ }
+
+ /**
* Initialized the manifest state from the given manifest model
*
* @param document the DOM document for the manifest XML document
@@ -583,6 +641,8 @@
mPackage = root.getAttribute(ATTR_PACKAGE);
// Initialize minSdk and targetSdk
+ mManifestMinSdk = AndroidVersion.DEFAULT;
+ mManifestTargetSdk = AndroidVersion.DEFAULT;
NodeList usesSdks = root.getElementsByTagName(TAG_USES_SDK);
if (usesSdks.getLength() > 0) {
Element element = (Element) usesSdks.item(0);
@@ -592,29 +652,22 @@
minSdk = element.getAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION);
}
if (minSdk != null) {
- try {
- mMinSdk = Integer.valueOf(minSdk);
- } catch (NumberFormatException e) {
- // Codename?
- mMinSdk = SdkVersionInfo.getApiByPreviewName(minSdk, true);
- }
+ IAndroidTarget[] targets = mClient.getTargets();
+ mManifestMinSdk = SdkVersionInfo.getVersion(minSdk, targets);
}
- String targetSdk = null;
if (element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) {
- targetSdk = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
- } else if (minSdk != null) {
- targetSdk = minSdk;
- }
- if (targetSdk != null) {
- try {
- mTargetSdk = Integer.valueOf(targetSdk);
- } catch (NumberFormatException e) {
- mTargetSdk = SdkVersionInfo.getApiByPreviewName(minSdk, false);
+ String targetSdk = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
+ if (targetSdk != null) {
+ IAndroidTarget[] targets = mClient.getTargets();
+ mManifestTargetSdk = SdkVersionInfo.getVersion(targetSdk, targets);
}
+ } else {
+ mManifestTargetSdk = mManifestMinSdk;
}
} else if (isAospBuildEnvironment()) {
extractAospMinSdkVersion();
+ mManifestTargetSdk = mManifestMinSdk;
}
}
@@ -765,6 +818,32 @@
}
/**
+ * Returns the Gradle build script files configured for this project, if any
+ *
+ * @return the Gradle files, if any
+ */
+ @NonNull
+ public List<File> getGradleBuildScripts() {
+ if (mGradleFiles == null) {
+ if (isGradleProject()) {
+ mGradleFiles = Lists.newArrayListWithExpectedSize(2);
+ File build = new File(mDir, SdkConstants.FN_BUILD_GRADLE);
+ if (build.exists()) {
+ mGradleFiles.add(build);
+ }
+ File settings = new File(mDir, SdkConstants.FN_SETTINGS_GRADLE);
+ if (settings.exists()) {
+ mGradleFiles.add(settings);
+ }
+ } else {
+ mGradleFiles = Collections.emptyList();
+ }
+ }
+
+ return mGradleFiles;
+ }
+
+ /**
* Returns the name of the project
*
* @return the name of the project, never null
@@ -846,7 +925,7 @@
sAospBuild = getAospTop() != null;
}
- return sAospBuild.booleanValue();
+ return sAospBuild;
}
/**
@@ -989,14 +1068,10 @@
found = true;
String version = matcher.group(1);
if (version.equals("current")) { //$NON-NLS-1$
- mMinSdk = findCurrentAospVersion();
+ mManifestMinSdk = findCurrentAospVersion();
} else {
- try {
- mMinSdk = Integer.valueOf(version);
- } catch (NumberFormatException e) {
- // Codename - just use current
- mMinSdk = findCurrentAospVersion();
- }
+ mManifestMinSdk = SdkVersionInfo.getVersion(version,
+ mClient.getTargets());
}
break;
}
@@ -1007,21 +1082,21 @@
}
if (!found) {
- mMinSdk = findCurrentAospVersion();
+ mManifestMinSdk = findCurrentAospVersion();
}
}
/** Cache for {@link #findCurrentAospVersion()} */
- private static int sCurrentVersion;
+ private static AndroidVersion sCurrentVersion;
/** In an AOSP build environment, identify the currently built image version, if available */
- private static int findCurrentAospVersion() {
- if (sCurrentVersion < 1) {
+ private static AndroidVersion findCurrentAospVersion() {
+ if (sCurrentVersion == null) {
File apiDir = new File(getAospTop(), "frameworks/base/api" //$NON-NLS-1$
.replace('/', File.separatorChar));
File[] apiFiles = apiDir.listFiles();
if (apiFiles == null) {
- sCurrentVersion = 1;
+ sCurrentVersion = AndroidVersion.DEFAULT;
return sCurrentVersion;
}
int max = 1;
@@ -1042,9 +1117,75 @@
}
}
}
- sCurrentVersion = max;
+ sCurrentVersion = new AndroidVersion(max, null);
}
return sCurrentVersion;
}
+
+ /**
+ * Returns true if this project depends on the given artifact. Note that
+ * the project doesn't have to be a Gradle project; the artifact is just
+ * an identifier for name a specific library, such as com.android.support:support-v4
+ * to identify the support library
+ *
+ * @param artifact the Gradle/Maven name of a library
+ * @return true if the library is installed, false if it is not, and null if
+ * we're not sure
+ */
+ @Nullable
+ public Boolean dependsOn(@NonNull String artifact) {
+ if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
+ if (mSupportLib == null) {
+ for (File file : getJavaLibraries()) {
+ String name = file.getName();
+ if (name.equals("android-support-v4.jar") //$NON-NLS-1$
+ || name.startsWith("support-v4-")) { //$NON-NLS-1$
+ mSupportLib = true;
+ break;
+ }
+ }
+ if (mSupportLib == null) {
+ for (Project dependency : getDirectLibraries()) {
+ Boolean b = dependency.dependsOn(artifact);
+ if (b != null && b) {
+ mSupportLib = true;
+ break;
+ }
+ }
+ }
+ if (mSupportLib == null) {
+ mSupportLib = false;
+ }
+ }
+
+ return mSupportLib;
+ } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
+ if (mAppCompat == null) {
+ for (File file : getJavaLibraries()) {
+ String name = file.getName();
+ if (name.startsWith("appcompat-v7-")) { //$NON-NLS-1$
+ mAppCompat = true;
+ break;
+ }
+ }
+ if (mAppCompat == null) {
+ for (Project dependency : getDirectLibraries()) {
+ Boolean b = dependency.dependsOn(artifact);
+ if (b != null && b) {
+ mAppCompat = true;
+ break;
+ }
+ }
+ }
+ if (mAppCompat == null) {
+ mAppCompat = false;
+ }
+ }
+
+ return mAppCompat;
+ }
+
+ return null;
+ }
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceContext.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceContext.java
new file mode 100644
index 0000000..dd78ffa
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceContext.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.detector.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.client.api.LintDriver;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Splitter;
+
+import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link com.android.tools.lint.detector.api.Context} used when checking resource files
+ * (both bitmaps and XML files; for XML files a subclass of this context is used:
+ * {@link com.android.tools.lint.detector.api.XmlContext}.)
+ * <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>
+ */
+@Beta
+public class ResourceContext extends Context {
+ private final ResourceFolderType mFolderType;
+
+ /**
+ * Construct a new {@link com.android.tools.lint.detector.api.ResourceContext}
+ *
+ * @param driver the driver running through the checks
+ * @param project the project containing the file being checked
+ * @param main the main project if this project is a library project, or
+ * null if this is not a library project. The main project is
+ * the root project of all library projects, not necessarily the
+ * directly including project.
+ * @param file the file being checked
+ * @param folderType the {@link com.android.resources.ResourceFolderType} of this file, if any
+ */
+ public ResourceContext(
+ @NonNull LintDriver driver,
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull File file,
+ @Nullable ResourceFolderType folderType) {
+ super(driver, project, main, file);
+ mFolderType = folderType;
+ }
+
+ /**
+ * Returns the resource folder type of this XML file, if any.
+ *
+ * @return the resource folder type or null
+ */
+ @Nullable
+ public ResourceFolderType getResourceFolderType() {
+ return mFolderType;
+ }
+
+ /** Pattern for version qualifiers */
+ private static final Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)$"); //$NON-NLS-1$
+
+ private static File sCachedFolder = null;
+ private static int sCachedFolderVersion = -1;
+
+ /**
+ * Returns the folder version. For example, for the file values-v14/foo.xml,
+ * it returns 14.
+ *
+ * @return the folder version, or -1 if no specific version was specified
+ */
+ public int getFolderVersion() {
+ return getFolderVersion(file);
+ }
+
+ /**
+ * Returns the folder version of the given file. For example, for the file values-v14/foo.xml,
+ * it returns 14.
+ *
+ * @param file the file to be checked
+ * @return the folder version, or -1 if no specific version was specified
+ */
+ public static int getFolderVersion(File file) {
+ File parent = file.getParentFile();
+ if (parent.equals(sCachedFolder)) {
+ return sCachedFolderVersion;
+ }
+
+ sCachedFolder = parent;
+ sCachedFolderVersion = -1;
+
+ for (String qualifier : Splitter.on('-').split(parent.getName())) {
+ Matcher matcher = VERSION_PATTERN.matcher(qualifier);
+ if (matcher.matches()) {
+ sCachedFolderVersion = Integer.parseInt(matcher.group(1));
+ break;
+ }
+ }
+
+ return sCachedFolderVersion;
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java
index 68685c6..bd57407 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java
@@ -47,6 +47,7 @@
* @return true if this detector can apply to resources in folders of the
* given type
*/
+ @Override
public boolean appliesTo(@NonNull ResourceFolderType folderType) {
return true;
}
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 681d5ed..35e1254 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
@@ -18,7 +18,10 @@
import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_GRADLE;
import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.DOT_PNG;
+import static com.android.SdkConstants.DOT_PROPERTIES;
import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
import static com.android.SdkConstants.OLD_PROGUARD_FILE;
@@ -51,6 +54,19 @@
RESOURCE_FILE,
/**
+ * The analysis only considers a single binary (typically a bitmap) resource file at a time.
+ * <p>
+ * Issues which are only affected by a single resource file can be checked
+ * for incrementally when a file is edited.
+ */
+ BINARY_RESOURCE_FILE,
+
+ /**
+ * The analysis considers the resource folders
+ */
+ RESOURCE_FOLDER,
+
+ /**
* The analysis considers <b>all</b> the resource file. This scope must not
* be used in conjunction with {@link #RESOURCE_FILE}; an issue scope is
* either considering just a single resource file or all the resources, not
@@ -100,6 +116,12 @@
*/
JAVA_LIBRARIES,
+ /** The analysis considers a Gradle build file */
+ GRADLE_FILE,
+
+ /** The analysis considers Java property files */
+ PROPERTY_FILE,
+
/**
* Scope for other files. Issues that specify a custom scope will be called unconditionally.
* This will call {@link Detector#run(Context)}} on the detectors unconditionally.
@@ -125,6 +147,8 @@
|| scopes.contains(CLASS_FILE)
|| scopes.contains(RESOURCE_FILE)
|| scopes.contains(PROGUARD_FILE)
+ || scopes.contains(PROPERTY_FILE)
+ || scopes.contains(GRADLE_FILE)
|| scopes.contains(MANIFEST));
}
}
@@ -164,17 +188,25 @@
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.endsWith(DOT_GRADLE)) {
+ scope.add(GRADLE_FILE);
} else if (name.equals(OLD_PROGUARD_FILE)
|| name.equals(FN_PROJECT_PROGUARD_FILE)) {
scope.add(PROGUARD_FILE);
+ } else if (name.endsWith(DOT_PROPERTIES)) {
+ scope.add(PROPERTY_FILE);
+ } else if (name.endsWith(DOT_PNG)) {
+ scope.add(BINARY_RESOURCE_FILE);
+ } else if (name.equals(RES_FOLDER)
+ || file.getParent().equals(RES_FOLDER)) {
+ scope.add(ALL_RESOURCE_FILES);
+ scope.add(RESOURCE_FILE);
+ scope.add(BINARY_RESOURCE_FILE);
+ scope.add(RESOURCE_FOLDER);
}
}
} else {
@@ -191,24 +223,36 @@
public static final EnumSet<Scope> ALL = EnumSet.allOf(Scope.class);
/** Scope-set used for detectors which are affected by a single resource file */
public static final EnumSet<Scope> RESOURCE_FILE_SCOPE = EnumSet.of(RESOURCE_FILE);
+ /** Scope-set used for detectors which are affected by a single resource folder */
+ public static final EnumSet<Scope> RESOURCE_FOLDER_SCOPE = EnumSet.of(RESOURCE_FOLDER);
/** Scope-set used for detectors which scan all resources */
public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES);
/** Scope-set used for detectors which are affected by a single Java source file */
public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(JAVA_FILE);
/** Scope-set used for detectors which are affected by a single Java class file */
public static final EnumSet<Scope> CLASS_FILE_SCOPE = EnumSet.of(CLASS_FILE);
+ /** Scope-set used for detectors which are affected by a single Gradle build file */
+ public static final EnumSet<Scope> GRADLE_SCOPE = EnumSet.of(GRADLE_FILE);
/** Scope-set used for detectors which are affected by the manifest only */
public static final EnumSet<Scope> MANIFEST_SCOPE = EnumSet.of(MANIFEST);
/** Scope-set used for detectors which correspond to some other context */
public static final EnumSet<Scope> OTHER_SCOPE = EnumSet.of(OTHER);
/** Scope-set used for detectors which are affected by a single ProGuard class file */
public static final EnumSet<Scope> PROGUARD_SCOPE = EnumSet.of(PROGUARD_FILE);
+ /** Scope-set used for detectors which correspond to property files */
+ public static final EnumSet<Scope> PROPERTY_SCOPE = EnumSet.of(PROPERTY_FILE);
/** Scope-set used for detectors which are affected by single XML and Java source files */
public static final EnumSet<Scope> JAVA_AND_RESOURCE_FILES =
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(ALL_RESOURCE_FILES, CLASS_FILE);
+ /** Scope-set used for analyzing all class files, including those in libraries */
+ public static final EnumSet<Scope> ALL_CLASSES_AND_LIBRARIES =
+ EnumSet.of(Scope.ALL_CLASS_FILES, Scope.JAVA_LIBRARIES);
/** Scope-set used for detectors which are affected by Java libraries */
public static final EnumSet<Scope> JAVA_LIBRARY_SCOPE = EnumSet.of(JAVA_LIBRARIES);
+ /** Scope-set used for detectors which are affected by a single binary resource file */
+ public static final EnumSet<Scope> BINARY_RESOURCE_FILE_SCOPE =
+ EnumSet.of(BINARY_RESOURCE_FILE);
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Severity.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Severity.java
index f74e6b5..e0f90f0 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Severity.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Severity.java
@@ -17,6 +17,7 @@
package com.android.tools.lint.detector.api;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.google.common.annotations.Beta;
/**
@@ -75,4 +76,28 @@
public String getDescription() {
return mDisplay;
}
+
+ /** Returns the name of this severity */
+ @NonNull
+ public String getName() {
+ return name();
+ }
+
+ /**
+ * Looks up the severity corresponding to a given named severity. The severity
+ * string should be one returned by {@link #toString()}
+ *
+ * @param name the name to look up
+ * @return the corresponding severity, or null if it is not a valid severity name
+ */
+ @Nullable
+ public static Severity fromName(@NonNull String name) {
+ for (Severity severity : values()) {
+ if (severity.name().equalsIgnoreCase(name)) {
+ return severity;
+ }
+ }
+
+ return null;
+ }
}
\ No newline at end of file
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Speed.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Speed.java
index c68dab0..4ef1eb1 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Speed.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Speed.java
@@ -34,7 +34,10 @@
NORMAL("Normal"),
/** The detector might take a long time to run */
- SLOW("Slow");
+ SLOW("Slow"),
+
+ /** The detector might take a huge amount of time to run */
+ REALLY_SLOW("Really Slow");
private final String mDisplayName;
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java
index 34d6816..c519848 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java
@@ -19,8 +19,8 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.IDomParser;
import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.client.api.XmlParser;
import com.google.common.annotations.Beta;
import org.w3c.dom.Document;
@@ -35,12 +35,13 @@
* to adjust your code for the next tools release.</b>
*/
@Beta
-public class XmlContext extends Context {
+public class XmlContext extends ResourceContext {
+ static final String SUPPRESS_COMMENT_PREFIX = "<!--suppress "; //$NON-NLS-1$
+
/** The XML parser */
- public IDomParser parser;
+ private final XmlParser mParser;
/** The XML document */
public Document document;
- private final ResourceFolderType mFolderType;
/**
* Construct a new {@link XmlContext}
@@ -59,9 +60,10 @@
@NonNull Project project,
@Nullable Project main,
@NonNull File file,
- @Nullable ResourceFolderType folderType) {
- super(driver, project, main, file);
- mFolderType = folderType;
+ @Nullable ResourceFolderType folderType,
+ @NonNull XmlParser parser) {
+ super(driver, project, main, file, folderType);
+ mParser = parser;
}
/**
@@ -72,11 +74,7 @@
*/
@NonNull
public Location getLocation(@NonNull Node node) {
- if (parser != null) {
- return parser.getLocation(this, node);
- }
-
- return Location.create(file);
+ return mParser.getLocation(this, node);
}
/**
@@ -90,13 +88,13 @@
@NonNull
public Location getLocation(@NonNull Node textNode, int begin, int end) {
assert textNode.getNodeType() == Node.TEXT_NODE;
- if (parser != null) {
- return parser.getLocation(this, textNode, begin, end);
- }
-
- return Location.create(file);
+ return mParser.getLocation(this, textNode, begin, end);
}
+ @NonNull
+ public XmlParser getParser() {
+ return mParser;
+ }
/**
* Reports an issue applicable to a given DOM node. The DOM node is used as the
@@ -116,7 +114,7 @@
@Nullable Location location,
@NonNull String message,
@Nullable Object data) {
- if (scope != null && mDriver.isSuppressed(issue, scope)) {
+ if (scope != null && mDriver.isSuppressed(this, issue, scope)) {
return;
}
super.report(issue, location, message, data);
@@ -135,20 +133,34 @@
// + " was reported without a scope node: Can't be suppressed.");
// For now just check the document root itself
- if (document != null && mDriver.isSuppressed(issue, document)) {
+ if (document != null && mDriver.isSuppressed(this, issue, document)) {
return;
}
super.report(issue, location, message, data);
}
- /**
- * Returns the resource folder type of this XML file, if any.
- *
- * @return the resource folder type or null
- */
+ @Override
@Nullable
- public ResourceFolderType getResourceFolderType() {
- return mFolderType;
+ protected String getSuppressCommentPrefix() {
+ return SUPPRESS_COMMENT_PREFIX;
+ }
+
+ public boolean isSuppressedWithComment(@NonNull Node node, @NonNull Issue issue) {
+ // Check whether there is a comment marker
+ String contents = getContents();
+ assert contents != null; // otherwise we wouldn't be here
+
+ int start = mParser.getNodeStartOffset(this, node);
+ if (start != -1) {
+ return isSuppressedWithComment(start, issue);
+ }
+
+ return false;
+ }
+
+ @NonNull
+ public Location.Handle createLocationHandle(@NonNull Node node) {
+ return mParser.createLocationHandle(this, node);
}
}
diff --git a/lint/libs/lint-checks/build.gradle b/lint/libs/lint-checks/build.gradle
index e0b6ed0..82ad376 100644
--- a/lint/libs/lint-checks/build.gradle
+++ b/lint/libs/lint-checks/build.gradle
@@ -1,16 +1,17 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools.lint'
archivesBaseName = 'lint-checks'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':lint-api')
+ compile project(':base:lint-api')
compile 'org.ow2.asm:asm-analysis:4.0'
testCompile 'org.easymock:easymock:3.1'
testCompile 'junit:junit:3.8.1'
- testCompile project(':testutils')
+ testCompile project(':base:testutils')
}
sourceSets {
@@ -21,7 +22,6 @@
project.ext.pomName = 'Android Lint Checks'
project.ext.pomDesc = 'Checks for Android Lint'
-apply from: '../../../baseVersion.gradle'
-apply from: '../../../publish.gradle'
-apply from: '../../../javadoc.gradle'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetector.java
new file mode 100644
index 0000000..03425b5
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetector.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tools.lint.client.api.JavaParser.ResolvedMethod;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_OBJECT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+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.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.MethodInvocation;
+
+/**
+ * Ensures that addJavascriptInterface is not called for API levels below 17.
+ */
+public class AddJavascriptInterfaceDetector extends Detector implements Detector.JavaScanner {
+ public static final Issue ISSUE = Issue.create(
+ "AddJavascriptInterface", //$NON-NLS-1$
+ "addJavascriptInterface Called",
+ "Checks that `WebView#addJavascriptInterface` is not called for API levels below 17",
+ "For applications built for API levels below 17, `WebView#addJavascriptInterface` "
+ + "presents a security hazard as JavaScript on the target web page has the "
+ + "ability to use reflection to access the injected object's public fields and "
+ + "thus manipulate the host application in unintended ways.",
+ Category.SECURITY,
+ 9,
+ Severity.WARNING,
+ new Implementation(
+ AddJavascriptInterfaceDetector.class,
+ Scope.JAVA_FILE_SCOPE)).
+ addMoreInfo(
+ "https://labs.mwrinfosecurity.com/blog/2013/09/24/webview-addjavascriptinterface-remote-code-execution/");
+
+ private static final String WEB_VIEW = "android.webkit.WebView"; //$NON-NLS-1$
+ private static final String ADD_JAVASCRIPT_INTERFACE = "addJavascriptInterface"; //$NON-NLS-1$
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList(ADD_JAVASCRIPT_INTERFACE);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ // Ignore the issue if we never build for any API less than 17.
+ if (context.getMainProject().getMinSdk() >= 17) {
+ return;
+ }
+
+ // Ignore if the method doesn't fit our description.
+ ResolvedNode resolved = context.resolve(node);
+ if (!(resolved instanceof ResolvedMethod)) {
+ return;
+ }
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (!method.getContainingClass().isSubclassOf(WEB_VIEW, false)) {
+ return;
+ }
+ if (method.getArgumentCount() != 2
+ || !method.getArgumentType(0).matchesName(TYPE_OBJECT)
+ || !method.getArgumentType(1).matchesName(TYPE_STRING)) {
+ return;
+ }
+
+ String message = "WebView.addJavascriptInterface should not be called";
+ context.report(ISSUE, node, context.getLocation(node.astName()), message, null);
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java
index 64194b0..d8d9c8e 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java
@@ -54,7 +54,6 @@
*/
public class AlwaysShowActionDetector extends ResourceXmlDetector implements JavaScanner {
-
/** The main issue discovered by this detector */
public static final Issue ISSUE = Issue.create(
"AlwaysShowAction", //$NON-NLS-1$
@@ -184,7 +183,7 @@
@Override
public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- if (context.getDriver().isSuppressed(ISSUE, attribute)) {
+ if (context.getDriver().isSuppressed(context, ISSUE, attribute)) {
mIgnoreFile = true;
return;
}
@@ -223,7 +222,7 @@
if ((isIfRoom || isAlways)
&& node.astOperand().toString().equals("MenuItem")) { //$NON-NLS-1$
if (isAlways) {
- if (mContext.getDriver().isSuppressed(ISSUE, node)) {
+ if (mContext.getDriver().isSuppressed(mContext, ISSUE, node)) {
return super.visitSelect(node);
}
if (mAlwaysFields == null) {
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 efd36f4..3414182 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
@@ -18,8 +18,13 @@
import static com.android.SdkConstants.ANDROID_PREFIX;
import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PARENT;
import static com.android.SdkConstants.ATTR_TARGET_API;
import static com.android.SdkConstants.CONSTRUCTOR_NAME;
import static com.android.SdkConstants.PREFIX_ANDROID;
@@ -40,6 +45,8 @@
import com.android.annotations.Nullable;
import com.android.ide.common.sdk.SdkVersionInfo;
import com.android.resources.ResourceFolderType;
+import com.android.sdklib.AndroidVersion;
+import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.client.api.LintDriver;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.ClassContext;
@@ -81,6 +88,7 @@
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
@@ -112,6 +120,7 @@
import lombok.ast.StringLiteral;
import lombok.ast.SuperConstructorInvocation;
import lombok.ast.Switch;
+import lombok.ast.Try;
import lombok.ast.TypeReference;
import lombok.ast.VariableDefinition;
import lombok.ast.VariableDefinitionEntry;
@@ -136,6 +145,7 @@
private static final boolean AOSP_BUILD = System.getenv("ANDROID_BUILD_TOP") != null; //$NON-NLS-1$
/** Accessing an unsupported API */
+ @SuppressWarnings("unchecked")
public static final Issue UNSUPPORTED = Issue.create(
"NewApi", //$NON-NLS-1$
"Calling new methods on older versions",
@@ -169,7 +179,8 @@
ApiDetector.class,
EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE, Scope.MANIFEST),
Scope.RESOURCE_FILE_SCOPE,
- Scope.CLASS_FILE_SCOPE));
+ Scope.CLASS_FILE_SCOPE,
+ Scope.MANIFEST_SCOPE));
/** Accessing an inlined API on older platforms */
public static final Issue INLINED = Issue.create(
@@ -232,11 +243,35 @@
ApiDetector.class,
Scope.CLASS_FILE_SCOPE));
+ /** Accessing an inlined API on older platforms */
+ public static final Issue UNUSED = Issue.create(
+ "UnusedAttribute", //$NON-NLS-1$
+ "Attribute unused on older versions",
+ "Finds usages of attributes that will not be used (read) on all targeted versions",
+
+ "This check finds attributes set in XML files that were introduced in a version " +
+ "newer than the oldest version targeted by your application (with the the " +
+ "`minSdkVersion` attribute).\n" +
+ "\n" +
+ "This is not an error; the application will simply ignore the attribute. However, " +
+ "if the attribute is important to the appearance of functionality of your " +
+ "application, you should consider finding an alternative way to achieve the " +
+ "same result with only available attributes, and then you can optionally create " +
+ "a copy of the layout in a layout-vNN folder which will be used on API NN or " +
+ "higher where you can take advantage of the newer attribute.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ ApiDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
private static final String TARGET_API_VMSIG = '/' + TARGET_API + ';';
private static final String SWITCH_TABLE_PREFIX = "$SWITCH_TABLE$"; //$NON-NLS-1$
private static final String ORDINAL_METHOD = "ordinal"; //$NON-NLS-1$
protected ApiLookup mApiDatabase;
+ private boolean mWarnedMissingDb;
private int mMinApi = -1;
private Map<String, List<Pair<String, Location>>> mPendingFields;
@@ -257,6 +292,12 @@
// The manifest file hasn't been processed yet in the -before- project hook.
// For now it's initialized lazily in getMinSdk(Context), but the
// lint infrastructure should be fixed to parse manifest file up front.
+
+ if (mApiDatabase == null && !mWarnedMissingDb) {
+ mWarnedMissingDb = true;
+ context.report(IssueRegistry.LINT_ERROR, Location.create(context.file),
+ "Can't find API database; API check not performed", null);
+ }
}
// ---- Implements XmlScanner ----
@@ -282,11 +323,43 @@
return;
}
- String value = attribute.getValue();
+ int attributeApiLevel = -1;
+ if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ String name = attribute.getLocalName();
+ if (!(name.equals(ATTR_LAYOUT_WIDTH) && !(name.equals(ATTR_LAYOUT_HEIGHT)) &&
+ !(name.equals(ATTR_ID)))) {
+ String owner = "android/R$attr"; //$NON-NLS-1$
+ attributeApiLevel = mApiDatabase.getFieldVersion(owner, name);
+ int minSdk = getMinSdk(context);
+ if (attributeApiLevel > minSdk && attributeApiLevel > context.getFolderVersion()
+ && attributeApiLevel > getLocalMinSdk(attribute.getOwnerElement())
+ // No need to warn for example that
+ // "layout_alignParentStart will only be used in API level 17 and higher"
+ // since we have a dedicated RTL lint rule dealing with those attributes
+ && !RtlDetector.isRtlAttributeName(name)) {
+ Location location = context.getLocation(attribute);
+ String message = String.format(
+ "Attribute \"%1$s\" is only used in API level %2$d and higher "
+ + "(current min is %3$d)",
+ attribute.getLocalName(), attributeApiLevel, minSdk
+ );
+ context.report(UNUSED, attribute, location, message, null);
+ }
+ }
+ // Special case:
+ // the dividers attribute is present in API 1, but it won't be read on older
+ // versions, so don't flag the common pattern
+ // android:divider="?android:attr/dividerHorizontal"
+ // since this will work just fine. See issue 67440 for more.
+ if (name.equals("divider")) {
+ return;
+ }
+ }
+
+ String value = attribute.getValue();
String owner = null;
String name = null;
-
String prefix;
if (value.startsWith(ANDROID_PREFIX)) {
prefix = ANDROID_PREFIX;
@@ -298,7 +371,12 @@
&& TAG_STYLE.equals(attribute.getOwnerElement().getParentNode().getNodeName())) {
owner = "android/R$attr"; //$NON-NLS-1$
name = value.substring(PREFIX_ANDROID.length());
- prefix = PREFIX_ANDROID;
+ prefix = null;
+ } else if (value.startsWith(PREFIX_ANDROID) && ATTR_PARENT.equals(attribute.getName())
+ && TAG_STYLE.equals(attribute.getOwnerElement().getTagName())) {
+ owner = "android/R$style"; //$NON-NLS-1$
+ name = getFieldName(value.substring(PREFIX_ANDROID.length()));
+ prefix = null;
} else {
return;
}
@@ -309,10 +387,7 @@
if (index != -1) {
owner = "android/R$" //$NON-NLS-1$
+ value.substring(prefix.length(), index);
- name = value.substring(index + 1);
- if (name.indexOf('.') != -1) {
- name = name.replace('.', '_');
- }
+ name = getFieldName(value.substring(index + 1));
} else if (value.startsWith(ANDROID_THEME_PREFIX)) {
owner = "android/R$attr"; //$NON-NLS-1$
name = value.substring(ANDROID_THEME_PREFIX.length());
@@ -332,14 +407,35 @@
return;
}
- Location location = context.getLocation(attribute);
- String message = String.format(
- "%1$s requires API level %2$d (current min is %3$d)",
- value, api, minSdk);
- context.report(UNSUPPORTED, attribute, location, message, null);
+ //noinspection StatementWithEmptyBody
+ if (attributeApiLevel >= api) {
+ // The attribute will only be *read* on platforms >= attributeApiLevel.
+ // If this isn't lower than the attribute reference's API level, it
+ // won't be a problem
+ } else if (attributeApiLevel > minSdk) {
+ String attributeName = attribute.getLocalName();
+ Location location = context.getLocation(attribute);
+ String message = String.format(
+ "%1$s requires API level %2$d (current min is %3$d), but note "
+ + "that attribute %4$s is only used in API level %5$d "
+ + "and higher",
+ name, api, minSdk, attributeName, attributeApiLevel
+ );
+ context.report(UNSUPPORTED, attribute, location, message, null);
+ } else {
+ Location location = context.getLocation(attribute);
+ String message = String.format(
+ "%1$s requires API level %2$d (current min is %3$d)",
+ value, api, minSdk);
+ context.report(UNSUPPORTED, attribute, location, message, null);
+ }
}
}
+ private static String getFieldName(String styleName) {
+ return styleName.replace('.', '_').replace('-', '_').replace(':', '_');
+ }
+
@Override
public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
if (mApiDatabase == null) {
@@ -359,7 +455,7 @@
Node textNode = childNodes.item(i);
if (textNode.getNodeType() == Node.TEXT_NODE) {
String text = textNode.getNodeValue();
- if (text.indexOf(ANDROID_PREFIX) != -1) {
+ if (text.contains(ANDROID_PREFIX)) {
text = text.trim();
// Convert @android:type/foo into android/R$type and "foo"
int index = text.indexOf('/', ANDROID_PREFIX.length());
@@ -384,7 +480,7 @@
}
}
}
- } else if (folderType == ResourceFolderType.LAYOUT) {
+ } else {
if (VIEW_TAG.equals(tag)) {
tag = element.getAttribute(ATTR_CLASS);
if (tag == null || tag.isEmpty()) {
@@ -393,8 +489,7 @@
}
// Check widgets to make sure they're available in this version of the SDK.
- if (tag.indexOf('.') != -1 ||
- folderType != ResourceFolderType.LAYOUT) {
+ if (tag.indexOf('.') != -1) {
// Custom views aren't in the index
return;
}
@@ -419,7 +514,8 @@
protected int getMinSdk(Context context) {
if (mMinApi == -1) {
- mMinApi = context.getMainProject().getMinSdk();
+ AndroidVersion minSdkVersion = context.getMainProject().getMinSdkVersion();
+ mMinApi = minSdkVersion.getFeatureLevel();
}
return mMinApi;
@@ -1064,6 +1160,10 @@
@Nullable
@Override
public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ if (mApiDatabase == null) {
+ return new ForwardingAstVisitor() {
+ };
+ }
return new ApiVisitor(context);
}
@@ -1078,6 +1178,7 @@
types.add(ConstructorDeclaration.class);
types.add(VariableDefinitionEntry.class);
types.add(VariableReference.class);
+ types.add(Try.class);
return types;
}
@@ -1398,6 +1499,42 @@
}
@Override
+ public boolean visitTry(Try node) {
+ Object nativeNode = node.getNativeNode();
+ if (nativeNode != null && nativeNode.getClass().getName().equals(
+ "org.eclipse.jdt.internal.compiler.ast.TryStatement")) {
+ boolean isTryWithResources = false;
+ try {
+ Field field = nativeNode.getClass().getDeclaredField("resources");
+ Object value = field.get(nativeNode);
+ if (value instanceof Object[]) {
+ Object[] resources = (Object[]) value;
+ isTryWithResources = resources.length > 0;
+ }
+ } catch (NoSuchFieldException e) {
+ // Unexpected: ECJ parser internals have changed; can't detect try block
+ } catch (IllegalAccessException e) {
+ // Unexpected: ECJ parser internals have changed; can't detect try block
+ }
+ if (isTryWithResources) {
+ int minSdk = getMinSdk(mContext);
+ int api = 19; // minSdk for try with resources
+ if (api > minSdk && api > getLocalMinSdk(node)) {
+ Location location = mContext.getLocation(node);
+ String message = String.format("Try-with-resources requires "
+ + "API level %1$d (current min is %2$d)", api, minSdk);
+ LintDriver driver = mContext.getDriver();
+ if (!driver.isSuppressed(mContext, UNSUPPORTED, node)) {
+ mContext.report(UNSUPPORTED, location, message, null);
+ }
+ }
+ }
+ }
+
+ return super.visitTry(node);
+ }
+
+ @Override
public void endVisit(lombok.ast.Node node) {
if (node == mCurrentMethod) {
mCurrentMethod = null;
@@ -1447,13 +1584,13 @@
api, minSdk, fqcn);
LintDriver driver = mContext.getDriver();
- if (driver.isSuppressed(INLINED, node)) {
+ if (driver.isSuppressed(mContext, INLINED, node)) {
return true;
}
// Also allow to suppress these issues with NewApi, since some
// fields used to get identified that way
- if (driver.isSuppressed(UNSUPPORTED, node)) {
+ if (driver.isSuppressed(mContext, UNSUPPORTED, node)) {
return true;
}
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 65e33ca..aca04bf 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
@@ -22,6 +22,10 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repository.local.LocalPkgInfo;
+import com.android.sdklib.repository.local.LocalSdk;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.LintUtils;
import com.google.common.base.Charsets;
@@ -84,9 +88,6 @@
/** Default size to reserve for each API entry when creating byte buffer to build up data */
private static final int BYTES_PER_ENTRY = 36;
- private final LintClient mClient;
- private final File mXmlFile;
- private final File mBinaryFile;
private final Api mInfo;
private byte[] mData;
private int[] mIndices;
@@ -107,7 +108,8 @@
* @return a (possibly shared) instance of the API database, or null
* if its data can't be found
*/
- public static ApiLookup get(LintClient client) {
+ @Nullable
+ public static ApiLookup get(@NonNull LintClient client) {
synchronized (ApiLookup.class) {
ApiLookup db = sInstance.get();
if (db == null) {
@@ -122,7 +124,6 @@
}
if (file == null || !file.exists()) {
- client.log(null, "Fatal error: No API database found at %1$s", file);
return null;
} else {
db = get(client, file);
@@ -135,14 +136,42 @@
}
@VisibleForTesting
- static String getCacheFileName(String xmlFileName) {
+ @Nullable
+ static String getPlatformVersion(@NonNull LintClient client) {
+ LocalSdk sdk = client.getSdk();
+ if (sdk != null) {
+ LocalPkgInfo pkgInfo = sdk.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
+ if (pkgInfo != null) {
+ FullRevision version = pkgInfo.getDesc().getFullRevision();
+ if (version != null) {
+ return version.toShortString();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ static String getCacheFileName(@NonNull String xmlFileName, @Nullable String platformVersion) {
if (LintUtils.endsWith(xmlFileName, DOT_XML)) {
xmlFileName = xmlFileName.substring(0, xmlFileName.length() - DOT_XML.length());
}
+ StringBuilder sb = new StringBuilder(100);
+ sb.append(xmlFileName);
+
// Incorporate version number in the filename to avoid upgrade filename
// conflicts on Windows (such as issue #26663)
- return xmlFileName + '-' + BINARY_FORMAT_VERSION + ".bin"; //$NON-NLS-1$
+ sb.append('-').append(BINARY_FORMAT_VERSION);
+
+ if (platformVersion != null) {
+ sb.append('-').append(platformVersion);
+ }
+
+ sb.append(".bin"); //$NON-NLS-1$
+ return sb.toString();
}
/**
@@ -166,7 +195,8 @@
cacheDir = xmlFile.getParentFile();
}
- File binaryData = new File(cacheDir, getCacheFileName(xmlFile.getName()));
+ String platformVersion = getPlatformVersion(client);
+ File binaryData = new File(cacheDir, getCacheFileName(xmlFile.getName(), platformVersion));
if (DEBUG_FORCE_REGENERATE_BINARY) {
System.err.println("\nTemporarily regenerating binary data unconditionally \nfrom "
@@ -220,13 +250,10 @@
@NonNull File xmlFile,
@Nullable File binaryFile,
@Nullable Api info) {
- mClient = client;
- mXmlFile = xmlFile;
- mBinaryFile = binaryFile;
mInfo = info;
if (binaryFile != null) {
- readData();
+ readData(client, xmlFile, binaryFile);
}
}
@@ -263,14 +290,15 @@
* in readData().
* </pre>
*/
- private void readData() {
- if (!mBinaryFile.exists()) {
- mClient.log(null, "%1$s does not exist", mBinaryFile);
+ private void readData(@NonNull LintClient client, @NonNull File xmlFile,
+ @NonNull File binaryFile) {
+ if (!binaryFile.exists()) {
+ client.log(null, "%1$s does not exist", binaryFile);
return;
}
long start = System.currentTimeMillis();
try {
- MappedByteBuffer buffer = Files.map(mBinaryFile, MapMode.READ_ONLY);
+ MappedByteBuffer buffer = Files.map(binaryFile, MapMode.READ_ONLY);
assert buffer.order() == ByteOrder.BIG_ENDIAN;
// First skip the header
@@ -278,7 +306,7 @@
buffer.rewind();
for (int offset = 0; offset < expectedHeader.length; offset++) {
if (expectedHeader[offset] != buffer.get()) {
- mClient.log(null, "Incorrect file header: not an API database cache " +
+ client.log(null, "Incorrect file header: not an API database cache " +
"file, or a corrupt cache file");
return;
}
@@ -287,8 +315,8 @@
// Read in the format number
if (buffer.get() != BINARY_FORMAT_VERSION) {
// Force regeneration of new binary data with up to date format
- if (createCache(mClient, mXmlFile, mBinaryFile)) {
- readData(); // Recurse
+ if (createCache(client, xmlFile, binaryFile)) {
+ readData(client, xmlFile, binaryFile); // Recurse
}
return;
@@ -332,10 +360,10 @@
// TODO: Investigate (profile) accessing the byte buffer directly instead of
// accessing a byte array.
} catch (Throwable e) {
- mClient.log(null, "Failure reading binary cache file %1$s", mBinaryFile.getPath());
- mClient.log(null, "Please delete the file and restart the IDE/lint: %1$s",
- mBinaryFile.getPath());
- mClient.log(e, null);
+ client.log(null, "Failure reading binary cache file %1$s", binaryFile.getPath());
+ client.log(null, "Please delete the file and restart the IDE/lint: %1$s",
+ binaryFile.getPath());
+ client.log(e, null);
}
if (WRITE_STATS) {
long end = System.currentTimeMillis();
@@ -346,7 +374,7 @@
}
}
- /** See the {@link #readData()} for documentation on the data format. */
+ /** See the {@link #readData(LintClient,File,File)} for documentation on the data format. */
private static void writeDatabase(File file, Api info) throws IOException {
/*
* 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatCallDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatCallDetector.java
new file mode 100644
index 0000000..6ad86d6
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatCallDetector.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.APPCOMPAT_LIB_ARTIFACT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+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.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.util.Arrays;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.MethodInvocation;
+
+public class AppCompatCallDetector extends Detector implements Detector.JavaScanner {
+ public static final Issue ISSUE = Issue.create(
+ "AppCompatMethod",
+ "Using Wrong AppCompat Method",
+ "Finds cases where a custom `appcompat` method should be used instead",
+ "When using the appcompat library, there are some methods you should be calling " +
+ "instead of the normal ones; for example, `getSupportActionBar()` instead of " +
+ "`getActionBar()`. This lint check looks for calls to the wrong method.",
+ Category.CORRECTNESS, 6, Severity.WARNING,
+ new Implementation(
+ AppCompatCallDetector.class,
+ Scope.JAVA_FILE_SCOPE)).
+ addMoreInfo("http://developer.android.com/tools/support-library/index.html");
+
+ private static final String GET_ACTION_BAR = "getActionBar";
+ private static final String START_ACTION_MODE = "startActionMode";
+ private static final String SET_PROGRESS_BAR_VIS = "setProgressBarVisibility";
+ private static final String SET_PROGRESS_BAR_IN_VIS = "setProgressBarIndeterminateVisibility";
+ private static final String SET_PROGRESS_BAR_INDETERMINATE = "setProgressBarIndeterminate";
+ private static final String REQUEST_WINDOW_FEATURE = "requestWindowFeature";
+
+ private boolean mDependsOnAppCompat;
+
+ public AppCompatCallDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ @Override
+ public void beforeCheckProject(@NonNull Context context) {
+ Boolean dependsOnAppCompat = context.getProject().dependsOn(APPCOMPAT_LIB_ARTIFACT);
+ mDependsOnAppCompat = dependsOnAppCompat != null && dependsOnAppCompat;
+ }
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Arrays.asList(
+ GET_ACTION_BAR,
+ START_ACTION_MODE,
+ SET_PROGRESS_BAR_VIS,
+ SET_PROGRESS_BAR_IN_VIS,
+ SET_PROGRESS_BAR_INDETERMINATE,
+ REQUEST_WINDOW_FEATURE);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ if (mDependsOnAppCompat && isAppBarActivityCall(context, node)) {
+ String name = node.astName().astValue();
+ String replace = null;
+ if (GET_ACTION_BAR.equals(name)) {
+ replace = "getSupportActionBar";
+ } else if (START_ACTION_MODE.equals(name)) {
+ replace = "startSupportActionMode";
+ } else if (SET_PROGRESS_BAR_VIS.equals(name)) {
+ replace = "setSupportProgressBarVisibility";
+ } else if (SET_PROGRESS_BAR_IN_VIS.equals(name)) {
+ replace = "setSupportProgressBarIndeterminateVisibility";
+ } else if (SET_PROGRESS_BAR_INDETERMINATE.equals(name)) {
+ replace = "setSupportProgressBarIndeterminate";
+ } else if (REQUEST_WINDOW_FEATURE.equals(name)) {
+ replace = "supportRequestWindowFeature";
+ }
+
+ if (replace != null) {
+ String message = String.format("Should use %1$s instead of %2$s name",
+ replace, name);
+ context.report(ISSUE, node, context.getLocation(node), message, null);
+ }
+ }
+ }
+
+ private static boolean isAppBarActivityCall(@NonNull JavaContext context,
+ @NonNull MethodInvocation node) {
+ JavaParser.ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof JavaParser.ResolvedMethod) {
+ JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
+ JavaParser.ResolvedClass containingClass = method.getContainingClass();
+ if (containingClass.isSubclassOf("android.app.Activity", false)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java
new file mode 100644
index 0000000..4e33fc0
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_SHOW_AS_ACTION;
+import static com.android.SdkConstants.ATTR_TITLE;
+import static com.android.SdkConstants.ATTR_VISIBLE;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.VALUE_FALSE;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+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 com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Check that the right namespace is used for app compat menu items
+ *
+ * Using app:showAsAction instead of android:showAsAction leads to problems, but
+ * isn't caught by the API Detector since it's not in the Android namespace.
+ */
+public class AppCompatResourceDetector extends ResourceXmlDetector implements JavaScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "AppCompatResource", //$NON-NLS-1$
+ "Menu namespace",
+ "Ensures that menu items are using the right namespace",
+
+ "When using the appcompat library, menu resources should refer to the " +
+ "`showAsAction` in the `app:` namespace, not the `android:` namespace.\n" +
+ "\n" +
+ "Similarly, when *not* using the appcompat library, you should be using " +
+ "the `android:showAsAction` attribute.",
+
+ Category.USABILITY,
+ 5,
+ Severity.ERROR,
+ new Implementation(
+ AppCompatResourceDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ /** Constructs a new {@link com.android.tools.lint.checks.AppCompatResourceDetector} */
+ public AppCompatResourceDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.MENU;
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return Collections.singletonList(ATTR_SHOW_AS_ACTION);
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ Project mainProject = context.getMainProject();
+ if (mainProject.isGradleProject()) {
+ Boolean appCompat = mainProject.dependsOn("com.android.support:appcompat-v7");
+ if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ if (context.getFolderVersion() >= 14) {
+ return;
+ }
+ if (appCompat == Boolean.TRUE) {
+ context.report(ISSUE, attribute,
+ context.getLocation(attribute),
+ "Should use app:showAsAction with the appcompat library with "
+ + "xmlns:app=\"http://schemas.android.com/apk/res-auto\"",
+ null);
+ }
+ } else {
+ if (appCompat == Boolean.FALSE) {
+ context.report(ISSUE, attribute,
+ context.getLocation(attribute),
+ "Should use android:showAsAction when not using the appcompat library",
+ null);
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java
index 230a904..25d3864 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java
@@ -23,7 +23,14 @@
import static com.android.SdkConstants.TAG_STRING_ARRAY;
import com.android.annotations.NonNull;
+import com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.client.api.LintDriver;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
@@ -31,6 +38,7 @@
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.tools.lint.detector.api.ResourceXmlDetector;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
@@ -81,7 +89,7 @@
Severity.WARNING,
new Implementation(
ArraySizeDetector.class,
- Scope.ALL_RESOURCES_SCOPE));
+ Scope.RESOURCE_FILE_SCOPE));
private Multimap<File, Pair<String, Integer>> mFileToArrayCount;
@@ -119,6 +127,11 @@
@Override
public void afterCheckProject(@NonNull Context context) {
if (context.getPhase() == 1) {
+ boolean haveAllResources = context.getScope().contains(Scope.ALL_RESOURCE_FILES);
+ if (!haveAllResources) {
+ return;
+ }
+
// Check that all arrays for the same name have the same number of translations
Set<String> alreadyReported = new HashSet<String>();
@@ -165,6 +178,7 @@
}
}
+ //noinspection VariableNotUsedInsideIf
if (mLocations != null) {
// Request another scan through the resources such that we can
// gather the actual locations
@@ -194,7 +208,7 @@
Object clientData = curr.getClientData();
if (clientData instanceof Node) {
Node node = (Node) clientData;
- if (driver.isSuppressed(INCONSISTENT, node)) {
+ if (driver.isSuppressed(null, INCONSISTENT, node)) {
continue;
}
int newCount = LintUtils.getChildCount(node);
@@ -245,12 +259,19 @@
if (phase == 1) {
if (context.getProject().getReportIssues()) {
int childCount = LintUtils.getChildCount(element);
+
+ if (!context.getScope().contains(Scope.ALL_RESOURCE_FILES) &&
+ context.getClient().supportsProjectResources()) {
+ incrementalCheckCount(context, element, name, childCount);
+ return;
+ }
+
mFileToArrayCount.put(context.file, Pair.of(name, childCount));
}
} else {
assert phase == 2;
if (mLocations.containsKey(name)) {
- if (context.getDriver().isSuppressed(INCONSISTENT, element)) {
+ if (context.getDriver().isSuppressed(context, INCONSISTENT, element)) {
return;
}
Location location = context.getLocation(element);
@@ -263,4 +284,42 @@
}
}
}
+
+ private static void incrementalCheckCount(@NonNull XmlContext context, @NonNull Element element,
+ @NonNull String name, int childCount) {
+ LintClient client = context.getClient();
+ Project project = context.getMainProject();
+ AbstractResourceRepository resources = client.getProjectResources(project, true);
+ if (resources == null) {
+ return;
+ }
+ List<ResourceItem> items = resources.getResourceItem(ResourceType.ARRAY, name);
+ if (items != null) {
+ for (ResourceItem item : items) {
+ ResourceFile source = item.getSource();
+ if (source != null && LintUtils.isSameResourceFile(context.file,
+ source.getFile())) {
+ continue;
+ }
+ ResourceValue rv = item.getResourceValue(false);
+ if (rv instanceof ArrayResourceValue) {
+ ArrayResourceValue arv = (ArrayResourceValue) rv;
+ if (childCount != arv.getElementCount()) {
+ String thisName = context.file.getParentFile().getName() + File.separator
+ + context.file.getName();
+ assert source != null;
+ File otherFile = source.getFile();
+ String otherName = otherFile.getParentFile().getName() + File.separator
+ + otherFile.getName();
+ String message = String.format(
+ "Array %1$s has an inconsistent number of items (%2$d in %3$s, %4$d in %5$s)",
+ name, childCount, thisName, arv.getElementCount(), otherName);
+
+ context.report(INCONSISTENT, element, context.getLocation(element),
+ message, null);
+ }
+ }
+ }
+ }
+ }
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AssertDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AssertDetector.java
new file mode 100644
index 0000000..dfb5fe0
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AssertDetector.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.Assert;
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.BooleanLiteral;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Node;
+import lombok.ast.NullLiteral;
+
+/**
+ * Looks for assertion usages.
+ */
+public class AssertDetector extends Detector implements Detector.JavaScanner {
+ /** Using assertions */
+ public static final Issue ISSUE = Issue.create(
+ "Assert", //$NON-NLS-1$
+ "Assertions",
+ "Looks for usages of the assert keyword",
+
+ "Assertions are not checked at runtime. There are ways to request that they be used " +
+ "by Dalvik (`adb shell setprop debug.assert 1`), but the property is ignored in " +
+ "many places and can not be relied upon. Instead, perform conditional checking " +
+ "inside `if (BuildConfig.DEBUG) { }` blocks. That constant is a static final boolean " +
+ "which is true in debug builds and false in release builds, and the Java compiler " +
+ "completely removes all code inside the if-body from the app.\n" +
+ "\n" +
+ "For example, you can replace `assert speed > 0` with " +
+ "`if (BuildConfig.DEBUG && !(speed > 0)) { throw new AssertionError() }`.\n" +
+ "\n" +
+ "(Note: This lint check does not flag assertions purely asserting nullness or " +
+ "non-nullness; these are typically more intended for tools usage than runtime " +
+ "checks.)",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ AssertDetector.class,
+ Scope.JAVA_FILE_SCOPE))
+ .addMoreInfo(
+ "https://code.google.com/p/android/issues/detail?id=65183"); //$NON-NLS-1$
+
+ /** Constructs a new {@link com.android.tools.lint.checks.AssertDetector} check */
+ public AssertDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ return Collections.<Class<? extends Node>>singletonList(Assert.class);
+ }
+
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull final JavaContext context) {
+ return new ForwardingAstVisitor() {
+ @Override
+ public boolean visitAssert(Assert node) {
+ Expression assertion = node.astAssertion();
+ // Allow "assert true"; it's basically a no-op
+ if (assertion instanceof BooleanLiteral) {
+ Boolean b = ((BooleanLiteral) assertion).astValue();
+ if (b != null && b) {
+ return false;
+ }
+ } else {
+ // Allow assertions of the form "assert foo != null" because they are often used
+ // to make statements to tools about known nullness properties. For example,
+ // findViewById() may technically return null in some cases, but a developer
+ // may know that it won't be when it's called correctly, so the assertion helps
+ // to clear nullness warnings.
+ if (isNullCheck(assertion)) {
+ return false;
+ }
+ }
+ String message
+ = "Assertions are unreliable. Use BuildConfig.DEBUG conditional checks instead.";
+ context.report(ISSUE, node, context.getLocation(node), message, null);
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Checks whether the given expression is purely a non-null check, e.g. it will return
+ * true for expressions like "a != null" and "a != null && b != null" and
+ * "b == null || c != null".
+ */
+ private static boolean isNullCheck(Expression expression) {
+ if (expression instanceof BinaryExpression) {
+ BinaryExpression binExp = (BinaryExpression) expression;
+ if (binExp.astLeft() instanceof NullLiteral ||
+ binExp.astRight() instanceof NullLiteral) {
+ return true;
+ } else {
+ return isNullCheck(binExp.astLeft()) && isNullCheck(binExp.astRight());
+ }
+ } else {
+ return false;
+ }
+ }
+}
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 af3a6ef..6db92b5 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
@@ -16,204 +16,227 @@
package com.android.tools.lint.checks;
-import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
-
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Scope;
import com.google.common.annotations.Beta;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
+import java.util.EnumSet;
import java.util.List;
import java.util.Set;
/** Registry which provides a list of checks to be performed on an Android project */
public class BuiltinIssueRegistry extends IssueRegistry {
private static final List<Issue> sIssues;
+ static final int INITIAL_CAPACITY = 198;
static {
- final int initialCapacity = 161;
- List<Issue> issues = new ArrayList<Issue>(initialCapacity);
+ List<Issue> issues = new ArrayList<Issue>(INITIAL_CAPACITY);
issues.add(AccessibilityDetector.ISSUE);
- issues.add(LabelForDetector.ISSUE);
- issues.add(MathDetector.ISSUE);
- issues.add(FieldGetterDetector.ISSUE);
- issues.add(SdCardDetector.ISSUE);
- issues.add(ApiDetector.UNSUPPORTED);
+ issues.add(AddJavascriptInterfaceDetector.ISSUE);
+ issues.add(AlwaysShowActionDetector.ISSUE);
+ issues.add(AnnotationDetector.ISSUE);
issues.add(ApiDetector.INLINED);
issues.add(ApiDetector.OVERRIDE);
- issues.add(InvalidPackageDetector.ISSUE);
+ issues.add(ApiDetector.UNSUPPORTED);
+ issues.add(ApiDetector.UNUSED);
+ issues.add(AppCompatCallDetector.ISSUE);
+ issues.add(AppCompatResourceDetector.ISSUE);
+ issues.add(ArraySizeDetector.INCONSISTENT);
+ issues.add(AssertDetector.ISSUE);
+ issues.add(ButtonDetector.BACK_BUTTON);
+ issues.add(ButtonDetector.CASE);
+ issues.add(ButtonDetector.ORDER);
+ issues.add(ButtonDetector.STYLE);
+ issues.add(ByteOrderMarkDetector.BOM);
+ issues.add(CallSuperDetector.ISSUE);
+ issues.add(CheckPermissionDetector.ISSUE);
+ issues.add(ChildCountDetector.ADAPTER_VIEW_ISSUE);
+ issues.add(ChildCountDetector.SCROLLVIEW_ISSUE);
+ issues.add(CipherGetInstanceDetector.ISSUE);
+ issues.add(CleanupDetector.COMMIT_FRAGMENT);
+ issues.add(CleanupDetector.RECYCLE_RESOURCE);
+ issues.add(ClickableViewAccessibilityDetector.ISSUE);
+ issues.add(ColorUsageDetector.ISSUE);
+ issues.add(CommentDetector.EASTER_EGG);
+ issues.add(CommentDetector.STOP_SHIP);
+ issues.add(CutPasteDetector.ISSUE);
+ issues.add(DeprecationDetector.ISSUE);
+ issues.add(DetectMissingPrefix.MISSING_NAMESPACE);
+ issues.add(DosLineEndingDetector.ISSUE);
issues.add(DuplicateIdDetector.CROSS_LAYOUT);
issues.add(DuplicateIdDetector.WITHIN_LAYOUT);
issues.add(DuplicateResourceDetector.ISSUE);
- issues.add(WrongIdDetector.UNKNOWN_ID);
- issues.add(WrongIdDetector.UNKNOWN_ID_LAYOUT);
- issues.add(WrongIdDetector.NOT_SIBLING);
- issues.add(WrongIdDetector.INVALID);
- issues.add(LayoutConsistencyDetector.INCONSISTENT_IDS);
- issues.add(StateListDetector.ISSUE);
- issues.add(StyleCycleDetector.ISSUE);
+ issues.add(DuplicateResourceDetector.TYPE_MISMATCH);
+ issues.add(ExtraTextDetector.ISSUE);
+ issues.add(FieldGetterDetector.ISSUE);
+ issues.add(FragmentDetector.ISSUE);
+ issues.add(GradleDetector.COMPATIBILITY);
+ issues.add(GradleDetector.GRADLE_PLUGIN_COMPATIBILITY);
+ issues.add(GradleDetector.DEPENDENCY);
+ issues.add(GradleDetector.DEPRECATED);
+ issues.add(GradleDetector.GRADLE_GETTER);
+ issues.add(GradleDetector.IDE_SUPPORT);
+ issues.add(GradleDetector.PATH);
+ issues.add(GradleDetector.PLUS);
+ issues.add(GradleDetector.STRING_INTEGER);
+ issues.add(GradleDetector.REMOTE_VERSION);
+ issues.add(GradleDetector.ACCIDENTAL_OCTAL);
+ issues.add(GradleDetector.IMPROPER_PROJECT_LEVEL_STATEMENT);
+ issues.add(GradleDetector.MISPLACED_STATEMENT);
+ issues.add(GridLayoutDetector.ISSUE);
+ issues.add(HandlerDetector.ISSUE);
+ issues.add(HardcodedDebugModeDetector.ISSUE);
+ issues.add(HardcodedValuesDetector.ISSUE);
+ issues.add(IconDetector.DUPLICATES_CONFIGURATIONS);
+ issues.add(IconDetector.DUPLICATES_NAMES);
+ issues.add(IconDetector.GIF_USAGE);
+ issues.add(IconDetector.ICON_COLORS);
+ issues.add(IconDetector.ICON_DENSITIES);
+ issues.add(IconDetector.ICON_DIP_SIZE);
+ issues.add(IconDetector.ICON_EXPECTED_SIZE);
+ issues.add(IconDetector.ICON_EXTENSION);
+ issues.add(IconDetector.ICON_LAUNCHER_SHAPE);
+ issues.add(IconDetector.ICON_LOCATION);
+ issues.add(IconDetector.ICON_MISSING_FOLDER);
+ issues.add(IconDetector.ICON_MIX_9PNG);
+ issues.add(IconDetector.ICON_NODPI);
+ issues.add(IconDetector.ICON_XML_AND_PNG);
+ issues.add(IncludeDetector.ISSUE);
+ issues.add(InefficientWeightDetector.BASELINE_WEIGHTS);
issues.add(InefficientWeightDetector.INEFFICIENT_WEIGHT);
issues.add(InefficientWeightDetector.NESTED_WEIGHTS);
- issues.add(InefficientWeightDetector.BASELINE_WEIGHTS);
- issues.add(InefficientWeightDetector.WRONG_0DP);
issues.add(InefficientWeightDetector.ORIENTATION);
- issues.add(ScrollViewChildDetector.ISSUE);
- issues.add(DeprecationDetector.ISSUE);
- issues.add(ObsoleteLayoutParamsDetector.ISSUE);
- issues.add(MergeRootFrameLayoutDetector.ISSUE);
- issues.add(NestedScrollingWidgetDetector.ISSUE);
- issues.add(ChildCountDetector.SCROLLVIEW_ISSUE);
- issues.add(ChildCountDetector.ADAPTER_VIEW_ISSUE);
- issues.add(UseCompoundDrawableDetector.ISSUE);
- issues.add(UselessViewDetector.USELESS_PARENT);
- issues.add(UselessViewDetector.USELESS_LEAF);
- issues.add(TooManyViewsDetector.TOO_MANY);
- 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);
+ issues.add(InefficientWeightDetector.WRONG_0DP);
+ issues.add(InvalidPackageDetector.ISSUE);
+ issues.add(JavaPerformanceDetector.PAINT_ALLOC);
+ issues.add(JavaPerformanceDetector.USE_SPARSE_ARRAY);
+ issues.add(JavaPerformanceDetector.USE_VALUE_OF);
+ issues.add(JavaScriptInterfaceDetector.ISSUE);
+ issues.add(LabelForDetector.ISSUE);
+ issues.add(LayoutConsistencyDetector.INCONSISTENT_IDS);
+ issues.add(LayoutInflationDetector.ISSUE);
issues.add(LocaleDetector.DATE_FORMAT);
- issues.add(RegistrationDetector.ISSUE);
- issues.add(MissingClassDetector.MISSING);
- issues.add(MissingClassDetector.INSTANTIATABLE);
- issues.add(MissingClassDetector.INNERCLASS);
- issues.add(MissingIdDetector.ISSUE);
- issues.add(WrongCaseDetector.WRONG_CASE);
- issues.add(HandlerDetector.ISSUE);
- issues.add(FragmentDetector.ISSUE);
- issues.add(TranslationDetector.EXTRA);
- issues.add(TranslationDetector.MISSING);
- issues.add(PluralsDetector.MISSING);
- issues.add(PluralsDetector.EXTRA);
- issues.add(HardcodedValuesDetector.ISSUE);
- issues.add(Utf8Detector.ISSUE);
- issues.add(DosLineEndingDetector.ISSUE);
- issues.add(CommentDetector.EASTER_EGG);
- issues.add(CommentDetector.STOP_SHIP);
- issues.add(ProguardDetector.WRONG_KEEP);
- issues.add(ProguardDetector.SPLIT_CONFIG);
- issues.add(PxUsageDetector.PX_ISSUE);
- issues.add(PxUsageDetector.DP_ISSUE);
- issues.add(PxUsageDetector.IN_MM_ISSUE);
- issues.add(PxUsageDetector.SMALL_SP_ISSUE);
- issues.add(TextFieldDetector.ISSUE);
- issues.add(TextViewDetector.ISSUE);
- issues.add(TextViewDetector.SELECTABLE);
- issues.add(UnusedResourceDetector.ISSUE);
- issues.add(UnusedResourceDetector.ISSUE_IDS);
- issues.add(ExtraTextDetector.ISSUE);
- issues.add(PrivateResourceDetector.ISSUE);
- issues.add(ArraySizeDetector.INCONSISTENT);
- issues.add(HardcodedDebugModeDetector.ISSUE);
- issues.add(ManifestDetector.ORDER);
- issues.add(ManifestDetector.USES_SDK);
- issues.add(ManifestDetector.MULTIPLE_USES_SDK);
- issues.add(ManifestDetector.WRONG_PARENT);
- issues.add(ManifestDetector.DUPLICATE_ACTIVITY);
- issues.add(ManifestDetector.TARGET_NEWER);
+ issues.add(LocaleDetector.STRING_LOCALE);
+ issues.add(LocaleFolderDetector.ISSUE);
issues.add(ManifestDetector.ALLOW_BACKUP);
- issues.add(ManifestDetector.UNIQUE_PERMISSION);
- issues.add(ManifestDetector.SET_VERSION);
- issues.add(ManifestDetector.ILLEGAL_REFERENCE);
- issues.add(ManifestDetector.DUPLICATE_USES_FEATURE);
issues.add(ManifestDetector.APPLICATION_ICON);
issues.add(ManifestDetector.DEVICE_ADMIN);
+ issues.add(ManifestDetector.DUPLICATE_ACTIVITY);
+ issues.add(ManifestDetector.DUPLICATE_USES_FEATURE);
+ issues.add(ManifestDetector.GRADLE_OVERRIDES);
+ issues.add(ManifestDetector.ILLEGAL_REFERENCE);
issues.add(ManifestDetector.MOCK_LOCATION);
+ issues.add(ManifestDetector.MULTIPLE_USES_SDK);
+ issues.add(ManifestDetector.ORDER);
+ issues.add(ManifestDetector.SET_VERSION);
+ issues.add(ManifestDetector.TARGET_NEWER);
+ issues.add(ManifestDetector.UNIQUE_PERMISSION);
+ issues.add(ManifestDetector.USES_SDK);
+ issues.add(ManifestDetector.WRONG_PARENT);
issues.add(ManifestTypoDetector.ISSUE);
+ issues.add(MathDetector.ISSUE);
+ issues.add(MergeRootFrameLayoutDetector.ISSUE);
+ issues.add(MissingClassDetector.INNERCLASS);
+ issues.add(MissingClassDetector.INSTANTIATABLE);
+ issues.add(MissingClassDetector.MISSING);
+ issues.add(MissingIdDetector.ISSUE);
+ issues.add(NamespaceDetector.CUSTOM_VIEW);
+ issues.add(NamespaceDetector.RES_AUTO);
+ issues.add(NamespaceDetector.TYPO);
+ issues.add(NamespaceDetector.UNUSED);
+ issues.add(NestedScrollingWidgetDetector.ISSUE);
+ issues.add(NfcTechListDetector.ISSUE);
+ issues.add(NonInternationalizedSmsDetector.ISSUE);
+ issues.add(ObsoleteLayoutParamsDetector.ISSUE);
+ issues.add(OnClickDetector.ISSUE);
+ issues.add(OverdrawDetector.ISSUE);
+ issues.add(OverrideDetector.ISSUE);
+ issues.add(ParcelDetector.ISSUE);
+ issues.add(PluralsDetector.EXTRA);
+ issues.add(PluralsDetector.MISSING);
+ issues.add(PluralsDetector.IMPLIED_QUANTITY);
+ issues.add(PreferenceActivityDetector.ISSUE);
+ issues.add(PrivateKeyDetector.ISSUE);
+ issues.add(PrivateResourceDetector.ISSUE);
+ issues.add(ProguardDetector.SPLIT_CONFIG);
+ issues.add(ProguardDetector.WRONG_KEEP);
+ issues.add(PropertyFileDetector.ISSUE);
+ issues.add(PxUsageDetector.DP_ISSUE);
+ issues.add(PxUsageDetector.IN_MM_ISSUE);
+ issues.add(PxUsageDetector.PX_ISSUE);
+ issues.add(PxUsageDetector.SMALL_SP_ISSUE);
+ issues.add(RegistrationDetector.ISSUE);
+ issues.add(RequiredAttributeDetector.ISSUE);
+ issues.add(ResourceCycleDetector.CRASH);
+ issues.add(ResourceCycleDetector.CYCLE);
+ issues.add(ResourcePrefixDetector.ISSUE);
+ issues.add(RtlDetector.COMPAT);
+ issues.add(RtlDetector.ENABLED);
+ issues.add(RtlDetector.SYMMETRY);
+ issues.add(RtlDetector.USE_START);
+ issues.add(ScrollViewChildDetector.ISSUE);
+ issues.add(SdCardDetector.ISSUE);
+ issues.add(SecureRandomDetector.ISSUE);
+ issues.add(SecureRandomGeneratorDetector.ISSUE);
issues.add(SecurityDetector.EXPORTED_PROVIDER);
- issues.add(SecurityDetector.EXPORTED_SERVICE);
issues.add(SecurityDetector.EXPORTED_RECEIVER);
+ issues.add(SecurityDetector.EXPORTED_SERVICE);
issues.add(SecurityDetector.OPEN_PROVIDER);
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);
- issues.add(IconDetector.ICON_DIP_SIZE);
- issues.add(IconDetector.ICON_EXPECTED_SIZE);
- issues.add(IconDetector.ICON_LOCATION);
- issues.add(IconDetector.DUPLICATES_NAMES);
- issues.add(IconDetector.DUPLICATES_CONFIGURATIONS);
- issues.add(IconDetector.ICON_NODPI);
- issues.add(IconDetector.ICON_MIX_9PNG);
- issues.add(IconDetector.ICON_EXTENSION);
- issues.add(IconDetector.ICON_COLORS);
- issues.add(IconDetector.ICON_XML_AND_PNG);
- issues.add(IconDetector.ICON_LAUNCHER_SHAPE);
- issues.add(TypographyDetector.DASHES);
- issues.add(TypographyDetector.QUOTES);
- issues.add(TypographyDetector.FRACTIONS);
- issues.add(TypographyDetector.ELLIPSIS);
- issues.add(TypographyDetector.OTHER);
- issues.add(ButtonDetector.ORDER);
- issues.add(ButtonDetector.CASE);
- issues.add(ButtonDetector.BACK_BUTTON);
- issues.add(ButtonDetector.STYLE);
- issues.add(DetectMissingPrefix.MISSING_NAMESPACE);
- issues.add(OverdrawDetector.ISSUE);
- issues.add(StringFormatDetector.INVALID);
+ issues.add(ServiceCastDetector.ISSUE);
+ issues.add(SetJavaScriptEnabledDetector.ISSUE);
+ issues.add(SharedPrefsDetector.ISSUE);
+ issues.add(SignatureOrSystemDetector.ISSUE);
+ issues.add(StateListDetector.ISSUE);
issues.add(StringFormatDetector.ARG_COUNT);
issues.add(StringFormatDetector.ARG_TYPES);
+ issues.add(StringFormatDetector.INVALID);
+ issues.add(SystemPermissionsDetector.ISSUE);
+ issues.add(TextFieldDetector.ISSUE);
+ issues.add(TextViewDetector.ISSUE);
+ issues.add(TextViewDetector.SELECTABLE);
+ issues.add(TitleDetector.ISSUE);
+ issues.add(ToastDetector.ISSUE);
+ issues.add(TooManyViewsDetector.TOO_DEEP);
+ issues.add(TooManyViewsDetector.TOO_MANY);
+ issues.add(TranslationDetector.EXTRA);
+ issues.add(TranslationDetector.MISSING);
issues.add(TypoDetector.ISSUE);
+ issues.add(TypographyDetector.DASHES);
+ issues.add(TypographyDetector.ELLIPSIS);
+ issues.add(TypographyDetector.FRACTIONS);
+ issues.add(TypographyDetector.OTHER);
+ issues.add(TypographyDetector.QUOTES);
+ issues.add(UnusedResourceDetector.ISSUE);
+ issues.add(UnusedResourceDetector.ISSUE_IDS);
+ issues.add(UseCompoundDrawableDetector.ISSUE);
+ issues.add(UselessViewDetector.USELESS_LEAF);
+ issues.add(UselessViewDetector.USELESS_PARENT);
+ issues.add(Utf8Detector.ISSUE);
+ issues.add(ViewConstructorDetector.ISSUE);
+ issues.add(ViewHolderDetector.ISSUE);
+ issues.add(ViewTagDetector.ISSUE);
issues.add(ViewTypeDetector.ISSUE);
- issues.add(ServiceCastDetector.ISSUE);
- issues.add(ParcelDetector.ISSUE);
+ issues.add(WakelockDetector.ISSUE);
+ issues.add(WebViewDetector.ISSUE);
+ issues.add(WrongCallDetector.ISSUE);
+ issues.add(WrongCaseDetector.WRONG_CASE);
+ issues.add(WrongIdDetector.INVALID);
+ issues.add(WrongIdDetector.NOT_SIBLING);
+ issues.add(WrongIdDetector.UNKNOWN_ID);
+ issues.add(WrongIdDetector.UNKNOWN_ID_LAYOUT);
issues.add(WrongImportDetector.ISSUE);
issues.add(WrongLocationDetector.ISSUE);
- issues.add(ViewConstructorDetector.ISSUE);
- issues.add(NamespaceDetector.CUSTOM_VIEW);
- issues.add(NamespaceDetector.UNUSED);
- issues.add(NamespaceDetector.TYPO);
- issues.add(NamespaceDetector.RES_AUTO);
- issues.add(AlwaysShowActionDetector.ISSUE);
- issues.add(TitleDetector.ISSUE);
- issues.add(ColorUsageDetector.ISSUE);
- issues.add(JavaPerformanceDetector.PAINT_ALLOC);
- issues.add(JavaPerformanceDetector.USE_VALUE_OF);
- issues.add(JavaPerformanceDetector.USE_SPARSE_ARRAY);
- issues.add(WakelockDetector.ISSUE);
- issues.add(CleanupDetector.RECYCLE_RESOURCE);
- issues.add(CleanupDetector.COMMIT_FRAGMENT);
- issues.add(SetJavaScriptEnabledDetector.ISSUE);
- issues.add(JavaScriptInterfaceDetector.ISSUE);
- issues.add(ToastDetector.ISSUE);
- issues.add(SharedPrefsDetector.ISSUE);
- issues.add(CutPasteDetector.ISSUE);
- issues.add(NonInternationalizedSmsDetector.ISSUE);
- issues.add(PrivateKeyDetector.ISSUE);
- issues.add(AnnotationDetector.ISSUE);
- issues.add(SystemPermissionsDetector.ISSUE);
- issues.add(RequiredAttributeDetector.ISSUE);
- issues.add(WrongCallDetector.ISSUE);
- issues.add(RtlDetector.COMPAT);
- issues.add(RtlDetector.ENABLED);
- issues.add(RtlDetector.USE_START);
-
- assert initialCapacity >= issues.size() : issues.size();
sIssues = Collections.unmodifiableList(issues);
-
- // Check that ids are unique
- if (assertionsEnabled()) {
- Set<String> ids = new HashSet<String>();
- for (Issue issue : sIssues) {
- String id = issue.getId();
- assert !ids.contains(id) : "Duplicate id " + id; //$NON-NLS-1$
- ids.add(id);
- }
- }
}
/**
@@ -228,6 +251,31 @@
return sIssues;
}
+ @Override
+ protected int getIssueCapacity(@NonNull EnumSet<Scope> scope) {
+ if (scope.equals(Scope.ALL)) {
+ return getIssues().size();
+ } else {
+ int initialSize = 12;
+ if (scope.contains(Scope.RESOURCE_FILE)) {
+ initialSize += 70;
+ } else if (scope.contains(Scope.ALL_RESOURCE_FILES)) {
+ initialSize += 10;
+ }
+
+ if (scope.contains(Scope.JAVA_FILE)) {
+ initialSize += 35;
+ } else if (scope.contains(Scope.CLASS_FILE)) {
+ initialSize += 15;
+ } else if (scope.contains(Scope.MANIFEST)) {
+ initialSize += 30;
+ } else if (scope.contains(Scope.GRADLE_FILE)) {
+ initialSize += 5;
+ }
+ return initialSize;
+ }
+ }
+
private static Set<Issue> sAdtFixes;
/**
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 d870445..711e318 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
@@ -279,7 +279,7 @@
context.report(CASE, child, context.getLocation(child),
String.format(
"The standard Android way to capitalize %1$s " +
- "is \"Cancel\" (tip: use @android:string/ok instead)",
+ "is \"Cancel\" (tip: use @android:string/cancel instead)",
label), null);
}
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ByteOrderMarkDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ByteOrderMarkDetector.java
new file mode 100644
index 0000000..ced4566
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ByteOrderMarkDetector.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ATTR_NAME;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+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;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Checks that byte order marks do not appear in resource names
+ */
+public class ByteOrderMarkDetector extends ResourceXmlDetector {
+
+ /** Detects BOM characters in the middle of files */
+ public static final Issue BOM = Issue.create(
+ "ByteOrderMark", //$NON-NLS-1$
+ "Byte order mark inside files",
+ "Looks for byte order mark characters in the middle of files",
+ "Lint will flag any byte-order-mark (BOM) characters it finds in the middle " +
+ "of a file. Since we expect files to be encoded with UTF-8 (see the EnforceUTF8 " +
+ "issue), the BOM characters are not necessary, and they are not handled correctly " +
+ "by all tools. For example, if you have a BOM as part of a resource name in one " +
+ "particular translation, that name will not be considered identical to the base " +
+ "resource's name and the translation will not be used.",
+ Category.I18N,
+ 8,
+ Severity.FATAL,
+ new Implementation(
+ ByteOrderMarkDetector.class,
+ Scope.RESOURCE_FILE_SCOPE))
+ .addMoreInfo("http://en.wikipedia.org/wiki/Byte_order_mark");
+
+ /** Constructs a new {@link ByteOrderMarkDetector} */
+ public ByteOrderMarkDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ @Nullable
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return Collections.singletonList(ATTR_NAME);
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ String name = attribute.getValue();
+ for (int i = 0, n = name.length(); i < n; i++) {
+ char c = name.charAt(i);
+ if (c == '\uFEFF') {
+ Location location = context.getLocation(attribute);
+ String message = "Found byte-order-mark in the middle of a file";
+ context.report(BOM, null, location, message, null);
+ break;
+ }
+ }
+ }
+}
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
index f0a9233..2f35a10 100644
--- 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
@@ -16,16 +16,27 @@
package com.android.tools.lint.checks;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.*;
-
-import lombok.ast.*;
+import com.android.tools.lint.detector.api.Category;
+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.Scope;
+import com.android.tools.lint.detector.api.Severity;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
+import lombok.ast.AstVisitor;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.MethodInvocation;
+
/**
* Ensures that calls to check permission use the result (otherwise they probably meant to call the
* <b>enforce</b> permission methods instead)
@@ -80,14 +91,12 @@
// 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;
- }
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ ResolvedClass containingClass = method.getContainingClass();
+ if (containingClass.isSubclassOf("android.content.Context", false)) {
+ return true;
}
}
return false;
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java
new file mode 100644
index 0000000..e42072e
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.client.api.JavaParser;
+import com.android.tools.lint.detector.api.Category;
+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.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.google.common.collect.Sets;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.StringLiteral;
+
+/**
+ * Ensures that Cipher.getInstance is not called with AES as the parameter.
+ */
+public class CipherGetInstanceDetector extends Detector implements Detector.JavaScanner {
+ public static final Issue ISSUE = Issue.create(
+ "GetInstance", //$NON-NLS-1$
+ "Cipher.getInstance with ECB",
+ "Checks that `Cipher#getInstance` is not called using the ECB cipher mode",
+ "`Cipher#getInstance` should not be called with ECB as the cipher mode or without "
+ + "setting the cipher mode because the default mode on android is ECB, which "
+ + "is insecure.",
+ Category.SECURITY,
+ 9,
+ Severity.WARNING,
+ new Implementation(
+ CipherGetInstanceDetector.class,
+ Scope.JAVA_FILE_SCOPE));
+
+ private static final String CIPHER = "javax.crypto.Cipher"; //$NON-NLS-1$
+ private static final String GET_INSTANCE = "getInstance"; //$NON-NLS-1$
+ private static final Set<String> ALGORITHM_ONLY =
+ Sets.newHashSet("AES", "DES", "DESede"); //$NON-NLS-1$
+ private static final String ECB = "ECB"; //$NON-NLS-1$
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList(GET_INSTANCE);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ // Ignore if the method doesn't fit our description.
+ JavaParser.ResolvedNode resolved = context.resolve(node);
+ if (!(resolved instanceof JavaParser.ResolvedMethod)) {
+ return;
+ }
+ JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
+ if (!method.getContainingClass().isSubclassOf(CIPHER, false)) {
+ return;
+ }
+ StrictListAccessor<Expression, MethodInvocation> argumentList = node.astArguments();
+ if (argumentList != null && argumentList.size() == 1) {
+ Expression expression = argumentList.first();
+ if (expression instanceof StringLiteral) {
+ StringLiteral argument = (StringLiteral)expression;
+ String parameter = argument.astValue();
+ checkParameter(context, node, argument, parameter, false);
+ } else {
+ JavaParser.ResolvedNode resolve = context.resolve(expression);
+ if (resolve instanceof JavaParser.ResolvedField) {
+ JavaParser.ResolvedField field = (JavaParser.ResolvedField) resolve;
+ Object value = field.getValue();
+ if (value instanceof String) {
+ checkParameter(context, node, expression, (String)value, true);
+ }
+ }
+ }
+ }
+ }
+
+ private static void checkParameter(@NonNull JavaContext context,
+ @NonNull MethodInvocation call, @NonNull Node node, @NonNull String value,
+ boolean includeValue) {
+ if (ALGORITHM_ONLY.contains(value)) {
+ String message = "Cipher.getInstance should not be called without setting the"
+ + " encryption mode and padding";
+ context.report(ISSUE, call, context.getLocation(node), message, null);
+ } else if (value.contains(ECB)) {
+ String message = "ECB encryption mode should not be used";
+ if (includeValue) {
+ message += " (was \"" + value + "\")";
+ }
+ context.report(ISSUE, call, context.getLocation(node), message, null);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetector.java
new file mode 100644
index 0000000..0045d36
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetector.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ANDROID_VIEW_VIEW;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+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.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Checks that views that override View#onTouchEvent also implement View#performClick
+ * and call performClick when click detection occurs.
+ */
+public class ClickableViewAccessibilityDetector extends Detector implements Detector.ClassScanner {
+
+ public static final Issue ISSUE = Issue.create(
+ "ClickableViewAccessibility", //$NON-NLS-1$
+ "Accessibility in Custom Views",
+ "Checks that custom views handle accessibility on click events",
+ "If a `View` that overrides `onTouchEvent` or uses an `OnTouchListener` does not also "
+ + "implement `performClick` and call it when clicks are detected, the `View` "
+ + "may not handle accessibility actions properly. Logic handling the click "
+ + "actions should ideally be placed in `View#performClick` as some "
+ + "accessibility services invoke `performClick` when a click action "
+ + "should occur.",
+ Category.A11Y,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ ClickableViewAccessibilityDetector.class,
+ Scope.CLASS_FILE_SCOPE));
+
+ private static final String ON_TOUCH_EVENT = "onTouchEvent"; //$NON-NLS-1$
+ private static final String ON_TOUCH_EVENT_SIG = "(Landroid/view/MotionEvent;)Z"; //$NON-NLS-1$
+ private static final String PERFORM_CLICK = "performClick"; //$NON-NLS-1$
+ private static final String PERFORM_CLICK_SIG = "()Z"; //$NON-NLS-1$
+ private static final String SET_ON_TOUCH_LISTENER = "setOnTouchListener"; //$NON-NLS-1$
+ private static final String SET_ON_TOUCH_LISTENER_SIG = "(Landroid/view/View$OnTouchListener;)V"; //$NON-NLS-1$
+ private static final String ON_TOUCH = "onTouch"; //$NON-NLS-1$
+ private static final String ON_TOUCH_SIG = "(Landroid/view/View;Landroid/view/MotionEvent;)Z"; //$NON-NLS-1$
+ private static final String ON_TOUCH_LISTENER = "android/view/View$OnTouchListener"; //$NON-NLS-1$
+
+
+ /** Constructs a new {@link ClickableViewAccessibilityDetector} */
+ public ClickableViewAccessibilityDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements ClassScanner ----
+ @Override
+ public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
+ scanForAndCheckSetOnTouchListenerCalls(context, classNode);
+
+ // Ignore abstract classes.
+ if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) {
+ return;
+ }
+
+ if (context.getDriver().isSubclassOf(classNode, ANDROID_VIEW_VIEW)) {
+ checkView(context, classNode);
+ }
+
+ if (implementsOnTouchListener(classNode)) {
+ checkOnTouchListener(context, classNode);
+ }
+ }
+
+ @SuppressWarnings("unchecked") // ASM API
+ public static void scanForAndCheckSetOnTouchListenerCalls(
+ ClassContext context,
+ ClassNode classNode) {
+ List<MethodNode> methods = classNode.methods;
+ for (MethodNode methodNode : methods) {
+ ListIterator<AbstractInsnNode> iterator = methodNode.instructions.iterator();
+ while (iterator.hasNext()) {
+ AbstractInsnNode abstractInsnNode = iterator.next();
+ if (abstractInsnNode.getType() == AbstractInsnNode.METHOD_INSN) {
+ MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode;
+ if (methodInsnNode.name.equals(SET_ON_TOUCH_LISTENER)
+ && methodInsnNode.desc.equals(SET_ON_TOUCH_LISTENER_SIG)) {
+ checkSetOnTouchListenerCall(context, methodNode, methodInsnNode);
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked") // ASM API
+ public static void checkSetOnTouchListenerCall(
+ @NonNull ClassContext context,
+ @NonNull MethodNode method,
+ @NonNull MethodInsnNode call) {
+ String owner = call.owner;
+
+ // Ignore the call if it was called on a non-view.
+ ClassNode ownerClass = context.getDriver().findClass(context, owner, 0);
+ if(ownerClass == null
+ || !context.getDriver().isSubclassOf(ownerClass, ANDROID_VIEW_VIEW)) {
+ return;
+ }
+
+ MethodNode performClick = findMethod(ownerClass.methods, PERFORM_CLICK, PERFORM_CLICK_SIG);
+ //noinspection VariableNotUsedInsideIf
+ if (performClick == null) {
+ String message = String.format(
+ "Custom view %1$s has setOnTouchListener called on it but does not "
+ + "override performClick", ownerClass.name);
+ context.report(ISSUE, method, call, context.getLocation(call), message, null);
+ }
+ }
+
+ @SuppressWarnings("unchecked") // ASM API
+ private static void checkOnTouchListener(ClassContext context, ClassNode classNode) {
+ MethodNode onTouchNode =
+ findMethod(
+ classNode.methods,
+ ON_TOUCH,
+ ON_TOUCH_SIG);
+ if (onTouchNode != null) {
+ AbstractInsnNode performClickInsnNode = findMethodCallInstruction(
+ onTouchNode.instructions,
+ ANDROID_VIEW_VIEW,
+ PERFORM_CLICK,
+ PERFORM_CLICK_SIG);
+ if (performClickInsnNode == null) {
+ String message = String.format(
+ "%1$s#onTouch should call View#performClick when a click is detected",
+ classNode.name);
+ context.report(
+ ISSUE,
+ onTouchNode,
+ null,
+ context.getLocation(onTouchNode, classNode),
+ message,
+ null);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked") // ASM API
+ private static void checkView(ClassContext context, ClassNode classNode) {
+ MethodNode onTouchEvent = findMethod(classNode.methods, ON_TOUCH_EVENT, ON_TOUCH_EVENT_SIG);
+ MethodNode performClick = findMethod(classNode.methods, PERFORM_CLICK, PERFORM_CLICK_SIG);
+
+ // Check if we override onTouchEvent.
+ if (onTouchEvent != null) {
+ // Ensure that we also override performClick.
+ //noinspection VariableNotUsedInsideIf
+ if (performClick == null) {
+ String message = String.format(
+ "Custom view %1$s overrides onTouchEvent but not performClick",
+ classNode.name);
+ context.report(ISSUE, onTouchEvent, null,
+ context.getLocation(onTouchEvent, classNode), message, null);
+ } else {
+ // If we override performClick, ensure that it is called inside onTouchEvent.
+ AbstractInsnNode performClickInOnTouchEventInsnNode = findMethodCallInstruction(
+ onTouchEvent.instructions,
+ classNode.name,
+ PERFORM_CLICK,
+ PERFORM_CLICK_SIG);
+ if (performClickInOnTouchEventInsnNode == null) {
+ String message = String.format(
+ "%1$s#onTouchEvent should call %1$s#performClick when a click is detected",
+ classNode.name);
+ context.report(ISSUE, onTouchEvent, null,
+ context.getLocation(onTouchEvent, classNode), message,
+ null);
+ }
+ }
+ }
+
+ // Ensure that, if performClick is implemented, performClick calls super.performClick.
+ if (performClick != null) {
+ AbstractInsnNode superPerformClickInPerformClickInsnNode = findMethodCallInstruction(
+ performClick.instructions,
+ classNode.superName,
+ PERFORM_CLICK,
+ PERFORM_CLICK_SIG);
+ if (superPerformClickInPerformClickInsnNode == null) {
+ String message = String.format(
+ "%1$s#performClick should call super#performClick",
+ classNode.name);
+ context.report(ISSUE, performClick, null,
+ context.getLocation(performClick, classNode), message, null);
+ }
+ }
+ }
+
+ @Nullable
+ private static MethodNode findMethod(
+ @NonNull List<MethodNode> methods,
+ @NonNull String name,
+ @NonNull String desc) {
+ for (MethodNode method : methods) {
+ if (name.equals(method.name)
+ && desc.equals(method.desc)) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked") // ASM API
+ @Nullable
+ private static AbstractInsnNode findMethodCallInstruction(
+ @NonNull InsnList instructions,
+ @NonNull String owner,
+ @NonNull String name,
+ @NonNull String desc) {
+ ListIterator<AbstractInsnNode> iterator = instructions.iterator();
+
+ while (iterator.hasNext()) {
+ AbstractInsnNode insnNode = iterator.next();
+ if (insnNode.getType() == AbstractInsnNode.METHOD_INSN) {
+ MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
+ if ((methodInsnNode.owner.equals(owner))
+ && (methodInsnNode.name.equals(name))
+ && (methodInsnNode.desc.equals(desc))) {
+ return methodInsnNode;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static boolean implementsOnTouchListener(ClassNode classNode) {
+ return (classNode.interfaces != null) && (classNode.interfaces.contains(ON_TOUCH_LISTENER));
+ }
+}
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 7923711..f58bbb4 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
@@ -86,7 +86,7 @@
"return an unexpected view.",
Category.CORRECTNESS,
7,
- Severity.WARNING,
+ Severity.FATAL,
IMPLEMENTATION);
/** The main issue discovered by this detector */
@@ -180,7 +180,7 @@
Object clientData = location.getClientData();
if (clientData instanceof Node) {
Node node = (Node) clientData;
- if (context.getDriver().isSuppressed(CROSS_LAYOUT, node)) {
+ if (context.getDriver().isSuppressed(null, CROSS_LAYOUT, node)) {
continue;
}
}
@@ -254,7 +254,7 @@
@Override
public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- assert attribute.getName().equals(ATTR_ID) || attribute.getLocalName().equals(ATTR_ID);
+ assert attribute.getName().equals(ATTR_ID) || ATTR_ID.equals(attribute.getLocalName());
String id = attribute.getValue();
if (context.getPhase() == 1) {
if (mIds.contains(id)) {
@@ -286,7 +286,8 @@
Collection<Occurrence> occurrences = map.get(id);
if (occurrences != null && !occurrences.isEmpty()) {
for (Occurrence occurrence : occurrences) {
- if (context.getDriver().isSuppressed(CROSS_LAYOUT, attribute)) {
+ if (context.getDriver().isSuppressed(context, CROSS_LAYOUT,
+ attribute)) {
return;
}
Location location = context.getLocation(attribute);
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
index ff7202d..abf1fab 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
@@ -23,12 +23,14 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceUrl;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.tools.lint.detector.api.Category;
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.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Location.Handle;
import com.android.tools.lint.detector.api.ResourceXmlDetector;
@@ -43,6 +45,8 @@
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
import java.io.File;
import java.util.Collection;
@@ -75,9 +79,25 @@
Severity.ERROR,
new Implementation(
DuplicateResourceDetector.class,
+ // We should be able to do this incrementally!
Scope.ALL_RESOURCES_SCOPE,
Scope.RESOURCE_FILE_SCOPE));
+ /** Wrong resource value type */
+ public static final Issue TYPE_MISMATCH = Issue.create(
+ "ReferenceType", //$NON-NLS-1$
+ "Incorrect reference types",
+ "Looks for resource aliases that are of the wrong type",
+ "When you generate a resource alias, the resource you are pointing to must be " +
+ "of the same type as the alias",
+ Category.CORRECTNESS,
+ 8,
+ Severity.FATAL,
+ new Implementation(
+ DuplicateResourceDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
+
private static final String PRODUCT = "product"; //$NON-NLS-1$
private Map<ResourceType, Set<String>> mTypeMap;
private Map<ResourceType, List<Pair<String, Location.Handle>>> mLocations;
@@ -126,6 +146,13 @@
String typeString = tag;
if (tag.equals(TAG_ITEM)) {
typeString = element.getAttribute(ATTR_TYPE);
+ if (typeString == null || typeString.isEmpty()) {
+ if (element.getParentNode().getNodeName().equals(
+ ResourceType.STYLE.getName()) && isFirstElementChild(element)) {
+ checkUniqueNames(context, (Element) element.getParentNode());
+ }
+ return;
+ }
}
ResourceType type = ResourceType.getEnum(typeString);
if (type == null) {
@@ -135,9 +162,44 @@
if (type == ResourceType.ATTR
&& element.getParentNode().getNodeName().equals(
ResourceType.DECLARE_STYLEABLE.getName())) {
+ if (isFirstElementChild(element)) {
+ checkUniqueNames(context, (Element) element.getParentNode());
+ }
return;
}
+ NodeList children = element.getChildNodes();
+ int childCount = children.getLength();
+ for (int i = 0; i < childCount; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ String text = child.getNodeValue();
+ for (int j = 0, length = text.length(); j < length; j++) {
+ char c = text.charAt(j);
+ if (c == '@') {
+ if (!text.regionMatches(false, j + 1, typeString, 0,
+ typeString.length()) && context.isEnabled(TYPE_MISMATCH)) {
+ ResourceUrl url = ResourceUrl.parse(text.trim());
+ if (url != null && url.type != type &&
+ // colors can apparently be used as drawables
+ !(type == ResourceType.DRAWABLE
+ && url.type == ResourceType.COLOR)) {
+ String message = "Unexpected resource reference type; "
+ + "expected value of type @" + type + "/";
+ context.report(TYPE_MISMATCH, element,
+ context.getLocation(child),
+ message, null);
+ }
+ }
+ break;
+ } else if (!Character.isWhitespace(c)) {
+ break;
+ }
+ }
+ break;
+ }
+ }
+
Set<String> names = mTypeMap.get(type);
if (names == null) {
names = Sets.newHashSetWithExpectedSize(40);
@@ -165,8 +227,52 @@
list = Lists.newArrayList();
mLocations.put(type, list);
}
- Location.Handle handle = context.parser.createLocationHandle(context, attribute);
+ Location.Handle handle = context.createLocationHandle(attribute);
list.add(Pair.of(name, handle));
}
}
+
+ private static void checkUniqueNames(XmlContext context, Element parent) {
+ List<Element> items = LintUtils.getChildren(parent);
+ if (items.size() > 1) {
+ Set<String> names = Sets.newHashSet();
+ for (Element item : items) {
+ Attr nameNode = item.getAttributeNode(ATTR_NAME);
+ if (nameNode != null) {
+ String name = nameNode.getValue();
+ if (names.contains(name) && context.isEnabled(ISSUE)) {
+ Location location = context.getLocation(nameNode);
+ for (Element prevItem : items) {
+ Attr attribute = item.getAttributeNode(ATTR_NAME);
+ if (attribute != null && name.equals(attribute.getValue())) {
+ assert prevItem != item;
+ Location prev = context.getLocation(prevItem);
+ prev.setMessage("Previously defined here");
+ location.setSecondary(prev);
+ break;
+ }
+ }
+ String message = String.format(
+ "%1$s has already been defined in this <%2$s>",
+ name, parent.getTagName());
+ context.report(ISSUE, nameNode, location, message,
+ null);
+ }
+ names.add(name);
+ }
+ }
+ }
+ }
+
+ private static boolean isFirstElementChild(Node node) {
+ node = node.getPreviousSibling();
+ while (node != null) {
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ return false;
+ }
+ node = node.getPreviousSibling();
+ }
+
+ return true;
+ }
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
index 04e04c5..fb4f5cc 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
@@ -16,29 +16,36 @@
package com.android.tools.lint.checks;
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.FRAGMENT;
-import static com.android.SdkConstants.FRAGMENT_V4;
+import static com.android.SdkConstants.CLASS_FRAGMENT;
+import static com.android.SdkConstants.CLASS_V4_FRAGMENT;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.LintDriver;
+import com.android.annotations.Nullable;
import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
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.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 org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-
+import java.lang.reflect.Modifier;
+import java.util.Collections;
import java.util.List;
+import lombok.ast.AstVisitor;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.TypeMember;
+
/**
* Checks that Fragment subclasses can be instantiated via
* {link {@link Class#newInstance()}}: the class is public, static, and has
@@ -48,9 +55,7 @@
* http://stackoverflow.com/questions/8058809/fragment-activity-crashes-on-screen-rotate
* (and countless duplicates)
*/
-public class FragmentDetector extends Detector implements ClassScanner {
- private static final String FRAGMENT_NAME_SUFFIX = "Fragment"; //$NON-NLS-1$
-
+public class FragmentDetector extends Detector implements JavaScanner {
/** Are fragment subclasses instantiatable? */
public static final Issue ISSUE = Issue.create(
"ValidFragment", //$NON-NLS-1$
@@ -67,10 +72,10 @@
Category.CORRECTNESS,
6,
- Severity.ERROR,
+ Severity.FATAL,
new Implementation(
FragmentDetector.class,
- Scope.CLASS_FILE_SCOPE)
+ Scope.JAVA_FILE_SCOPE)
).addMoreInfo(
"http://developer.android.com/reference/android/app/Fragment.html#Fragment()"); //$NON-NLS-1$
@@ -85,75 +90,100 @@
return Speed.FAST;
}
- // ---- Implements ClassScanner ----
+ // ---- Implements JavaScanner ----
+ @Nullable
@Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) {
- // Ignore abstract classes since they are clearly (and by definition) not intended to
- // be instantiated. We're looking for accidental non-static or missing constructor
- // scenarios here.
- return;
- }
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ return Collections.<Class<? extends Node>>singletonList(ClassDeclaration.class);
+ }
- LintDriver driver = context.getDriver();
-
- if (!(driver.isSubclassOf(classNode, FRAGMENT)
- || driver.isSubclassOf(classNode, FRAGMENT_V4))) {
- return;
- }
-
- if ((classNode.access & Opcodes.ACC_PUBLIC) == 0) {
- context.report(ISSUE, context.getLocation(classNode), String.format(
- "This fragment class should be public (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- return;
- }
-
- if (classNode.name.indexOf('$') != -1 && !LintUtils.isStaticInnerClass(classNode)) {
- context.report(ISSUE, context.getLocation(classNode), String.format(
- "This fragment inner class should be static (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- return;
- }
-
- boolean hasDefaultConstructor = false;
- @SuppressWarnings("rawtypes") // ASM API
- List methodList = classNode.methods;
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
- if (method.name.equals(CONSTRUCTOR_NAME)) {
- if (method.desc.equals("()V")) { //$NON-NLS-1$
- // The constructor must be public
- if ((method.access & Opcodes.ACC_PUBLIC) != 0) {
- hasDefaultConstructor = true;
- } else {
- context.report(ISSUE, context.getLocation(method, classNode),
- "The default constructor must be public",
- null);
- // Also mark that we have a constructor so we don't complain again
- // below since we've already emitted a more specific error related
- // to the default constructor
- hasDefaultConstructor = true;
- }
- } else if (!method.desc.contains("()")) { //$NON-NLS-1$
- context.report(ISSUE, context.getLocation(method, classNode),
- // TODO: Use separate issue for this which isn't an error
- "Avoid non-default constructors in fragments: use a default constructor " +
- "plus Fragment#setArguments(Bundle) instead",
- null);
+ @Nullable
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull final JavaContext context) {
+ return new ForwardingAstVisitor() {
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ int flags = node.astModifiers().getEffectiveModifierFlags();
+ if ((flags & Modifier.ABSTRACT) != 0) {
+ return true;
}
- }
- }
- if (!hasDefaultConstructor) {
- context.report(ISSUE, context.getLocation(classNode), String.format(
- "This fragment should provide a default constructor (a public " +
- "constructor with no arguments) (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- }
+ ResolvedNode resolved = context.resolve(node);
+ if (!(resolved instanceof ResolvedClass)) {
+ return true;
+ }
+
+ ResolvedClass cls = (ResolvedClass) resolved;
+
+ if (!cls.isSubclassOf(CLASS_FRAGMENT, false)
+ && !cls.isSubclassOf(CLASS_V4_FRAGMENT, false)) {
+ return true;
+ }
+
+ if ((flags & Modifier.PUBLIC) == 0) {
+ String message = String.format("This fragment class should be public (%1$s)",
+ cls.getName());
+ context.report(ISSUE, node, context.getLocation(node.astName()), message,
+ null);
+ return true;
+ }
+
+ if (cls.getContainingClass() != null && (flags & Modifier.STATIC) == 0) {
+ String message = String.format(
+ "This fragment inner class should be static (%1$s)", cls.getName());
+ context.report(ISSUE, node, context.getLocation(node.astName()), message,
+ null);
+ return true;
+ }
+
+ boolean hasDefaultConstructor = false;
+ boolean hasConstructor = false;
+ NormalTypeBody body = node.astBody();
+ if (body != null) {
+ for (TypeMember member : body.astMembers()) {
+ if (member instanceof ConstructorDeclaration) {
+ hasConstructor = true;
+ ConstructorDeclaration constructor = (ConstructorDeclaration) member;
+ if (constructor.astParameters().isEmpty()) {
+ // The constructor must be public
+ if (constructor.astModifiers().isPublic()) {
+ hasDefaultConstructor = true;
+ } else {
+ Location location = context.getLocation(
+ constructor.astTypeName());
+ context.report(ISSUE, constructor, location,
+ "The default constructor must be public",
+ null);
+ // Also mark that we have a constructor so we don't complain again
+ // below since we've already emitted a more specific error related
+ // to the default constructor
+ hasDefaultConstructor = true;
+ }
+ } else {
+ Location location = context.getLocation(constructor.astTypeName());
+ // TODO: Use separate issue for this which isn't an error
+ String message = "Avoid non-default constructors in fragments: "
+ + "use a default constructor plus "
+ + "Fragment#setArguments(Bundle) instead";
+ context.report(ISSUE, constructor, location, message, null);
+ }
+ }
+ }
+ }
+
+ if (!hasDefaultConstructor && hasConstructor) {
+ String message = String.format(
+ "This fragment should provide a default constructor (a public " +
+ "constructor with no arguments) (%1$s)",
+ cls.getName()
+ );
+ context.report(ISSUE, node, context.getLocation(node.astName()), message,
+ null);
+ }
+
+ return true;
+ }
+ };
}
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java
new file mode 100644
index 0000000..654a038
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java
@@ -0,0 +1,1025 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.FD_BUILD_TOOLS;
+import static com.android.SdkConstants.FD_EXTRAS;
+import static com.android.SdkConstants.FD_M2_REPOSITORY;
+import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_HIGHER;
+import static com.android.tools.lint.checks.ManifestDetector.TARGET_NEWER;
+import static com.google.common.base.Charsets.UTF_8;
+import static java.io.File.separator;
+import static java.io.File.separatorChar;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.repository.GradleCoordinate;
+import com.android.sdklib.repository.FullRevision;
+import com.android.tools.lint.client.api.LintClient;
+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.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 com.android.tools.lint.detector.api.Speed;
+import com.google.common.collect.Lists;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Checks Gradle files for potential errors
+ */
+public class GradleDetector extends Detector implements Detector.GradleScanner {
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ GradleDetector.class,
+ Scope.GRADLE_SCOPE);
+
+ /** Obsolete dependencies */
+ public static final Issue DEPENDENCY = Issue.create(
+ "GradleDependency", //$NON-NLS-1$
+ "Obsolete Gradle Dependency",
+ "Looks for old or obsolete Gradle library dependencies",
+ "This detector looks for usages of libraries where the version you are using " +
+ "is not the current stable release. Using older versions is fine, and there are " +
+ "cases where you deliberately want to stick with an older version. However, " +
+ "you may simply not be aware that a more recent version is available, and that is " +
+ "what this lint check helps find.",
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Deprecated Gradle constructs */
+ public static final Issue DEPRECATED = Issue.create(
+ "GradleDeprecated", //$NON-NLS-1$
+ "Deprecated Gradle Construct",
+ "Looks for deprecated Gradle constructs",
+ "This detector looks for deprecated Gradle constructs which currently work but " +
+ "will likely stop working in a future update.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Incompatible Android Gradle plugin */
+ public static final Issue GRADLE_PLUGIN_COMPATIBILITY = Issue.create(
+ "AndroidGradlePluginVersion", //$NON-NLS-1$
+ "Incompatible Android Gradle Plugin",
+ "Ensures that the Android Gradle plugin version is compatible with this SDK",
+ "Not all versions of the Android Gradle plugin are compatible with all versions " +
+ "of the SDK. If you update your tools, or if you are trying to open a project that " +
+ "was built with an old version of the tools, you may need to update your plugin " +
+ "version number.",
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Invalid or dangerous paths */
+ public static final Issue PATH = Issue.create(
+ "GradlePath", //$NON-NLS-1$
+ "Gradle Path Issues",
+ "Looks for Gradle path problems such as using platform specific path separators",
+ "Gradle build scripts are meant to be cross platform, so file paths use " +
+ "Unix-style path separators (a forward slash) rather than Windows path separators " +
+ "(a backslash). Similarly, to keep projects portable and repeatable, avoid " +
+ "using absolute paths on the system; keep files within the project instead. To " +
+ "share code between projects, consider creating an android-library and an AAR " +
+ "dependency",
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Constructs the IDE support struggles with */
+ public static final Issue IDE_SUPPORT = Issue.create(
+ "GradleIdeError", //$NON-NLS-1$
+ "Gradle IDE Support Issues",
+ "Looks for constructs in Gradle files which affect IDE usage",
+ "Gradle is highly flexible, and there are things you can do in Gradle files which " +
+ "can make it hard or impossible for IDEs to properly handle the project. This lint " +
+ "check looks for constructs that potentially break IDE support.",
+ Category.CORRECTNESS,
+ 4,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Using + in versions */
+ public static final Issue PLUS = Issue.create(
+ "GradleDynamicVersion", //$NON-NLS-1$
+ "Gradle Dynamic Version",
+ "Looks for dependencies using a dynamic version rather than a fixed version",
+ "Using `+` in dependencies lets you automatically pick up the latest available " +
+ "version rather than a specific, named version. However, this is not recommended; " +
+ "your builds are not repeatable; you may have tested with a slightly different " +
+ "version than what the build server used. (Using a dynamic version as the major " +
+ "version number is more problematic than using it in the minor version position.)",
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION).setEnabledByDefault(false);
+
+ /** Accidentally calling a getter instead of your own methods */
+ public static final Issue GRADLE_GETTER = Issue.create(
+ "GradleGetter", //$NON-NLS-1$
+ "Gradle Implicit Getter Call",
+ "Identifies accidental calls to implicit getters",
+ "Gradle will let you replace specific constants in your build scripts with method " +
+ "calls, so you can for example dynamically compute a version string based on your " +
+ "current version control revision number, rather than hardcoding a number.\n" +
+ "\n" +
+ "When computing a version name, it's tempting to for example call the method to do " +
+ "that `getVersionName`. However, when you put that method call inside the " +
+ "`defaultConfig` block, you will actually be calling the Groovy getter for the " +
+ "`versionName` property instead. Therefore, you need to name your method something " +
+ "which does not conflict with the existing implicit getters. Consider using " +
+ "`compute` as a prefix instead of `get`.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Using incompatible versions */
+ public static final Issue COMPATIBILITY = Issue.create(
+ "GradleCompatible", //$NON-NLS-1$
+ "Incompatible Gradle Versions",
+ "Ensures that tool and library versions are compatible",
+
+ "There are some combinations of libraries, or tools and libraries, that are " +
+ "incompatible, or can lead to bugs. One such incompatibility is compiling with " +
+ "a version of the Android support libraries that is not the latest version (or in " +
+ "particular, a version lower than your `targetSdkVersion`.)",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Using a string where an integer is expected */
+ public static final Issue STRING_INTEGER = Issue.create(
+ "StringShouldBeInt", //$NON-NLS-1$
+ "String should be int",
+ "Checks for uses of strings where an integer should be used",
+
+ "The properties `compileSdkVersion`, `minSdkVersion` and `targetSdkVersion` are " +
+ "usually numbers, but can be strings when you are using an add-on (in the case " +
+ "of `compileSdkVersion`) or a preview platform (for the other two properties).\n" +
+ "\n" +
+ "However, you can not use a number as a string (e.g. \"19\" instead of 19); that " +
+ "will result in a platform not found error message at build/sync time.",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ public static final Issue REMOTE_VERSION = Issue.create(
+ "NewerVersionAvailable", //$NON-NLS-1$
+ "Newer Library Versions Available",
+ "Looks for Gradle library dependencies that can be replaced by newer versions",
+ "This detector checks with a central repository to see if there are newer versions " +
+ "available for the dependencies used by this project.\n" +
+ "This is similar to the `GradleDependency` check, which checks for newer versions " +
+ "available in the Android SDK tools and libraries, but this works with any " +
+ "MavenCentral dependency, and connects to the library every time, which makes " +
+ "it more flexible but also *much* slower.",
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION).setEnabledByDefault(false);
+
+ /** Accidentally using octal numbers */
+ public static final Issue ACCIDENTAL_OCTAL = Issue.create(
+ "AccidentalOctal", //$NON-NLS-1$
+ "Accidental Octal",
+ "Looks for integer literals that are interpreted as octal numbers",
+
+ "In Groovy, an integer literal that starts with a leading 0 will be interpreted " +
+ "as an octal number. That is usually (always?) an accident and can lead to " +
+ "subtle bugs, for example when used in the `versionCode` of an app.",
+
+ Category.CORRECTNESS,
+ 2,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** A statement appearing at the root of the top-level build file that shouldn't be there */
+ public static final Issue IMPROPER_PROJECT_LEVEL_STATEMENT = Issue.create(
+ "ImproperProjectLevelStatement", //$NON-NLS-1$
+ "Improper project-level build file statement",
+ "Looks for statements that likely don't belong in a project-level build file",
+
+ "The top-level build file in a multi-module project is generally used to configure project-wide " +
+ "build parameters and often does not describe a corresponding top-level module. In build files " +
+ "without a module, it is an error to use build file constructs that require a module; doing so can " +
+ "lead to unpredictable error messages.",
+
+ Category.CORRECTNESS,
+ 2,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** A statement appearing within the wrong scope of a build file */
+ public static final Issue MISPLACED_STATEMENT = Issue.create(
+ "MisplacedStatement", //$NON-NLS-1$
+ "Misplaced statement",
+ "Looks for build file statements that belong elsewhere in the build file",
+
+ "Most build file directives only make sense in certain contexts in the build file. If you put a " +
+ "statement in the wrong place, you can get errors or unexpected behavior.",
+
+ Category.CORRECTNESS,
+ 2,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** The Gradle plugin ID for Android applications */
+ public static final String APP_PLUGIN_ID = "com.android.application";
+ /** The Gradle plugin ID for Android libraries */
+ public static final String LIB_PLUGIN_ID = "com.android.library";
+ /** Previous plugin id for applications */
+ public static final String OLD_APP_PLUGIN_ID = "android";
+ /** Previous plugin id for libraries */
+ public static final String OLD_LIB_PLUGIN_ID = "android-library";
+
+ private int mMinSdkVersion;
+ private int mCompileSdkVersion;
+ private int mTargetSdkVersion;
+ private Object myAndroidPluginCookie;
+ private Object myDependenciesCookie;
+ private Object myRepositoriesCookie;
+ private Object myAndroidBlockCookie;
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ @Override
+ @NonNull
+ public Speed getSpeed(@SuppressWarnings("UnusedParameters") @NonNull Issue issue) {
+ return issue == REMOTE_VERSION ? Speed.REALLY_SLOW : Speed.NORMAL;
+ }
+
+ // ---- Implements Detector.GradleScanner ----
+
+ @Override
+ public void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData) {
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ protected static boolean isInterestingBlock(
+ @NonNull String parent,
+ @Nullable String parentParent) {
+ return parent.equals("defaultConfig")
+ || parent.equals("android")
+ || parent.equals("dependencies")
+ || parent.equals("repositories")
+ || parentParent != null && parentParent.equals("buildTypes");
+ }
+
+ protected static boolean isInterestingStatement(
+ @NonNull String statement,
+ @Nullable String parent) {
+ return parent == null && statement.equals("apply");
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ protected static boolean isInterestingProperty(
+ @NonNull String property,
+ @SuppressWarnings("UnusedParameters")
+ @NonNull String parent,
+ @Nullable String parentParent) {
+ return property.equals("targetSdkVersion")
+ || property.equals("buildToolsVersion")
+ || property.equals("versionName")
+ || property.equals("versionCode")
+ || property.equals("compileSdkVersion")
+ || property.equals("minSdkVersion")
+ || property.equals("applicationIdSuffix")
+ || property.equals("packageName")
+ || property.equals("packageNameSuffix")
+ || parent.equals("dependencies");
+ }
+
+ protected void checkOctal(
+ @NonNull Context context,
+ @NonNull String value,
+ @NonNull Object cookie) {
+ if (value.length() >= 2
+ && value.charAt(0) == '0'
+ && (value.length() > 2 || value.charAt(1) >= '8'
+ && isInteger(value))
+ && context.isEnabled(ACCIDENTAL_OCTAL)) {
+ String message = "The leading 0 turns this number into octal which is probably "
+ + "not what was intended";
+ try {
+ long numericValue = Long.decode(value);
+ message += " (interpreted as " + numericValue + ")";
+ } catch (NumberFormatException nufe) {
+ message += " (and it is not a valid octal number)";
+ }
+ report(context, cookie, ACCIDENTAL_OCTAL, message);
+ }
+ }
+
+ /** Called with for example "android", "defaultConfig", "minSdkVersion", "7" */
+ @SuppressWarnings("UnusedDeclaration")
+ protected void checkDslPropertyAssignment(
+ @NonNull Context context,
+ @NonNull String property,
+ @NonNull String value,
+ @NonNull String parent,
+ @Nullable String parentParent,
+ @NonNull Object valueCookie,
+ @NonNull Object statementCookie) {
+ if (parent.equals("defaultConfig")) {
+ if (property.equals("targetSdkVersion")) {
+ int version = getIntLiteralValue(value, -1);
+ if (version > 0 && version < context.getClient().getHighestKnownApiLevel()) {
+ String message =
+ "Not targeting the latest versions of Android; compatibility " +
+ "modes apply. Consider testing and updating this version. " +
+ "Consult the android.os.Build.VERSION_CODES javadoc for details.";
+ report(context, valueCookie, TARGET_NEWER, message);
+ }
+ if (version > 0) {
+ mTargetSdkVersion = version;
+ checkTargetCompatibility(context, valueCookie);
+ } else {
+ checkIntegerAsString(context, value, valueCookie);
+ }
+ } else if (property.equals("minSdkVersion")) {
+ int version = getIntLiteralValue(value, -1);
+ if (version > 0) {
+ mMinSdkVersion = version;
+ } else {
+ checkIntegerAsString(context, value, valueCookie);
+ }
+ }
+
+ if (value.startsWith("0")) {
+ checkOctal(context, value, valueCookie);
+ }
+
+ if (property.equals("versionName") || property.equals("versionCode") &&
+ !isInteger(value) || !isStringLiteral(value)) {
+ // Method call -- make sure it does not match one of the getters in the
+ // configuration!
+ if ((value.equals("getVersionCode") ||
+ value.equals("getVersionName"))) {
+ String message = "Bad method name: pick a unique method name which does not "
+ + "conflict with the implicit getters for the defaultConfig "
+ + "properties. For example, try using the prefix compute- "
+ + "instead of get-.";
+ report(context, valueCookie, GRADLE_GETTER, message);
+ }
+ } else if (property.equals("packageName")) {
+ if (isModelOlderThan011(context)) {
+ return;
+ }
+ String message = "Deprecated: Replace 'packageName' with 'applicationId'";
+ report(context, getPropertyKeyCookie(valueCookie), IDE_SUPPORT, message);
+ }
+ } else if (property.equals("compileSdkVersion") && parent.equals("android")) {
+ int version = getIntLiteralValue(value, -1);
+ if (version > 0) {
+ mCompileSdkVersion = version;
+ checkTargetCompatibility(context, valueCookie);
+ } else {
+ checkIntegerAsString(context, value, valueCookie);
+ }
+ } else if (property.equals("buildToolsVersion") && parent.equals("android")) {
+ String versionString = getStringLiteralValue(value);
+ if (versionString != null) {
+ FullRevision version = parseRevisionSilently(versionString);
+ if (version != null) {
+ FullRevision recommended = getLatestBuildTools(context.getClient(),
+ version.getMajor());
+ if (recommended != null && version.compareTo(recommended) < 0) {
+ String message = "Old buildToolsVersion; recommended version "
+ + "is " + recommended + " or later";
+ report(context, valueCookie, DEPENDENCY, message);
+ }
+ }
+ }
+ } else if (parent.equals("dependencies")) {
+ if (value.startsWith("files('") && value.endsWith("')")) {
+ String path = value.substring("files('".length(), value.length() - 2);
+ if (path.contains("\\\\")) {
+ String message = "Do not use Windows file separators in .gradle files; "
+ + "use / instead";
+ report(context, valueCookie, PATH, message);
+
+ } else if (new File(path.replace('/', File.separatorChar)).isAbsolute()) {
+ String message = "Avoid using absolute paths in .gradle files";
+ report(context, valueCookie, PATH, message);
+ }
+ } else {
+ String dependency = getStringLiteralValue(value);
+ if (dependency != null) {
+ GradleCoordinate gc = GradleCoordinate.parseCoordinateString(dependency);
+ if (gc != null) {
+ if (gc.acceptsGreaterRevisions()) {
+ String message = "Avoid using + in version numbers; can lead "
+ + "to unpredictable and unrepeatable builds";
+ report(context, valueCookie, PLUS, message);
+ }
+ if (!dependency.startsWith(SdkConstants.GRADLE_PLUGIN_NAME) ||
+ !checkGradlePluginDependency(context, gc, valueCookie)) {
+ checkDependency(context, gc, valueCookie);
+ }
+ }
+ }
+ }
+ if ((!property.equals("classpath")) && "buildscript".equals(parentParent)) {
+ String message = "Only `classpath` dependencies should appear in the `buildscript` dependencies block";
+ report(context, statementCookie, IMPROPER_PROJECT_LEVEL_STATEMENT, message);
+ }
+ } else if (property.equals("packageNameSuffix")) {
+ if (isModelOlderThan011(context)) {
+ return;
+ }
+ String message = "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'";
+ report(context, getPropertyKeyCookie(valueCookie), IDE_SUPPORT, message);
+ } else if (property.equals("applicationIdSuffix")) {
+ String suffix = getStringLiteralValue(value);
+ if (suffix != null && !suffix.startsWith(".")) {
+ String message = "Package suffix should probably start with a \".\"";
+ report(context, valueCookie, PATH, message);
+ }
+ }
+ }
+
+ private void checkIntegerAsString(Context context, String value, Object valueCookie) {
+ // When done developing with a preview platform you might be tempted to switch from
+ // compileSdkVersion 'android-G'
+ // to
+ // compileSdkVersion '19'
+ // but that won't work; it needs to be
+ // compileSdkVersion 19
+ String string = getStringLiteralValue(value);
+ if (isNumberString(string)) {
+ String quote = Character.toString(value.charAt(0));
+ String message = String.format("Use an integer rather than a string here "
+ + "(replace %1$s%2$s%1$s with just %2$s)", quote, string);
+ report(context, valueCookie, STRING_INTEGER, message);
+ }
+ }
+
+ private static boolean isNumberString(@Nullable String s) {
+ if (s == null || s.isEmpty()) {
+ return false;
+ }
+ for (int i = 0, n = s.length(); i < n; i++) {
+ if (!Character.isDigit(s.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected void checkBlock(
+ @NonNull Context context,
+ @NonNull String block,
+ @Nullable String parent,
+ @NonNull Object cookie) {
+ if ("android".equals(block) && parent == null) {
+ myAndroidBlockCookie = cookie;
+ } else if ("dependencies".equals(block)) {
+ if (parent == null) {
+ myDependenciesCookie = cookie;
+ } else if (!parent.equals("buildscript") && !parent.equals("allprojects") && !parent.equals("subprojects")) {
+ String message = "A `dependencies` block doesn't belong here.";
+ report(context, cookie, MISPLACED_STATEMENT, message);
+ }
+ } else if ("repositories".equals(block)) {
+ if (parent == null) {
+ myRepositoriesCookie = cookie;
+ } else if (!parent.equals("buildscript") && !parent.equals("allprojects") && !parent.equals("subprojects")) {
+ String message = "A `repositories` block doesn't belong here.";
+ report(context, cookie, MISPLACED_STATEMENT, message);
+ }
+ }
+ }
+
+ protected void checkMethodCall(
+ @NonNull Context context,
+ @NonNull String statement,
+ @Nullable String parent,
+ @NonNull Map<String, String> namedArguments,
+ @NonNull List<String> unnamedArguments,
+ @NonNull Object cookie) {
+ String plugin = namedArguments.get("plugin");
+ if (statement.equals("apply") && parent == null) {
+ boolean isOldAppPlugin = OLD_APP_PLUGIN_ID.equals(plugin);
+ if (isOldAppPlugin || OLD_LIB_PLUGIN_ID.equals(plugin)) {
+ myAndroidPluginCookie = cookie;
+ String replaceWith = isOldAppPlugin ? APP_PLUGIN_ID : LIB_PLUGIN_ID;
+ String message = String.format("'%1$s' is deprecated; use '%2$s' instead", plugin,
+ replaceWith);
+ report(context, cookie, DEPRECATED, message);
+ } else if (APP_PLUGIN_ID.equals(plugin) || LIB_PLUGIN_ID.equals(plugin)) {
+ myAndroidPluginCookie = cookie;
+ }
+ }
+ }
+
+ @Override
+ public void afterCheckFile(@NonNull Context context) {
+ if (myAndroidPluginCookie != null && !isAndroidProject()) {
+ String message = "The `apply plugin` statement should only be used if there is a corresponding module for this build file.";
+ report(context, myAndroidPluginCookie, IMPROPER_PROJECT_LEVEL_STATEMENT, message);
+ }
+ if (myAndroidBlockCookie != null && !isAndroidProject()) {
+ String message = "An `android` block should only appear in build files that correspond to a module and have an " +
+ "`apply plugin: 'com.android.application'` or `apply plugin: 'com.android.library'` statement.";
+ report(context, myAndroidBlockCookie, IMPROPER_PROJECT_LEVEL_STATEMENT, message);
+ }
+ if (myDependenciesCookie != null && !isAndroidProject()) {
+ String message = "A top-level `dependencies` block should only appear in build files that correspond to a module.";
+ report(context, myDependenciesCookie, IMPROPER_PROJECT_LEVEL_STATEMENT, message);
+ super.afterCheckFile(context);
+ }
+ if (myRepositoriesCookie != null && !isAndroidProject()) {
+ String message = "A top-level `repositories` block should only appear in build files that correspond to a module.";
+ report(context, myRepositoriesCookie, IMPROPER_PROJECT_LEVEL_STATEMENT, message);
+ super.afterCheckFile(context);
+ }
+ }
+
+ private boolean isAndroidProject() {
+ return myAndroidBlockCookie != null && myAndroidPluginCookie != null;
+ }
+
+ @Nullable
+ private static FullRevision parseRevisionSilently(String versionString) {
+ try {
+ return FullRevision.parseRevision(versionString);
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ private static boolean isModelOlderThan011(@NonNull Context context) {
+ return LintUtils.isModelOlderThan(context.getProject().getGradleProjectModel(), 0, 11, 0);
+ }
+
+ private static int sMajorBuildTools;
+ private static FullRevision sLatestBuildTools;
+
+ /** Returns the latest build tools installed for the given major version.
+ * We just cache this once; we don't need to be accurate in the sense that if the
+ * user opens the SDK manager and installs a more recent version, we capture this in
+ * the same IDE session.
+ *
+ * @param client the associated client
+ * @param major the major version of build tools to look up (e.g. typically 18, 19, ...)
+ * @return the corresponding highest known revision
+ */
+ @Nullable
+ private static FullRevision getLatestBuildTools(@NonNull LintClient client, int major) {
+ if (major != sMajorBuildTools) {
+ sMajorBuildTools = major;
+
+ List<FullRevision> revisions = Lists.newArrayList();
+ if (major == 19) {
+ revisions.add(new FullRevision(19, 1, 0));
+ } else if (major == 18) {
+ revisions.add(new FullRevision(18, 1, 1));
+ }
+ // The above versions can go stale.
+ // Check if a more recent one is installed. (The above are still useful for
+ // people who haven't updated with the SDK manager recently.)
+ File sdkHome = client.getSdkHome();
+ if (sdkHome != null) {
+ File[] dirs = new File(sdkHome, FD_BUILD_TOOLS).listFiles();
+ if (dirs != null) {
+ for (File dir : dirs) {
+ String name = dir.getName();
+ if (!dir.isDirectory() || !Character.isDigit(name.charAt(0))) {
+ continue;
+ }
+ FullRevision v = parseRevisionSilently(name);
+ if (v != null && v.getMajor() == major) {
+ revisions.add(v);
+ }
+ }
+ }
+ }
+
+ if (!revisions.isEmpty()) {
+ sLatestBuildTools = Collections.max(revisions);
+ }
+ }
+
+ return sLatestBuildTools;
+ }
+
+ private void checkTargetCompatibility(Context context, Object cookie) {
+ if (mCompileSdkVersion > 0 && mTargetSdkVersion > 0
+ && mTargetSdkVersion > mCompileSdkVersion) {
+ String message = "The targetSdkVersion should not be higher than the compileSdkVersion";
+ report(context, cookie, DEPENDENCY, message);
+ }
+ }
+
+ @Nullable
+ private static String getStringLiteralValue(@NonNull String value) {
+ if (value.length() > 2 && (value.startsWith("'") && value.endsWith("'") ||
+ value.startsWith("\"") && value.endsWith("\""))) {
+ return value.substring(1, value.length() - 1);
+ }
+
+ return null;
+ }
+
+ private static int getIntLiteralValue(@NonNull String value, int defaultValue) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ private static boolean isInteger(String token) {
+ return token.matches("\\d+");
+ }
+
+ private static boolean isStringLiteral(String token) {
+ return token.startsWith("\"") && token.endsWith("\"") ||
+ token.startsWith("'") && token.endsWith("'");
+ }
+
+ private void checkDependency(
+ @NonNull Context context,
+ @NonNull GradleCoordinate dependency,
+ @NonNull Object cookie) {
+ if ("com.android.support".equals(dependency.getGroupId()) &&
+ ("support-v4".equals(dependency.getArtifactId()) ||
+ "appcompat-v7".equals(dependency.getArtifactId()))) {
+ checkSupportLibraries(context, dependency, cookie);
+ if (mMinSdkVersion >= 14 && "appcompat-v7".equals(dependency.getArtifactId())) {
+ report(context, cookie, DEPENDENCY,
+ "Using the appcompat library when minSdkVersion >= 14 is not necessary");
+ }
+ return;
+ } else if ("com.google.android.gms".equals(dependency.getGroupId()) &&
+ "play-services".equals(dependency.getArtifactId())) {
+ checkPlayServices(context, dependency, cookie);
+ return;
+ }
+
+ FullRevision version = null;
+ Issue issue = DEPENDENCY;
+ if ("com.android.tools.build".equals(dependency.getGroupId()) &&
+ "gradle".equals(dependency.getArtifactId())) {
+ version = getNewerRevision(dependency, 0, 11, 0);
+ } else if ("com.google.guava".equals(dependency.getGroupId()) &&
+ "guava".equals(dependency.getArtifactId())) {
+ version = getNewerRevision(dependency, 17, 0, 0);
+ } else if ("com.google.code.gson".equals(dependency.getGroupId()) &&
+ "gson".equals(dependency.getArtifactId())) {
+ version = getNewerRevision(dependency, 2, 2, 4);
+ } else if ("org.apache.httpcomponents".equals(dependency.getGroupId()) &&
+ "httpclient".equals(dependency.getArtifactId())) {
+ version = getNewerRevision(dependency, 4, 3, 3);
+ }
+
+ // Network check for really up to date libraries? Only done in batch mode
+ if (context.getScope().size() > 1 && context.isEnabled(REMOTE_VERSION)) {
+ FullRevision latest = getLatestVersion(context, dependency);
+ if (latest != null && isOlderThan(dependency, latest.getMajor(), latest.getMinor(),
+ latest.getMicro())) {
+ version = latest;
+ issue = REMOTE_VERSION;
+ }
+ }
+
+ if (version != null) {
+ String message = "A newer version of " + dependency.getGroupId() + ":" +
+ dependency.getArtifactId() + " than " + dependency.getFullRevision() +
+ " is available: " + version.toShortString();
+ report(context, cookie, issue, message);
+ }
+ }
+
+ /** TODO: Cache these results somewhere! */
+ private static FullRevision getLatestVersion(@NonNull Context context,
+ @NonNull GradleCoordinate dependency) {
+ StringBuilder query = new StringBuilder();
+ String encoding = UTF_8.name();
+ try {
+ query.append("http://search.maven.org/solrsearch/select?q=g:%22");
+ query.append(URLEncoder.encode(dependency.getGroupId(), encoding));
+ query.append("%22+AND+a:%22");
+ query.append(URLEncoder.encode(dependency.getArtifactId(), encoding));
+ } catch (UnsupportedEncodingException ee) {
+ return null;
+ }
+ query.append("%22&core=gav&rows=1&wt=json");
+
+ String response = readUrlData(context, dependency, query.toString());
+ if (response == null) {
+ return null;
+ }
+
+ // Sample response:
+ // {
+ // "responseHeader": {
+ // "status": 0,
+ // "QTime": 0,
+ // "params": {
+ // "fl": "id,g,a,v,p,ec,timestamp,tags",
+ // "sort": "score desc,timestamp desc,g asc,a asc,v desc",
+ // "indent": "off",
+ // "q": "g:\"com.google.guava\" AND a:\"guava\"",
+ // "core": "gav",
+ // "wt": "json",
+ // "rows": "1",
+ // "version": "2.2"
+ // }
+ // },
+ // "response": {
+ // "numFound": 37,
+ // "start": 0,
+ // "docs": [{
+ // "id": "com.google.guava:guava:17.0",
+ // "g": "com.google.guava",
+ // "a": "guava",
+ // "v": "17.0",
+ // "p": "bundle",
+ // "timestamp": 1398199666000,
+ // "tags": ["spec", "libraries", "classes", "google", "code"],
+ // "ec": ["-javadoc.jar", "-sources.jar", ".jar", "-site.jar", ".pom"]
+ // }]
+ // }
+ // }
+
+ // Look for version info: This is just a cheap skim of the above JSON results
+ int index = response.indexOf("\"response\""); //$NON-NLS-1$
+ if (index != -1) {
+ index = response.indexOf("\"v\":", index); //$NON-NLS-1$
+ if (index != -1) {
+ index += 4;
+ int start = response.indexOf('"', index) + 1;
+ int end = response.indexOf('"', start + 1);
+ if (end > start && start >= 0) {
+ return parseRevisionSilently(response.substring(start, end));
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static String readUrlData(
+ @NonNull Context context,
+ @NonNull GradleCoordinate dependency,
+ @NonNull String query) {
+ LintClient client = context.getClient();
+ try {
+ URL url = new URL(query);
+
+ URLConnection connection = client.openConnection(url);
+ if (connection == null) {
+ return null;
+ }
+ try {
+ InputStream is = connection.getInputStream();
+ if (is == null) {
+ return null;
+ }
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8));
+ try {
+ StringBuilder sb = new StringBuilder(500);
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ sb.append('\n');
+ }
+
+ return sb.toString();
+ } finally {
+ reader.close();
+ }
+ } finally {
+ client.closeConnection(connection);
+ }
+ } catch (IOException ioe) {
+ client.log(ioe, "Could not connect to maven central to look up the " + "latest available version for %1$s", dependency);
+ return null;
+ }
+ }
+
+ private boolean checkGradlePluginDependency(Context context, GradleCoordinate dependency,
+ Object cookie) {
+ GradleCoordinate latestPlugin = GradleCoordinate.parseCoordinateString(SdkConstants.GRADLE_PLUGIN_NAME +
+ SdkConstants.GRADLE_PLUGIN_MINIMUM_VERSION);
+ if (GradleCoordinate.COMPARE_PLUS_HIGHER.compare(dependency, latestPlugin) < 0) {
+ String message = "You must use a newer version of the Android Gradle plugin. The "
+ + "minimum supported version is " + SdkConstants.GRADLE_PLUGIN_MINIMUM_VERSION +
+ " and the recommended version is " + SdkConstants.GRADLE_PLUGIN_RECOMMENDED_VERSION;
+ report(context, cookie, GRADLE_PLUGIN_COMPATIBILITY, message);
+ return true;
+ }
+ return false;
+ }
+
+ private void checkSupportLibraries(Context context, GradleCoordinate dependency,
+ Object cookie) {
+ String groupId = dependency.getGroupId();
+ String artifactId = dependency.getArtifactId();
+ assert groupId != null && artifactId != null;
+
+ // See if the support library version is lower than the targetSdkVersion
+ if (mTargetSdkVersion > 0 && dependency.getMajorVersion() < mTargetSdkVersion &&
+ dependency.getMajorVersion() != GradleCoordinate.PLUS_REV_VALUE &&
+ context.isEnabled(COMPATIBILITY)) {
+ String message = "This support library should not use a lower version ("
+ + dependency.getMajorVersion() + ") than the targetSdkVersion ("
+ + mTargetSdkVersion + ")";
+ report(context, cookie, COMPATIBILITY, message);
+ }
+
+ // Check to make sure you have the Android support repository installed
+ File repository = findRepository(context.getClient(), "android");
+ if (repository == null) {
+ report(context, cookie, DEPENDENCY,
+ "Dependency on a support library, but the SDK installation does not "
+ + "have the \"Extras > Android Support Repository\" installed. "
+ + "Open the SDK manager and install it.");
+ } else {
+ checkLocalMavenVersions(context, dependency, cookie, groupId, artifactId,
+ repository);
+ }
+ }
+
+ private void checkPlayServices(Context context, GradleCoordinate dependency, Object cookie) {
+ String groupId = dependency.getGroupId();
+ String artifactId = dependency.getArtifactId();
+ assert groupId != null && artifactId != null;
+
+ File repository = findRepository(context.getClient(), "google");
+ if (repository == null) {
+ report(context, cookie, DEPENDENCY,
+ "Dependency on Play Services, but the SDK installation does not "
+ + "have the \"Extras > Google Repository\" installed. "
+ + "Open the SDK manager and install it.");
+ } else {
+ checkLocalMavenVersions(context, dependency, cookie, groupId, artifactId,
+ repository);
+ }
+ }
+
+ private void checkLocalMavenVersions(Context context, GradleCoordinate dependency,
+ Object cookie, String groupId, String artifactId, File repository) {
+ GradleCoordinate max = getHighestInstalledVersion(groupId, artifactId, repository);
+ if (max != null) {
+ if (COMPARE_PLUS_HIGHER.compare(dependency, max) < 0
+ && context.isEnabled(DEPENDENCY)) {
+ String message = "A newer version of " + groupId
+ + ":" + artifactId + " than " +
+ dependency.getFullRevision() + " is available: " +
+ max.getFullRevision();
+ report(context, cookie, DEPENDENCY, message);
+ }
+ }
+ }
+
+ private static File findRepository(LintClient client, String extrasName) {
+ File sdkHome = client.getSdkHome();
+ if (sdkHome != null) {
+ File repository = new File(sdkHome, FD_EXTRAS + separator + extrasName + separator
+ + FD_M2_REPOSITORY);
+ if (repository.exists()) {
+ return repository;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static GradleCoordinate getHighestInstalledVersion(
+ @NonNull String groupId,
+ @NonNull String artifactId,
+ @NonNull File repository) {
+ File versionDir = new File(repository,
+ groupId.replace('.', separatorChar) + separator + artifactId);
+ File[] versions = versionDir.listFiles();
+ if (versions != null) {
+ List<GradleCoordinate> versionCoordinates = Lists.newArrayList();
+ for (File dir : versions) {
+ if (!dir.isDirectory()) {
+ continue;
+ }
+ GradleCoordinate gc = GradleCoordinate.parseCoordinateString(
+ groupId + ":" + artifactId + ":" + dir.getName());
+ if (gc != null) {
+ versionCoordinates.add(gc);
+ }
+ }
+ if (!versionCoordinates.isEmpty()) {
+ return Collections.max(versionCoordinates, COMPARE_PLUS_HIGHER);
+ }
+ }
+
+ return null;
+ }
+
+ private static FullRevision getNewerRevision(@NonNull GradleCoordinate dependency,
+ int major, int minor, int micro) {
+ assert dependency.getGroupId() != null;
+ assert dependency.getArtifactId() != null;
+ if (COMPARE_PLUS_HIGHER.compare(dependency,
+ new GradleCoordinate(dependency.getGroupId(),
+ dependency.getArtifactId(), major, minor, micro)) < 0) {
+ return new FullRevision(major, minor, micro);
+ } else {
+ return null;
+ }
+ }
+
+ private static boolean isOlderThan(@NonNull GradleCoordinate dependency, int major, int minor,
+ int micro) {
+ assert dependency.getGroupId() != null;
+ assert dependency.getArtifactId() != null;
+ return COMPARE_PLUS_HIGHER.compare(dependency,
+ new GradleCoordinate(dependency.getGroupId(),
+ dependency.getArtifactId(), major, minor, micro)) < 0;
+ }
+
+ private void report(@NonNull Context context, @NonNull Object cookie, @NonNull Issue issue,
+ @NonNull String message) {
+ if (context.isEnabled(issue)) {
+ // Suppressed?
+ // Temporarily unconditionally checking for suppress comments in Gradle files
+ // since Studio insists on an AndroidLint id prefix
+ boolean checkComments = /*context.getClient().checkForSuppressComments()
+ &&*/ context.containsCommentSuppress();
+ if (checkComments) {
+ int startOffset = getStartOffset(context, cookie);
+ if (startOffset >= 0 && context.isSuppressedWithComment(startOffset, issue)) {
+ return;
+ }
+ }
+
+ context.report(issue, createLocation(context, cookie), message, null);
+ }
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ @NonNull
+ protected Object getPropertyKeyCookie(@NonNull Object cookie) {
+ return cookie;
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ @NonNull
+ protected Object getPropertyPairCookie(@NonNull Object cookie) {
+ return cookie;
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ protected int getStartOffset(@NonNull Context context, @NonNull Object cookie) {
+ return -1;
+ }
+
+ @SuppressWarnings({"MethodMayBeStatic", "UnusedParameters"})
+ protected Location createLocation(@NonNull Context context, @NonNull Object cookie) {
+ return null;
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java
index c95b0b6..de7269c 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java
@@ -19,10 +19,20 @@
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_COLUMN_COUNT;
import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN;
+import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN;
+import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
import static com.android.SdkConstants.ATTR_LAYOUT_ROW;
+import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN;
+import static com.android.SdkConstants.ATTR_ORIENTATION;
import static com.android.SdkConstants.ATTR_ROW_COUNT;
+import static com.android.SdkConstants.ATTR_USE_DEFAULT_MARGINS;
+import static com.android.SdkConstants.AUTO_URI;
+import static com.android.SdkConstants.FQCN_GRID_LAYOUT_V7;
+import static com.android.SdkConstants.GRID_LAYOUT;
+import static com.android.SdkConstants.XMLNS_PREFIX;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
@@ -34,10 +44,13 @@
import com.android.tools.lint.detector.api.XmlContext;
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 java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
/**
* Check which looks for potential errors in declarations of GridLayouts, such as specifying
@@ -71,8 +84,9 @@
@Override
public Collection<String> getApplicableElements() {
- return Collections.singletonList(
- "GridLayout" //$NON-NLS-1$
+ return Arrays.asList(
+ GRID_LAYOUT,
+ FQCN_GRID_LAYOUT_V7
);
}
@@ -116,5 +130,66 @@
}
}
}
+
+ if (element.getTagName().equals(FQCN_GRID_LAYOUT_V7)) {
+ // Make sure that we're not using android: namespace attributes where we should
+ // be using app namespace attributes!
+ ensureAppNamespace(context, element, ATTR_COLUMN_COUNT);
+ ensureAppNamespace(context, element, ATTR_ORIENTATION);
+ ensureAppNamespace(context, element, ATTR_ROW_COUNT);
+ ensureAppNamespace(context, element, ATTR_USE_DEFAULT_MARGINS);
+ ensureAppNamespace(context, element, "alignmentMode");
+ ensureAppNamespace(context, element, "columnOrderPreserved");
+ ensureAppNamespace(context, element, "rowOrderPreserved");
+
+ for (Element child : LintUtils.getChildren(element)) {
+ ensureAppNamespace(context, child, ATTR_LAYOUT_COLUMN);
+ ensureAppNamespace(context, child, ATTR_LAYOUT_COLUMN_SPAN);
+ ensureAppNamespace(context, child, ATTR_LAYOUT_GRAVITY);
+ ensureAppNamespace(context, child, ATTR_LAYOUT_ROW);
+ ensureAppNamespace(context, child, ATTR_LAYOUT_ROW_SPAN);
+ }
+ }
+ }
+
+ private static void ensureAppNamespace(XmlContext context, Element element, String name) {
+ Attr attribute = element.getAttributeNodeNS(ANDROID_URI, name);
+ if (attribute != null) {
+ String prefix = getNamespacePrefix(element.getOwnerDocument(), AUTO_URI);
+ boolean haveNamespace = prefix != null;
+ if (!haveNamespace) {
+ prefix = "app";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("Wrong namespace; with v7 GridLayout you should use ").append(prefix)
+ .append(":").append(name);
+ if (!haveNamespace) {
+ sb.append(" (and add xmlns:app=\"").append(AUTO_URI)
+ .append("\" to your root element.)");
+ }
+ String message = sb.toString();
+
+ context.report(ISSUE, attribute, context.getLocation(attribute), message, null);
+ }
+ }
+
+ @Nullable
+ private static String getNamespacePrefix(Document document, String uri) {
+ Element root = document.getDocumentElement();
+ if (root == null) {
+ return null;
+ }
+ NamedNodeMap attributes = root.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Node attribute = attributes.item(i);
+ if (attribute.getNodeName().startsWith(XMLNS_PREFIX) &&
+ attribute.getNodeValue().equals(uri)) {
+ return attribute.getNodeName().substring(XMLNS_PREFIX.length());
+ }
+
+ }
+
+ return null;
}
}
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 91704f0..7c9830e 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
@@ -18,7 +18,6 @@
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;
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
index 76a242d..cef2d10 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
@@ -59,7 +59,7 @@
Category.SECURITY,
5,
- Severity.WARNING,
+ Severity.FATAL,
new Implementation(
HardcodedDebugModeDetector.class,
Scope.MANIFEST_SCOPE));
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
index e16f4b8..4f91c38 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
@@ -59,8 +59,8 @@
"* The application cannot be translated to other languages by just adding new " +
"translations for existing string resources.\n" +
"\n" +
- "In Eclipse there is a quickfix to automatically extract this hardcoded string into " +
- "a resource lookup.",
+ "In Android Studio and Eclipse there are quickfixes to automatically extract this " +
+ "hardcoded string into a resource lookup.",
Category.I18N,
5,
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IconDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IconDetector.java
index 3babaef..2739789 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IconDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IconDetector.java
@@ -54,6 +54,7 @@
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.tools.lint.detector.api.ResourceContext;
import com.android.tools.lint.detector.api.ResourceXmlDetector;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
@@ -67,7 +68,7 @@
import org.w3c.dom.Element;
-import java.awt.Dimension;
+import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
@@ -135,7 +136,8 @@
"-ldpi", //$NON-NLS-1$
"-mdpi", //$NON-NLS-1$
"-hdpi", //$NON-NLS-1$
- "-xhdpi" //$NON-NLS-1$
+ "-xhdpi", //$NON-NLS-1$
+ "-xxhdpi",//$NON-NLS-1$
};
/** Scope needed to detect the types of icons (which involves scanning .java files,
@@ -475,6 +477,8 @@
}
}
+ /** Like {@link LintUtils#isBitmapFile(File)} but (a) operates on Strings instead
+ * of files and (b) also considers XML drawables as images */
private static boolean isDrawableFile(String name) {
// endsWith(name, DOT_PNG) is also true for endsWith(name, DOT_9PNG)
return endsWith(name, DOT_PNG)|| endsWith(name, DOT_JPG) || endsWith(name, DOT_GIF)
@@ -890,6 +894,7 @@
// should also define -hdpi and -xhdpi.
if (context.isEnabled(ICON_MISSING_FOLDER)) {
List<String> missing = new ArrayList<String>();
+ // TODO: If it's a launcher icon, also insist on xxhdpi!
for (String density : REQUIRED_DENSITIES) {
if (!definedDensities.contains(density)) {
missing.add(density);
@@ -1176,6 +1181,7 @@
context.getProject().getMinSdk() >= 4) {
for (File file : files) {
String name = file.getName();
+ //noinspection StatementWithEmptyBody
if (name.endsWith(DOT_XML)) {
// pass - most common case, avoids checking other extensions
} else if (endsWith(name, DOT_PNG)
@@ -1308,7 +1314,7 @@
* if known (for use by other checks)
*/
private Dimension checkColor(Context context, File file, boolean isActionBarIcon) {
- int folderVersion = Context.getFolderVersion(file);
+ int folderVersion = ResourceContext.getFolderVersion(file);
if (isActionBarIcon) {
if (folderVersion != -1 && folderVersion < 11
|| !isAndroid30(context, folderVersion)) {
@@ -1357,8 +1363,9 @@
for (int y = 0, height = image.getHeight(); y < height; y++) {
for (int x = 0, width = image.getWidth(); x < width; x++) {
int rgb = image.getRGB(x, y);
- if ((rgb & 0xFF000000) != 0
- && rgb != 0xFFFFFFFF) {
+ // If the pixel is not completely transparent, insist that
+ // its RGB channel must be white (with any alpha value)
+ if ((rgb & 0xFF000000) != 0 && (rgb & 0xFFFFFF) != 0xFFFFFF) {
int r = (rgb & 0xFF0000) >>> 16;
int g = (rgb & 0x00FF00) >>> 8;
int b = (rgb & 0x0000FF);
@@ -1455,9 +1462,7 @@
}
}
} finally {
- if (input != null) {
- input.close();
- }
+ input.close();
}
}
} catch (IOException e) {
@@ -1526,7 +1531,7 @@
}
}
- assert !conflicts.isEmpty() : conflictSet;
+ assert conflicts != null && !conflicts.isEmpty() : conflictSet;
List<String> names = new ArrayList<String>(conflicts.keySet());
Collections.sort(names);
for (String name : names) {
@@ -1559,7 +1564,7 @@
}
String folderName = folder.getName();
- int folderVersion = Context.getFolderVersion(files[0]);
+ int folderVersion = ResourceContext.getFolderVersion(files[0]);
for (File file : files) {
String name = file.getName();
@@ -1693,7 +1698,7 @@
Dimension size = getSize(file);
if (size != null) {
- if (exactMatch && size.width != width || size.height != height) {
+ if (exactMatch && (size.width != width || size.height != height)) {
context.report(
ICON_EXPECTED_SIZE,
Location.create(file),
@@ -1702,7 +1707,7 @@
folderName + File.separator + file.getName(),
width, height, size.width, size.height),
null);
- } else if (!exactMatch && size.width > width || size.height > height) {
+ } else if (!exactMatch && (size.width > width || size.height > height)) {
context.report(
ICON_EXPECTED_SIZE,
Location.create(file),
@@ -1731,9 +1736,7 @@
}
}
} finally {
- if (input != null) {
- input.close();
- }
+ input.close();
}
}
@@ -1760,6 +1763,7 @@
assert name.indexOf('.') == -1 : name; // Should supply base name
// Naming convention
+ //noinspection SimplifiableIfStatement
if (name.startsWith("ic_launcher")) { //$NON-NLS-1$
return true;
}
@@ -1770,6 +1774,7 @@
assert name.indexOf('.') == -1; // Should supply base name
// Naming convention
+ //noinspection SimplifiableIfStatement
if (name.startsWith("ic_stat_")) { //$NON-NLS-1$
return true;
}
@@ -1781,6 +1786,7 @@
assert name.indexOf('.') == -1; // Should supply base name
// Naming convention
+ //noinspection SimplifiableIfStatement
if (name.startsWith("ic_action_")) { //$NON-NLS-1$
return true;
}
@@ -1796,8 +1802,9 @@
}
// As of Android 3.0 ic_menu_ are action icons
+ //noinspection SimplifiableIfStatement,RedundantIfStatement
if (file != null && name.startsWith("ic_menu_") //$NON-NLS-1$
- && isAndroid30(context, Context.getFolderVersion(file))) {
+ && isAndroid30(context, ResourceContext.getFolderVersion(file))) {
// Naming convention
return true;
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IncludeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IncludeDetector.java
new file mode 100644
index 0000000..2d08ea5
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IncludeDetector.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.VIEW_INCLUDE;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Checks for problems with include tags, such as providing layout parameters
+ * without specifying both layout_width and layout_height
+ */
+public class IncludeDetector extends LayoutDetector {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "IncludeLayoutParam", //$NON-NLS-1$
+ "Ignored layout params on include",
+ "Ensures that layout params on `<include>` tags are used correctly",
+
+ "Layout parameters specified on an `<include>` tag will only be used if you " +
+ "also override `layout_width` and `layout_height` on the `<include>` tag; " +
+ "otherwise they will be ignored.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.ERROR,
+ new Implementation(
+ IncludeDetector.class,
+ Scope.RESOURCE_FILE_SCOPE)).addMoreInfo(
+ "http://stackoverflow.com/questions/2631614/does-android-xml-layouts-include-tag-really-work");
+
+ @Nullable
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList(VIEW_INCLUDE);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ NamedNodeMap attributes = element.getAttributes();
+ int length = attributes.getLength();
+ boolean hasWidth = false;
+ boolean hasHeight = false;
+ boolean hasOtherLayoutParam = false;
+ for (int i = 0; i < length; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ String name = attribute.getLocalName();
+ if (name != null && name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
+ if (ATTR_LAYOUT_WIDTH.equals(name)) {
+ hasWidth = true;
+ } else if (ATTR_LAYOUT_HEIGHT.equals(name)) {
+ hasHeight = true;
+ } else if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ hasOtherLayoutParam = true;
+ }
+ }
+ }
+
+ boolean flagWidth = !hasOtherLayoutParam && hasWidth && !hasHeight;
+ boolean flagHeight = !hasOtherLayoutParam && !hasWidth && hasHeight;
+
+ if (hasOtherLayoutParam && (!hasWidth || !hasHeight) || flagWidth || flagHeight) {
+ for (int i = 0; i < length; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ String name = attribute.getLocalName();
+ if (name != null && name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
+ && (!ATTR_LAYOUT_WIDTH.equals(name) || flagWidth)
+ && (!ATTR_LAYOUT_HEIGHT.equals(name) || flagHeight)
+ && ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ String condition = !hasWidth && !hasHeight ?
+ "both layout_width and layout_height are also specified"
+ : !hasWidth ? "layout_width is also specified"
+ : "layout_height is also specified";
+ String message = String.format(
+ "Layout parameter %1$s ignored unless %2$s on <include> tag",
+ name, condition);
+ context.report(ISSUE, element, context.getLocation(attribute),
+ message, null);
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java
index a704086..e681f40 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java
@@ -35,6 +35,7 @@
import static com.android.SdkConstants.VIEW_TAG;
import com.android.annotations.NonNull;
+import com.android.ide.common.rendering.api.ResourceValue;
import com.android.tools.lint.client.api.SdkInfo;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Implementation;
@@ -180,7 +181,8 @@
if (weightChild != null) {
// More than one child defining a weight!
multipleWeights = true;
- } else if (!multipleWeights) {
+ } else //noinspection ConstantConditions
+ if (!multipleWeights) {
weightChild = child;
}
@@ -235,9 +237,24 @@
} else if (children.isEmpty() && (orientation == null || orientation.isEmpty())
&& context.isEnabled(ORIENTATION)
&& element.hasAttributeNS(ANDROID_URI, ATTR_ID)) {
- String message = "No orientation specified, and the default is horizontal. "
- + "This is a common source of bugs when children are added dynamically.";
- context.report(ORIENTATION, element, context.getLocation(element), message, null);
+ boolean ignore;
+ if (element.hasAttribute(ATTR_STYLE)) {
+ if (context.getClient().supportsProjectResources()) {
+ List<ResourceValue> values = LintUtils.getStyleAttributes(
+ context.getMainProject(), context.getClient(),
+ element.getAttribute(ATTR_STYLE), ANDROID_URI, ATTR_ORIENTATION);
+ ignore = values != null && !values.isEmpty();
+ } else {
+ ignore = true;
+ }
+ } else {
+ ignore = false;
+ }
+ if (!ignore) {
+ String message = "No orientation specified, and the default is horizontal. "
+ + "This is a common source of bugs when children are added dynamically.";
+ context.report(ORIENTATION, element, context.getLocation(element), message, null);
+ }
}
if (context.isEnabled(BASELINE_WEIGHTS) && weightChild != null
@@ -277,9 +294,25 @@
}
Attr sizeNode = weightChild.getAttributeNodeNS(ANDROID_URI, dimension);
String size = sizeNode != null ? sizeNode.getValue() : "(undefined)";
+ if (sizeNode == null && weightChild.hasAttribute(ATTR_STYLE)) {
+ String style = weightChild.getAttribute(ATTR_STYLE);
+ List<ResourceValue> sizes = LintUtils.getStyleAttributes(context.getMainProject(),
+ context.getClient(), style, ANDROID_URI, dimension);
+ if (sizes != null) {
+ for (ResourceValue value : sizes) {
+ String v = value.getValue();
+ if (v != null) {
+ size = v;
+ if (v.startsWith("0")) {
+ break;
+ }
+ }
+ }
+ }
+ }
if (!size.startsWith("0")) { //$NON-NLS-1$
String msg = String.format(
- "Use a %1$s of 0dip instead of %2$s for better performance",
+ "Use a %1$s of 0dp instead of %2$s for better performance",
dimension, size);
context.report(INEFFICIENT_WEIGHT,
weightChild,
@@ -334,10 +367,8 @@
if (noWidth && noHeight) {
return;
}
- assert noWidth || noHeight;
if (noWidth) {
- assert widthNode != null;
if (!hasWeight) {
context.report(WRONG_0DP, widthNode, context.getLocation(widthNode),
"Suspicious size: this will make the view invisible, should be " +
@@ -348,8 +379,6 @@
"intended for layout_height", null);
}
} else {
- assert noHeight;
- assert heightNode != null;
if (!hasWeight) {
context.report(WRONG_0DP, widthNode, context.getLocation(heightNode),
"Suspicious size: this will make the view invisible, should be " +
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java
index 072ec05..1fa3578 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java
@@ -27,6 +27,7 @@
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 com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.objectweb.asm.Opcodes;
@@ -43,8 +44,6 @@
import java.util.List;
import java.util.Set;
-import lombok.ast.libs.org.parboiled.google.collect.Lists;
-
/**
* Looks for usages of Java packages that are not included in Android.
*/
@@ -123,6 +122,11 @@
return;
}
+ if ((classNode.access & Opcodes.ACC_ANNOTATION) != 0
+ || classNode.superName.startsWith("javax/annotation/")) {
+ // Don't flag references from annotations and annotation processors
+ return;
+ }
if (classNode.name.startsWith(JAVAX_PKG_PREFIX)) {
mJavaxLibraryClasses.add(classNode.name);
}
@@ -204,8 +208,18 @@
}
private boolean isInvalidPackage(String owner) {
- if (owner.startsWith(JAVA_PKG_PREFIX)
- || owner.startsWith(JAVAX_PKG_PREFIX)) {
+ if (owner.startsWith(JAVA_PKG_PREFIX)) {
+ return !mApiDatabase.isValidJavaPackage(owner);
+ }
+
+ if (owner.startsWith(JAVAX_PKG_PREFIX)) {
+ // Annotations-related code is usually fine; these tend to be for build time
+ // jars, such as dagger
+ //noinspection SimplifiableIfStatement
+ if (owner.startsWith("javax/annotation/") || owner.startsWith("javax/lang/model")) {
+ return false;
+ }
+
return !mApiDatabase.isValidJavaPackage(owner);
}
@@ -232,6 +246,8 @@
return;
}
+ Set<String> seen = Sets.newHashSet();
+
for (Candidate candidate : mCandidates) {
String type = candidate.mClass;
if (mJavaxLibraryClasses.contains(type)) {
@@ -241,7 +257,21 @@
String referencedIn = candidate.mReferencedIn;
Location location = Location.create(jarFile);
- Object pkg = getPackageName(type);
+ String pkg = getPackageName(type);
+ if (seen.contains(pkg)) {
+ continue;
+ }
+ seen.add(pkg);
+
+ if (pkg.equals("javax.inject")) {
+ String name = jarFile.getName();
+ //noinspection SpellCheckingInspection
+ if (name.startsWith("dagger-") || name.startsWith("guice-")) {
+ // White listed
+ return;
+ }
+ }
+
String message = String.format(
"Invalid package reference in library; not included in Android: %1$s. " +
"Referenced from %2$s.", pkg, ClassContext.getFqcn(referencedIn));
@@ -249,7 +279,7 @@
}
}
- private static Object getPackageName(String owner) {
+ private static String getPackageName(String owner) {
String pkg = owner;
int index = pkg.lastIndexOf('/');
if (index != -1) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java
index b9c5bf2..d4a810b 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java
@@ -16,6 +16,10 @@
package com.android.tools.lint.checks;
+import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BOOLEAN;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+
import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
@@ -129,10 +133,9 @@
static final String ON_MEASURE = "onMeasure"; //$NON-NLS-1$
static final String ON_DRAW = "onDraw"; //$NON-NLS-1$
static final String ON_LAYOUT = "onLayout"; //$NON-NLS-1$
- private static final String INT = "int"; //$NON-NLS-1$
private static final String INTEGER = "Integer"; //$NON-NLS-1$
- private static final String BOOL = "boolean"; //$NON-NLS-1$
private static final String BOOLEAN = "Boolean"; //$NON-NLS-1$
+ private static final String BYTE = "Byte"; //$NON-NLS-1$
private static final String LONG = "Long"; //$NON-NLS-1$
private static final String CHARACTER = "Character"; //$NON-NLS-1$
private static final String DOUBLE = "Double"; //$NON-NLS-1$
@@ -222,7 +225,8 @@
|| typeName.equals(FLOAT)
|| typeName.equals(CHARACTER)
|| typeName.equals(LONG)
- || typeName.equals(DOUBLE))
+ || typeName.equals(DOUBLE)
+ || typeName.equals(BYTE))
&& node.astTypeReference().astParts().size() == 1
&& node.astArguments().size() == 1) {
String argument = node.astArguments().first().toString();
@@ -419,12 +423,12 @@
// Ensure that the argument list matches boolean, int, int, int, int
TypeReferencePart type = iterator.next().astTypeReference().astParts().last();
- if (!type.getTypeName().equals(BOOL) || !iterator.hasNext()) {
+ if (!type.getTypeName().equals(TYPE_BOOLEAN) || !iterator.hasNext()) {
return false;
}
for (int i = 0; i < 4; i++) {
type = iterator.next().astTypeReference().astParts().last();
- if (!type.getTypeName().equals(INT)) {
+ if (!type.getTypeName().equals(TYPE_INT)) {
return false;
}
if (!iterator.hasNext()) {
@@ -450,7 +454,8 @@
VariableDefinition arg1 = parameters.last();
TypeReferencePart type1 = arg0.astTypeReference().astParts().last();
TypeReferencePart type2 = arg1.astTypeReference().astParts().last();
- return INT.equals(type1.getTypeName()) && INT.equals(type2.getTypeName());
+ return TYPE_INT.equals(type1.getTypeName())
+ && TYPE_INT.equals(type2.getTypeName());
}
}
@@ -473,7 +478,7 @@
}
VariableDefinition next = iterator.next();
TypeReferencePart type = next.astTypeReference().astParts().last();
- if (!INT.equals(type.getTypeName())) {
+ if (!TYPE_INT.equals(type.getTypeName())) {
return false;
}
}
@@ -496,28 +501,37 @@
if (types != null && types.size() == 2) {
TypeReference first = types.first();
String typeName = first.getTypeName();
- if (typeName.equals(INTEGER)) {
+ int minSdk = mContext.getMainProject().getMinSdk();
+ if (typeName.equals(INTEGER) || typeName.equals(BYTE)) {
String valueType = types.last().getTypeName();
if (valueType.equals(INTEGER)) {
mContext.report(USE_SPARSE_ARRAY, node, mContext.getLocation(node),
"Use new SparseIntArray(...) instead for better performance",
null);
+ } else if (valueType.equals(LONG) && minSdk >= 18) {
+ mContext.report(USE_SPARSE_ARRAY, node, mContext.getLocation(node),
+ "Use new SparseLongArray(...) instead for better performance",
+ null);
} else if (valueType.equals(BOOLEAN)) {
mContext.report(USE_SPARSE_ARRAY, node, mContext.getLocation(node),
"Use new SparseBooleanArray(...) instead for better performance",
null);
} else {
- // Don't suggest SparseLongArray; it is marked @hide
mContext.report(USE_SPARSE_ARRAY, node, mContext.getLocation(node),
String.format(
"Use new SparseArray<%1$s>(...) instead for better performance",
valueType),
null);
}
- } else if (typeName.equals(LONG) && mContext.getProject().getMinSdk() >= 17) {
+ } else if (typeName.equals(LONG) && (minSdk >= 16 ||
+ Boolean.TRUE == mContext.getMainProject().dependsOn(
+ SUPPORT_LIB_ARTIFACT))) {
+ boolean useBuiltin = minSdk >= 16;
+ String message = useBuiltin ?
+ "Use new LongSparseArray(...) instead for better performance" :
+ "Use new android.support.v4.util.LongSparseArray(...) instead for better performance";
mContext.report(USE_SPARSE_ARRAY, node, mContext.getLocation(node),
- "Use new LongSparseArray(...) instead for better performance",
- null);
+ message, null);
}
}
}
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 7bd6e6c..7789264 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
@@ -20,43 +20,35 @@
import com.android.annotations.Nullable;
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.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 com.android.tools.lint.detector.api.Speed;
-import com.google.common.collect.Maps;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
-import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.SourceInterpreter;
import org.objectweb.asm.tree.analysis.SourceValue;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
/**
- * Looks for add
+ * Looks for addJavascriptInterface calls on interfaces have been properly annotated
+ * with {@code @JavaScriptInterface}
*/
public class JavaScriptInterfaceDetector extends Detector implements Detector.ClassScanner {
/** The main issue discovered by this detector */
@@ -66,7 +58,7 @@
"Ensures that interfaces added with addJavascriptInterface are annotated with @JavascriptInterface",
"As of API 17, you must annotate methods in objects registered with the " +
- "`addJavascriptInterface` method with a `@JavascriptInterface` annotation.",
+ "`addJavascriptInterface` method with a `@JavascriptInterface` annotation.",
Category.SECURITY,
8,
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 466d0b2..6f283b6 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
@@ -195,7 +195,7 @@
String id = getId(element);
if (id != null) {
if (map.containsKey(id)) {
- if (context.getDriver().isSuppressed(INCONSISTENT_IDS, element)) {
+ if (context.getDriver().isSuppressed(context, INCONSISTENT_IDS, element)) {
map.remove(id);
return;
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutInflationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutInflationDetector.java
new file mode 100644
index 0000000..e551a10
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutInflationDetector.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
+import static com.android.tools.lint.checks.ViewHolderDetector.INFLATE;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.LintClient;
+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.LayoutDetector;
+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.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.kxml2.io.KXmlParser;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.NullLiteral;
+import lombok.ast.Select;
+import lombok.ast.StrictListAccessor;
+
+/**
+ * Looks for layout inflation calls passing null as the view root
+ */
+public class LayoutInflationDetector extends LayoutDetector implements Detector.JavaScanner {
+
+ @SuppressWarnings("unchecked")
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ LayoutInflationDetector.class,
+ Scope.JAVA_AND_RESOURCE_FILES,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Passing in a null parent to a layout inflater */
+ public static final Issue ISSUE = Issue.create(
+ "InflateParams", //$NON-NLS-1$
+ "Layout Inflation without a Parent",
+ "Ensures that a proper parent context is passed to a layout inflater",
+
+ "When inflating a layout, avoid passing in null as the parent view, since " +
+ "otherwise any layout parameters on the root of the inflated layout will be ignored.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .addMoreInfo("http://www.doubleencore.com/2013/05/layout-inflation-as-intended");
+
+ private static final String ERROR_MESSAGE =
+ "Avoid passing null as the view root (needed to resolve "
+ + "layout parameters on the inflated layout's root element)";
+
+ /** Constructs a new {@link LayoutInflationDetector} check */
+ public LayoutInflationDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mPendingErrors != null) {
+ for (Pair<String,Location> pair : mPendingErrors) {
+ String inflatedLayout = pair.getFirst();
+ if (mLayoutsWithRootLayoutParams == null ||
+ !mLayoutsWithRootLayoutParams.contains(inflatedLayout)) {
+ // No root layout parameters on the inflated layout: no need to complain
+ continue;
+ }
+ Location location = pair.getSecond();
+ context.report(ISSUE, location, ERROR_MESSAGE, null);
+ }
+ }
+ }
+
+ // ---- Implements XmlScanner ----
+
+ private Set<String> mLayoutsWithRootLayoutParams;
+ private List<Pair<String,Location>> mPendingErrors;
+
+ @Override
+ public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+ Element root = document.getDocumentElement();
+ if (root != null) {
+ NamedNodeMap attributes = root.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ if (attribute.getLocalName() != null
+ && attribute.getLocalName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
+ if (mLayoutsWithRootLayoutParams == null) {
+ mLayoutsWithRootLayoutParams = Sets.newHashSetWithExpectedSize(20);
+ }
+ mLayoutsWithRootLayoutParams.add(LintUtils.getBaseName(context.file.getName()));
+ break;
+ }
+ }
+ }
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList(INFLATE);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ assert node.astName().astValue().equals(INFLATE);
+ if (node.astOperand() == null) {
+ return;
+ }
+ StrictListAccessor<Expression, MethodInvocation> arguments = node.astArguments();
+ if (arguments.size() < 2) {
+ return;
+ }
+ Iterator<Expression> iterator = arguments.iterator();
+ Expression first = iterator.next();
+ Expression second = iterator.next();
+ if (!(second instanceof NullLiteral) || !(first instanceof Select)) {
+ return;
+ }
+ Select select = (Select) first;
+ Expression operand = select.astOperand();
+ if (operand instanceof Select) {
+ Select rLayout = (Select) operand;
+ if (rLayout.astIdentifier().astValue().equals(ResourceType.LAYOUT.getName()) &&
+ rLayout.astOperand().toString().endsWith(SdkConstants.R_CLASS)) {
+ String layoutName = select.astIdentifier().astValue();
+ if (context.getScope().contains(Scope.RESOURCE_FILE)) {
+ // We're doing a full analysis run: we can gather this information
+ // incrementally
+ if (!context.getDriver().isSuppressed(context, ISSUE, node)) {
+ if (mPendingErrors == null) {
+ mPendingErrors = Lists.newArrayList();
+ }
+ Location location = context.getLocation(second);
+ mPendingErrors.add(Pair.of(layoutName, location));
+ }
+ } else if (hasLayoutParams(context, layoutName)) {
+ context.report(ISSUE, node, context.getLocation(second), ERROR_MESSAGE, null);
+ }
+ }
+ }
+
+ super.visitMethod(context, visitor, node);
+ }
+
+ private static boolean hasLayoutParams(@NonNull JavaContext context, String name) {
+ LintClient client = context.getClient();
+ if (!client.supportsProjectResources()) {
+ return true; // not certain
+ }
+
+ Project project = context.getProject();
+ AbstractResourceRepository resources = client.getProjectResources(project, true);
+ if (resources == null) {
+ return true; // not certain
+ }
+
+ List<ResourceItem> items = resources.getResourceItem(ResourceType.LAYOUT, name);
+ if (items == null || items.isEmpty()) {
+ return false;
+ }
+
+ for (ResourceItem item : items) {
+ ResourceFile source = item.getSource();
+ if (source == null) {
+ return true; // not certain
+ }
+ File file = source.getFile();
+ if (file.exists()) {
+ try {
+ String s = context.getClient().readFile(file);
+ if (hasLayoutParams(new StringReader(s))) {
+ return true;
+ }
+ } catch (Exception e) {
+ context.log(e, "Could not read/parse inflated layout");
+ return true; // not certain
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @VisibleForTesting
+ static boolean hasLayoutParams(@NonNull Reader reader)
+ throws XmlPullParserException, IOException {
+ KXmlParser parser = new KXmlParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(reader);
+
+ while (true) {
+ int event = parser.next();
+ if (event == XmlPullParser.START_TAG) {
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ if (parser.getAttributeName(i).startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
+ String prefix = parser.getAttributePrefix(i);
+ if (prefix != null && !prefix.isEmpty() &&
+ ANDROID_URI.equals(parser.getNamespace(prefix))) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
index 3005cb9..a403e53 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
@@ -47,13 +47,16 @@
import java.util.Arrays;
import java.util.Collections;
-import java.util.EnumSet;
import java.util.List;
/**
* Checks for errors related to locale handling
*/
public class LocaleDetector extends Detector implements ClassScanner {
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ LocaleDetector.class,
+ Scope.CLASS_FILE_SCOPE);
+
/** Calling risky convenience methods */
public static final Issue STRING_LOCALE = Issue.create(
"DefaultLocale", //$NON-NLS-1$
@@ -74,11 +77,9 @@
Category.CORRECTNESS,
6,
Severity.WARNING,
- new Implementation(
- LocaleDetector.class,
- Scope.CLASS_AND_ALL_RESOURCE_FILES))
+ IMPLEMENTATION)
.addMoreInfo(
- "http://developer.android.com/reference/java/util/Locale.html#default_locale"); //$NON-NLS-1$
+ "http://developer.android.com/reference/java/util/Locale.html#default_locale"); //$NON-NLS-1$
/** Constructing SimpleDateFormat without an explicit locale */
public static final Issue DATE_FORMAT = Issue.create(
@@ -100,9 +101,7 @@
Category.CORRECTNESS,
6,
Severity.WARNING,
- new Implementation(
- LocaleDetector.class,
- Scope.CLASS_FILE_SCOPE))
+ IMPLEMENTATION)
.addMoreInfo(
"http://developer.android.com/reference/java/text/SimpleDateFormat.html"); //$NON-NLS-1$
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleFolderDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleFolderDetector.java
new file mode 100644
index 0000000..5cb4164
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleFolderDetector.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+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.Location;
+import com.android.tools.lint.detector.api.ResourceContext;
+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 com.android.utils.Pair;
+
+/**
+ * Checks for errors related to locale handling
+ */
+public class LocaleFolderDetector extends Detector implements Detector.ResourceFolderScanner {
+ public static final Implementation IMPLEMENTATION = new Implementation(
+ LocaleFolderDetector.class,
+ Scope.RESOURCE_FOLDER_SCOPE);
+
+ /**
+ * Using a locale folder that is not consulted
+ */
+ public static final Issue ISSUE = Issue.create(
+ "LocaleFolder", //$NON-NLS-1$
+ "Wrong locale name",
+ "Using the new locale name",
+ "From the `java.util.Locale` documentation:\n" +
+ "\"Note that Java uses several deprecated two-letter codes. The Hebrew (\"he\") " +
+ "language code is rewritten as \"iw\", Indonesian (\"id\") as \"in\", and " +
+ "Yiddish (\"yi\") as \"ji\". This rewriting happens even if you construct your " +
+ "own Locale object, not just for instances returned by the various lookup methods.\n" +
+ "\n" +
+ "Because of this, if you add your localized resources in for example `values-he` " +
+ "they will not be used, since the system will look for `values-iw` instead.\n" +
+ "\n" +
+ "To work around this, place your resources in a `values` folder using the " +
+ "deprecated language code instead.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ LocaleFolderDetector.class,
+ Scope.RESOURCE_FOLDER_SCOPE)).addMoreInfo(
+ "http://developer.android.com/reference/java/util/Locale.html");
+
+ /**
+ * Constructs a new {@link LocaleFolderDetector}
+ */
+ public LocaleFolderDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements ResourceFolderScanner ----
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return true;
+ }
+
+ @Override
+ public void checkFolder(@NonNull ResourceContext context, @NonNull String folderName) {
+ Pair<String, String> locale = TypoDetector.getLocale(folderName);
+ if (locale != null) {
+ String language = locale.getFirst();
+ if (language != null) {
+ String replace = null;
+ if (language.equals("he")) {
+ replace = "iw";
+ } else if (language.equals("id")) {
+ replace = "in";
+ } else if (language.equals("yi")) {
+ replace = "ji";
+ }
+ // Note: there is also fil=>tl
+
+ if (replace != null) {
+ // TODO: Check for suppress somewhere other than lint.xml?
+ String message = String.format("The locale folder \"%1$s\" should be "
+ + "called \"%2$s\" instead; see the "
+ + "java.util.Locale documentation",
+ language, replace
+ );
+ context.report(ISSUE, Location.create(context.file), message, null);
+ }
+ }
+ }
+ }
+}
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 c296f0b..62c134f 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
@@ -18,9 +18,13 @@
import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ALLOW_BACKUP;
+import static com.android.SdkConstants.ATTR_ICON;
import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION;
+import static com.android.SdkConstants.ATTR_VERSION_CODE;
+import static com.android.SdkConstants.ATTR_VERSION_NAME;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.TAG_ACTIVITY;
import static com.android.SdkConstants.TAG_APPLICATION;
@@ -37,10 +41,13 @@
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.annotations.Nullable;
import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.Variant;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
@@ -48,6 +55,7 @@
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.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
@@ -182,7 +190,7 @@
Category.CORRECTNESS,
5,
- Severity.ERROR,
+ Severity.FATAL,
IMPLEMENTATION);
/** Not explicitly defining allowBackup */
@@ -226,19 +234,17 @@
"Checks that permission names are unique",
"The unqualified names or your permissions must be unique. The reason for this " +
- "is that at build time, the `aapt` tool will generate a class named `Manifest` "
- +
- "which contains a field for each of your permissions. These fields are named " +
- "using your permission unqualified names (i.e. the name portion after the last "
- +
- "dot).\n" +
- "\n" +
- "If more than one permission maps to the same field name, that field will " +
- "arbitrarily name just one of them.",
+ "is that at build time, the `aapt` tool will generate a class named `Manifest` " +
+ "which contains a field for each of your permissions. These fields are named " +
+ "using your permission unqualified names (i.e. the name portion after the last " +
+ "dot).\n" +
+ "\n" +
+ "If more than one permission maps to the same field name, that field will " +
+ "arbitrarily name just one of them.",
Category.CORRECTNESS,
6,
- Severity.ERROR,
+ Severity.FATAL,
IMPLEMENTATION);
/** Using a resource for attributes that do not allow it */
@@ -267,9 +273,9 @@
"Checks for resource references where only literals are allowed",
"For the `versionCode` attribute, you have to specify an actual integer " +
- "literal; you cannot use an indirection with a `@dimen/name` resource. " +
- "Similarly, the `versionName` attribute should be an actual string, not " +
- "a string resource url.",
+ "literal; you cannot use an indirection with a `@dimen/name` resource. " +
+ "Similarly, the `versionName` attribute should be an actual string, not " +
+ "a string resource url.",
Category.CORRECTNESS,
8,
@@ -342,7 +348,24 @@
Category.CORRECTNESS,
8,
- Severity.ERROR,
+ Severity.FATAL,
+ IMPLEMENTATION);
+
+ /** Defining a value that is overridden by Gradle */
+ public static final Issue GRADLE_OVERRIDES = Issue.create(
+ "GradleOverrides", //$NON-NLS-1$
+ "Value overridden by Gradle build script",
+ "Looks for values specified in the manifest file which are overridden by values "
+ + "in Gradle",
+
+ "The value of (for example) `minSdkVersion` is only used if it is not specified in " +
+ "the `build.gradle` build scripts. When specified in the Gradle build scripts, " +
+ "the manifest value is ignored and can be misleading, so should be removed to " +
+ "avoid ambiguity.",
+
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
IMPLEMENTATION);
/** Permission name of mock location permission */
@@ -367,6 +390,17 @@
/** Permission basenames */
private Map<String, String> mPermissionNames;
+ /** Handle to the {@code <application>} tag */
+ private Location.Handle mApplicationTagHandle;
+
+ /** Whether we've seen an application icon definition in any of the manifest files (or
+ * if a manifest tag warning for this has been explicitly disabled) */
+ private boolean mSeenAppIcon;
+
+ /** Whether we've seen an allow backup definition in any of the manifest files (or
+ * if a manifest tag warning for this has been explicitly disabled) */
+ private boolean mSeenAllowBackup;
+
@NonNull
@Override
public Speed getSpeed() {
@@ -403,8 +437,41 @@
}
}
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (!mSeenAllowBackup && context.isEnabled(ALLOW_BACKUP)
+ && context.getMainProject().getMinSdk() >= 4) {
+ Location location = getMainApplicationTagLocation(context);
+ context.report(ALLOW_BACKUP, location,
+ "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 (!context.getMainProject().isLibrary()
+ && !mSeenAppIcon && context.isEnabled(APPLICATION_ICON)) {
+ Location location = getMainApplicationTagLocation(context);
+ context.report(APPLICATION_ICON, location,
+ "Should explicitly set android:icon, there is no default", null);
+ }
+ }
+
+ @Nullable
+ private Location getMainApplicationTagLocation(@NonNull Context context) {
+ if (mApplicationTagHandle != null) {
+ return mApplicationTagHandle.resolve();
+ }
+
+ List<File> manifestFiles = context.getMainProject().getManifestFiles();
+ if (!manifestFiles.isEmpty()) {
+ return Location.create(manifestFiles.get(0));
+ }
+
+ return null;
+ }
+
private static void checkDocumentElement(XmlContext context, Element element) {
- Attr codeNode = element.getAttributeNodeNS(ANDROID_URI, "versionCode");//$NON-NLS-1$
+ Attr codeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_VERSION_CODE);
if (codeNode != null && codeNode.getValue().startsWith(PREFIX_RESOURCE_REF)
&& context.isEnabled(ILLEGAL_REFERENCE)) {
context.report(ILLEGAL_REFERENCE, element, context.getLocation(element),
@@ -417,19 +484,68 @@
context.report(SET_VERSION, element, context.getLocation(element),
"Should set android:versionCode to specify the application version", null);
}
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, "versionName");//$NON-NLS-1$
- if (nameNode != null && nameNode.getValue().startsWith(PREFIX_RESOURCE_REF)
- && context.isEnabled(ILLEGAL_REFERENCE)) {
- 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)
+ Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_VERSION_NAME);
+ 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);
}
+
+ checkOverride(context, element, ATTR_VERSION_CODE);
+ checkOverride(context, element, ATTR_VERSION_NAME);
+ }
+
+ private static void checkOverride(XmlContext context, Element element, String attributeName) {
+ Project project = context.getProject();
+ Attr attribute = element.getAttributeNodeNS(ANDROID_URI, attributeName);
+ if (project.isGradleProject() && attribute != null && context.isEnabled(GRADLE_OVERRIDES)) {
+ Variant variant = project.getCurrentVariant();
+ if (variant != null) {
+ ProductFlavor flavor = variant.getMergedFlavor();
+ String gradleValue = null;
+ if (ATTR_MIN_SDK_VERSION.equals(attributeName)) {
+ try {
+ ApiVersion minSdkVersion = flavor.getMinSdkVersion();
+ gradleValue = minSdkVersion != null ? minSdkVersion.getApiString() : null;
+ } catch (Throwable e) {
+ // TODO: REMOVE ME
+ // This method was added in the 0.11 model. We'll need to drop support
+ // for 0.10 shortly but until 0.11 is available this is a stopgap measure
+ }
+ } else if (ATTR_TARGET_SDK_VERSION.equals(attributeName)) {
+ try {
+ ApiVersion targetSdkVersion = flavor.getTargetSdkVersion();
+ gradleValue = targetSdkVersion != null ? targetSdkVersion.getApiString() : null;
+ } catch (Throwable e) {
+ // TODO: REMOVE ME
+ // This method was added in the 0.11 model. We'll need to drop support
+ // for 0.10 shortly but until 0.11 is available this is a stopgap measure
+ }
+ } else if (ATTR_VERSION_CODE.equals(attributeName)) {
+ int versionCode = flavor.getVersionCode();
+ if (versionCode != -1) {
+ gradleValue = Integer.toString(versionCode);
+ }
+ } else if (ATTR_VERSION_NAME.equals(attributeName)) {
+ gradleValue = flavor.getVersionName();
+ } else {
+ assert false : attributeName;
+ return;
+ }
+
+ if (gradleValue != null) {
+ String manifestValue = attribute.getValue();
+
+ String message = String.format("This %1$s value (%2$s) is not used; it is "
+ + "always overridden by the value specified in the Gradle build "
+ + "script (%3$s)", attributeName, manifestValue, gradleValue);
+ context.report(GRADLE_OVERRIDES, attribute, context.getLocation(attribute),
+ message, null);
+ }
+ }
+ }
}
// ---- Implements Detector.XmlScanner ----
@@ -552,6 +668,8 @@
"The android:minSdkVersion cannot be a resource url, it must be "
+ "a literal integer (or string if a preview codename)", null);
}
+
+ checkOverride(context, element, ATTR_MIN_SDK_VERSION);
}
if (!element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) {
@@ -565,19 +683,23 @@
"compatibility behaviors may be enabled) with " +
"android:targetSdkVersion=\"?\"", null);
}
- } else if (context.isEnabled(TARGET_NEWER)){
- String target = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
- try {
- int api = Integer.parseInt(target);
- if (api < context.getClient().getHighestKnownApiLevel()) {
- context.report(TARGET_NEWER, element, context.getLocation(element),
- "Not targeting the latest versions of Android; compatibility " +
- "modes apply. Consider testing and updating this version. " +
- "Consult the android.os.Build.VERSION_CODES javadoc for details.",
- null);
+ } else {
+ checkOverride(context, element, ATTR_TARGET_SDK_VERSION);
+
+ if (context.isEnabled(TARGET_NEWER)){
+ String target = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
+ try {
+ int api = Integer.parseInt(target);
+ if (api < context.getClient().getHighestKnownApiLevel()) {
+ context.report(TARGET_NEWER, element, context.getLocation(element),
+ "Not targeting the latest versions of Android; compatibility " +
+ "modes apply. Consider testing and updating this version. " +
+ "Consult the android.os.Build.VERSION_CODES javadoc for details.",
+ null);
+ }
+ } catch (NumberFormatException nufe) {
+ // Ignore: AAPT will enforce this.
}
- } catch (NumberFormatException nufe) {
- // Ignore: AAPT will enforce this.
}
}
@@ -643,20 +765,22 @@
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);
+ boolean recordLocation = false;
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_ALLOW_BACKUP)
+ || context.getDriver().isSuppressed(context, ALLOW_BACKUP, element)) {
+ mSeenAllowBackup = true;
+ } else {
+ recordLocation = true;
}
-
- 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);
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_ICON)
+ || context.getDriver().isSuppressed(context, APPLICATION_ICON, element)) {
+ mSeenAppIcon = true;
+ } else {
+ recordLocation = true;
+ }
+ if (recordLocation && !context.getProject().isLibrary() &&
+ (mApplicationTagHandle == null || isMainManifest(context, context.file))) {
+ mApplicationTagHandle = context.createLocationHandle(element);
}
} else if (mSeenApplication) {
if (context.isEnabled(ORDER)) {
@@ -686,6 +810,18 @@
}
}
+ /** Returns true iff the given manifest file is the main manifest file */
+ private static boolean isMainManifest(XmlContext context, File manifestFile) {
+ if (!context.getProject().isGradleProject()) {
+ // In non-gradle projects, just one manifest per project
+ return true;
+ }
+
+ AndroidProject model = context.getProject().getGradleProjectModel();
+ return model == null || manifestFile
+ .equals(model.getDefaultConfig().getSourceProvider().getManifestFile());
+ }
+
/** 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();
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestOrderDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestOrderDetector.java
deleted file mode 100644
index 0c77786..0000000
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestOrderDetector.java
+++ /dev/null
@@ -1,28 +0,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.
- */
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Issue;
-
-// Temporary compatibility for ADT until prebuilts are updated
-@Deprecated
-public class ManifestOrderDetector {
- @Deprecated
- public static final Issue ALLOW_BACKUP = ManifestDetector.ALLOW_BACKUP;
- @Deprecated
- public static final Issue TARGET_NEWER = ManifestDetector.TARGET_NEWER;
-
-}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java
index 147e0e9..408c3e4 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java
@@ -79,7 +79,7 @@
"that look like likely misspellings, they are flagged.",
Category.CORRECTNESS,
5,
- Severity.WARNING,
+ Severity.FATAL,
new Implementation(
ManifestTypoDetector.class,
Scope.MANIFEST_SCOPE));
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
index baee367..6b318c9 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
@@ -131,7 +131,7 @@
Object clientData = handle.getClientData();
if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ISSUE, (Node) clientData)) {
+ if (context.getDriver().isSuppressed(null, ISSUE, (Node) clientData)) {
return;
}
}
@@ -169,7 +169,7 @@
&& !element.hasAttributeNS(ANDROID_URI, ATTR_FOREGROUND)
&& !hasPadding(element)) {
String layout = LintUtils.getLayoutName(context.file);
- Handle handle = context.parser.createLocationHandle(context, element);
+ Handle handle = context.createLocationHandle(element);
handle.setClientData(element);
if (!context.getProject().getReportIssues()) {
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 33ccbfb..d4e1199 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
@@ -109,7 +109,7 @@
Category.CORRECTNESS,
6,
- Severity.WARNING,
+ Severity.FATAL,
new Implementation(
MissingClassDetector.class,
Scope.CLASS_FILE_SCOPE));
@@ -261,13 +261,13 @@
}
Handle handle = null;
- if (!context.getDriver().isSuppressed(MISSING, element)) {
+ if (!context.getDriver().isSuppressed(context, MISSING, element)) {
if (mReferencedClasses == null) {
mReferencedClasses = Maps.newHashMapWithExpectedSize(16);
mCustomViews = Sets.newHashSetWithExpectedSize(8);
}
- handle = context.parser.createLocationHandle(context, element);
+ handle = context.createLocationHandle(element);
mReferencedClasses.put(signature, handle);
if (folderType == LAYOUT && !tag.equals(VIEW_FRAGMENT)) {
mCustomViews.add(ClassContext.getInternalName(className));
@@ -301,8 +301,23 @@
// When generating errors we'll look for these an rip them back out if
// it looks like one of the two variations have been seen.
if (handle != null) {
- signature = signature.replace('$', '/');
- mReferencedClasses.put(signature, handle);
+ // Assume that each successive $ is really a capitalized package name
+ // instead. In other words, for A$B$C$D (assumed to be class A with
+ // inner classes A.B, A.B.C and A.B.C.D) generate the following possible
+ // referenced classes A/B$C$D (class B in package A with inner classes C and C.D),
+ // A/B/C$D and A/B/C/D
+ while (true) {
+ int index = signature.indexOf('$');
+ if (index == -1) {
+ break;
+ }
+ signature = signature.substring(0, index) + '/'
+ + signature.substring(index + 1);
+ mReferencedClasses.put(signature, handle);
+ if (folderType == LAYOUT && !tag.equals(VIEW_FRAGMENT)) {
+ mCustomViews.add(signature);
+ }
+ }
}
}
}
@@ -376,7 +391,7 @@
String curr = classNode.name;
if (mReferencedClasses != null && mReferencedClasses.containsKey(curr)) {
boolean isCustomView = mCustomViews.contains(curr);
- mReferencedClasses.remove(curr);
+ removeReferences(curr);
// Ensure that the class is public, non static and has a null constructor!
@@ -429,4 +444,39 @@
}
}
}
+
+ private void removeReferences(String curr) {
+ mReferencedClasses.remove(curr);
+
+ // Since "A.B.C" is ambiguous whether it's referencing a class in package A.B or
+ // an inner class C in package A, we insert multiple possible references when we
+ // encounter the A.B.C reference; now that we've seen the actual class we need to
+ // remove all the possible permutations we've added such that the permutations
+ // don't count as unreferenced classes.
+ int index = curr.lastIndexOf('/');
+ if (index == -1) {
+ return;
+ }
+ boolean hasCapitalizedPackageName = false;
+ for (int i = index - 1; i >= 0; i--) {
+ char c = curr.charAt(i);
+ if (Character.isUpperCase(c)) {
+ hasCapitalizedPackageName = true;
+ break;
+ }
+ }
+ if (!hasCapitalizedPackageName) {
+ // No path ambiguity
+ return;
+ }
+
+ while (true) {
+ index = curr.lastIndexOf('/');
+ if (index == -1) {
+ break;
+ }
+ curr = curr.substring(0, index) + '$' + curr.substring(index + 1);
+ mReferencedClasses.remove(curr);
+ }
+ }
}
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 f7de894..fa22161 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
@@ -21,7 +21,6 @@
import static com.android.SdkConstants.URI_PREFIX;
import static com.android.SdkConstants.XMLNS_PREFIX;
-import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Implementation;
@@ -64,7 +63,7 @@
"help track these down.",
Category.CORRECTNESS,
8,
- Severity.WARNING,
+ Severity.FATAL,
IMPLEMENTATION);
/** Unused namespace declarations */
@@ -94,7 +93,7 @@
"application project.",
Category.CORRECTNESS,
6,
- Severity.ERROR,
+ Severity.FATAL,
IMPLEMENTATION);
/** Unused namespace declarations */
@@ -112,7 +111,7 @@
Category.CORRECTNESS,
9,
- Severity.ERROR,
+ Severity.FATAL,
IMPLEMENTATION);
@@ -153,6 +152,8 @@
}
mUnusedNamespaces.put(item.getNodeName().substring(XMLNS_PREFIX.length()),
attribute);
+ } else if (value.startsWith("urn:")) { //$NON-NLS-1$
+ continue;
} else if (!value.startsWith("http://")) { //$NON-NLS-1$
if (context.isEnabled(TYPO)) {
context.report(TYPO, attribute, context.getLocation(attribute),
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NfcTechListDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NfcTechListDetector.java
new file mode 100644
index 0000000..11f9fa0
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NfcTechListDetector.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+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 com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Check which makes sure NFC tech lists do not include spaces around {@code <tech>} values
+ * since that's not handled correctly by the inflater
+ */
+public class NfcTechListDetector extends ResourceXmlDetector implements JavaScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "NfcTechWhitespace", //$NON-NLS-1$
+ "Whitespace in NFC tech lists",
+ "Ensures that NFC <tech> lists do not include whitespace",
+
+ "In a <tech-list>, there can be whitespace around the <tech> elements," +
+ "but not inside them. This is because the code which reads in the tech " +
+ "list is currently very strict and will include the whitespace as part " +
+ "of the name.\n" +
+ "\n" +
+ "In other words, use <tech>name</tech>, not <tech> name </tech>.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.FATAL,
+ new Implementation(
+ NfcTechListDetector.class,
+ Scope.RESOURCE_FILE_SCOPE))
+ .addMoreInfo(
+ "https://code.google.com/p/android/issues/detail?id=65351"); //$NON-NLS-1$
+
+ /** Constructs a new {@link NfcTechListDetector} */
+ public NfcTechListDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.XML;
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ @Nullable
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList("tech");
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ Node parentNode = element.getParentNode();
+ if (parentNode == null || parentNode.getNodeType() != Node.ELEMENT_NODE ||
+ !"tech-list".equals(parentNode.getNodeName())) {
+ return;
+ }
+
+ NodeList children = element.getChildNodes();
+ if (children.getLength() != 1) {
+ return;
+ }
+ Node child = children.item(0);
+ if (child.getNodeType() != Node.TEXT_NODE) {
+ // TODO: Warn if you have comment nodes etc too? Will probably also break inflater.
+ return;
+ }
+
+ String text = child.getNodeValue();
+ if (!text.equals(text.trim())) {
+ String message = "There should not be any whitespace inside <tech> elements";
+ context.report(ISSUE, element, context.getLocation(child), message, null);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java
index e0e40cb..d74421f 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java
@@ -22,12 +22,16 @@
import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BOTTOM;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_END;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_END;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_START;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_START;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_TOP;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING;
import static com.android.SdkConstants.ATTR_LAYOUT_BELOW;
@@ -40,15 +44,19 @@
import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_BOTTOM;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_END;
import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT;
import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_START;
import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_TOP;
import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.ATTR_LAYOUT_ROW;
import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN;
import static com.android.SdkConstants.ATTR_LAYOUT_SPAN;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_END_OF;
import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_START_OF;
import static com.android.SdkConstants.ATTR_LAYOUT_WEIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
import static com.android.SdkConstants.ATTR_LAYOUT_X;
@@ -64,7 +72,6 @@
import static com.android.SdkConstants.VIEW_TAG;
import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.IDomParser;
import com.android.tools.lint.client.api.SdkInfo;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
@@ -148,7 +155,9 @@
// From ViewGroup.MarginLayoutParams
VALID.add(ATTR_LAYOUT_MARGIN_LEFT);
+ VALID.add(ATTR_LAYOUT_MARGIN_START);
VALID.add(ATTR_LAYOUT_MARGIN_RIGHT);
+ VALID.add(ATTR_LAYOUT_MARGIN_END);
VALID.add(ATTR_LAYOUT_MARGIN_TOP);
VALID.add(ATTR_LAYOUT_MARGIN_BOTTOM);
VALID.add(ATTR_LAYOUT_MARGIN);
@@ -179,20 +188,26 @@
// Relative Layout
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_LEFT, RELATIVE_LAYOUT);
+ PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_START, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_RIGHT, RELATIVE_LAYOUT);
+ PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_END, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_TOP, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_BOTTOM, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_TOP, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_LEFT, RELATIVE_LAYOUT);
+ PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_START, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_RIGHT, RELATIVE_LAYOUT);
+ PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_END, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_BASELINE, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_CENTER_IN_PARENT, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_CENTER_VERTICAL, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_CENTER_HORIZONTAL, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_TO_RIGHT_OF, RELATIVE_LAYOUT);
+ PARAM_TO_VIEW.put(ATTR_LAYOUT_TO_END_OF, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_TO_LEFT_OF, RELATIVE_LAYOUT);
+ PARAM_TO_VIEW.put(ATTR_LAYOUT_TO_START_OF, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_BELOW, RELATIVE_LAYOUT);
PARAM_TO_VIEW.put(ATTR_LAYOUT_ABOVE, RELATIVE_LAYOUT);
}
@@ -258,8 +273,7 @@
// We can't do that yet since we may be processing the include tag to
// this layout after the layout itself. Instead, stash a work order...
if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
- IDomParser parser = context.parser;
- Location.Handle handle = parser.createLocationHandle(context, attribute);
+ Location.Handle handle = context.createLocationHandle(attribute);
handle.setClientData(attribute);
mPending.add(Pair.of(name, handle));
}
@@ -273,8 +287,7 @@
// wherever they are. This has to be done after all the files have been
// scanned since we are not processing the files in any particular order.
if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
- IDomParser parser = context.parser;
- Location.Handle handle = parser.createLocationHandle(context, attribute);
+ Location.Handle handle = context.createLocationHandle(attribute);
handle.setClientData(attribute);
mPending.add(Pair.of(name, handle));
}
@@ -373,7 +386,7 @@
if (!isValid) {
Object clientData = handle.getClientData();
if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ISSUE, (Node) clientData)) {
+ if (context.getDriver().isSuppressed(null, ISSUE, (Node) clientData)) {
return;
}
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java
index 549db6f..bd1d452 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java
@@ -100,7 +100,7 @@
Object clientData = handle.getClientData();
if (clientData instanceof Node) {
- if (driver.isSuppressed(ISSUE, (Node) clientData)) {
+ if (driver.isSuppressed(null, ISSUE, (Node) clientData)) {
continue;
}
}
@@ -144,7 +144,7 @@
if (mNames == null) {
mNames = new HashMap<String, Location.Handle>();
}
- Handle handle = context.parser.createLocationHandle(context, attribute);
+ Handle handle = context.createLocationHandle(attribute);
handle.setClientData(attribute);
// Replace unicode characters with the actual value since that's how they
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java
index 9e336c1..6b150b4 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java
@@ -37,7 +37,6 @@
import static com.android.SdkConstants.VALUE_DISABLED;
import static com.android.tools.lint.detector.api.LintUtils.endsWith;
-import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.resources.ResourceFolderType;
import com.android.tools.lint.detector.api.Category;
@@ -211,7 +210,7 @@
Object clientData = location.getClientData();
if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ISSUE, (Node) clientData)) {
+ if (context.getDriver().isSuppressed(null, ISSUE, (Node) clientData)) {
return;
}
}
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 bb64ef5..cfe6b54 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
@@ -15,31 +15,37 @@
*/
package com.android.tools.lint.checks;
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedField;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
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 org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.MethodNode;
-import java.io.File;
+import java.util.Collections;
import java.util.List;
+import lombok.ast.AstVisitor;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Node;
+import lombok.ast.TypeReference;
+
/**
* Looks for Parcelable classes that are missing a CREATOR field
*/
-public class ParcelDetector extends Detector implements Detector.ClassScanner {
+public class ParcelDetector extends Detector implements Detector.JavaScanner {
/** The main issue discovered by this detector */
public static final Issue ISSUE = Issue.create(
@@ -57,7 +63,7 @@
Severity.WARNING,
new Implementation(
ParcelDetector.class,
- Scope.CLASS_FILE_SCOPE))
+ Scope.JAVA_FILE_SCOPE))
.addMoreInfo("http://developer.android.com/reference/android/os/Parcelable.html");
/** Constructs a new {@link com.android.tools.lint.checks.ParcelDetector} check */
@@ -70,41 +76,65 @@
return Speed.FAST;
}
- // ---- Implements ClassScanner ----
+ // ---- Implements JavaScanner ----
+ @Nullable
@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) {
- if ("android/os/Parcelable".equals(o)) {
- if (!hasCreatorField(context, classNode)) {
- Location location = context.getLocation(classNode);
- context.report(ISSUE, location, "This class implements Parcelable but does not provide a CREATOR field", null);
- }
- break;
- }
- }
- }
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ return Collections.<Class<? extends Node>>singletonList(ClassDeclaration.class);
}
- private static boolean hasCreatorField(@NonNull ClassContext context,
- @NonNull ClassNode classNode) {
- List<FieldNode> fields = classNode.fields;
- if (fields != null) {
- for (FieldNode field : fields) {
- if (field.name.equals("CREATOR")) {
- // TODO: Make sure it has the right type
- String desc = field.desc;
- return true;
- }
- }
+ @Nullable
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull final JavaContext context) {
+ return new ParcelVisitor(context);
+ }
+
+ private static class ParcelVisitor extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+
+ public ParcelVisitor(JavaContext context) {
+ mContext = context;
}
- return false;
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ // Only applies to concrete classes
+ int flags = node.astModifiers().getExplicitModifierFlags();
+ if ((flags & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)) != 0) {
+ return true;
+ }
+
+ if (node.astImplementing() != null)
+ for (TypeReference reference : node.astImplementing()) {
+ String name = reference.astParts().last().astIdentifier().astValue();
+ if (name.equals("Parcelable")) {
+ JavaParser.ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedClass) {
+ ResolvedClass cls = (ResolvedClass) resolved;
+ ResolvedField field = cls.getField("CREATOR");
+ if (field == null) {
+ // Make doubly sure that we're really implementing
+ // android.os.Parcelable
+ JavaParser.ResolvedNode r = mContext.resolve(reference);
+ if (r instanceof ResolvedClass) {
+ ResolvedClass parcelable = (ResolvedClass) r;
+ if (!parcelable.isSubclassOf("android.os.Parcelable", false)) {
+ return true;
+ }
+ }
+ Location location = mContext.getLocation(node.astName());
+ mContext.report(ISSUE, node, location,
+ "This class implements Parcelable but does not "
+ + "provide a CREATOR field",
+ null);
+ }
+ }
+ break;
+ }
+ }
+
+ return true;
+ }
}
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java
new file mode 100644
index 0000000..6cae0fe
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tools.lint.checks.PluralsDatabase.Quantity.few;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.many;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.one;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.two;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.zero;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.google.common.collect.Maps;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Database used by the {@link com.android.tools.lint.checks.PluralsDetector} to get information
+ * about plural forms for a given language
+ */
+public class PluralsDatabase {
+ private static final PluralsDatabase sInstance = new PluralsDatabase();
+
+ private Map<String, EnumSet<Quantity>> mPlurals;
+ private Map<Quantity, Map<Set<Quantity>,Boolean>> mMultiValueSetNames =
+ Maps.newEnumMap(Quantity.class);
+
+
+ @NonNull
+ public static PluralsDatabase get() {
+ return sInstance;
+ }
+
+ @Nullable
+ public EnumSet<Quantity> getRelevant(@NonNull String language) {
+ ensureInitialized();
+ return mPlurals.get(language);
+ }
+
+ public boolean hasMultipleValuesForQuantity(@NonNull String language,
+ @NonNull Quantity quantity) {
+ if (quantity == Quantity.one || quantity == Quantity.two || quantity == Quantity.zero) {
+ ensureInitialized();
+ EnumSet<Quantity> relevant = mPlurals.get(language);
+ if (relevant != null) {
+ Map<Set<Quantity>,Boolean> names = mMultiValueSetNames.get(quantity);
+ assert names != null : quantity;
+ return names.containsKey(relevant);
+ }
+ }
+
+ return false;
+ }
+
+ private void ensureInitialized() {
+ // Based on the plurals table in plurals.txt in icu4c, version 52:
+ // external/icu4c/data/misc/plurals.txt
+ // The format is documented here:
+ // http://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules
+
+ if (mPlurals == null) {
+ initialize();
+ }
+ }
+
+ @SuppressWarnings({"UnnecessaryLocalVariable", "UnusedDeclaration"})
+ private void initialize() {
+ // Quantity.other appears in every single set, so it was instead removed and
+ // handled at the check site.
+ EnumSet<Quantity> empty = EnumSet.noneOf(Quantity.class);
+
+ EnumSet<Quantity> set0 = EnumSet.of(few, many, one, two, zero);
+ EnumSet<Quantity> set1 = EnumSet.of(many, one, two);
+ EnumSet<Quantity> set10 = EnumSet.of(few, many, one);
+ EnumSet<Quantity> set11 = EnumSet.of(few, one); // "many" are only for fractions
+ EnumSet<Quantity> set12 = set10;
+ EnumSet<Quantity> set13 = EnumSet.of(few, one, two);
+ EnumSet<Quantity> set14 = set10;
+ EnumSet<Quantity> set15 = EnumSet.of(one);
+ EnumSet<Quantity> set16 = set0;
+ EnumSet<Quantity> set17 = EnumSet.of(one, zero);
+ EnumSet<Quantity> set18 = set11;
+ EnumSet<Quantity> set19 = EnumSet.of(few, many, one, two);
+ EnumSet<Quantity> set2 = set15;
+ EnumSet<Quantity> set20 = set17;
+ EnumSet<Quantity> set21 = set15;
+ EnumSet<Quantity> set22 = set13;
+ EnumSet<Quantity> set23 = set22;
+ EnumSet<Quantity> set24 = empty;
+ EnumSet<Quantity> set25 = set15;
+ EnumSet<Quantity> set26 = set15;
+ EnumSet<Quantity> set27 = set15;
+ EnumSet<Quantity> set28 = set15;
+ EnumSet<Quantity> set29 = set15;
+ EnumSet<Quantity> set3 = set15;
+ EnumSet<Quantity> set30 = set15;
+ EnumSet<Quantity> set31 = set15;
+ EnumSet<Quantity> set32 = set15;
+ EnumSet<Quantity> set33 = set18;
+ EnumSet<Quantity> set34 = EnumSet.of(many, one);
+ EnumSet<Quantity> set35 = set10;
+ EnumSet<Quantity> set36 = set15;
+ EnumSet<Quantity> set37 = set15;
+ EnumSet<Quantity> set38 = set15;
+ EnumSet<Quantity> set39 = set13;
+ EnumSet<Quantity> set4 = set15;
+ EnumSet<Quantity> set40 = EnumSet.of(many);
+ EnumSet<Quantity> set41 = set13;
+ EnumSet<Quantity> set42 = set13;
+ EnumSet<Quantity> set43 = set19;
+ EnumSet<Quantity> set44 = set19;
+ EnumSet<Quantity> set45 = set10;
+ EnumSet<Quantity> set5 = set17;
+ EnumSet<Quantity> set6 = EnumSet.of(one, two);
+ EnumSet<Quantity> set7 = set19;
+ EnumSet<Quantity> set8 = set18;
+ EnumSet<Quantity> set9 = set18; // "many" only for fractions, so using different set
+
+ // The following sets are used by the mMultiValueSetNames map, and therefore need
+ // to have their own instances since we will look up set identity
+ set10 = EnumSet.copyOf(set10);
+ set13 = EnumSet.copyOf(set13);
+ set15 = EnumSet.copyOf(set15);
+ set18 = EnumSet.copyOf(set18);
+ set19 = EnumSet.copyOf(set19);
+ set21 = EnumSet.copyOf(set21);
+ set22 = EnumSet.copyOf(set22);
+ set23 = EnumSet.copyOf(set23);
+ set25 = EnumSet.copyOf(set25);
+ set3 = EnumSet.copyOf(set3);
+ set30 = EnumSet.copyOf(set30);
+ set31 = EnumSet.copyOf(set31);
+ set32 = EnumSet.copyOf(set32);
+ set33 = EnumSet.copyOf(set33);
+ set34 = EnumSet.copyOf(set34);
+ set35 = EnumSet.copyOf(set35);
+ set38 = EnumSet.copyOf(set38);
+ set39 = EnumSet.copyOf(set39);
+ set4 = EnumSet.copyOf(set4);
+ set40 = EnumSet.copyOf(set40);
+ set42 = EnumSet.copyOf(set42);
+ set43 = EnumSet.copyOf(set43);
+ set44 = EnumSet.copyOf(set44);
+ set45 = EnumSet.copyOf(set45);
+ set5 = EnumSet.copyOf(set5);
+ set9 = EnumSet.copyOf(set9);
+
+ final int INITIAL_CAPACITY = 133;
+ mPlurals = Maps.newHashMapWithExpectedSize(INITIAL_CAPACITY);
+ mPlurals.put("af", set2);
+ mPlurals.put("ak", set3);
+ mPlurals.put("am", set30);
+ mPlurals.put("ar", set0);
+ mPlurals.put("az", set2);
+ mPlurals.put("be", set10);
+ mPlurals.put("bg", set2);
+ mPlurals.put("bh", set3);
+ mPlurals.put("bm", set24);
+ mPlurals.put("bn", set30);
+ mPlurals.put("bo", set24);
+ mPlurals.put("br", set19);
+ mPlurals.put("bs", set33);
+ mPlurals.put("ca", set26);
+ mPlurals.put("cs", set11);
+ mPlurals.put("cy", set16);
+ mPlurals.put("da", set28);
+ mPlurals.put("de", set26);
+ mPlurals.put("dv", set2);
+ mPlurals.put("dz", set24);
+ mPlurals.put("ee", set2);
+ mPlurals.put("el", set2);
+ mPlurals.put("en", set26);
+ mPlurals.put("eo", set2);
+ mPlurals.put("es", set2);
+ mPlurals.put("et", set26);
+ mPlurals.put("eu", set2);
+ mPlurals.put("fa", set30);
+ mPlurals.put("ff", set4);
+ mPlurals.put("fi", set26);
+ mPlurals.put("fo", set2);
+ mPlurals.put("fr", set4);
+ mPlurals.put("fy", set2);
+ mPlurals.put("ga", set7);
+ mPlurals.put("gd", set23);
+ mPlurals.put("gl", set26);
+ mPlurals.put("gu", set30);
+ mPlurals.put("gv", set22);
+ mPlurals.put("ha", set2);
+ mPlurals.put("he", set1);
+ mPlurals.put("hi", set30);
+ mPlurals.put("hr", set33);
+ mPlurals.put("hu", set2);
+ mPlurals.put("hy", set4);
+ mPlurals.put("id", set24);
+ mPlurals.put("ig", set24);
+ mPlurals.put("ii", set24);
+ mPlurals.put("in", set24);
+ mPlurals.put("is", set31);
+ mPlurals.put("it", set26);
+ mPlurals.put("iu", set6);
+ mPlurals.put("iw", set1);
+ mPlurals.put("ja", set24);
+ mPlurals.put("ji", set26);
+ mPlurals.put("jv", set24);
+ // Javanese replaced by "jv"
+ //mPlurals.put("jw", set24);
+ mPlurals.put("ka", set2);
+ mPlurals.put("kk", set2);
+ mPlurals.put("kl", set2);
+ mPlurals.put("km", set24);
+ mPlurals.put("kn", set30);
+ mPlurals.put("ko", set24);
+ mPlurals.put("ks", set2);
+ mPlurals.put("ku", set2);
+ mPlurals.put("kw", set6);
+ mPlurals.put("ky", set2);
+ mPlurals.put("lb", set2);
+ mPlurals.put("lg", set2);
+ mPlurals.put("ln", set3);
+ mPlurals.put("lo", set24);
+ mPlurals.put("lt", set9);
+ mPlurals.put("lv", set5);
+ mPlurals.put("mg", set3);
+ mPlurals.put("mk", set15);
+ mPlurals.put("ml", set2);
+ mPlurals.put("mn", set2);
+ // Deprecated
+ //mPlurals.put("mo", set8);
+ mPlurals.put("mr", set30);
+ mPlurals.put("ms", set24);
+ mPlurals.put("mt", set14);
+ mPlurals.put("my", set24);
+ mPlurals.put("nb", set2);
+ mPlurals.put("nd", set2);
+ mPlurals.put("ne", set2);
+ mPlurals.put("nl", set26);
+ mPlurals.put("nn", set2);
+ mPlurals.put("no", set2);
+ mPlurals.put("nr", set2);
+ mPlurals.put("ny", set2);
+ mPlurals.put("om", set2);
+ mPlurals.put("or", set2);
+ mPlurals.put("os", set2);
+ mPlurals.put("pa", set3);
+ mPlurals.put("pl", set12);
+ mPlurals.put("ps", set2);
+ mPlurals.put("pt", set27);
+ // Luckily these sets are identical so we don't need to make a region distinction
+ // in the API
+ //mPlurals.put("pt_PT", set29); // XXX
+ mPlurals.put("rm", set2);
+ mPlurals.put("ro", set8);
+ mPlurals.put("ru", set34);
+ mPlurals.put("se", set6);
+ mPlurals.put("sg", set24);
+ // sh was removed from 639-1 to 639-2
+ //mPlurals.put("sh", set33);
+ mPlurals.put("si", set32);
+ mPlurals.put("sk", set11);
+ mPlurals.put("sl", set13);
+ mPlurals.put("sn", set2);
+ mPlurals.put("so", set2);
+ mPlurals.put("sq", set2);
+ mPlurals.put("sr", set33);
+ mPlurals.put("ss", set2);
+ mPlurals.put("st", set2);
+ mPlurals.put("sv", set26);
+ mPlurals.put("sw", set26);
+ mPlurals.put("ta", set2);
+ mPlurals.put("te", set2);
+ mPlurals.put("th", set24);
+ mPlurals.put("ti", set3);
+ mPlurals.put("tk", set2);
+ mPlurals.put("tl", set25);
+ mPlurals.put("tn", set2);
+ mPlurals.put("to", set24);
+ mPlurals.put("tr", set2);
+ mPlurals.put("ts", set2);
+ mPlurals.put("uk", set35);
+ mPlurals.put("ur", set26);
+ mPlurals.put("uz", set2);
+ mPlurals.put("ve", set2);
+ mPlurals.put("vi", set24);
+ mPlurals.put("vo", set2);
+ mPlurals.put("wa", set3);
+ mPlurals.put("wo", set24);
+ mPlurals.put("xh", set2);
+ mPlurals.put("yi", set26);
+ mPlurals.put("yo", set24);
+ mPlurals.put("zh", set24);
+ mPlurals.put("zu", set30);
+
+ assert mPlurals.size() == INITIAL_CAPACITY : mPlurals.size();
+
+ // Sets where more than a single integer maps to one. Take for example
+ // set 10:
+ // set10{
+ // one{
+ // "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81,"
+ // " 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0"
+ // ", 101.0, 1001.0, …"
+ // }
+ // }
+ // Here we see that both "1" and "21" will match the "one" category.
+ // Note that this only applies to integers (since getQuantityString only takes integer)
+ // whereas the plurals data also covers fractions. I was not sure what to do about
+ // set17:
+ // set17{
+ // one{"i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6"}
+ // }
+ // since it looks to me like this only differs from 1 in the fractional part.
+
+ //noinspection unchecked
+ mMultiValueSetNames.put(Quantity.one, newIdentityHashMap(
+ set10, set13, set15, set18, set19, set21, set22, set23, set25, set3, set30,
+ set31, set32, set33, set34, set35, set38, set39, set4, set40, set42, set45,
+ set5, set9
+ ));
+
+ // Sets where more than a single integer maps to two.
+ //noinspection unchecked
+ mMultiValueSetNames.put(Quantity.two, newIdentityHashMap(
+ set13, set19, set22, set23, set40, set43, set44, set45
+ ));
+
+ // Sets where more than a single integer maps to zero.
+ //noinspection unchecked
+ mMultiValueSetNames.put(Quantity.zero, newIdentityHashMap(set5));
+ }
+
+ private static Map<Set<Quantity>,Boolean> newIdentityHashMap(Set<Quantity>... elements) {
+ Map<Set<Quantity>,Boolean> map = Maps.newIdentityHashMap();
+ for (Set<Quantity> set : elements) {
+ map.put(set, true);
+ }
+ return map;
+ }
+
+ @SuppressWarnings({"MethodMayBeStatic", "UnusedParameters"})
+ @Nullable
+ public String findIntegerExamples(@NonNull String language, @NonNull Quantity quantity) {
+ // Need plurals database
+ return null;
+ }
+
+ public enum Quantity {
+ // deliberately lower case to match attribute names
+ few, many, one, two, zero, other;
+
+ @Nullable
+ public static Quantity get(@NonNull String name) {
+ for (Quantity quantity : values()) {
+ if (name.equals(quantity.name())) {
+ return quantity;
+ }
+ }
+
+ return null;
+ }
+
+ public static String formatSet(EnumSet<Quantity> set) {
+ List<String> list = new ArrayList<String>(set.size());
+ for (Quantity quantity : set) {
+ list.add(quantity.name());
+ }
+ return LintUtils.formatList(list, Integer.MAX_VALUE);
+ }
+ }
+}
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 ae62f0e..d512103 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
@@ -19,14 +19,8 @@
import static com.android.SdkConstants.ATTR_QUANTITY;
import static com.android.SdkConstants.TAG_ITEM;
import static com.android.SdkConstants.TAG_PLURALS;
-import static com.android.tools.lint.checks.PluralsDetector.Quantity.few;
-import static com.android.tools.lint.checks.PluralsDetector.Quantity.many;
-import static com.android.tools.lint.checks.PluralsDetector.Quantity.one;
-import static com.android.tools.lint.checks.PluralsDetector.Quantity.two;
-import static com.android.tools.lint.checks.PluralsDetector.Quantity.zero;
-
+import com.android.tools.lint.checks.PluralsDatabase.Quantity;
import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
import com.android.resources.ResourceFolderType;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Implementation;
@@ -37,21 +31,18 @@
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.XmlContext;
import com.android.utils.Pair;
-import com.google.common.collect.Maps;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
/**
* Checks for issues with quantity strings
+ * <p>
* https://code.google.com/p/android/issues/detail?id=53015
* 53015: lint could report incorrect usage of Resource.getQuantityString
*/
@@ -79,7 +70,7 @@
"Similarly, a Czech translation must provide a string for `quantity=\"few\"`.",
Category.MESSAGES,
8,
- Severity.WARNING,
+ Severity.ERROR,
IMPLEMENTATION).addMoreInfo(
"http://developer.android.com/guide/topics/resources/string-resource.html#Plurals");
@@ -105,6 +96,27 @@
IMPLEMENTATION).addMoreInfo(
"http://developer.android.com/guide/topics/resources/string-resource.html#Plurals");
+ /** This plural does not use the quantity value */
+ public static final Issue IMPLIED_QUANTITY = Issue.create(
+ "ImpliedQuantity", //$NON-NLS-1$
+ "Implied Quantities",
+ "Looks for quantity string translations which do not include the quantity",
+
+ "Plural strings should generally include a `%s` or `%d` formatting argument. " +
+ "In locales like English, the `one` quantity only applies to a single value, " +
+ "1, but that's not true everywhere. For example, in Slovene, the `one` quantity " +
+ "will apply to 1, 101, 201, 301, and so on. Similarly, there are locales where " +
+ "multiple values match the `zero` and `two` quantities.\n" +
+ "\n" +
+ "In these locales, it is usually an error to have a message which does not " +
+ "include a formatting argument (such as '%d'), since it will not be clear from " +
+ "the grammar what quantity the quantity string is describing.",
+ Category.MESSAGES,
+ 5,
+ Severity.ERROR,
+ IMPLEMENTATION).addMoreInfo(
+ "http://developer.android.com/guide/topics/resources/string-resource.html#Plurals");
+
/** Constructs a new {@link PluralsDetector} */
public PluralsDetector() {
}
@@ -138,6 +150,13 @@
return;
}
+ PluralsDatabase plurals = PluralsDatabase.get();
+
+ EnumSet<Quantity> relevant = plurals.getRelevant(language);
+ if (relevant == null) {
+ return;
+ }
+
EnumSet<Quantity> defined = EnumSet.noneOf(Quantity.class);
NodeList children = element.getChildNodes();
for (int i = 0, n = children.getLength(); i < n; i++) {
@@ -149,22 +168,34 @@
if (!TAG_ITEM.equals(child.getTagName())) {
continue;
}
+
String quantityString = child.getAttribute(ATTR_QUANTITY);
if (quantityString == null || quantityString.isEmpty()) {
continue;
}
Quantity quantity = Quantity.get(quantityString);
- if (quantity == Quantity.other) { // Not stored in the database
+ if (quantity == null || quantity == Quantity.other) { // Not stored in the database
continue;
}
- if (quantity != null) {
- defined.add(quantity);
- }
- }
+ defined.add(quantity);
- EnumSet<Quantity> relevant = getRelevant(language);
- if (relevant == null) {
- return;
+ if (plurals.hasMultipleValuesForQuantity(language, quantity)
+ && !haveFormattingParameter(child) && context.isEnabled(IMPLIED_QUANTITY)) {
+ String example = plurals.findIntegerExamples(language, quantity);
+ String append;
+ if (example == null) {
+ append = "";
+ } else {
+ append = " (" + example + ")";
+ }
+ String message = String.format("The quantity '%1$s' matches more than one "
+ + "specific number in this locale%2$s, but the message did "
+ + "not include a formatting argument (such as %%d). "
+ + "This is usually an internationalization error. See full issue "
+ + "explanation for more.",
+ quantity, append);
+ context.report(IMPLIED_QUANTITY, child, context.getLocation(child), message, null);
+ }
}
if (relevant.equals(defined)) {
@@ -176,8 +207,9 @@
missing.removeAll(defined);
if (!missing.isEmpty()) {
String message = String.format(
- "For locale \"%1$s\" the following quantities should also be defined: %2$s",
- language, formatSet(missing));
+ "For locale %1$s the following quantities should also be defined: %2$s",
+ TranslationDetector.getLanguageDescription(language),
+ Quantity.formatSet(missing));
context.report(MISSING, element, context.getLocation(element), message, null);
}
@@ -186,195 +218,37 @@
extra.removeAll(relevant);
if (!extra.isEmpty()) {
String message = String.format(
- "For language \"%1$s\" the following quantities are not relevant: %2$s",
- language, formatSet(extra));
+ "For language %1$s the following quantities are not relevant: %2$s",
+ TranslationDetector.getLanguageDescription(language),
+ Quantity.formatSet(extra));
context.report(EXTRA, element, context.getLocation(element), message, null);
}
}
- private static String formatSet(EnumSet<Quantity> set) {
- List<String> list = new ArrayList<String>(set.size());
- for (Quantity quantity : set) {
- list.add(quantity.name());
- }
- return LintUtils.formatList(list, Integer.MAX_VALUE);
- }
-
- enum Quantity {
- few, many, one, two, zero, other; // deliberately lower case to match attribute names
-
- @Nullable
- public static Quantity get(@NonNull String name) {
- for (Quantity quantity : values()) {
- if (name.equals(quantity.name())) {
- return quantity;
+ /**
+ * Returns true if the given string/plurals item element contains a formatting parameter,
+ * possibly within HTML markup or xliff metadata tags
+ */
+ private static boolean haveFormattingParameter(@NonNull Element element) {
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ short nodeType = child.getNodeType();
+ if (nodeType == Node.ELEMENT_NODE) {
+ if (haveFormattingParameter((Element)child)) {
+ return true;
}
+ } else if (nodeType == Node.TEXT_NODE) {
+ String text = child.getNodeValue();
+ if (text.indexOf('%') == -1) {
+ continue;
+ }
+ if (StringFormatDetector.getFormatArgumentCount(text, null) >= 1) {
+ return true;
+ }
+
}
-
- return null;
}
- }
-
- private static Map<String, EnumSet<Quantity>> sPlurals;
-
- @SuppressWarnings({"UnnecessaryLocalVariable", "UnusedDeclaration"})
- @Nullable
- 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);
- EnumSet<Quantity> set1 = EnumSet.of(few, many, one, two, zero);
- EnumSet<Quantity> set10 = EnumSet.of(few, many, one);
- EnumSet<Quantity> set11 = EnumSet.of(few, one);
- EnumSet<Quantity> set12 = set10;
- EnumSet<Quantity> set13 = EnumSet.of(few, one, two);
- EnumSet<Quantity> set14 = set12;
- EnumSet<Quantity> set15 = EnumSet.of(one);
- EnumSet<Quantity> set16 = set1;
- EnumSet<Quantity> set17 = EnumSet.of(one, zero);
- EnumSet<Quantity> set18 = set11;
- EnumSet<Quantity> set19 = EnumSet.of(few, many, one, two);
- EnumSet<Quantity> set2 = set15;
- EnumSet<Quantity> set20 = set17;
- EnumSet<Quantity> set21 = set2;
- EnumSet<Quantity> set22 = set2;
- EnumSet<Quantity> set23 = set13;
- EnumSet<Quantity> set3 = set2;
- EnumSet<Quantity> set4 = set2;
- EnumSet<Quantity> set5 = set20;
- EnumSet<Quantity> set6 = EnumSet.of(one, two);
- EnumSet<Quantity> set7 = set19;
- EnumSet<Quantity> set8 = set11;
- EnumSet<Quantity> set9 = set8;
-
- final int INITIAL_CAPACITY = 124;
- sPlurals = Maps.newHashMapWithExpectedSize(INITIAL_CAPACITY);
- sPlurals.put("af", set2);
- sPlurals.put("ak", set3);
- sPlurals.put("am", set3);
- sPlurals.put("ar", set1);
- sPlurals.put("az", empty);
- sPlurals.put("be", set10);
- sPlurals.put("bg", set2);
- sPlurals.put("bh", set3);
- sPlurals.put("bm", empty);
- sPlurals.put("bn", set2);
- sPlurals.put("bo", empty);
- sPlurals.put("br", set19);
- sPlurals.put("bs", set10);
- sPlurals.put("ca", set2);
- sPlurals.put("cs", set11);
- sPlurals.put("cy", set16);
- sPlurals.put("da", set2);
- sPlurals.put("de", set2);
- sPlurals.put("dv", set2);
- sPlurals.put("dz", empty);
- sPlurals.put("ee", set2);
- sPlurals.put("el", set2);
- sPlurals.put("en", set2);
- sPlurals.put("eo", set2);
- sPlurals.put("es", set2);
- sPlurals.put("et", set2);
- sPlurals.put("eu", set2);
- sPlurals.put("fa", empty);
- sPlurals.put("ff", set4);
- sPlurals.put("fi", set2);
- sPlurals.put("fo", set2);
- sPlurals.put("fr", set4);
- sPlurals.put("fy", set2);
- sPlurals.put("ga", set7);
- sPlurals.put("gd", set23);
- sPlurals.put("gl", set2);
- sPlurals.put("gu", set2);
- sPlurals.put("gv", set22);
- sPlurals.put("ha", set2);
- sPlurals.put("he", set2);
- sPlurals.put("hi", set3);
- sPlurals.put("hr", set10);
- sPlurals.put("hu", empty);
- sPlurals.put("id", empty);
- sPlurals.put("ig", empty);
- sPlurals.put("ii", empty);
- sPlurals.put("is", set2);
- sPlurals.put("it", set2);
- sPlurals.put("iu", set6);
- sPlurals.put("ja", empty);
- sPlurals.put("jv", empty);
- sPlurals.put("ka", empty);
- sPlurals.put("kk", set2);
- sPlurals.put("kl", set2);
- sPlurals.put("km", empty);
- sPlurals.put("kn", empty);
- sPlurals.put("ko", empty);
- sPlurals.put("ku", set2);
- sPlurals.put("kw", set6);
- sPlurals.put("lb", set2);
- sPlurals.put("lg", set2);
- sPlurals.put("ln", set3);
- sPlurals.put("lo", empty);
- sPlurals.put("lt", set9);
- sPlurals.put("lv", set5);
- sPlurals.put("mg", set3);
- sPlurals.put("mk", set15);
- sPlurals.put("ml", set2);
- sPlurals.put("mn", set2);
- sPlurals.put("mo", set8);
- sPlurals.put("mr", set2);
- sPlurals.put("ms", empty);
- sPlurals.put("mt", set14);
- sPlurals.put("my", empty);
- sPlurals.put("nb", set2);
- sPlurals.put("nd", set2);
- sPlurals.put("ne", set2);
- sPlurals.put("nl", set2);
- sPlurals.put("nn", set2);
- sPlurals.put("no", set2);
- sPlurals.put("nr", set2);
- sPlurals.put("ny", set2);
- sPlurals.put("om", set2);
- sPlurals.put("or", set2);
- sPlurals.put("pa", set2);
- sPlurals.put("pl", set12);
- sPlurals.put("ps", set2);
- sPlurals.put("pt", set2);
- sPlurals.put("rm", set2);
- sPlurals.put("ro", set8);
- sPlurals.put("ru", set10);
- sPlurals.put("se", set6);
- sPlurals.put("sg", empty);
- sPlurals.put("sh", set10);
- sPlurals.put("sk", set11);
- sPlurals.put("sl", set13);
- sPlurals.put("sn", set2);
- sPlurals.put("so", set2);
- sPlurals.put("sq", set2);
- sPlurals.put("sr", set10);
- sPlurals.put("ss", set2);
- sPlurals.put("st", set2);
- sPlurals.put("sv", set2);
- sPlurals.put("sw", set2);
- sPlurals.put("ta", set2);
- sPlurals.put("te", set2);
- sPlurals.put("th", empty);
- sPlurals.put("ti", set3);
- sPlurals.put("tk", set2);
- sPlurals.put("tl", set3);
- sPlurals.put("tn", set2);
- sPlurals.put("to", empty);
- sPlurals.put("tr", empty);
- sPlurals.put("ts", set2);
- sPlurals.put("uk", set10);
- sPlurals.put("ur", set2);
- sPlurals.put("ve", set2);
- sPlurals.put("vi", empty);
- sPlurals.put("wa", set3);
- sPlurals.put("wo", empty);
- sPlurals.put("xh", set2);
- sPlurals.put("yo", empty);
- sPlurals.put("zh", empty);
- sPlurals.put("zu", set2);
- assert sPlurals.size() == INITIAL_CAPACITY : sPlurals.size();
- }
- return sPlurals.get(language);
+ return false;
}
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PreferenceActivityDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PreferenceActivityDetector.java
new file mode 100644
index 0000000..f9c536c
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PreferenceActivityDetector.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PACKAGE;
+import static com.android.SdkConstants.TAG_ACTIVITY;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.detector.api.Category;
+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 com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+
+/**
+ * Ensures that PreferenceActivity and its subclasses are never exported.
+ */
+public class PreferenceActivityDetector extends Detector
+ implements Detector.XmlScanner, Detector.JavaScanner {
+ // TODO Allow exporting PreferenceActivity if isValidFragment() is also overridden
+ // and the build target is always higher than Android 4.4 (API level 19).
+
+ public static final Issue ISSUE = Issue.create(
+ "ExportedPreferenceActivity", //$NON-NLS-1$
+ "PreferenceActivity should not be exported",
+ "Checks that PreferenceActivity and its subclasses are never exported",
+ "Fragment injection gives anyone who can send your PreferenceActivity an intent the "
+ + "ability to load any fragment, with any arguments, in your process.",
+ Category.SECURITY,
+ 8,
+ Severity.WARNING,
+ new Implementation(
+ PreferenceActivityDetector.class,
+ EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE)))
+ .addMoreInfo("http://securityintelligence.com/"
+ + "new-vulnerability-android-framework-fragment-injection");
+ private static final String PREFERENCE_ACTIVITY = "android.preference.PreferenceActivity"; //$NON-NLS-1$
+
+ private final Map<String, Location.Handle> mExportedActivities =
+ new HashMap<String, Location.Handle>();
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements XmlScanner ----
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList(TAG_ACTIVITY);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ if (SecurityDetector.getExported(element)) {
+ String fqcn = getFqcn(element);
+ if (fqcn != null) {
+ if (fqcn.equals(PREFERENCE_ACTIVITY) &&
+ !context.getDriver().isSuppressed(context, ISSUE, element)) {
+ String message = "PreferenceActivity should not be exported";
+ context.report(ISSUE, context.getLocation(element), message, null);
+ }
+ mExportedActivities.put(fqcn, context.createLocationHandle(element));
+ }
+ }
+ }
+
+ private static String getFqcn(@NonNull Element activityElement) {
+ String activityClassName = activityElement.getAttributeNS(ANDROID_URI, ATTR_NAME);
+
+ if (activityClassName == null || activityClassName.isEmpty()) {
+ return null;
+ }
+
+ // If the activity class name starts with a '.', it is shorthand for prepending the
+ // package name specified in the manifest.
+ if (activityClassName.startsWith(".")) {
+ String pkg = activityElement.getOwnerDocument().getDocumentElement()
+ .getAttribute(ATTR_PACKAGE);
+ if (pkg != null) {
+ return pkg + activityClassName;
+ } else {
+ return null;
+ }
+ }
+
+ return activityClassName;
+ }
+
+ // ---- Implements JavaScanner ----
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ if (!context.getProject().getReportIssues()) {
+ return null;
+ }
+ return new PreferenceActivityVisitor(context);
+ }
+
+ private class PreferenceActivityVisitor extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+
+ public PreferenceActivityVisitor(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ ResolvedNode resolvedNode = mContext.resolve(node);
+ if (!(resolvedNode instanceof ResolvedClass)) {
+ return false; // There might be an inner class that we need to inspect.
+ }
+ ResolvedClass resolvedClass = (ResolvedClass) resolvedNode;
+ String className = resolvedClass.getName();
+ if (resolvedClass.isSubclassOf(PREFERENCE_ACTIVITY, false)
+ && mExportedActivities.containsKey(className)) {
+ String message = String.format(
+ "PreferenceActivity subclass %1$s should not be exported",
+ className);
+ mContext.report(ISSUE, mExportedActivities.get(className).resolve(), message, null);
+ }
+
+ return true; // Done: No need to look inside this class
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java
index fea7cd9..edddf41 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java
@@ -48,7 +48,7 @@
Category.SECURITY,
8,
- Severity.WARNING,
+ Severity.FATAL,
new Implementation(PrivateKeyDetector.class, Scope.OTHER_SCOPE));
/** Constructs a new {@link PrivateKeyDetector} check */
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java
new file mode 100644
index 0000000..0208db2
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.DOT_PROPERTIES;
+
+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.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.base.Splitter;
+
+import java.io.File;
+import java.util.Iterator;
+
+/**
+ * Check for errors in .property files
+ * <p>
+ * TODO: Warn about bad paths like sdk properties with ' in the path, or suffix of " " etc
+ */
+public class PropertyFileDetector extends Detector {
+ /** Property file not escaped */
+ public static final Issue ISSUE = Issue.create(
+ "PropertyEscape", //$NON-NLS-1$
+ "Incorrect property escapes",
+ "Looks for property files with incorrect paths",
+ "All backslashes and colons in .property files must be escaped with " +
+ "a backslash (\\). This means that when writing a Windows path, you " +
+ "must escape the file separators, so the path \\My\\Files should be " +
+ "written as `key=\\\\My\\\\Files.`",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ new Implementation(
+ PropertyFileDetector.class,
+ Scope.PROPERTY_SCOPE));
+
+ /** Constructs a new {@link PropertyFileDetector} */
+ public PropertyFileDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return file.getPath().endsWith(DOT_PROPERTIES);
+ }
+
+ @Override
+ public void run(@NonNull Context context) {
+ String contents = context.getContents();
+ if (contents == null) {
+ return;
+ }
+ int offset = 0;
+ Iterator<String> iterator = Splitter.on('\n').split(contents).iterator();
+ String line;
+ for (; iterator.hasNext(); offset += line.length() + 1) {
+ line = iterator.next();
+ if (line.startsWith("#") || line.startsWith(" ")) {
+ continue;
+ }
+ if (line.indexOf('\\') == -1) {
+ continue;
+ }
+ int valueStart = line.indexOf('=') + 1;
+ if (valueStart == 0) {
+ continue;
+ }
+ if (line.indexOf('\\') == -1) {
+ continue;
+ }
+ checkLine(context, contents, line, offset, valueStart);
+ }
+ }
+
+ private static void checkLine(@NonNull Context context, @NonNull String contents,
+ @NonNull String line, int offset, int valueStart) {
+ boolean escaped = false;
+ int hadUnescapedColon = -1;
+ boolean hadNonPathEscape = false;
+ StringBuilder path = new StringBuilder();
+ for (int i = valueStart; i < line.length(); i++) {
+ char c = line.charAt(i);
+ if (c == '\\') {
+ escaped = !escaped;
+ if (escaped) {
+ path.append(c);
+ }
+ } else {
+ if (escaped) {
+ if (c != ':') {
+ hadNonPathEscape = true;
+ }
+ } else if (c == ':' && hadUnescapedColon == -1) {
+ hadUnescapedColon = i;
+ }
+ escaped = false;
+ path.append(c);
+ }
+ }
+ String pathString = path.toString();
+ String key = line.substring(0, valueStart);
+ if ((hadNonPathEscape || hadUnescapedColon != -1) &&
+ key.endsWith(".dir=") || new File(pathString).exists()) {
+ if (hadNonPathEscape) {
+ String escapedPath = pathString.replace("\\", "\\\\");
+ String message = "Windows file separators (\\) must be escaped (\\\\); use "
+ + escapedPath;
+ int startOffset = offset + valueStart;
+ int endOffset = offset + line.length();
+ Location location = Location.create(context.file, contents, startOffset,
+ endOffset);
+ context.report(ISSUE, location, message, null);
+ }
+ if (hadUnescapedColon != -1) {
+ String message = "Colon (:) must be escaped in .property files";
+ int startOffset = offset + hadUnescapedColon;
+ Location location = Location.create(context.file, contents, startOffset,
+ startOffset + 1);
+ context.report(ISSUE, location, message, null);
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java
index c9f3800..eeddbb8 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java
@@ -19,6 +19,8 @@
import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_TEXT_SIZE;
+import static com.android.SdkConstants.DIMEN_PREFIX;
+import static com.android.SdkConstants.TAG_DIMEN;
import static com.android.SdkConstants.TAG_ITEM;
import static com.android.SdkConstants.TAG_STYLE;
import static com.android.SdkConstants.UNIT_DIP;
@@ -30,11 +32,19 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.ide.common.resources.ResourceUrl;
import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Location;
+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.tools.lint.detector.api.Speed;
@@ -47,6 +57,8 @@
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
/**
* Check for px dimensions instead of dp dimensions.
@@ -133,6 +145,8 @@
Severity.WARNING,
IMPLEMENTATION);
+ private HashMap<String, Location.Handle> mTextSizeUsage;
+
/** Constructs a new {@link PxUsageDetector} */
public PxUsageDetector() {
@@ -164,6 +178,30 @@
@Override
public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
if (context.getResourceFolderType() != ResourceFolderType.LAYOUT) {
+ assert context.getResourceFolderType() == ResourceFolderType.VALUES;
+ if (mTextSizeUsage != null
+ && attribute.getOwnerElement().getTagName().equals(TAG_DIMEN)) {
+ Element element = attribute.getOwnerElement();
+ String name = element.getAttribute(ATTR_NAME);
+ if (name != null && mTextSizeUsage.containsKey(name)
+ && context.isEnabled(DP_ISSUE)) {
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE &&
+ isDpUnit(child.getNodeValue())) {
+ String message = "This dimension is used as a text size: "
+ + "Should use \"sp\" instead of \"dp\"";
+ Location location = context.getLocation(child);
+ Location secondary = mTextSizeUsage.get(name).resolve();
+ secondary.setMessage("Dimension used as a text size here");
+ location.setSecondary(secondary);
+ context.report(DP_ISSUE, attribute, location, message, null);
+ break;
+ }
+ }
+ }
+ }
return;
}
@@ -202,16 +240,63 @@
String.format("Avoid using sizes smaller than 12sp: %1$s", value),
null);
}
- } else if (ATTR_TEXT_SIZE.equals(attribute.getLocalName())
- && (value.endsWith(UNIT_DP) || value.endsWith(UNIT_DIP))
- && (value.matches("\\d+di?p"))) { //$NON-NLS-1$
- if (context.isEnabled(DP_ISSUE)) {
- context.report(DP_ISSUE, attribute, context.getLocation(attribute),
- "Should use \"sp\" instead of \"dp\" for text sizes", null);
+ } else if (ATTR_TEXT_SIZE.equals(attribute.getLocalName())) {
+ if (isDpUnit(value)) { //$NON-NLS-1$
+ if (context.isEnabled(DP_ISSUE)) {
+ context.report(DP_ISSUE, attribute, context.getLocation(attribute),
+ "Should use \"sp\" instead of \"dp\" for text sizes", null);
+ }
+ } else if (value.startsWith(DIMEN_PREFIX)) {
+ if (context.getClient().supportsProjectResources()) {
+ LintClient client = context.getClient();
+ Project project = context.getProject();
+ AbstractResourceRepository resources = client.getProjectResources(project,
+ true);
+ ResourceUrl url = ResourceUrl.parse(value);
+ if (resources != null && url != null) {
+ List<ResourceItem> items = resources.getResourceItem(url.type, url.name);
+ if (items != null) {
+ for (ResourceItem item : items) {
+ ResourceValue resourceValue = item.getResourceValue(false);
+ if (resourceValue != null) {
+ String dimenValue = resourceValue.getValue();
+ if (dimenValue != null && isDpUnit(dimenValue)
+ && context.isEnabled(DP_ISSUE)) {
+ ResourceFile sourceFile = item.getSource();
+ assert sourceFile != null;
+ String message = String.format(
+ "Should use \"sp\" instead of \"dp\" for text sizes (%1$s is defined as %2$s in %3$s",
+ value, dimenValue, sourceFile.getFile());
+ context.report(DP_ISSUE, attribute,
+ context.getLocation(attribute),
+ message,
+ null
+ );
+ break;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ ResourceUrl url = ResourceUrl.parse(value);
+ if (url != null) {
+ if (mTextSizeUsage == null) {
+ mTextSizeUsage = new HashMap<String, Location.Handle>();
+ }
+ Location.Handle handle = context.createLocationHandle(attribute);
+ mTextSizeUsage.put(url.name, handle);
+ }
+ }
}
}
}
+ private static boolean isDpUnit(String value) {
+ return (value.endsWith(UNIT_DP) || value.endsWith(UNIT_DIP))
+ && (value.matches("\\d+di?p"));
+ }
+
private static int getSize(String text) {
assert text.matches("\\d+sp") : text; //$NON-NLS-1$
return Integer.parseInt(text.substring(0, text.length() - 2));
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 a6492fe..b5fa2ea 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
@@ -54,6 +54,8 @@
/**
* Checks for missing manifest registrations for activities, services etc
* and also makes sure that they are registered with the correct tag
+ * <p>
+ * TODO: Rewrite as Java visitor!
*/
public class RegistrationDetector extends LayoutDetector implements ClassScanner {
/** Unregistered activities and services */
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceCycleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceCycleDetector.java
new file mode 100644
index 0000000..4cba388
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceCycleDetector.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_COLOR;
+import static com.android.SdkConstants.ATTR_DRAWABLE;
+import static com.android.SdkConstants.ATTR_LAYOUT;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PARENT;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.COLOR_RESOURCE_PREFIX;
+import static com.android.SdkConstants.DRAWABLE_PREFIX;
+import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.TAG_COLOR;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_STYLE;
+import static com.android.SdkConstants.VIEW_INCLUDE;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.detector.api.Category;
+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.LintUtils;
+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;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.base.Joiner;
+import com.google.common.base.Supplier;
+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.Multimaps;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Checks for cycles in resource definitions
+ */
+public class ResourceCycleDetector extends ResourceXmlDetector {
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ ResourceCycleDetector.class,
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** Style parent cycles, resource alias cycles, layout include cycles, etc */
+ public static final Issue CYCLE = Issue.create(
+ "ResourceCycle", //$NON-NLS-1$
+ "Cycle in resource definitions",
+ "Looks for cycles in resource definitions",
+ "There should be no cycles in resource definitions as this can lead to runtime " +
+ "exceptions.",
+ Category.CORRECTNESS,
+ 8,
+ Severity.FATAL,
+ IMPLEMENTATION
+ );
+
+ /** Parent cycles */
+ public static final Issue CRASH = Issue.create(
+ "AaptCrash", //$NON-NLS-1$
+ "Potential AAPT crash",
+ "Looks for source constructs that can cause AAPT to crash",
+ "Defining a style which sets `android:id` to a dynamically generated id can cause " +
+ "many versions of `aapt`, the resource packaging tool, to crash. To work around " +
+ "this, declare the id explicitly with `<item type=\"id\" name=\"...\" />` instead.",
+ Category.CORRECTNESS,
+ 8,
+ Severity.FATAL,
+ IMPLEMENTATION)
+ .addMoreInfo("https://code.google.com/p/android/issues/detail?id=20479"); //$NON-NLS-1$
+
+ /**
+ * For each resource type, a map from a key (style name, layout name, color name, etc) to
+ * a value (parent style, included layout, referenced color, etc). Note that we only initialize
+ * this if we are in "batch mode" (not editor incremental mode) since we allow this detector
+ * to also run incrementally to look for trivial chains (e.g. of length 1).
+ */
+ private Map<ResourceType, Multimap<String, String>> mReferences;
+
+ /**
+ * If in batch analysis and cycles were found, in phase 2 this map should be initialized
+ * with locations for declaration definitions of the keys and values in {@link #mReferences}
+ */
+ private Map<ResourceType, Multimap<String, Location>> mLocations;
+
+ /**
+ * If in batch analysis and cycles were found, for each resource type this is a list
+ * of chains (where each chain is a list of keys as described in {@link #mReferences})
+ */
+ private Map<ResourceType, List<List<String>>> mChains;
+
+ /** Constructs a new {@link ResourceCycleDetector} */
+ public ResourceCycleDetector() {
+ }
+
+ @Override
+ public void beforeCheckProject(@NonNull Context context) {
+ // In incremental mode, or checking all files (full lint analysis) ? If the latter,
+ // we should store state and look for deeper cycles
+ if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
+ mReferences = Maps.newEnumMap(ResourceType.class);
+ }
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.VALUES
+ || folderType == ResourceFolderType.COLOR
+ || folderType == ResourceFolderType.DRAWABLE
+ || folderType == ResourceFolderType.LAYOUT;
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(
+ VIEW_INCLUDE,
+ TAG_STYLE,
+ TAG_COLOR,
+ TAG_ITEM
+ );
+ }
+
+ private void recordReference(@NonNull ResourceType type, @NonNull String from,
+ @NonNull String to) {
+ if (to.isEmpty() || to.startsWith(ANDROID_PREFIX)) {
+ return;
+ }
+ assert mReferences != null;
+ Multimap<String, String> map = mReferences.get(type);
+ if (map == null) {
+ // Multimap which preserves insert order (for predictable output order)
+ map = Multimaps.newListMultimap(
+ new TreeMap<String, Collection<String>>(),
+ new Supplier<List<String>>() {
+ @Override
+ public List<String> get() {
+ return Lists.newArrayListWithExpectedSize(6);
+ }
+ });
+ mReferences.put(type, map);
+ }
+
+ if (to.charAt(0) == '@') {
+ int index = to.indexOf('/');
+ if (index != -1) {
+ to = to.substring(index + 1);
+ }
+ }
+
+ map.put(from, to);
+ }
+
+ private void recordLocation(@NonNull XmlContext context, @NonNull Node node,
+ @NonNull ResourceType type, @NonNull String from) {
+ assert mLocations != null;
+ // Cycles were already found; we're now in phase 2 looking up specific
+ // locations
+ Multimap<String, Location> map = mLocations.get(type);
+ if (map == null) {
+ map = ArrayListMultimap.create(30, 4);
+ mLocations.put(type, map);
+ }
+
+ Location location = context.getLocation(node);
+ map.put(from, location);
+ }
+
+ @SuppressWarnings("VariableNotUsedInsideIf")
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ String tagName = element.getTagName();
+ if (tagName.equals(TAG_ITEM)) {
+ if (mReferences == null) {
+ // Nothing to do in incremental mode
+ return;
+ }
+ ResourceFolderType folderType = context.getResourceFolderType();
+ if (folderType == ResourceFolderType.VALUES) {
+ // Aliases
+ Attr typeNode = element.getAttributeNode(ATTR_TYPE);
+ if (typeNode != null) {
+ String typeName = typeNode.getValue();
+ ResourceType type = ResourceType.getEnum(typeName);
+ Attr nameNode = element.getAttributeNode(ATTR_NAME);
+ if (type != null && nameNode != null) {
+ NodeList childNodes = element.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ String text = child.getNodeValue();
+ for (int k = 0, max = text.length(); k < max; k++) {
+ char c = text.charAt(k);
+ if (Character.isWhitespace(c)) {
+ break;
+ } else if (c == '@' &&
+ text.startsWith(type.getName(), k + 1)) {
+ String to = text.trim();
+ if (mReferences != null) {
+ String name = nameNode.getValue();
+ if (mLocations != null) {
+ recordLocation(context, child, type,
+ name);
+ } else {
+ recordReference(type, name, to);
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (folderType == ResourceFolderType.COLOR ) {
+ String color = element.getAttributeNS(ANDROID_URI, ATTR_COLOR);
+ if (color != null && color.startsWith(COLOR_RESOURCE_PREFIX)) {
+ String currentColor = LintUtils.getBaseName(context.file.getName());
+ if (mLocations != null) {
+ recordLocation(context, element, ResourceType.COLOR,
+ currentColor);
+ } else {
+ recordReference(ResourceType.COLOR, currentColor,
+ color.substring(COLOR_RESOURCE_PREFIX.length()));
+ }
+ }
+ } else if (folderType == ResourceFolderType.DRAWABLE) {
+ String drawable = element.getAttributeNS(ANDROID_URI, ATTR_DRAWABLE);
+ if (drawable != null && drawable.startsWith(DRAWABLE_PREFIX)) {
+ String currentColor = LintUtils.getBaseName(context.file.getName());
+ if (mLocations != null) {
+ recordLocation(context, element, ResourceType.DRAWABLE,
+ currentColor);
+ } else {
+ recordReference(ResourceType.DRAWABLE, currentColor,
+ drawable.substring(DRAWABLE_PREFIX.length()));
+ }
+ }
+ }
+ } else if (tagName.equals(TAG_STYLE)) {
+ Attr nameNode = element.getAttributeNode(ATTR_NAME);
+ // Look for recursive style parent declarations
+ Attr parentNode = element.getAttributeNode(ATTR_PARENT);
+ if (parentNode != null && nameNode != null) {
+ String name = nameNode.getValue();
+ String parent = parentNode.getValue();
+ if (parent.endsWith(name) &&
+ parent.equals(STYLE_RESOURCE_PREFIX + name) && context.isEnabled(CYCLE)
+ && context.getDriver().getPhase() == 1) {
+ context.report(CYCLE, parentNode, context.getLocation(parentNode),
+ String.format("Style %1$s should not extend itself", name), null);
+ } else if (parent.startsWith(STYLE_RESOURCE_PREFIX)
+ && parent.startsWith(name, STYLE_RESOURCE_PREFIX.length())
+ && parent.startsWith(".", STYLE_RESOURCE_PREFIX.length() + name.length())
+ && context.isEnabled(CYCLE) && context.getDriver().getPhase() == 1) {
+ context.report(CYCLE, parentNode, context.getLocation(parentNode),
+ String.format("Potential cycle: %1$s is the implied parent of %2$s and " +
+ "this defines the opposite", name,
+ parent.substring(STYLE_RESOURCE_PREFIX.length())), null);
+ // Don't record this reference; we don't want to double report this
+ // as a chain, since this error is more helpful
+ return;
+ }
+ if (mReferences != null && !parent.isEmpty()) {
+ if (mLocations != null) {
+ recordLocation(context, parentNode, ResourceType.STYLE, name);
+ } else {
+ recordReference(ResourceType.STYLE, name, parent);
+ }
+ }
+ } else if (mReferences != null && nameNode != null) {
+ String name = nameNode.getValue();
+ int index = name.lastIndexOf('.');
+ if (index > 0) {
+ String parent = name.substring(0, index);
+ if (mReferences != null) {
+ if (mLocations != null) {
+ Attr node = element.getAttributeNode(ATTR_NAME);
+ recordLocation(context, node, ResourceType.STYLE, name);
+ } else {
+ recordReference(ResourceType.STYLE, name, parent);
+ }
+ }
+ }
+ }
+
+ if (context.isEnabled(CRASH) && context.getDriver().getPhase() == 1) {
+ for (Element item : LintUtils.getChildren(element)) {
+ if ("android:id".equals(item.getAttribute(ATTR_NAME))) {
+ checkCrashItem(context, item);
+ }
+ }
+ }
+ } else if (tagName.equals(VIEW_INCLUDE)) {
+ Attr layoutNode = element.getAttributeNode(ATTR_LAYOUT);
+ if (layoutNode != null) {
+ String layout = layoutNode.getValue();
+ if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) {
+ String currentLayout = LintUtils.getBaseName(context.file.getName());
+ if (mReferences != null) {
+ if (mLocations != null) {
+ recordLocation(context, layoutNode, ResourceType.LAYOUT,
+ currentLayout);
+ } else {
+ recordReference(ResourceType.LAYOUT, currentLayout, layout);
+ }
+ }
+ if (layout.startsWith(currentLayout, LAYOUT_RESOURCE_PREFIX.length()) &&
+ layout.length() == currentLayout.length()
+ + LAYOUT_RESOURCE_PREFIX.length()
+ && context.isEnabled(CYCLE)
+ && context.getDriver().getPhase() == 1) {
+ String message = String.format("Layout %1$s should not include itself",
+ currentLayout);
+ context.report(CYCLE, layoutNode, context.getLocation(layoutNode),
+ message, null);
+ }
+ }
+ }
+ } else if (tagName.equals(TAG_COLOR)) {
+ NodeList childNodes = element.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ String text = child.getNodeValue();
+ for (int k = 0, max = text.length(); k < max; k++) {
+ char c = text.charAt(k);
+ if (Character.isWhitespace(c)) {
+ break;
+ } else if (text.startsWith(COLOR_RESOURCE_PREFIX, k)) {
+ String color = text.trim().substring(COLOR_RESOURCE_PREFIX.length());
+ String name = element.getAttribute(ATTR_NAME);
+ if (mReferences != null) {
+ if (mLocations != null) {
+ recordLocation(context, child, ResourceType.COLOR, name);
+ } else {
+ recordReference(ResourceType.COLOR, name, color);
+ }
+ }
+ if (color.equals(name)
+ && context.isEnabled(CYCLE)
+ && context.getDriver().getPhase() == 1) {
+ context.report(CYCLE, child, context.getLocation(child),
+ String.format("Color %1$s should not reference itself",
+ color), null);
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void checkCrashItem(@NonNull XmlContext context, @NonNull Element item) {
+ NodeList childNodes = item.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ String text = child.getNodeValue();
+
+ for (int k = 0, max = text.length(); k < max; k++) {
+ char c = text.charAt(k);
+ if (Character.isWhitespace(c)) {
+ return;
+ } else if (text.startsWith(NEW_ID_PREFIX, k)) {
+ String name = text.trim().substring(NEW_ID_PREFIX.length());
+ String message = "This construct can potentially crash aapt during a "
+ + "build. Change @+id/" + name + " to @id/" + name + " and define "
+ + "the id explicitly using "
+ + "<item type=\"id\" name=\"" + name + "\"/> instead.";
+ context.report(CRASH, item, context.getLocation(item),
+ message, null);
+ } else {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mReferences == null) {
+ // Incremental analysis in a single file only; nothing to do
+ return;
+ }
+
+ int phase = context.getDriver().getPhase();
+ if (phase == 1) {
+ // Perform DFS of each resource type and look for cycles
+ for (Map.Entry<ResourceType, Multimap<String, String>> entry
+ : mReferences.entrySet()) {
+ ResourceType type = entry.getKey();
+ Multimap<String, String> map = entry.getValue();
+ findCycles(context, type, map);
+ }
+ } else {
+ assert phase == 2;
+ // Emit cycle report
+ for (Map.Entry<ResourceType, List<List<String>>> entry : mChains.entrySet()) {
+ ResourceType type = entry.getKey();
+ Multimap<String, Location> locations = mLocations.get(type);
+ if (locations == null) {
+ // No locations found. Unlikely.
+ locations = ArrayListMultimap.create();
+ }
+ List<List<String>> chains = entry.getValue();
+ for (List<String> chain : chains) {
+ Location location = null;
+ assert !chain.isEmpty();
+ for (int i = 0, n = chain.size(); i < n; i++) {
+ String item = chain.get(i);
+ Collection<Location> itemLocations = locations.get(item);
+ if (!itemLocations.isEmpty()) {
+ Location itemLocation = itemLocations.iterator().next();
+ String next = chain.get((i + 1) % chain.size());
+ String label = "Reference from @" + type.getName() + "/" + item
+ + " to " + type.getName() + "/" + next + " here";
+ itemLocation.setMessage(label);
+ itemLocation.setSecondary(location);
+ location = itemLocation;
+ }
+ }
+
+ if (location == null) {
+ location = Location.create(context.getProject().getDir());
+ } else {
+ // Break off chain
+ Location curr = location.getSecondary();
+ while (curr != null) {
+ Location next = curr.getSecondary();
+ if (next == location) {
+ curr.setSecondary(null);
+ break;
+ }
+ curr = next;
+ }
+ }
+
+ String message = String.format("%1$s Resource definition cycle: %2$s",
+ type.getDisplayName(), Joiner.on(" => ").join(chain));
+
+ context.report(CYCLE, location, message, null);
+ }
+ }
+ }
+ }
+
+ private void findCycles(
+ @NonNull Context context,
+ @NonNull ResourceType type,
+ @NonNull Multimap<String, String> map) {
+ Set<String> visiting = Sets.newHashSetWithExpectedSize(map.size());
+ Set<String> seen = Sets.newHashSetWithExpectedSize(map.size());
+ for (String from : map.keySet()) {
+ if (seen.contains(from)) {
+ continue;
+ }
+ List<String> chain = dfs(map, from, visiting);
+ if (chain != null && chain.size() > 2) { // size 1 chains are handled directly
+ seen.addAll(chain);
+ Collections.reverse(chain);
+ if (mChains == null) {
+ mChains = Maps.newEnumMap(ResourceType.class);
+ mLocations = Maps.newEnumMap(ResourceType.class);
+ context.getDriver().requestRepeat(this, Scope.RESOURCE_FILE_SCOPE);
+ }
+ List<List<String>> list = mChains.get(type);
+ if (list == null) {
+ list = Lists.newArrayList();
+ mChains.put(type, list);
+ }
+ list.add(chain);
+ }
+ }
+ }
+
+ // ----- Cycle detection -----
+
+ @Nullable
+ private static List<String> dfs(
+ @NonNull Multimap<String, String> map,
+ @NonNull String from,
+ @NonNull Set<String> visiting) {
+ visiting.add(from);
+
+ Collection<String> targets = map.get(from);
+ if (targets != null && !targets.isEmpty()) {
+ for (String target : targets) {
+ if (visiting.contains(target)) {
+ List<String> chain = Lists.newArrayList();
+ chain.add(target);
+ chain.add(from);
+ return chain;
+ }
+ List<String> chain = dfs(map, target, visiting);
+ if (chain != null) {
+ chain.add(from);
+ return chain;
+ }
+ }
+ }
+
+ visiting.remove(from);
+
+ return null;
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java
new file mode 100644
index 0000000..17cee7b
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ATTR_NAME;
+import static com.android.SdkConstants.TAG_DECLARE_STYLEABLE;
+import static com.android.SdkConstants.TAG_RESOURCES;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+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.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceContext;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+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 com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+
+/**
+ * Ensure that resources in Gradle projects which specify a resource prefix
+ * conform to the given name
+ *
+ * TODO: What about id's?
+ */
+public class ResourcePrefixDetector extends ResourceXmlDetector implements
+ Detector.BinaryResourceScanner {
+ /** The main issue discovered by this detector */
+ @SuppressWarnings("unchecked")
+ public static final Issue ISSUE = Issue.create(
+ "ResourceName", //$NON-NLS-1$
+ "Resource with Wrong Prefix",
+ "Ensures that resource names follow the specified name prefix for the project",
+ "In Gradle projects you can specify a resource prefix that all resources " +
+ "in the project must conform to. This makes it easier to ensure that you don't " +
+ "accidentally combine resources from different libraries, since they all end " +
+ "up in the same shared app namespace.",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.FATAL,
+ new Implementation(
+ ResourcePrefixDetector.class,
+ EnumSet.of(Scope.RESOURCE_FILE, Scope.BINARY_RESOURCE_FILE),
+ Scope.RESOURCE_FILE_SCOPE,
+ Scope.BINARY_RESOURCE_FILE_SCOPE));
+
+ /** Constructs a new {@link com.android.tools.lint.checks.ResourcePrefixDetector} */
+ public ResourcePrefixDetector() {
+ }
+
+ private String mPrefix;
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(TAG_RESOURCES, TAG_DECLARE_STYLEABLE);
+ }
+
+ @Nullable
+ private static String computeResourcePrefix(@NonNull Project project) {
+ if (project.isGradleProject()) {
+ return LintUtils.computeResourcePrefix(project.getGradleProjectModel());
+ }
+
+ return null;
+ }
+
+ @Override
+ public void beforeCheckProject(@NonNull Context context) {
+ mPrefix = computeResourcePrefix(context.getProject());
+ }
+
+ @Override
+ public void beforeCheckLibraryProject(@NonNull Context context) {
+ // TODO: Make sure this doesn't wipe out the prefix for the remaining projects
+ mPrefix = computeResourcePrefix(context.getProject());
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ mPrefix = null;
+ }
+
+ @Override
+ public void afterCheckLibraryProject(@NonNull Context context) {
+ mPrefix = null;
+ }
+
+ @Override
+ public void beforeCheckFile(@NonNull Context context) {
+ if (mPrefix != null && context instanceof XmlContext) {
+ XmlContext xmlContext = (XmlContext) context;
+ ResourceFolderType folderType = xmlContext.getResourceFolderType();
+ if (folderType != null && folderType != ResourceFolderType.VALUES) {
+ String name = LintUtils.getBaseName(context.file.getName());
+ if (!name.startsWith(mPrefix)) {
+ // Attempt to report the error on the root tag of the associated
+ // document to make suppressing the error with a tools:suppress
+ // attribute etc possible
+ if (xmlContext.document != null) {
+ Element root = xmlContext.document.getDocumentElement();
+ if (root != null) {
+ xmlContext.report(ISSUE, root, xmlContext.getLocation(root),
+ getErrorMessage(name), null);
+ return;
+ }
+ }
+ context.report(ISSUE, Location.create(context.file),
+ getErrorMessage(name), null);
+ }
+ }
+ }
+ }
+
+ private String getErrorMessage(String name) {
+ assert mPrefix != null && !name.startsWith(mPrefix);
+ return String.format("Resource named '%1$s' does not start "
+ + "with the project's resource prefix '%2$s'; rename to '%3$s' ?",
+ name, mPrefix, LintUtils.computeResourceName(mPrefix, name));
+ }
+
+ // --- Implements XmlScanner ----
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ if (mPrefix == null || context.getResourceFolderType() != ResourceFolderType.VALUES) {
+ return;
+ }
+
+ for (Element item : LintUtils.getChildren(element)) {
+ Attr nameAttribute = item.getAttributeNode(ATTR_NAME);
+ if (nameAttribute != null) {
+ String name = nameAttribute.getValue();
+ if (!name.startsWith(mPrefix)) {
+ String message = getErrorMessage(name);
+ context.report(ISSUE, nameAttribute, context.getLocation(nameAttribute),
+ message, null);
+ }
+ }
+ }
+ }
+
+ // ---- Implements BinaryResourceScanner ---
+
+ @Override
+ public void checkBinaryResource(@NonNull ResourceContext context) {
+ if (mPrefix != null) {
+ ResourceFolderType folderType = context.getResourceFolderType();
+ if (folderType != null && folderType != ResourceFolderType.VALUES) {
+ String name = LintUtils.getBaseName(context.file.getName());
+ if (!name.startsWith(mPrefix)) {
+ Location location = Location.create(context.file);
+ context.report(ISSUE, location, getErrorMessage(name), null);
+ }
+ }
+ }
+ }
+}
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 08acba2..539101b 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
@@ -31,6 +31,7 @@
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_START;
import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_END;
import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT;
import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT;
@@ -43,17 +44,19 @@
import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT;
import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT;
import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_START;
+import static com.android.SdkConstants.ATTR_PADDING;
import static com.android.SdkConstants.ATTR_PADDING_END;
import static com.android.SdkConstants.ATTR_PADDING_LEFT;
import static com.android.SdkConstants.ATTR_PADDING_RIGHT;
import static com.android.SdkConstants.ATTR_PADDING_START;
-import static com.android.SdkConstants.TAG_APPLICATION;
import static com.android.SdkConstants.GRAVITY_VALUE_END;
import static com.android.SdkConstants.GRAVITY_VALUE_LEFT;
import static com.android.SdkConstants.GRAVITY_VALUE_RIGHT;
import static com.android.SdkConstants.GRAVITY_VALUE_START;
+import static com.android.SdkConstants.TAG_APPLICATION;
import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
@@ -98,7 +101,8 @@
RtlDetector.class,
EnumSet.of(Scope.RESOURCE_FILE, Scope.JAVA_FILE, Scope.MANIFEST),
Scope.RESOURCE_FILE_SCOPE,
- Scope.JAVA_FILE_SCOPE
+ Scope.JAVA_FILE_SCOPE,
+ Scope.MANIFEST_SCOPE
);
public static final Issue USE_START = Issue.create(
@@ -137,6 +141,17 @@
Category.RTL, 6, Severity.ERROR, IMPLEMENTATION).setEnabledByDefault(false);
+ public static final Issue SYMMETRY = Issue.create(
+ "RtlSymmetry", //$NON-NLS-1$
+ "Padding and margin symmetry",
+ "Ensures that specifying padding on one side is matched by padding on the other",
+
+ "If you specify padding or margin on the left side of a layout, you should " +
+ "probably also specify padding on the right side (and vice versa) for " +
+ "right-to-left layout symmetry.",
+
+ Category.RTL, 6, Severity.ERROR, IMPLEMENTATION).setEnabledByDefault(false);
+
public static final Issue ENABLED = Issue.create(
"RtlEnabled", //$NON-NLS-1$
@@ -187,6 +202,11 @@
/** API version in which RTL support was added */
private static final int RTL_API = 17;
+ private static final String LEFT = "Left";
+ private static final String START = "Start";
+ private static final String RIGHT = "Right";
+ private static final String END = "End";
+
private Boolean mEnabledRtlSupport;
private boolean mUsesRtlAttributes;
@@ -211,6 +231,7 @@
return false;
}
+ //noinspection RedundantIfStatement
if (mEnabledRtlSupport != null && !mEnabledRtlSupport) {
return false;
}
@@ -234,7 +255,8 @@
// ---- Implements XmlDetector ----
- private static final String[] ATTRIBUTES = new String[] {
+ @VisibleForTesting
+ static final String[] ATTRIBUTES = new String[] {
// Pairs, from left/right constants to corresponding start/end constants
ATTR_LAYOUT_ALIGN_PARENT_LEFT, ATTR_LAYOUT_ALIGN_PARENT_START,
ATTR_LAYOUT_ALIGN_PARENT_RIGHT, ATTR_LAYOUT_ALIGN_PARENT_END,
@@ -264,41 +286,43 @@
}
}
- private static String convertOldToNew(String attribute) {
- int index = attribute.indexOf("eft"); //$NON-NLS-1$
- if (index == -1) {
- index = attribute.indexOf("ight"); //$NON-NLS-1$
- assert index > 0 : attribute;
- if (attribute.charAt(index - 1) == 'R') {
- return attribute.replace("Right", "End");
- } else {
- return attribute.replace("right", "end");
+ public static boolean isRtlAttributeName(@NonNull String attribute) {
+ for (int i = 1; i < ATTRIBUTES.length; i += 2) {
+ if (attribute.equals(ATTRIBUTES[i])) {
+ return true;
}
}
- assert index > 0 : attribute;
- if (attribute.charAt(index - 1) == 'L') {
- return attribute.replace("Left", "Start");
+ return false;
+ }
+
+ @VisibleForTesting
+ static String convertOldToNew(String attribute) {
+ if (attribute.contains(LEFT)) {
+ return attribute.replace(LEFT, START);
} else {
- return attribute.replace("left", "start");
+ return attribute.replace(RIGHT, END);
}
}
- private static String convertNewToOld(String attribute) {
- int index = attribute.indexOf("tart"); //$NON-NLS-1$
- if (index == -1) {
- index = attribute.indexOf("nd"); //$NON-NLS-1$
- assert index > 0 : attribute;
- if (attribute.charAt(index - 1) == 'E') {
- return attribute.replace("End", "Right");
- } else {
- return attribute.replace("end", "right");
- }
- }
- assert index > 0 : attribute;
- if (attribute.charAt(index - 1) == 'S') {
- return attribute.replace("Start", "Left");
+ @VisibleForTesting
+ static String convertNewToOld(String attribute) {
+ if (attribute.contains(START)) {
+ return attribute.replace(START, LEFT);
} else {
- return attribute.replace("start", "left");
+ return attribute.replace(END, RIGHT);
+ }
+ }
+
+ @VisibleForTesting
+ static String convertToOppositeDirection(String attribute) {
+ if (attribute.contains(LEFT)) {
+ return attribute.replace(LEFT, RIGHT);
+ } else if (attribute.contains(RIGHT)) {
+ return attribute.replace(RIGHT, LEFT);
+ } else if (attribute.contains(START)) {
+ return attribute.replace(START, END);
+ } else {
+ return attribute.replace(END, START);
}
}
@@ -335,7 +359,16 @@
Project project = context.getMainProject();
String value = attribute.getValue();
+ if (!ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ // Layout attribute not in the Android namespace (or a custom namespace).
+ // This is likely an application error (which should get caught by
+ // the MissingPrefixDetector)
+ return;
+ }
+
String name = attribute.getLocalName();
+ assert name != null : attribute.getName();
+
if (name.equals(ATTR_SUPPORTS_RTL)) {
mEnabledRtlSupport = Boolean.valueOf(value);
if (!attribute.getOwnerElement().getTagName().equals(TAG_APPLICATION)) {
@@ -377,13 +410,16 @@
"To support older versions than API 17 (project specifies %1$d) "
+ "you must *also* specify gravity or layout_gravity=\"%2$s\"",
project.getMinSdk(), value);
- context.report(COMPAT, attribute, context.getLocation(attribute), message, null);
+ if (context.isEnabled(COMPAT)) {
+ context.report(COMPAT, attribute, context.getLocation(attribute), message,
+ null);
+ }
return;
} else {
return;
}
- if (!value.equals(gravity)) {
+ if (!value.equals(gravity) && context.isEnabled(COMPAT)) {
// TODO: Only compare horizontal alignment attributes?
String message = "Inconsistent alignment specification between "
+ "textAlignment and gravity attributes";
@@ -406,8 +442,10 @@
+ "right-to-left locales",
isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END,
isLeft ? GRAVITY_VALUE_LEFT : GRAVITY_VALUE_RIGHT);
- context.report(USE_START, attribute, context.getLocation(attribute), message,
- null);
+ if (context.isEnabled(USE_START)) {
+ context.report(USE_START, attribute, context.getLocation(attribute), message,
+ null);
+ }
return;
}
@@ -419,8 +457,30 @@
// want to consider adding a specialized image in the -ldrtl folder as well
Element element = attribute.getOwnerElement();
- boolean isOld = name.contains("eft") || name.contains("ight"); //$NON-NLS-1$ //$NON-NLS-2$
+ boolean isPaddingAttribute = isPaddingAttribute(name);
+ if (isPaddingAttribute || isMarginAttribute(name)) {
+ String opposite = convertToOppositeDirection(name);
+ if (element.hasAttributeNS(ANDROID_URI, opposite)) {
+ String oldValue = element.getAttributeNS(ANDROID_URI, opposite);
+ if (value.equals(oldValue)) {
+ return;
+ }
+ } else if (isPaddingAttribute
+ && !element.hasAttributeNS(ANDROID_URI,
+ isOldAttribute(opposite) ? convertOldToNew(opposite)
+ : convertNewToOld(opposite)) && context.isEnabled(SYMMETRY)) {
+ String message = "When you define %1$s you should probably also define %2$s for "
+ + "right-to-left symmetry";
+ context.report(SYMMETRY, attribute, context.getLocation(attribute),
+ message, null);
+ }
+ }
+
+ boolean isOld = isOldAttribute(name);
if (isOld) {
+ if (!context.isEnabled(USE_START)) {
+ return;
+ }
String rtl = convertOldToNew(name);
if (element.hasAttributeNS(ANDROID_URI, rtl)) {
if (project.getMinSdk() >= RTL_API || context.getFolderVersion() >= RTL_API) {
@@ -449,7 +509,7 @@
message, null);
}
} else {
- if (project.getMinSdk() >= RTL_API) {
+ if (project.getMinSdk() >= RTL_API || !context.isEnabled(COMPAT)) {
// Only supporting 17+: no need to define older attributes
return;
}
@@ -470,6 +530,18 @@
}
}
+ private static boolean isOldAttribute(String name) {
+ return name.contains(LEFT) || name.contains(RIGHT);
+ }
+
+ private static boolean isMarginAttribute(@NonNull String name) {
+ return name.startsWith(ATTR_LAYOUT_MARGIN);
+ }
+
+ private static boolean isPaddingAttribute(@NonNull String name) {
+ return name.startsWith(ATTR_PADDING);
+ }
+
// ---- Implements JavaScanner ----
@Override
@@ -516,7 +588,7 @@
}
if (parent instanceof VariableReference) {
// No operand: make sure it's statically imported
- if (!LintUtils.isImported(mContext.compilationUnit,
+ if (!LintUtils.isImported(mContext.getCompilationUnit(),
FQCN_GRAVITY_PREFIX + identifier)) {
return false;
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
index b1ba6c9..0835621 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
@@ -212,7 +212,7 @@
}
}
- private static boolean getExported(Element element) {
+ public static boolean getExported(Element element) {
// Used to check whether an activity, service or broadcast receiver is exported.
String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED);
if (exportValue != null && !exportValue.isEmpty()) {
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 4af96c0..8e31593 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
@@ -55,7 +55,7 @@
Category.CORRECTNESS,
6,
- Severity.ERROR,
+ Severity.FATAL,
new Implementation(
ServiceCastDetector.class,
Scope.JAVA_FILE_SCOPE));
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
index c265142..e855bf3 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
@@ -16,8 +16,13 @@
package com.android.tools.lint.checks;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
@@ -32,18 +37,28 @@
import java.util.Collections;
import java.util.List;
+import lombok.ast.Assert;
import lombok.ast.AstVisitor;
+import lombok.ast.Block;
+import lombok.ast.Case;
+import lombok.ast.ClassDeclaration;
import lombok.ast.ConstructorDeclaration;
+import lombok.ast.DoWhile;
import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.For;
import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.If;
import lombok.ast.MethodDeclaration;
import lombok.ast.MethodInvocation;
import lombok.ast.Node;
import lombok.ast.NormalTypeBody;
import lombok.ast.Return;
+import lombok.ast.Statement;
import lombok.ast.VariableDeclaration;
import lombok.ast.VariableDefinition;
import lombok.ast.VariableReference;
+import lombok.ast.While;
/**
* Detector looking for SharedPreferences.edit() calls without a corresponding
@@ -66,6 +81,11 @@
SharedPrefsDetector.class,
Scope.JAVA_FILE_SCOPE));
+ public static final String ANDROID_CONTENT_SHARED_PREFERENCES =
+ "android.content.SharedPreferences"; //$NON-NLS-1$
+ private static final String ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR =
+ "android.content.SharedPreferences.Editor"; //$NON-NLS-1$
+
/** Constructs a new {@link SharedPrefsDetector} check */
public SharedPrefsDetector() {
}
@@ -104,6 +124,19 @@
public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
@NonNull MethodInvocation node) {
assert node.astName().astValue().equals("edit");
+
+ boolean verifiedType = false;
+ ResolvedNode resolve = context.resolve(node);
+ if (resolve instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolve;
+ TypeDescriptor returnType = method.getReturnType();
+ if (returnType == null ||
+ !returnType.matchesName(ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR)) {
+ return;
+ }
+ verifiedType = true;
+ }
+
Expression operand = node.astOperand();
if (operand == null) {
return;
@@ -114,30 +147,35 @@
// of the API (e.g. assigning it to a previously declared variable) but
// is needed until we have type attribution in the AST itself.
Node parent = node.getParent();
+
VariableDefinition definition = getLhs(parent);
boolean allowCommitBeforeTarget;
if (definition == null) {
if (operand instanceof VariableReference) {
- NormalTypeBody body = findSurroundingTypeBody(parent);
- if (body == null) {
- return;
- }
- String variableName = ((VariableReference) operand).astIdentifier().astValue();
- String type = getFieldType(body, variableName);
- if (type == null || !type.equals("SharedPreferences")) { //$NON-NLS-1$
- return;
+ if (!verifiedType) {
+ NormalTypeBody body = findSurroundingTypeBody(parent);
+ if (body == null) {
+ return;
+ }
+ String variableName = ((VariableReference) operand).astIdentifier().astValue();
+ String type = getFieldType(body, variableName);
+ if (type == null || !type.equals("SharedPreferences")) { //$NON-NLS-1$
+ return;
+ }
}
allowCommitBeforeTarget = true;
} else {
return;
}
} else {
- String type = definition.astTypeReference().toString();
- if (!type.endsWith("SharedPreferences.Editor")) { //$NON-NLS-1$
- if (!type.equals("Editor") || //$NON-NLS-1$
- !LintUtils.isImported(context.compilationUnit,
- "android.content.SharedPreferences.Editor")) { //$NON-NLS-1$
- return;
+ if (!verifiedType) {
+ String type = definition.astTypeReference().toString();
+ if (!type.endsWith("SharedPreferences.Editor")) { //$NON-NLS-1$
+ if (!type.equals("Editor") || //$NON-NLS-1$
+ !LintUtils.isImported(context.getCompilationUnit(),
+ ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR)) {
+ return;
+ }
}
}
allowCommitBeforeTarget = false;
@@ -148,7 +186,7 @@
return;
}
- CommitFinder finder = new CommitFinder(node, allowCommitBeforeTarget);
+ CommitFinder finder = new CommitFinder(context, node, allowCommitBeforeTarget);
method.accept(finder);
if (!finder.isCommitCalled()) {
context.report(ISSUE, method, context.getLocation(node),
@@ -195,12 +233,17 @@
private final MethodInvocation mTarget;
/** whether it allows the commit call to be seen before the target node */
private final boolean mAllowCommitBeforeTarget;
+
+ private final JavaContext mContext;
+
/** Whether we've found one of the commit/cancel methods */
private boolean mFound;
/** Whether we've seen the target edit node yet */
private boolean mSeenTarget;
- private CommitFinder(MethodInvocation target, boolean allowCommitBeforeTarget) {
+ private CommitFinder(JavaContext context, MethodInvocation target,
+ boolean allowCommitBeforeTarget) {
+ mContext = context;
mTarget = target;
mAllowCommitBeforeTarget = allowCommitBeforeTarget;
}
@@ -211,10 +254,58 @@
mSeenTarget = true;
} else if (mAllowCommitBeforeTarget || mSeenTarget || node.astOperand() == mTarget) {
String name = node.astName().astValue();
- if ("commit".equals(name) || "apply".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$
+ boolean isCommit = "commit".equals(name);
+ if (isCommit || "apply".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$
// TODO: Do more flow analysis to see whether we're really calling commit/apply
// on the right type of object?
mFound = true;
+
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof JavaParser.ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ JavaParser.ResolvedClass clz = method.getContainingClass();
+ if (clz.isSubclassOf("android.content.SharedPreferences.Editor", false)
+ && mContext.getProject().getMinSdkVersion().getApiLevel() >= 9) {
+ // See if the return value is read: can only replace commit with
+ // apply if the return value is not considered
+ Node parent = node.getParent();
+ boolean returnValueIgnored = false;
+ if (parent instanceof MethodDeclaration ||
+ parent instanceof ConstructorDeclaration ||
+ parent instanceof ClassDeclaration ||
+ parent instanceof Block ||
+ parent instanceof ExpressionStatement) {
+ returnValueIgnored = true;
+ } else if (parent instanceof Statement) {
+ if (parent instanceof If) {
+ returnValueIgnored = ((If) parent).astCondition() != node;
+ } else if (parent instanceof Return) {
+ returnValueIgnored = false;
+ } else if (parent instanceof VariableDeclaration) {
+ returnValueIgnored = false;
+ } else if (parent instanceof For) {
+ returnValueIgnored = ((For) parent).astCondition() != node;
+ } else if (parent instanceof While) {
+ returnValueIgnored = ((While) parent).astCondition() != node;
+ } else if (parent instanceof DoWhile) {
+ returnValueIgnored = ((DoWhile) parent).astCondition() != node;
+ } else if (parent instanceof Case) {
+ returnValueIgnored = ((Case) parent).astCondition() != node;
+ } else if (parent instanceof Assert) {
+ returnValueIgnored = ((Assert) parent).astAssertion() != node;
+ } else {
+ returnValueIgnored = true;
+ }
+ }
+ if (returnValueIgnored) {
+ String message = "Consider using apply() instead; commit writes "
+ + "its data to persistent storage immediately, whereas "
+ + "apply will handle it in the background";
+ mContext.report(ISSUE, node, mContext.getLocation(node), message,
+ null);
+ }
+ }
+ }
}
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SignatureOrSystemDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SignatureOrSystemDetector.java
new file mode 100644
index 0000000..f68aa4a
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SignatureOrSystemDetector.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ANDROID_MANIFEST_XML;
+
+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.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Checks if signatureOrSystem level permissions are set.
+ */
+public class SignatureOrSystemDetector extends Detector implements Detector.XmlScanner {
+ public static final Issue ISSUE = Issue.create(
+ "SignatureOrSystemPermissions", //$NON-NLS-1$
+ "signatureOrSystem permissions declared",
+ "Checks for signatureOrSystem level permissions",
+ "The `signature` protection level should probably be sufficient for most needs and "
+ + "works regardless of where applications are installed. The "
+ + "`signatureOrSystem` level is used for certain situations where "
+ + "multiple vendors have applications built into a system image and "
+ + "need to share specific features explicitly because they are being built "
+ + "together.",
+ Category.SECURITY,
+ 5,
+ Severity.WARNING,
+ new Implementation(
+ SignatureOrSystemDetector.class,
+ Scope.MANIFEST_SCOPE
+ ));
+ private static final String SIGNATURE_OR_SYSTEM = "signatureOrSystem"; //$NON-NLS-1$
+ private static final String PROTECTION_LEVEL = "protectionLevel"; //$NON-NLS-1$
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return file.getName().equals(ANDROID_MANIFEST_XML);
+ }
+
+ // ---- Implements Detector.XmlScanner ---- TAG_PERMISSION
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return Collections.singletonList(PROTECTION_LEVEL);
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ String protectionLevel = attribute.getValue();
+ if (protectionLevel != null
+ && protectionLevel.equals(SIGNATURE_OR_SYSTEM)) {
+ String message = "protectionLevel should probably not be set to signatureOrSystem";
+ context.report(ISSUE, attribute, context.getLocation(attribute), message, null);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
index b272b6c..e5e245b 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
@@ -23,12 +23,30 @@
import static com.android.SdkConstants.R_CLASS;
import static com.android.SdkConstants.R_PREFIX;
import static com.android.SdkConstants.TAG_STRING;
+import static com.android.tools.lint.checks.SharedPrefsDetector.ANDROID_CONTENT_SHARED_PREFERENCES;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BOOLEAN;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BYTE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_CHAR;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_DOUBLE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_FLOAT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_LONG;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_NULL;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_OBJECT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_SHORT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
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.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.IJavaParser;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
@@ -44,6 +62,9 @@
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.XmlContext;
import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -65,6 +86,7 @@
import java.util.regex.Pattern;
import lombok.ast.AstVisitor;
+import lombok.ast.BooleanLiteral;
import lombok.ast.CharLiteral;
import lombok.ast.ConstructorDeclaration;
import lombok.ast.ConstructorInvocation;
@@ -78,7 +100,6 @@
import lombok.ast.Select;
import lombok.ast.StrictListAccessor;
import lombok.ast.StringLiteral;
-import lombok.ast.TypeReference;
import lombok.ast.VariableDefinitionEntry;
import lombok.ast.VariableReference;
@@ -86,17 +107,19 @@
* Check which looks for problems with formatting strings such as inconsistencies between
* translations or between string declaration and string usage in Java.
* <p>
+ * TODO: Verify booleans!
* TODO: Handle Resources.getQuantityString as well
*/
public class StringFormatDetector extends ResourceXmlDetector implements Detector.JavaScanner {
-
private static final Implementation IMPLEMENTATION_XML = new Implementation(
StringFormatDetector.class,
Scope.ALL_RESOURCES_SCOPE);
+ @SuppressWarnings("unchecked")
private static final Implementation IMPLEMENTATION_XML_AND_JAVA = new Implementation(
StringFormatDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.JAVA_FILE));
+ EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.JAVA_FILE),
+ Scope.JAVA_FILE_SCOPE);
/** Whether formatting strings are invalid */
@@ -130,14 +153,17 @@
/** Whether formatting argument types are consistent across translations */
public static final Issue ARG_COUNT = Issue.create(
"StringFormatCount", //$NON-NLS-1$
- "Formatting argument types inconsistent across translations",
- "Ensures that all format strings are used and that the same number is defined "
- + "across translations",
+ "Formatting argument types incomplete or inconsistent",
+ "Ensures that all format strings are used and that the same number is defined " +
+ "across translations",
"When a formatted string takes arguments, it usually needs to reference the " +
- "same arguments in all translations. There are cases where this is not the case, " +
- "so this issue is a warning rather than an error by default. However, this usually " +
- "happens when a language is not translated or updated correctly.",
+ "same arguments in all translations (or all arguments if there are no " +
+ "translations.\n" +
+ "\n" +
+ "There are cases where this is not the case, so this issue is a warning rather " +
+ "than an error by default. However, this usually happens when a language is not " +
+ "translated or updated correctly.",
Category.MESSAGES,
5,
Severity.WARNING,
@@ -270,7 +296,7 @@
String formatted = element.getAttribute("formatted"); //$NON-NLS-1$
if (!formatted.isEmpty() && !Boolean.parseBoolean(formatted)) {
if (!mNotFormatStrings.containsKey(name)) {
- Handle handle = context.parser.createLocationHandle(context, element);
+ Handle handle = context.createLocationHandle(element);
handle.setClientData(element);
mNotFormatStrings.put(name, handle);
}
@@ -284,7 +310,7 @@
Matcher matcher = FORMAT.matcher(text);
if (!matcher.find(j)) {
if (!mNotFormatStrings.containsKey(name)) {
- Handle handle = context.parser.createLocationHandle(context, element);
+ Handle handle = context.createLocationHandle(element);
handle.setClientData(element);
mNotFormatStrings.put(name, handle);
}
@@ -326,7 +352,7 @@
list = new ArrayList<Pair<Handle, String>>();
mFormatStrings.put(name, list);
}
- Handle handle = context.parser.createLocationHandle(context, element);
+ Handle handle = context.createLocationHandle(element);
handle.setClientData(element);
list.add(Pair.of(handle, text));
}
@@ -390,7 +416,7 @@
index = matcher.end(); // Ensure loop proceeds
String str = formatString.substring(matchStart, matcher.end());
- if (str.equals("%%")) { //$NON-NLS-1$
+ if (str.equals("%%") || str.equals("%n")) { //$NON-NLS-1$ //$NON-NLS-2$
// Just an escaped %
continue;
}
@@ -410,7 +436,7 @@
if (last != 'd' && last != 'o' && last != 'x' && last != 'X') {
Object clientData = handle.getClientData();
if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(INVALID,
+ if (context.getDriver().isSuppressed(null, INVALID,
(Node) clientData)) {
return;
}
@@ -453,7 +479,8 @@
Object clientData = handle.getClientData();
if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ARG_TYPES, (Node) clientData)) {
+ if (context.getDriver().isSuppressed(null, ARG_TYPES,
+ (Node) clientData)) {
return;
}
}
@@ -582,17 +609,16 @@
private static Location refineLocation(Context context, Location location,
String formatString, int substringStart, int substringEnd) {
Position startLocation = location.getStart();
- Position endLocation = location.getStart();
+ Position endLocation = location.getEnd();
if (startLocation != null && endLocation != null) {
int startOffset = startLocation.getOffset();
int endOffset = endLocation.getOffset();
if (startOffset >= 0) {
String contents = context.getClient().readFile(location.getFile());
- if (contents != null
- && endOffset <= contents.length() && startOffset < endOffset) {
+ if (endOffset <= contents.length() && startOffset < endOffset) {
int formatOffset = contents.indexOf(formatString, startOffset);
if (formatOffset != -1 && formatOffset <= endOffset) {
- return Location.create(context.file, contents,
+ return Location.create(location.getFile(), contents,
formatOffset + substringStart, formatOffset + substringEnd);
}
}
@@ -616,7 +642,7 @@
if (prevCount != -1 && prevCount != count) {
Object clientData = handle.getClientData();
if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ARG_COUNT, (Node) clientData)) {
+ if (context.getDriver().isSuppressed(null, ARG_COUNT, (Node) clientData)) {
return;
}
}
@@ -635,7 +661,8 @@
if (!indices.contains(i)) {
Object clientData = handle.getClientData();
if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ARG_COUNT, (Node) clientData)) {
+ if (context.getDriver().isSuppressed(null, ARG_COUNT,
+ (Node) clientData)) {
return;
}
}
@@ -686,6 +713,7 @@
/** Given a format string returns the format type of the given argument */
@VisibleForTesting
+ @Nullable
static String getFormatArgumentType(String s, int argument) {
Matcher matcher = FORMAT.matcher(s);
int index = 0;
@@ -693,6 +721,11 @@
int nextNumber = 1;
while (true) {
if (matcher.find(index)) {
+ String value = matcher.group(6);
+ if ("%".equals(value) || "n".equals(value)) { //$NON-NLS-1$ //$NON-NLS-2$
+ index = matcher.end();
+ continue;
+ }
int matchStart = matcher.start();
// Make sure this is not an escaped '%'
for (; prevIndex < matchStart; prevIndex++) {
@@ -738,7 +771,7 @@
* observed arguments into it.
*/
@VisibleForTesting
- static int getFormatArgumentCount(String s, Set<Integer> seenArguments) {
+ static int getFormatArgumentCount(@NonNull String s, @Nullable Set<Integer> seenArguments) {
Matcher matcher = FORMAT.matcher(s);
int index = 0;
int prevIndex = 0;
@@ -746,7 +779,8 @@
int max = 0;
while (true) {
if (matcher.find(index)) {
- if ("%".equals(matcher.group(6))) { //$NON-NLS-1$
+ String value = matcher.group(6);
+ if ("%".equals(value) || "n".equals(value)) { //$NON-NLS-1$ //$NON-NLS-2$
index = matcher.end();
continue;
}
@@ -807,8 +841,7 @@
return false;
}
- String s = format;
- Matcher matcher = FORMAT.matcher(s);
+ Matcher matcher = FORMAT.matcher(format);
int index = 0;
int prevIndex = 0;
while (true) {
@@ -816,7 +849,7 @@
int matchStart = matcher.start();
// Make sure this is not an escaped '%'
for (; prevIndex < matchStart; prevIndex++) {
- char c = s.charAt(prevIndex);
+ char c = format.charAt(prevIndex);
if (c == '\\') {
prevIndex++;
}
@@ -861,7 +894,7 @@
@Override
public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
@NonNull MethodInvocation node) {
- if (mFormatStrings == null) {
+ if (mFormatStrings == null && !context.getClient().supportsProjectResources()) {
return;
}
@@ -927,7 +960,7 @@
Handle handle = mNotFormatStrings.get(name);
Object clientData = handle.getClientData();
if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(INVALID, (Node) clientData)) {
+ if (context.getDriver().isSuppressed(null, INVALID, (Node) clientData)) {
return;
}
}
@@ -945,15 +978,9 @@
Expression second = argIterator.hasNext() ? argIterator.next() : null;
boolean specifiesLocale;
- TypeReference parameterType;
- lombok.ast.Node resolved = context.parser.resolve(context, first);
- if (resolved != null) {
- parameterType = context.parser.getType(context, resolved);
- } else {
- parameterType = context.parser.getType(context, first);
- }
+ TypeDescriptor parameterType = context.getType(first);
if (parameterType != null) {
- specifiesLocale = parameterType.getTypeName().equals("java.util.Locale"); //$NON-NLS-1$
+ specifiesLocale = isLocaleReference(parameterType.getName());
} else if (!call.astName().astValue().equals(FORMAT_METHOD)) {
specifiesLocale = false;
} else {
@@ -969,14 +996,83 @@
&& !(first instanceof StringLiteral));
}
- List<Pair<Handle, String>> list = mFormatStrings.get(name);
+ List<Pair<Handle, String>> list = mFormatStrings != null ? mFormatStrings.get(name) : null;
+ if (list == null) {
+ LintClient client = context.getClient();
+ if (client.supportsProjectResources() &&
+ !context.getScope().contains(Scope.RESOURCE_FILE)) {
+ AbstractResourceRepository resources = client
+ .getProjectResources(context.getMainProject(), true);
+ List<ResourceItem> items = resources
+ .getResourceItem(ResourceType.STRING, name);
+ if (items != null) {
+ for (final ResourceItem item : items) {
+ ResourceValue v = item.getResourceValue(false);
+ if (v != null) {
+ String value = v.getRawXmlValue();
+ if (value != null) {
+ // Make sure it's really a formatting string,
+ // not for example "Battery remaining: 90%"
+ boolean isFormattingString = value.indexOf('%') != -1;
+ for (int j = 0, m = value.length();
+ j < m && isFormattingString;
+ j++) {
+ char c = value.charAt(j);
+ if (c == '\\') {
+ j++;
+ } else if (c == '%') {
+ Matcher matcher = FORMAT.matcher(value);
+ if (!matcher.find(j)) {
+ isFormattingString = false;
+ }
+ String conversion = matcher.group(6);
+ int conversionClass = getConversionClass(
+ conversion.charAt(0));
+ if (conversionClass == CONVERSION_CLASS_UNKNOWN
+ || matcher.group(5) != null) {
+ // Some date format etc - don't process
+ return;
+ }
+ j++; // Don't process second % in a %%
+ }
+ // If the user marked the string with
+ }
+
+ if (isFormattingString) {
+ if (list == null) {
+ list = Lists.newArrayList();
+ if (mFormatStrings == null) {
+ mFormatStrings = Maps.newHashMap();
+ }
+ mFormatStrings.put(name, list);
+ }
+ Handle handle = client.createResourceItemHandle(item);
+ list.add(Pair.of(handle, value));
+ }
+ }
+ }
+ }
+ }
+ } else {
+ return;
+ }
+ }
+
if (list != null) {
+ Set<String> reported = null;
for (Pair<Handle, String> pair : list) {
String s = pair.getSecond();
+ if (reported != null && reported.contains(s)) {
+ continue;
+ }
int count = getFormatArgumentCount(s, null);
Handle handle = pair.getFirst();
if (count != args.size() - 1 - (specifiesLocale ? 1 : 0)) {
- Location location = context.parser.getLocation(context, call);
+ if (isSharedPreferenceGetString(context, call)) {
+ continue;
+ }
+
+ Location location = context.getLocation(call);
Location secondary = handle.resolve();
secondary.setMessage(String.format("This definition requires %1$d arguments",
count));
@@ -986,6 +1082,10 @@
"call supplies %3$d",
name, count, args.size() - 1 - (specifiesLocale ? 1 : 0));
context.report(ARG_TYPES, method, location, message, null);
+ if (reported == null) {
+ reported = Sets.newHashSet();
+ }
+ reported.add(s);
} else {
for (int i = 1; i <= count; i++) {
int argumentIndex = i + (specifiesLocale ? 1 : 0);
@@ -993,6 +1093,9 @@
if (type != null) {
boolean valid = true;
String formatType = getFormatArgumentType(s, i);
+ if (formatType == null) {
+ continue;
+ }
char last = formatType.charAt(formatType.length() - 1);
if (formatType.length() >= 2 &&
Character.toLowerCase(
@@ -1023,7 +1126,11 @@
case 'a':
case 'A':
valid = type == Integer.TYPE
- || type == Float.TYPE;
+ || type == Float.TYPE
+ || type == Double.TYPE
+ || type == Long.TYPE
+ || type == Byte.TYPE
+ || type == Short.TYPE;
break;
case 'c':
case 'C':
@@ -1039,23 +1146,32 @@
// specific formatting. Use special issue
// explanation for this?
valid = type != Boolean.TYPE &&
- !type.isAssignableFrom(Number.class);
+ !Number.class.isAssignableFrom(type);
break;
}
if (!valid) {
- IJavaParser parser = context.parser;
+ if (isSharedPreferenceGetString(context, call)) {
+ continue;
+ }
+
Expression argument = tracker.getArgument(argumentIndex);
- Location location = parser.getLocation(context, argument);
+ Location location = context.getLocation(argument);
Location secondary = handle.resolve();
secondary.setMessage("Conflicting argument declaration here");
location.setSecondary(secondary);
String message = String.format(
"Wrong argument type for formatting argument '#%1$d' " +
- "in %2$s: conversion is '%3$s', received %4$s",
- i, name, formatType, type.getSimpleName());
+ "in %2$s: conversion is '%3$s', received %4$s " +
+ "(argument #%5$d in method call)",
+ i, name, formatType, type.getSimpleName(),
+ argumentIndex + 1);
context.report(ARG_TYPES, method, location, message, null);
+ if (reported == null) {
+ reported = Sets.newHashSet();
+ }
+ reported.add(s);
}
}
}
@@ -1064,9 +1180,34 @@
}
}
+ private static boolean isSharedPreferenceGetString(@NonNull JavaContext context,
+ @NonNull MethodInvocation call) {
+ if (!GET_STRING_METHOD.equals(call.astName().astValue())) {
+ return false;
+ }
+
+ JavaParser.ResolvedNode resolved = context.resolve(call);
+ if (resolved instanceof JavaParser.ResolvedMethod) {
+ JavaParser.ResolvedMethod resolvedMethod = (JavaParser.ResolvedMethod) resolved;
+ JavaParser.ResolvedClass containingClass = resolvedMethod.getContainingClass();
+ return containingClass.isSubclassOf(ANDROID_CONTENT_SHARED_PREFERENCES, false);
+ }
+
+ return false; // not certain
+ }
+
+ private static boolean isLocaleReference(@Nullable TypeDescriptor reference) {
+ return reference != null && isLocaleReference(reference.getName());
+ }
+
+ private static boolean isLocaleReference(@Nullable String typeName) {
+ return typeName != null && (typeName.equals("Locale") //$NON-NLS-1$
+ || typeName.equals("java.util.Locale")); //$NON-NLS-1$
+ }
+
/** Returns the parent method of the given AST node */
@Nullable
- static lombok.ast.Node getParentMethod(@NonNull lombok.ast.Node node) {
+ public static lombok.ast.Node getParentMethod(@NonNull lombok.ast.Node node) {
lombok.ast.Node current = node.getParent();
while (current != null
&& !(current instanceof MethodDeclaration)
@@ -1078,23 +1219,28 @@
}
/** Returns the resource name corresponding to the first argument in the given call */
- static String getResourceForFirstArg(lombok.ast.Node method, lombok.ast.Node call) {
+ @Nullable
+ public static String getResourceForFirstArg(
+ @NonNull lombok.ast.Node method,
+ @NonNull lombok.ast.Node call) {
assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
StringTracker tracker = new StringTracker(null, method, call, 0);
method.accept(tracker);
- String name = tracker.getFormatStringName();
- return name;
+ return tracker.getFormatStringName();
}
/** Returns the resource name corresponding to the given argument in the given call */
- static String getResourceArg(lombok.ast.Node method, lombok.ast.Node call, int argIndex) {
+ @Nullable
+ public static String getResourceArg(
+ @NonNull lombok.ast.Node method,
+ @NonNull lombok.ast.Node call,
+ int argIndex) {
assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
StringTracker tracker = new StringTracker(null, method, call, argIndex);
method.accept(tracker);
- String name = tracker.getFormatStringName();
- return name;
+ return tracker.getFormatStringName();
}
/**
@@ -1170,34 +1316,68 @@
// If the AST supports type resolution, use that for other types
// of expressions
if (mContext != null) {
- TypeReference parameterType;
- lombok.ast.Node resolved = mContext.parser.resolve(mContext, arg);
- if (resolved != null) {
- parameterType = mContext.parser.getType(mContext, resolved);
- } else {
- parameterType = mContext.parser.getType(mContext, arg);
- }
- if (parameterType != null) {
- String fqcn = parameterType.getTypeName();
- if (fqcn.equals("java.lang.String") //$NON-NLS-1$
- || fqcn.equals("String")) { //$NON-NLS-1$
- return String.class;
- } else if (fqcn.equals("int")) { //$NON-NLS-1$
- return Integer.TYPE;
- } else if (fqcn.equals("null")) { //$NON-NLS-1$
- return Object.class;
- } else if (fqcn.equals("float")) { //$NON-NLS-1$
- return Float.TYPE;
- } else if (fqcn.equals("char")) { //$NON-NLS-1$
- return Character.TYPE;
- }
- }
+ return getTypeClass(mContext.getType(arg));
}
}
return null;
}
+ private static Class<?> getTypeClass(@Nullable TypeDescriptor type) {
+ if (type != null) {
+ return getTypeClass(type.getName());
+ }
+ return null;
+ }
+
+ private static Class<?> getTypeClass(@Nullable String fqcn) {
+ if (fqcn == null) {
+ return null;
+ } else if (fqcn.equals(TYPE_STRING) || fqcn.equals("String")) { //$NON-NLS-1$
+ return String.class;
+ } else if (fqcn.equals(TYPE_INT)) {
+ return Integer.TYPE;
+ } else if (fqcn.equals(TYPE_BOOLEAN)) {
+ return Boolean.TYPE;
+ } else if (fqcn.equals(TYPE_NULL)) {
+ return Object.class;
+ } else if (fqcn.equals(TYPE_LONG)) {
+ return Long.TYPE;
+ } else if (fqcn.equals(TYPE_FLOAT)) {
+ return Float.TYPE;
+ } else if (fqcn.equals(TYPE_DOUBLE)) {
+ return Double.TYPE;
+ } else if (fqcn.equals(TYPE_CHAR)) {
+ return Character.TYPE;
+ } else if (fqcn.equals("BigDecimal") //$NON-NLS-1$
+ || fqcn.equals("java.math.BigDecimal")) { //$NON-NLS-1$
+ return Float.TYPE;
+ } else if (fqcn.equals("BigInteger") //$NON-NLS-1$
+ || fqcn.equals("java.math.BigInteger")) { //$NON-NLS-1$
+ return Integer.TYPE;
+ } else if (fqcn.equals(TYPE_OBJECT)) {
+ return null;
+ } else if (fqcn.startsWith("java.lang.")) {
+ if (fqcn.equals("java.lang.Integer")
+ || fqcn.equals("java.lang.Short")
+ || fqcn.equals("java.lang.Byte")
+ || fqcn.equals("java.lang.Long")) {
+ return Integer.TYPE;
+ } else if (fqcn.equals("java.lang.Float")
+ || fqcn.equals("java.lang.Double")) {
+ return Float.TYPE;
+ } else {
+ return null;
+ }
+ } else if (fqcn.equals(TYPE_BYTE)) {
+ return Byte.TYPE;
+ } else if (fqcn.equals(TYPE_SHORT)) {
+ return Short.TYPE;
+ } else {
+ return null;
+ }
+ }
+
public Expression getArgument(int argument) {
if (!(mTargetNode instanceof MethodInvocation)) {
return null;
@@ -1222,11 +1402,8 @@
@Override
public boolean visitNode(lombok.ast.Node node) {
- if (mDone) {
- return true;
- }
+ return mDone || super.visitNode(node);
- return super.visitNode(node);
}
@Override
@@ -1273,7 +1450,17 @@
i++;
}
if (iterator.hasNext()) {
- return iterator.next();
+ Expression next = iterator.next();
+ if (next != null && mContext != null && iterator.hasNext()) {
+ TypeDescriptor type = mContext.getType(next);
+ if (isLocaleReference(type)) {
+ next = iterator.next();
+ } else if (type == null
+ && next.toString().startsWith("Locale.")) { //$NON-NLS-1$
+ next = iterator.next();
+ }
+ }
+ return next;
}
return null;
@@ -1339,10 +1526,14 @@
if (expression == null) {
return null;
}
+
if (expression instanceof VariableReference) {
VariableReference reference = (VariableReference) expression;
String variable = reference.astIdentifier().astValue();
- return mTypes.get(variable);
+ Class<?> type = mTypes.get(variable);
+ if (type != null) {
+ return type;
+ }
} else if (expression instanceof MethodInvocation) {
MethodInvocation method = (MethodInvocation) expression;
String methodName = method.astName().astValue();
@@ -1357,10 +1548,24 @@
return Float.TYPE;
} else if (expression instanceof CharLiteral) {
return Character.TYPE;
+ } else if (expression instanceof BooleanLiteral) {
+ return Boolean.TYPE;
} else if (expression instanceof NullLiteral) {
return Object.class;
}
+ if (mContext != null) {
+ TypeDescriptor type = mContext.getType(expression);
+ if (type != null) {
+ Class<?> typeClass = getTypeClass(type);
+ if (typeClass != null) {
+ return typeClass;
+ } else {
+ return Object.class;
+ }
+ }
+ }
+
return null;
}
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StyleCycleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StyleCycleDetector.java
deleted file mode 100644
index 4c316bd..0000000
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StyleCycleDetector.java
+++ /dev/null
@@ -1,101 +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.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PARENT;
-import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.TAG_STYLE;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Checks for cycles in style definitions
- */
-public class StyleCycleDetector extends ResourceXmlDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "StyleCycle", //$NON-NLS-1$
- "Cycle in style definitions",
- "Looks for cycles in style definitions",
- "There should be no cycles in style definitions as this can lead to runtime " +
- "exceptions.",
- Category.CORRECTNESS,
- 8,
- Severity.FATAL,
- new Implementation(
- StyleCycleDetector.class,
- Scope.RESOURCE_FILE_SCOPE))
- .addMoreInfo(
- "http://developer.android.com/guide/topics/ui/themes.html#Inheritance"); //$NON-NLS-1$
-
- /** Constructs a new {@link StyleCycleDetector} */
- public StyleCycleDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singleton(TAG_STYLE);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- Attr parentNode = element.getAttributeNode(ATTR_PARENT);
- if (parentNode != null) {
- String parent = parentNode.getValue();
- String name = element.getAttribute(ATTR_NAME);
- if (parent.endsWith(name) &&
- parent.equals(STYLE_RESOURCE_PREFIX + name)) {
- context.report(ISSUE, parentNode, context.getLocation(parentNode),
- String.format("Style %1$s should not extend itself", name), null);
- } else if (parent.startsWith(STYLE_RESOURCE_PREFIX)
- && parent.startsWith(name, STYLE_RESOURCE_PREFIX.length())
- && parent.startsWith(".", STYLE_RESOURCE_PREFIX.length() + name.length())) {
- context.report(ISSUE, parentNode, context.getLocation(parentNode),
- String.format("Potential cycle: %1$s is the implied parent of %2$s and " +
- "this defines the opposite", name,
- parent.substring(STYLE_RESOURCE_PREFIX.length())), null);
- }
- }
- }
-}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java
index 55cb407..e2682d3 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java
@@ -30,11 +30,15 @@
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LayoutDetector;
+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.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
@@ -42,9 +46,11 @@
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
/**
* Checks for usability problems in text fields: omitting inputType, or omitting a hint.
@@ -94,19 +100,45 @@
@Override
public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String style = element.getAttribute(ATTR_STYLE);
- if (style != null && !style.isEmpty()) {
- // The input type might be specified via a style. This will require
- // us to track these (similar to what is done for the
- // RequiredAttributeDetector to track layout_width and layout_height
- // in style declarations). For now, simply ignore these elements
- // to avoid producing false positives.
- return;
+ Node inputTypeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_INPUT_TYPE);
+ String inputType = "";
+ if (inputTypeNode != null) {
+ inputType = inputTypeNode.getNodeValue();
}
- Attr inputTypeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_INPUT_TYPE);
- if (inputTypeNode == null &&
- !element.hasAttributeNS(ANDROID_URI, ATTR_HINT)) {
+ boolean haveHint = false;
+ if (inputTypeNode == null) {
+ haveHint = element.hasAttributeNS(ANDROID_URI, ATTR_HINT);
+ String style = element.getAttribute(ATTR_STYLE);
+ if (style != null && !style.isEmpty()) {
+ LintClient client = context.getClient();
+ if (client.supportsProjectResources()) {
+ Project project = context.getMainProject();
+ List<ResourceValue> styles = LintUtils.getStyleAttributes(project, client,
+ style, ANDROID_URI, ATTR_INPUT_TYPE);
+ if (styles != null && !styles.isEmpty()) {
+ ResourceValue value = styles.get(0);
+ inputType = value.getValue();
+ inputTypeNode = element;
+ } else if (!haveHint) {
+ styles = LintUtils.getStyleAttributes(project, client, style,
+ ANDROID_URI, ATTR_HINT);
+ if (styles != null && !styles.isEmpty()) {
+ haveHint = true;
+ }
+ }
+ } else {
+ // The input type might be specified via a style. This will require
+ // us to track these (similar to what is done for the
+ // RequiredAttributeDetector to track layout_width and layout_height
+ // in style declarations). For now, simply ignore these elements
+ // to avoid producing false positives.
+ return;
+ }
+ }
+ }
+
+ if (inputTypeNode == null && !haveHint) {
// Also make sure the EditText does not set an inputMethod in which case
// an inputType might be provided from the input.
if (element.hasAttributeNS(ANDROID_URI, ATTR_INPUT_METHOD)) {
@@ -130,11 +162,6 @@
return;
}
- String inputType = "";
- if (inputTypeNode != null) {
- inputType = inputTypeNode.getValue();
- }
-
// TODO: See if the name is just the default names (button1, editText1 etc)
// and if so, do nothing
// TODO: Unit test this
@@ -208,7 +235,7 @@
}
}
- private static void reportMismatch(XmlContext context, Attr idNode, Attr inputTypeNode,
+ private static void reportMismatch(XmlContext context, Attr idNode, Node inputTypeNode,
String message) {
Location location;
if (inputTypeNode != null) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java
index 6bbbda6..d902e06 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java
@@ -65,7 +65,7 @@
Category.USABILITY,
5,
- Severity.WARNING,
+ Severity.ERROR,
new Implementation(
TitleDetector.class,
Scope.RESOURCE_FILE_SCOPE))
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
index d3afbfb..479aa51 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
@@ -28,6 +28,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.resources.LocaleManager;
import com.android.resources.ResourceFolderType;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
@@ -55,6 +56,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
@@ -179,7 +181,7 @@
public void afterCheckFile(@NonNull Context context) {
if (context.getPhase() == 1) {
// Store this layout's set of ids for full project analysis in afterCheckProject
- if (context.getProject().getReportIssues() && mNames != null) {
+ if (context.getProject().getReportIssues() && mNames != null && !mNames.isEmpty()) {
mFileToNames.put(context.file, mNames);
Element root = ((XmlContext) context).document.getDocumentElement();
@@ -315,25 +317,25 @@
Set<String> defaultStrings = languageToStrings.get(defaultLanguage);
if (defaultStrings == null) {
defaultStrings = new HashSet<String>();
+ }
- // See if it looks like the user has named a specific locale as the base language
- // (this impacts whether we report strings as "extra" or "missing")
- if (mFileToLocale != null) {
- Set<String> specifiedLocales = Sets.newHashSet();
- for (Map.Entry<File, String> entry : mFileToLocale.entrySet()) {
- String locale = entry.getValue();
- int index = locale.indexOf('-');
- if (index != -1) {
- locale = locale.substring(0, index);
- }
- specifiedLocales.add(locale);
+ // See if it looks like the user has named a specific locale as the base language
+ // (this impacts whether we report strings as "extra" or "missing")
+ if (mFileToLocale != null) {
+ Set<String> specifiedLocales = Sets.newHashSet();
+ for (Map.Entry<File, String> entry : mFileToLocale.entrySet()) {
+ String locale = entry.getValue();
+ int index = locale.indexOf('-');
+ if (index != -1) {
+ locale = locale.substring(0, index);
}
- if (specifiedLocales.size() == 1) {
- String first = specifiedLocales.iterator().next();
- Set<String> languageStrings = languageToStrings.get(first);
- assert languageStrings != null;
- defaultStrings.addAll(languageStrings);
- }
+ specifiedLocales.add(locale);
+ }
+ if (specifiedLocales.size() == 1) {
+ String first = specifiedLocales.iterator().next();
+ Set<String> languageStrings = languageToStrings.get(first);
+ assert languageStrings != null;
+ defaultStrings.addAll(languageStrings);
}
}
@@ -411,9 +413,9 @@
String message = mDescriptions.get(s);
if (message == null) {
message = String.format("\"%1$s\" is not translated in %2$s",
- s, language);
+ s, getLanguageDescription(language));
} else {
- message = message + ", " + language;
+ message = message + ", " + getLanguageDescription(language);
}
mDescriptions.put(s, message);
}
@@ -445,6 +447,31 @@
}
}
+ public static String getLanguageDescription(@NonNull String locale) {
+ int index = locale.indexOf('-');
+ String regionCode = null;
+ String languageCode = locale;
+ if (index != -1) {
+ regionCode = locale.substring(index + 2).toUpperCase(Locale.US); // +2: Skip "r"
+ languageCode = locale.substring(0, index).toLowerCase(Locale.US);
+ }
+
+ String languageName = LocaleManager.getLanguageName(languageCode);
+ if (languageName != null) {
+ if (regionCode != null) {
+ String regionName = LocaleManager.getRegionName(regionCode);
+ if (regionName != null) {
+ languageName = languageName + ": " + regionName;
+ }
+ }
+
+ return String.format("\"%1$s\" (%2$s)", locale, languageName);
+ } else {
+ return '"' + locale + '"';
+ }
+ }
+
+
/** Look up the language for the given folder name */
private static String getLanguage(String name) {
String[] segments = name.split("-"); //$NON-NLS-1$
@@ -493,7 +520,7 @@
if (mMissingLocations != null && mMissingLocations.containsKey(name)) {
String language = getLanguage(context.file.getParentFile().getName());
if (language == null) {
- if (context.getDriver().isSuppressed(MISSING, element)) {
+ if (context.getDriver().isSuppressed(context, MISSING, element)) {
mMissingLocations.remove(name);
return;
}
@@ -505,7 +532,7 @@
}
}
if (mExtraLocations != null && mExtraLocations.containsKey(name)) {
- if (context.getDriver().isSuppressed(EXTRA, element)) {
+ if (context.getDriver().isSuppressed(context, EXTRA, element)) {
mExtraLocations.remove(name);
return;
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
index 7d9d8f2..de901ac 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
@@ -60,9 +60,6 @@
/** Default size to reserve for each API entry when creating byte buffer to build up data */
private static final int BYTES_PER_ENTRY = 28;
- private final LintClient mClient;
- private final File mXmlFile;
- private final File mBinaryFile;
private byte[] mData;
private int[] mIndices;
private int mWordCount;
@@ -117,6 +114,7 @@
}
if (file == null || !file.exists()) {
+ //noinspection VariableNotUsedInsideIf
if (region != null) {
// Fall back to the generic locale (non-region-specific) database
return get(client, locale, null);
@@ -223,29 +221,23 @@
@NonNull LintClient client,
@NonNull File xmlFile,
@Nullable File binaryFile) {
- mClient = client;
- mXmlFile = xmlFile;
- mBinaryFile = binaryFile;
-
if (binaryFile != null) {
- readData();
+ readData(client, xmlFile, binaryFile);
}
}
private TypoLookup() {
- mClient = null;
- mXmlFile = null;
- mBinaryFile = null;
}
- private void readData() {
- if (!mBinaryFile.exists()) {
- mClient.log(null, "%1$s does not exist", mBinaryFile);
+ private void readData(@NonNull LintClient client, @NonNull File xmlFile,
+ @NonNull File binaryFile) {
+ if (!binaryFile.exists()) {
+ client.log(null, "%1$s does not exist", binaryFile);
return;
}
long start = System.currentTimeMillis();
try {
- MappedByteBuffer buffer = Files.map(mBinaryFile, MapMode.READ_ONLY);
+ MappedByteBuffer buffer = Files.map(binaryFile, MapMode.READ_ONLY);
assert buffer.order() == ByteOrder.BIG_ENDIAN;
// First skip the header
@@ -253,7 +245,7 @@
buffer.rewind();
for (int offset = 0; offset < expectedHeader.length; offset++) {
if (expectedHeader[offset] != buffer.get()) {
- mClient.log(null, "Incorrect file header: not an typo database cache " +
+ client.log(null, "Incorrect file header: not an typo database cache " +
"file, or a corrupt cache file");
return;
}
@@ -262,8 +254,8 @@
// Read in the format number
if (buffer.get() != BINARY_FORMAT_VERSION) {
// Force regeneration of new binary data with up to date format
- if (createCache(mClient, mXmlFile, mBinaryFile)) {
- readData(); // Recurse
+ if (createCache(client, xmlFile, binaryFile)) {
+ readData(client, xmlFile, binaryFile); // Recurse
}
return;
@@ -296,7 +288,7 @@
// TODO: Investigate (profile) accessing the byte buffer directly instead of
// accessing a byte array.
} catch (IOException e) {
- mClient.log(e, null);
+ client.log(e, null);
}
if (WRITE_STATS) {
long end = System.currentTimeMillis();
@@ -307,7 +299,7 @@
}
}
- /** See the {@link #readData()} for documentation on the data format. */
+ /** See the {@link #readData(LintClient,File,File)} for documentation on the data format. */
private static void writeDatabase(File file, List<String> lines) throws IOException {
/*
* 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
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 974c108..f5006f9 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
@@ -20,9 +20,6 @@
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_REF_PREFIX;
import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.DOT_GIF;
-import static com.android.SdkConstants.DOT_JPG;
-import static com.android.SdkConstants.DOT_PNG;
import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.RESOURCE_CLR_STYLEABLE;
import static com.android.SdkConstants.RESOURCE_CLZ_ARRAY;
@@ -38,7 +35,6 @@
import static com.android.SdkConstants.TAG_RESOURCES;
import static com.android.SdkConstants.TAG_STRING_ARRAY;
import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -158,12 +154,9 @@
public void beforeCheckFile(@NonNull Context context) {
File file = context.file;
- String fileName = file.getName();
- boolean isXmlFile = endsWith(fileName, DOT_XML);
- if (isXmlFile
- || endsWith(fileName, DOT_PNG)
- || endsWith(fileName, DOT_JPG)
- || endsWith(fileName, DOT_GIF)) {
+ boolean isXmlFile = LintUtils.isXmlFile(file);
+ if (isXmlFile || LintUtils.isBitmapFile(file)) {
+ String fileName = file.getName();
String parentName = file.getParentFile().getName();
int dash = parentName.indexOf('-');
String typeName = parentName.substring(0, dash == -1 ? parentName.length() : dash);
@@ -183,7 +176,7 @@
if (xmlContext.document != null
&& xmlContext.document.getDocumentElement() != null) {
Element root = xmlContext.document.getDocumentElement();
- if (xmlContext.getDriver().isSuppressed(ISSUE, root)) {
+ if (xmlContext.getDriver().isSuppressed(xmlContext, ISSUE, root)) {
// Also remove it from consideration such that even the
// presence of this field in the R file is ignored.
mUnused.remove(resource);
@@ -256,6 +249,7 @@
for (Map.Entry<String, Location> entry : mUnused.entrySet()) {
String resource = entry.getKey();
Location location = entry.getValue();
+ //noinspection VariableNotUsedInsideIf
if (location != null) {
continue;
}
@@ -408,7 +402,8 @@
} else {
assert context.getPhase() == 2;
if (mUnused.containsKey(resource)) {
- if (context.getDriver().isSuppressed(getIssue(resource), item)) {
+ if (context.getDriver().isSuppressed(context, getIssue(resource),
+ item)) {
mUnused.remove(resource);
continue;
}
@@ -426,7 +421,8 @@
}
}
}
- } else if (mReferences != null) {
+ } else //noinspection VariableNotUsedInsideIf
+ if (mReferences != null) {
assert TAG_STYLE.equals(element.getTagName())
|| TAG_ARRAY.equals(element.getTagName())
|| TAG_PLURALS.equals(element.getTagName())
@@ -491,7 +487,7 @@
if (context.getPhase() == 1) {
mDeclarations.add(resource);
} else if (mUnused.containsKey(resource)) {
- if (context.getDriver().isSuppressed(getIssue(resource), attribute)) {
+ if (context.getDriver().isSuppressed(context, getIssue(resource), attribute)) {
mUnused.remove(resource);
return;
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java
index 1616500..8bbc778 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java
@@ -20,18 +20,25 @@
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_BACKGROUND;
import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_PADDING;
+import static com.android.SdkConstants.ATTR_PADDING_BOTTOM;
+import static com.android.SdkConstants.ATTR_PADDING_END;
+import static com.android.SdkConstants.ATTR_PADDING_LEFT;
+import static com.android.SdkConstants.ATTR_PADDING_RIGHT;
+import static com.android.SdkConstants.ATTR_PADDING_START;
+import static com.android.SdkConstants.ATTR_PADDING_TOP;
import static com.android.SdkConstants.ATTR_STYLE;
import static com.android.SdkConstants.FRAME_LAYOUT;
import static com.android.SdkConstants.GRID_LAYOUT;
import static com.android.SdkConstants.GRID_VIEW;
import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW;
import static com.android.SdkConstants.LINEAR_LAYOUT;
-import static com.android.SdkConstants.VIEW_MERGE;
import static com.android.SdkConstants.RADIO_GROUP;
import static com.android.SdkConstants.RELATIVE_LAYOUT;
import static com.android.SdkConstants.SCROLL_VIEW;
import static com.android.SdkConstants.TABLE_LAYOUT;
import static com.android.SdkConstants.TABLE_ROW;
+import static com.android.SdkConstants.VIEW_MERGE;
import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Category;
@@ -199,6 +206,18 @@
return;
}
+ // If we define a padding, and the parent provides a background, then
+ // this view is not *necessarily* useless.
+ if (parentHasBackground && element.hasAttributeNS(ANDROID_URI, ATTR_PADDING)
+ || element.hasAttributeNS(ANDROID_URI, ATTR_PADDING_LEFT)
+ || element.hasAttributeNS(ANDROID_URI, ATTR_PADDING_RIGHT)
+ || element.hasAttributeNS(ANDROID_URI, ATTR_PADDING_TOP)
+ || element.hasAttributeNS(ANDROID_URI, ATTR_PADDING_BOTTOM)
+ || element.hasAttributeNS(ANDROID_URI, ATTR_PADDING_START)
+ || element.hasAttributeNS(ANDROID_URI, ATTR_PADDING_END)) {
+ return;
+ }
+
boolean hasId = element.hasAttributeNS(ANDROID_URI, ATTR_ID);
Location location = context.getLocation(element);
String tag = element.getTagName();
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java
index 1be9813..fe80396 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java
@@ -20,8 +20,8 @@
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
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;
import com.android.tools.lint.detector.api.Speed;
@@ -33,9 +33,13 @@
import java.util.regex.Pattern;
/**
- * Checks that the encoding used in resource files is always UTF-8.
+ * Checks that the encoding used in resource files is always UTF-8
+ * <p>
+ * TODO: Add a check which looks at files which do not specify the encoding
+ * and check the contents to see if it contains characters where it's ambiguous.
*/
-public class Utf8Detector extends LayoutDetector {
+public class Utf8Detector extends ResourceXmlDetector {
+
/** Detects non-utf8 encodings */
public static final Issue ISSUE = Issue.create(
"EnforceUTF8", //$NON-NLS-1$
@@ -44,10 +48,13 @@
"XML supports encoding in a wide variety of character sets. However, not all " +
"tools handle the XML encoding attribute correctly, and nearly all Android " +
"apps use UTF-8, so by using UTF-8 you can protect yourself against subtle " +
- "bugs when using non-ASCII characters.",
+ "bugs when using non-ASCII characters.\n" +
+ "\n" +
+ "In particular, the Android Gradle build system will merge resource XML files " +
+ "assuming the resource files are using UTF-8 encoding.\n",
Category.I18N,
- 2,
- Severity.WARNING,
+ 5,
+ Severity.FATAL,
new Implementation(
Utf8Detector.class,
Scope.RESOURCE_FILE_SCOPE));
@@ -63,7 +70,7 @@
@NonNull
@Override
public Speed getSpeed() {
- return Speed.FAST;
+ return Speed.NORMAL;
}
@Override
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java
index b8ccec1..6767339 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java
@@ -16,44 +16,46 @@
package com.android.tools.lint.checks;
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
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 org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.io.File;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
import java.util.List;
+import lombok.ast.AstVisitor;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.TypeMember;
+
/**
* Looks for custom views that do not define the view constructors needed by UI builders
*/
-public class ViewConstructorDetector extends Detector implements Detector.ClassScanner {
- private static final String SIG1 =
- "(Landroid/content/Context;)V"; //$NON-NLS-1$
- private static final String SIG2 =
- "(Landroid/content/Context;Landroid/util/AttributeSet;)V"; //$NON-NLS-1$
- private static final String SIG3 =
- "(Landroid/content/Context;Landroid/util/AttributeSet;I)V"; //$NON-NLS-1$
-
+public class ViewConstructorDetector extends Detector implements Detector.JavaScanner {
/** The main issue discovered by this detector */
public static final Issue ISSUE = Issue.create(
"ViewConstructor", //$NON-NLS-1$
"Missing View constructors for XML inflation",
"Checks that custom views define the expected constructors",
- "Some layout tools (such as the Android layout editor for Eclipse) needs to " +
+ "Some layout tools (such as the Android layout editor for Studio & Eclipse) needs to " +
"find a constructor with one of the following signatures:\n" +
"* `View(Context context)`\n" +
"* `View(Context context, AttributeSet attrs)`\n" +
@@ -69,7 +71,7 @@
Severity.WARNING,
new Implementation(
ViewConstructorDetector.class,
- Scope.CLASS_FILE_SCOPE));
+ Scope.JAVA_FILE_SCOPE));
/** Constructs a new {@link ViewConstructorDetector} check */
public ViewConstructorDetector() {
@@ -81,71 +83,100 @@
return Speed.FAST;
}
- // ---- Implements ClassScanner ----
+ // ---- Implements JavaScanner ----
+ @Nullable
@Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- if (classNode.name.indexOf('$') != -1
- && (classNode.access & Opcodes.ACC_STATIC) == 0) {
- // Ignore inner classes that aren't static: we can't create these
- // anyway since we'd need the outer instance
- return;
- }
-
- // Ignore abstract classes
- if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) {
- return;
- }
-
- if (isViewClass(context, classNode)) {
- checkConstructors(context, classNode);
- }
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ return Collections.<Class<? extends Node>>singletonList(ClassDeclaration.class);
}
- private static boolean isViewClass(ClassContext context, ClassNode node) {
- String superName = node.superName;
- while (superName != null) {
- if (superName.equals("android/view/View") //$NON-NLS-1$
- || superName.equals("android/view/ViewGroup") //$NON-NLS-1$
- || superName.startsWith("android/widget/") //$NON-NLS-1$
- && !((superName.endsWith("Adapter") //$NON-NLS-1$
- || superName.endsWith("Controller") //$NON-NLS-1$
- || superName.endsWith("Service") //$NON-NLS-1$
- || superName.endsWith("Provider") //$NON-NLS-1$
- || superName.endsWith("Filter")))) { //$NON-NLS-1$
+ @Nullable
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull final JavaContext context) {
+ return new ViewConstructorVisitor(context);
+ }
+
+ private static boolean isXmlConstructor(ResolvedMethod method) {
+ // Accept
+ // android.content.Context
+ // android.content.Context,android.util.AttributeSet
+ // android.content.Context,android.util.AttributeSet,int
+ int argumentCount = method.getArgumentCount();
+ if (argumentCount == 0 || argumentCount > 3) {
+ return false;
+ }
+ if (!method.getArgumentType(0).matchesName("android.content.Context")) {
+ return false;
+ }
+ if (argumentCount == 1) {
+ return true;
+ }
+ if (!method.getArgumentType(1).matchesName("android.util.AttributeSet")) {
+ return false;
+ }
+ //noinspection SimplifiableIfStatement
+ if (argumentCount == 2) {
+ return true;
+ }
+ return method.getArgumentType(2).matchesName("int");
+ }
+
+ private static class ViewConstructorVisitor extends ForwardingAstVisitor {
+
+ private final JavaContext mContext;
+
+ public ViewConstructorVisitor(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ // Only applies to concrete classes
+ int flags = node.astModifiers().getEffectiveModifierFlags();
+ // Ignore abstract classes
+ if ((flags & Modifier.ABSTRACT) != 0) {
return true;
}
- superName = context.getDriver().getSuperClass(superName);
- }
+ if (node.getParent() instanceof NormalTypeBody
+ && ((flags & Modifier.STATIC) == 0)) {
+ // Ignore inner classes that aren't static: we can't create these
+ // anyway since we'd need the outer instance
+ return true;
+ }
- return false;
- }
+ ResolvedNode resolved = mContext.resolve(node);
+ if (!(resolved instanceof ResolvedClass)) {
+ return true;
+ }
- private static void checkConstructors(ClassContext context, ClassNode classNode) {
- // Look through constructors
- @SuppressWarnings("rawtypes")
- List methods = classNode.methods;
- for (Object methodObject : methods) {
- MethodNode method = (MethodNode) methodObject;
- if (method.name.equals(CONSTRUCTOR_NAME)) {
- String desc = method.desc;
- if (desc.equals(SIG1) || desc.equals(SIG2) || desc.equals(SIG3)) {
- return;
+ ResolvedClass cls = (ResolvedClass) resolved;
+ if (!cls.isSubclassOf(SdkConstants.CLASS_VIEW, false)) {
+ return true;
+ }
+
+ boolean found = false;
+ for (ResolvedMethod constructor : cls.getConstructors()) {
+ if (isXmlConstructor(constructor)) {
+ found = true;
+ break;
}
}
+
+ if (!found) {
+ String message = String.format(
+ "Custom view %1$s is missing constructor used by tools: "
+ + "(Context) or (Context,AttributeSet) "
+ + "or (Context,AttributeSet,int)",
+ node.astName().astValue()
+ );
+ Location location = mContext.getLocation(node.astName());
+ mContext.report(ISSUE, node, location,
+ message, null /*data*/);
+ }
+
+ return true;
}
-
- // If we get this far, none of the expected constructors were found.
-
- // Use location of one of the constructors?
- String message = String.format(
- "Custom view %1$s is missing constructor used by tools: " +
- "(Context) or (Context,AttributeSet) or (Context,AttributeSet,int)",
- classNode.name);
- File sourceFile = context.getSourceFile();
- Location location = Location.create(sourceFile != null
- ? sourceFile : context.file);
- context.report(ISSUE, location, message, null /*data*/);
}
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewHolderDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewHolderDetector.java
new file mode 100644
index 0000000..efea80b
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewHolderDetector.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.VIEW;
+import static com.android.SdkConstants.VIEW_GROUP;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Category;
+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.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.google.common.collect.Lists;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.If;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.VariableDefinition;
+
+/**
+ * Looks for ListView scrolling performance: should use view holder pattern
+ */
+public class ViewHolderDetector extends Detector implements Detector.JavaScanner {
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ ViewHolderDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Using a view inflater unconditionally in an AdapterView */
+ public static final Issue ISSUE = Issue.create(
+ "ViewHolder", //$NON-NLS-1$
+ "View Holder Candidates",
+ "Looks for candidates for the view holder pattern",
+
+ "When implementing a view Adapter, you should avoid unconditionally inflating a " +
+ "new layout; if an available item is passed in for reuse, you should try to " +
+ "use that one instead. This helps make for example ListView scrolling much " +
+ "smoother.",
+
+ Category.PERFORMANCE,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .addMoreInfo(
+ "http://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder");
+
+ private static final String GET_VIEW = "getView"; //$NON-NLS-1$
+ static final String INFLATE = "inflate"; //$NON-NLS-1$
+
+ /** Constructs a new {@link ViewHolderDetector} check */
+ public ViewHolderDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ // ---- 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 ViewAdapterVisitor(context);
+ }
+
+ private static class ViewAdapterVisitor extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+
+ public ViewAdapterVisitor(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitMethodDeclaration(MethodDeclaration node) {
+ if (isViewAdapterMethod(node)) {
+ InflationVisitor visitor = new InflationVisitor(mContext);
+ node.accept(visitor);
+ visitor.finish();
+ }
+ return super.visitMethodDeclaration(node);
+ }
+
+ /**
+ * Returns true if this method looks like it's overriding android.widget.Adapter's getView
+ * method: getView(int position, View convertView, ViewGroup parent)
+ */
+ private static boolean isViewAdapterMethod(MethodDeclaration node) {
+ if (GET_VIEW.equals(node.astMethodName().astValue())) {
+ StrictListAccessor<VariableDefinition, MethodDeclaration> parameters =
+ node.astParameters();
+ if (parameters != null && parameters.size() == 3) {
+ Iterator<VariableDefinition> iterator = parameters.iterator();
+ if (!iterator.hasNext()) {
+ return false;
+ }
+
+ VariableDefinition first = iterator.next();
+ if (!first.astTypeReference().astParts().last().getTypeName().equals(
+ TYPE_INT)) {
+ return false;
+ }
+
+ if (!iterator.hasNext()) {
+ return false;
+ }
+
+ VariableDefinition second = iterator.next();
+ if (!second.astTypeReference().astParts().last().getTypeName().equals(
+ VIEW)) {
+ return false;
+ }
+
+ if (!iterator.hasNext()) {
+ return false;
+ }
+
+ VariableDefinition third = iterator.next();
+ //noinspection RedundantIfStatement
+ if (!third.astTypeReference().astParts().last().getTypeName().equals(
+ VIEW_GROUP)) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ private static class InflationVisitor extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+ private List<Node> mNodes;
+ private boolean mHaveConditional;
+
+ public InflationVisitor(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (node.astOperand() != null) {
+ String methodName = node.astName().astValue();
+ if (methodName.equals(INFLATE) && node.astArguments().size() >= 1) {
+ // See if we're inside a conditional
+ boolean insideIf = false;
+ Node p = node.getParent();
+ while (p != null) {
+ if (p instanceof If || p instanceof InlineIfExpression) {
+ insideIf = true;
+ mHaveConditional = true;
+ break;
+ } else if (p == node) {
+ break;
+ }
+ p = p.getParent();
+ }
+ if (!insideIf) {
+ // Rather than reporting immediately, we only report if we didn't
+ // find any conditionally executed inflate statements in the method.
+ // This is because there are cases where getView method is complicated
+ // and inflates not just the top level layout but also children
+ // of the view, and we don't want to flag these. (To be more accurate
+ // should perform flow analysis and only report unconditional inflation
+ // of layouts that wind up flowing to the return value; that requires
+ // more work, and this simple heuristic is good enough for nearly all test
+ // cases I've come across.
+ if (mNodes == null) {
+ mNodes = Lists.newArrayList();
+ }
+ mNodes.add(node);
+ }
+ }
+ }
+
+ return super.visitMethodInvocation(node);
+ }
+
+ public void finish() {
+ if (!mHaveConditional && mNodes != null) {
+ for (Node node : mNodes) {
+ String message = "Unconditional layout inflation from view adapter: "
+ + "Should use View Holder pattern (use recycled view passed "
+ + "into this method as the second parameter) for smoother "
+ + "scrolling";
+ mContext.report(ISSUE, node, mContext.getLocation(node), message, null);
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
index 77770d3..4976de5 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
@@ -43,7 +43,6 @@
import org.objectweb.asm.tree.analysis.Frame;
import java.util.Collections;
-import java.util.EnumSet;
import java.util.List;
/**
@@ -69,7 +68,7 @@
Severity.WARNING,
new Implementation(
ViewTagDetector.class,
- Scope.CLASS_AND_ALL_RESOURCE_FILES));
+ Scope.CLASS_FILE_SCOPE));
/** Constructs a new {@link ViewTagDetector} */
public ViewTagDetector() {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
index 6ed60fa..e839ec9 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
@@ -16,16 +16,22 @@
package com.android.tools.lint.checks;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_CLASS;
import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.ID_PREFIX;
import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.VIEW_TAG;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
@@ -38,9 +44,19 @@
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.XmlUtils;
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 org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
import java.io.File;
import java.util.ArrayList;
@@ -50,6 +66,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import lombok.ast.AstVisitor;
import lombok.ast.Cast;
@@ -58,9 +75,21 @@
import lombok.ast.Select;
import lombok.ast.StrictListAccessor;
-/** Detector for finding inconsistent usage of views and casts */
+/** Detector for finding inconsistent usage of views and casts
+ * <p>
+ * TODO: Check findFragmentById
+ * <pre>
+ * ((ItemListFragment) getSupportFragmentManager()
+ * .findFragmentById(R.id.item_list))
+ * .setActivateOnItemClick(true);
+ * </pre>
+ * Here we should check the {@code <fragment>} tag pointed to by the id, and
+ * check its name or class attributes to make sure the cast is compatible with
+ * the named fragment class!
+ */
public class ViewTypeDetector extends ResourceXmlDetector implements Detector.JavaScanner {
/** Mismatched view types */
+ @SuppressWarnings("unchecked")
public static final Issue ISSUE = Issue.create(
"WrongViewCast", //$NON-NLS-1$
"Mismatched view type",
@@ -69,10 +98,15 @@
"the id in the Java code it ensures that it is treated as the same type.",
Category.CORRECTNESS,
9,
- Severity.ERROR,
+ Severity.FATAL,
new Implementation(
ViewTypeDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES)));
+ EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES),
+ Scope.JAVA_FILE_SCOPE));
+
+ /** Flag used to do no work if we're running in incremental mode in a .java file without
+ * a client supporting project resources */
+ private Boolean mIgnore = null;
private final Map<String, Object> mIdToViewTag = new HashMap<String, Object>(50);
@@ -88,15 +122,6 @@
}
@Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- if (LintUtils.endsWith(file.getName(), DOT_JAVA)) {
- return true;
- }
-
- return super.appliesTo(context, file);
- }
-
- @Override
public Collection<String> getApplicableAttributes() {
return Collections.singletonList(ATTR_ID);
}
@@ -149,6 +174,16 @@
@Override
public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
@NonNull MethodInvocation node) {
+ LintClient client = context.getClient();
+ if (mIgnore == Boolean.TRUE) {
+ return;
+ } else if (mIgnore == null) {
+ mIgnore = !context.getScope().contains(Scope.ALL_RESOURCE_FILES) &&
+ !client.supportsProjectResources();
+ if (mIgnore) {
+ return;
+ }
+ }
assert node.astName().astValue().equals("findViewById");
if (node.getParent() instanceof Cast) {
Cast cast = (Cast) node.getParent();
@@ -162,14 +197,39 @@
String resource = first.toString();
if (resource.startsWith("R.id.")) { //$NON-NLS-1$
String id = ((Select) first).astIdentifier().astValue();
- Object types = mIdToViewTag.get(id);
- if (types instanceof String) {
- String layoutType = (String) types;
- checkCompatible(context, castType, layoutType, null, cast);
- } else if (types instanceof List<?>) {
- @SuppressWarnings("unchecked")
- List<String> layoutTypes = (List<String>) types;
- checkCompatible(context, castType, null, layoutTypes, cast);
+
+ if (client.supportsProjectResources()) {
+ AbstractResourceRepository resources = client
+ .getProjectResources(context.getMainProject(), true);
+ if (resources == null) {
+ return;
+ }
+
+ List<ResourceItem> items = resources.getResourceItem(ResourceType.ID,
+ id);
+ if (items != null && !items.isEmpty()) {
+ Set<String> compatible = Sets.newHashSet();
+ for (ResourceItem item : items) {
+ Collection<String> tags = getViewTags(context, item);
+ if (tags != null) {
+ compatible.addAll(tags);
+ }
+ }
+ if (!compatible.isEmpty()) {
+ ArrayList<String> layoutTypes = Lists.newArrayList(compatible);
+ checkCompatible(context, castType, null, layoutTypes, cast);
+ }
+ }
+ } else {
+ Object types = mIdToViewTag.get(id);
+ if (types instanceof String) {
+ String layoutType = (String) types;
+ checkCompatible(context, castType, layoutType, null, cast);
+ } else if (types instanceof List<?>) {
+ @SuppressWarnings("unchecked")
+ List<String> layoutTypes = (List<String>) types;
+ checkCompatible(context, castType, null, layoutTypes, cast);
+ }
}
}
}
@@ -177,6 +237,69 @@
}
}
+ @Nullable
+ protected Collection<String> getViewTags(
+ @NonNull Context context,
+ @NonNull ResourceItem item) {
+ // Check view tag in this file. Can I do it cheaply? Try with
+ // an XML pull parser. Or DOM if we have multiple resources looked
+ // up?
+ ResourceFile source = item.getSource();
+ if (source != null) {
+ File file = source.getFile();
+ Multimap<String,String> map = getIdToTagsIn(context, file);
+ if (map != null) {
+ return map.get(item.getName());
+ }
+ }
+
+ return null;
+ }
+
+
+ private Map<File, Multimap<String, String>> mFileIdMap;
+
+ @Nullable
+ private Multimap<String, String> getIdToTagsIn(@NonNull Context context, @NonNull File file) {
+ if (!file.getPath().endsWith(DOT_XML)) {
+ return null;
+ }
+ if (mFileIdMap == null) {
+ mFileIdMap = Maps.newHashMap();
+ }
+ Multimap<String, String> map = mFileIdMap.get(file);
+ if (map == null) {
+ map = ArrayListMultimap.create();
+ mFileIdMap.put(file, map);
+
+ String xml = context.getClient().readFile(file);
+ // TODO: Use pull parser instead for better performance!
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ if (document != null && document.getDocumentElement() != null) {
+ addViewTags(map, document.getDocumentElement());
+ }
+ }
+ return map;
+ }
+
+ private static void addViewTags(Multimap<String, String> map, Element element) {
+ String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if (id != null && !id.isEmpty()) {
+ id = LintUtils.stripIdPrefix(id);
+ if (!map.containsEntry(id, element.getTagName())) {
+ map.put(id, element.getTagName());
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ addViewTags(map, (Element) child);
+ }
+ }
+ }
+
/** Check if the view and cast type are compatible */
private static void checkCompatible(JavaContext context, String castType, String layoutType,
List<String> layoutTypes, Cast node) {
@@ -206,8 +329,7 @@
String message = String.format(
"Unexpected cast to %1$s: layout tag was %2$s",
castType, layoutType);
- context.report(ISSUE, node, context.parser.getLocation(context, node), message,
- null);
+ context.report(ISSUE, node, context.getLocation(node), message, null);
}
}
}
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 9807e0a..51dd822 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
@@ -126,6 +126,9 @@
if (call.owner.equals(WAKELOCK_OWNER)) {
String name = call.name;
if (name.equals(ACQUIRE_METHOD)) {
+ if (call.desc.equals("(J)V")) { // acquire(long timeout) does not require a corresponding release
+ return;
+ }
mHasAcquire = true;
if (context.getDriver().getPhase() == 2) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WebViewDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WebViewDetector.java
new file mode 100644
index 0000000..8b4dfef
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WebViewDetector.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
+import static com.android.SdkConstants.WEB_VIEW;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+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 com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class WebViewDetector extends LayoutDetector {
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ WebViewDetector.class,
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "WebViewLayout", //$NON-NLS-1$
+ "WebViews in wrap_content parents",
+ "Ensures that WebViews are not placed in parents with wrap_content layout",
+
+ "The WebView implementation has certain performance optimizations which will not " +
+ "work correctly if the parent view is using `wrap_content` rather than " +
+ "`match_parent`. This can lead to subtle UI bugs.",
+
+ Category.CORRECTNESS,
+ 7,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Constructs a new {@link WebViewDetector} */
+ public WebViewDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList(WEB_VIEW);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ Node parentNode = element.getParentNode();
+ if (parentNode != null && parentNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element parent = (Element)parentNode;
+ Attr width = parent.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
+ Attr height = parent.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT);
+ Attr attr = null;
+ if (width != null && VALUE_WRAP_CONTENT.equals(height.getValue())) {
+ attr = width;
+ }
+ if (height != null && VALUE_WRAP_CONTENT.equals(height.getValue())) {
+ attr = height;
+ }
+ if (attr != null) {
+ String message = "Placing a <WebView> in a parent element that uses a "
+ + "wrap_content size can lead to subtle bugs; use match_parent";
+ Location location = context.getLocation(element);
+ location.setSecondary(context.getLocation(attr));
+ context.report(ISSUE, element, location, message, null);
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
index 3b10d55..f5ce676 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
@@ -16,18 +16,22 @@
package com.android.tools.lint.checks;
+import static com.android.SdkConstants.CLASS_VIEW;
import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_DRAW;
import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_LAYOUT;
import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_MEASURE;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.ClassContext;
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.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
@@ -40,26 +44,34 @@
import java.util.Arrays;
import java.util.List;
+import lombok.ast.AstVisitor;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.Expression;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Super;
+
/**
* Checks for cases where the wrong call is being made
*/
-public class WrongCallDetector extends Detector implements ClassScanner {
+public class WrongCallDetector extends Detector implements Detector.JavaScanner {
/** Calling the wrong method */
public static final Issue ISSUE = Issue.create(
"WrongCall", //$NON-NLS-1$
"Using wrong draw/layout method",
- "Finds cases where the wrong call is made, such as calling `onMeasure` "
- + "instead of `measure`",
+ "Finds cases where the wrong call is made, such as calling `onMeasure` " +
+ "instead of `measure`",
"Custom views typically need to call `measure()` on their children, not `onMeasure`. " +
"Ditto for onDraw, onLayout, etc.",
Category.CORRECTNESS,
6,
- Severity.ERROR,
+ Severity.FATAL,
new Implementation(
WrongCallDetector.class,
- Scope.CLASS_FILE_SCOPE));
+ Scope.JAVA_FILE_SCOPE));
/** Constructs a new {@link WrongCallDetector} */
public WrongCallDetector() {
@@ -71,11 +83,11 @@
return Speed.FAST;
}
- // ---- Implements ClassScanner ----
+ // ---- Implements JavaScanner ----
@Override
@Nullable
- public List<String> getApplicableCallNames() {
+ public List<String> getApplicableMethodNames() {
return Arrays.asList(
ON_DRAW,
ON_MEASURE,
@@ -84,18 +96,42 @@
}
@Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- String name = call.name;
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+
// Call is only allowed if it is both only called on the super class (invoke special)
// as well as within the same overriding method (e.g. you can't call super.onLayout
// from the onMeasure method)
- if (call.getOpcode() != Opcodes.INVOKESPECIAL || !name.equals(method.name)) {
- String suggestion = Character.toLowerCase(name.charAt(2)) + name.substring(3);
- String message = String.format(
- "Suspicious method call; should probably call \"%1$s\" rather than \"%2$s\"",
- suggestion, name);
- context.report(ISSUE, method, call, context.getLocation(call), message, null);
+ Expression operand = node.astOperand();
+ if (!(operand instanceof Super)) {
+ report(context, node);
+ return;
}
+
+ Node method = StringFormatDetector.getParentMethod(node);
+ if (!(method instanceof MethodDeclaration) ||
+ !((MethodDeclaration)method).astMethodName().astValue().equals(
+ node.astName().astValue())) {
+ report(context, node);
+ }
+ }
+
+ private static void report(JavaContext context, MethodInvocation node) {
+ // Make sure the call is on a view
+ JavaParser.ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof JavaParser.ResolvedMethod) {
+ JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
+ JavaParser.ResolvedClass containingClass = method.getContainingClass();
+ if (!containingClass.isSubclassOf(CLASS_VIEW, false)) {
+ return;
+ }
+ }
+
+ String name = node.astName().astValue();
+ String suggestion = Character.toLowerCase(name.charAt(2)) + name.substring(3);
+ String message = String.format(
+ "Suspicious method call; should probably call \"%1$s\" rather than \"%2$s\"",
+ suggestion, name);
+ context.report(ISSUE, node, context.getLocation(node.astName()), message, null);
}
}
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 40cd5d6..0539c84 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
@@ -48,8 +48,8 @@
"lint check looks for incorrect capitalizations.",
Category.CORRECTNESS,
- 8,
- Severity.WARNING,
+ 4,
+ Severity.FATAL,
new Implementation(
WrongCaseDetector.class,
Scope.RESOURCE_FILE_SCOPE))
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
index 759f47e..95f4162 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
@@ -21,22 +21,30 @@
import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.FD_RES_VALUES;
import static com.android.SdkConstants.ID_PREFIX;
import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.RELATIVE_LAYOUT;
import static com.android.SdkConstants.TAG_ITEM;
import static com.android.SdkConstants.VALUE_ID;
+import static com.android.tools.lint.detector.api.LintUtils.editDistance;
+import static com.android.tools.lint.detector.api.LintUtils.getChildren;
+import static com.android.tools.lint.detector.api.LintUtils.isSameResourceFile;
import static com.android.tools.lint.detector.api.LintUtils.stripIdPrefix;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.IDomParser;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Category;
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.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Location.Handle;
import com.android.tools.lint.detector.api.Scope;
@@ -54,6 +62,7 @@
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -90,6 +99,7 @@
private List<Element> mRelativeLayouts;
/** Reference to an unknown id */
+ @SuppressWarnings("unchecked")
public static final Issue UNKNOWN_ID = Issue.create(
"UnknownId", //$NON-NLS-1$
"Reference to an unknown id",
@@ -105,7 +115,8 @@
Severity.FATAL,
new Implementation(
WrongIdDetector.class,
- Scope.ALL_RESOURCES_SCOPE));
+ Scope.ALL_RESOURCES_SCOPE,
+ Scope.RESOURCE_FILE_SCOPE));
/** Reference to an id that is not a sibling */
public static final Issue NOT_SIBLING = Issue.create(
@@ -116,7 +127,7 @@
"within the same relative layout.",
Category.CORRECTNESS,
6,
- Severity.ERROR,
+ Severity.FATAL,
IMPLEMENTATION);
/** An ID declaration which is not valid */
@@ -132,7 +143,7 @@
"instead, such as `login_button1` and `login_button2`.",
Category.CORRECTNESS,
6,
- Severity.ERROR,
+ Severity.FATAL,
IMPLEMENTATION);
/** Reference to an id that is not in the current layout */
@@ -198,7 +209,7 @@
}
for (Element layout : mRelativeLayouts) {
- List<Element> children = LintUtils.getChildren(layout);
+ List<Element> children = getChildren(layout);
Set<String> ids = Sets.newHashSetWithExpectedSize(children.size());
for (Element child : children) {
String id = child.getAttributeNS(ANDROID_URI, ATTR_ID);
@@ -221,8 +232,7 @@
// since it's too early to conclude here that the id does
// not exist (you are allowed to have forward references)
XmlContext xmlContext = (XmlContext) context;
- IDomParser parser = xmlContext.parser;
- Handle handle = parser.createLocationHandle(xmlContext, attr);
+ Handle handle = xmlContext.createLocationHandle(attr);
handle.setClientData(attr);
if (mHandles == null) {
@@ -260,23 +270,46 @@
}
mFileIds = null;
+
+ if (!context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
+ checkHandles(context);
+ }
}
@Override
public void afterCheckProject(@NonNull Context context) {
+ if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
+ checkHandles(context);
+ }
+ }
+
+ private void checkHandles(@NonNull Context context) {
if (mHandles != null) {
boolean checkSameLayout = context.isEnabled(UNKNOWN_ID_LAYOUT);
boolean checkExists = context.isEnabled(UNKNOWN_ID);
boolean projectScope = context.getScope().contains(Scope.ALL_RESOURCE_FILES);
for (Pair<String, Handle> pair : mHandles) {
String id = pair.getFirst();
- boolean isBound = idDefined(mGlobalIds, id);
- if (!isBound && checkExists && projectScope) {
+ boolean isBound = projectScope ? idDefined(mGlobalIds, id) :
+ idDefined(context, id, context.file);
+ LintClient client = context.getClient();
+ if (!isBound && checkExists
+ && (projectScope || client.supportsProjectResources())) {
Handle handle = pair.getSecond();
boolean isDeclared = idDefined(mDeclaredIds, id);
id = stripIdPrefix(id);
String suggestionMessage;
- List<String> suggestions = getSpellingSuggestions(id, mGlobalIds);
+ Set<String> spellingDictionary = mGlobalIds;
+ if (!projectScope && client.supportsProjectResources()) {
+ AbstractResourceRepository resources =
+ client.getProjectResources(context.getProject(), true);
+ if (resources != null) {
+ spellingDictionary = Sets.newHashSet(
+ resources.getItemsOfType(ResourceType.ID));
+ spellingDictionary.remove(id);
+ }
+ }
+ List<String> suggestions = getSpellingSuggestions(id, spellingDictionary);
if (suggestions.size() > 1) {
suggestionMessage = String.format(" Did you mean one of {%2$s} ?",
id, Joiner.on(", ").join(suggestions));
@@ -316,7 +349,7 @@
Location location = handle.resolve();
Object clientData = handle.getClientData();
if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(issue, (Node) clientData)) {
+ if (context.getDriver().isSuppressed(null, issue, (Node) clientData)) {
return;
}
}
@@ -354,10 +387,13 @@
mGlobalIds.add(id);
if (id.startsWith("@+") && !id.startsWith(NEW_ID_PREFIX) //$NON-NLS-1$
- && !id.startsWith("@+android:id/")) {//$NON-NLS-1$
+ && !id.startsWith("@+android:id/") //$NON-NLS-1$
+ || id.startsWith(NEW_ID_PREFIX)
+ && id.indexOf('/', NEW_ID_PREFIX.length()) != -1) {
+ int nameStart = id.startsWith(NEW_ID_PREFIX) ? NEW_ID_PREFIX.length() : 2;
+ String suggested = NEW_ID_PREFIX + id.substring(nameStart).replace('/', '_');
String message = String.format(
- "ID definitions *must* be of the form @+id/name; try using %1$s",
- NEW_ID_PREFIX + id.substring(2).replace('/', '_'));
+ "ID definitions *must* be of the form @+id/name; try using %1$s", suggested);
context.report(INVALID, attribute, context.getLocation(attribute), message, null);
}
}
@@ -380,6 +416,41 @@
return definedLocally;
}
+ private boolean idDefined(@NonNull Context context, @NonNull String id,
+ @Nullable File notIn) {
+ AbstractResourceRepository resources =
+ context.getClient().getProjectResources(context.getProject(), true);
+ if (resources != null) {
+ List<ResourceItem> items = resources.getResourceItem(ResourceType.ID,
+ stripIdPrefix(id));
+ if (items == null || items.isEmpty()) {
+ return false;
+ }
+ for (ResourceItem item : items) {
+ ResourceFile source = item.getSource();
+ if (source != null) {
+ File file = source.getFile();
+ if (file.getParentFile().getName().startsWith(FD_RES_VALUES)) {
+ if (mDeclaredIds == null) {
+ mDeclaredIds = Sets.newHashSet();
+ }
+ mDeclaredIds.add(id);
+ continue;
+ }
+
+ // Ignore definitions in the given file. This is used to ignore
+ // matches in the same file as the reference, since the reference
+ // is often expressed as a definition
+ if (!isSameResourceFile(file, notIn)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
private static List<String> getSpellingSuggestions(String id, Collection<String> ids) {
int maxDistance = id.length() >= 4 ? 2 : 1;
@@ -395,7 +466,7 @@
// O(n*m) storage and O(n*m) speed, where n and m are the string lengths)
continue;
}
- int distance = LintUtils.editDistance(id, matchWith);
+ int distance = editDistance(id, matchWith);
if (distance <= maxDistance) {
matches.put(distance, matchWith);
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java
index 35b9058..4758d76 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java
@@ -45,7 +45,7 @@
"rather than the `values/` folder where it belongs.",
Category.CORRECTNESS,
8,
- Severity.ERROR,
+ Severity.FATAL,
new Implementation(
WrongLocationDetector.class,
Scope.RESOURCE_FILE_SCOPE));
diff --git a/misc/api-generator/.classpath b/misc/api-generator/.classpath
new file mode 100644
index 0000000..8fc7414
--- /dev/null
+++ b/misc/api-generator/.classpath
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/net/sf/kxml/kxml2/2.3.0/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/net/sf/kxml/kxml2/2.3.0/kxml2-2.3.0-sources.jar"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/org/ow2/asm/asm/4.0/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/org/ow2/asm/asm/4.0/asm-4.0-sources.jar"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/org/ow2/asm/asm-tree/4.0/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/org/ow2/asm/asm-tree/4.0/asm-tree-4.0-sources.jar"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/common"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/misc/apigenerator/.gitignore b/misc/api-generator/.gitignore
similarity index 100%
rename from misc/apigenerator/.gitignore
rename to misc/api-generator/.gitignore
diff --git a/misc/api-generator/.project b/misc/api-generator/.project
new file mode 100644
index 0000000..ef098b6
--- /dev/null
+++ b/misc/api-generator/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>api-generator</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/misc/api-generator/.settings/org.eclipse.jdt.core.prefs b/misc/api-generator/.settings/org.eclipse.jdt.core.prefs
new file mode 100755
index 0000000..7ce49c8
--- /dev/null
+++ b/misc/api-generator/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Wed Jun 04 15:07:10 PDT 2014
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/misc/api-generator/build.gradle b/misc/api-generator/build.gradle
new file mode 100755
index 0000000..9fe3ab2
--- /dev/null
+++ b/misc/api-generator/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'java'
+
+dependencies {
+ compile project(':base:common')
+ compile 'net.sf.kxml:kxml2:2.3.0'
+ compile 'org.ow2.asm:asm:4.0'
+ compile 'org.ow2.asm:asm-tree:4.0'
+}
+
+group = 'com.android.tools'
+archivesBaseName = 'api-generator'
+version = rootProject.ext.baseVersion
+
+// configure the manifest of the sdkJar task
+jar.manifest.attributes("Main-Class": "com.android.apigenerator.Main")
diff --git a/misc/api-generator/src/main/java/com/android/apigenerator/AndroidJarReader.java b/misc/api-generator/src/main/java/com/android/apigenerator/AndroidJarReader.java
new file mode 100644
index 0000000..8cdfe80
--- /dev/null
+++ b/misc/api-generator/src/main/java/com/android/apigenerator/AndroidJarReader.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apigenerator;
+
+import com.android.utils.Pair;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Reads all the android.jar files found in an SDK and generate a map of {@link ApiClass}.
+ *
+ */
+public class AndroidJarReader {
+
+ private static final byte[] BUFFER = new byte[65535];
+
+ private final int mMinApi;
+ private final ArrayList<String> mPatterns;
+
+ public AndroidJarReader(ArrayList<String> patterns, int minApi) {
+ mPatterns = patterns;
+ mMinApi = minApi;
+ }
+
+ public Map<String, ApiClass> getClasses() {
+ HashMap<String, ApiClass> map = new HashMap<String, ApiClass>();
+
+ // Get all the android.jar. They are in platforms-#
+ int apiLevel = mMinApi - 1;
+ while (true) {
+ apiLevel++;
+ try {
+ File jar = getAndroidJarFile(apiLevel);
+ if (jar == null || !jar.isFile()) {
+ System.out.println("Last API level found: " + (apiLevel-1));
+ break;
+ }
+ System.out.println("Found API " + apiLevel + " at " + jar.getPath());
+
+ FileInputStream fis = new FileInputStream(jar);
+ ZipInputStream zis = new ZipInputStream(fis);
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ String name = entry.getName();
+
+ if (name.endsWith(".class")) {
+
+ int index = 0;
+ do {
+ int size = zis.read(BUFFER, index, BUFFER.length - index);
+ if (size >= 0) {
+ index += size;
+ } else {
+ break;
+ }
+ } while (true);
+
+ byte[] b = new byte[index];
+ System.arraycopy(BUFFER, 0, b, 0, index);
+
+ ClassReader reader = new ClassReader(b);
+ ClassNode classNode = new ClassNode();
+ reader.accept(classNode, 0 /*flags*/);
+
+ if (classNode != null) {
+ ApiClass theClass = addClass(map, classNode.name, apiLevel);
+
+ // super class
+ if (classNode.superName != null) {
+ theClass.addSuperClass(classNode.superName, apiLevel);
+ }
+
+ // interfaces
+ for (Object interfaceName : classNode.interfaces) {
+ theClass.addInterface((String) interfaceName, apiLevel);
+ }
+
+ // fields
+ for (Object field : classNode.fields) {
+ FieldNode fieldNode = (FieldNode) field;
+ if ((fieldNode.access & Opcodes.ACC_PRIVATE) != 0) {
+ continue;
+ }
+ if (fieldNode.name.startsWith("this$") == false &&
+ fieldNode.name.equals("$VALUES") == false) {
+ theClass.addField(fieldNode.name, apiLevel);
+ }
+ }
+
+ // methods
+ for (Object method : classNode.methods) {
+ MethodNode methodNode = (MethodNode) method;
+ if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0) {
+ continue;
+ }
+ if (methodNode.name.equals("<clinit>") == false) {
+ theClass.addMethod(methodNode.name + methodNode.desc, apiLevel);
+ }
+ }
+ }
+ }
+ entry = zis.getNextEntry();
+ }
+
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+
+ }
+ }
+
+ postProcessClasses(map);
+
+ return map;
+ }
+
+ private File getAndroidJarFile(int apiLevel) {
+ for (String pattern : mPatterns) {
+ File f = new File(pattern.replace("%", Integer.toString(apiLevel)));
+ if (f.isFile()) {
+ return f;
+ }
+ }
+ return null;
+ }
+
+ private void postProcessClasses(Map<String, ApiClass> classes) {
+ for (ApiClass theClass : classes.values()) {
+ Map<String, Integer> methods = theClass.getMethods();
+ Map<String, Integer> fixedMethods = new HashMap<String, Integer>();
+
+ List<Pair<String, Integer>> superClasses = theClass.getSuperClasses();
+ List<Pair<String, Integer>> interfaces = theClass.getInterfaces();
+
+ methodLoop: for (Entry<String, Integer> method : methods.entrySet()) {
+ String methodName = method.getKey();
+ int apiLevel = method.getValue();
+
+ if (methodName.startsWith("<init>(") == false) {
+
+ for (Pair<String, Integer> parent : superClasses) {
+ // only check the parent if it was a parent class at the introduction
+ // of the method.
+ if (parent.getSecond() <= apiLevel) {
+ ApiClass parentClass = classes.get(parent.getFirst());
+ assert parentClass != null;
+ if (parentClass != null &&
+ checkClassContains(theClass.getName(),
+ methodName, apiLevel,
+ classes, parentClass)) {
+ continue methodLoop;
+ }
+ }
+ }
+
+ for (Pair<String, Integer> parent : interfaces) {
+ // only check the parent if it was a parent class at the introduction
+ // of the method.
+ if (parent.getSecond() <= apiLevel) {
+ ApiClass parentClass = classes.get(parent.getFirst());
+ assert parentClass != null;
+ if (parentClass != null &&
+ checkClassContains(theClass.getName(),
+ methodName, apiLevel,
+ classes, parentClass)) {
+ continue methodLoop;
+ }
+ }
+ }
+ }
+
+ // if we reach here. the method isn't an override
+ fixedMethods.put(methodName, method.getValue());
+ }
+
+ theClass.replaceMethods(fixedMethods);
+ }
+ }
+
+ private boolean checkClassContains(String className, String methodName, int apiLevel,
+ Map<String, ApiClass> classMap, ApiClass parentClass) {
+
+ Integer parentMethodApiLevel = parentClass.getMethods().get(methodName);
+ if (parentMethodApiLevel != null && parentMethodApiLevel <= apiLevel) {
+ // the parent class has the method and it was introduced in the parent at the
+ // same api level as the method, or before.
+ return true;
+ }
+
+ // check on this class parents.
+ List<Pair<String, Integer>> superClasses = parentClass.getSuperClasses();
+ List<Pair<String, Integer>> interfaces = parentClass.getInterfaces();
+
+ for (Pair<String, Integer> parent : superClasses) {
+ // only check the parent if it was a parent class at the introduction
+ // of the method.
+ if (parent.getSecond() <= apiLevel) {
+ ApiClass superParentClass = classMap.get(parent.getFirst());
+ assert superParentClass != null;
+ if (superParentClass != null && checkClassContains(className, methodName, apiLevel,
+ classMap, superParentClass)) {
+ return true;
+ }
+ }
+ }
+
+ for (Pair<String, Integer> parent : interfaces) {
+ // only check the parent if it was a parent class at the introduction
+ // of the method.
+ if (parent.getSecond() <= apiLevel) {
+ ApiClass superParentClass = classMap.get(parent.getFirst());
+ assert superParentClass != null;
+ if (superParentClass != null && checkClassContains(className, methodName, apiLevel,
+ classMap, superParentClass)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private ApiClass addClass(HashMap<String, ApiClass> classes, String name, int apiLevel) {
+ ApiClass theClass = classes.get(name);
+ if (theClass == null) {
+ theClass = new ApiClass(name, apiLevel);
+ classes.put(name, theClass);
+ }
+
+ return theClass;
+ }
+}
diff --git a/misc/apigenerator/src/com/android/apigenerator/ApiClass.java b/misc/api-generator/src/main/java/com/android/apigenerator/ApiClass.java
similarity index 100%
rename from misc/apigenerator/src/com/android/apigenerator/ApiClass.java
rename to misc/api-generator/src/main/java/com/android/apigenerator/ApiClass.java
diff --git a/misc/api-generator/src/main/java/com/android/apigenerator/Main.java b/misc/api-generator/src/main/java/com/android/apigenerator/Main.java
new file mode 100644
index 0000000..ef50c1d
--- /dev/null
+++ b/misc/api-generator/src/main/java/com/android/apigenerator/Main.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apigenerator;
+
+
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Main class for command line command to convert the existing API XML/TXT files into diff-based
+ * simple text files.
+ */
+public class Main {
+
+ public static void main(String[] args) {
+
+ boolean error = false;
+ int minApi = 1;
+ ArrayList<String> patterns = new ArrayList<String>();
+ String outPath = null;
+
+ for (int i = 0; i < args.length && !error; i++) {
+ String arg = args[i];
+
+ if (arg.equals("--pattern")) {
+ i++;
+ if (i < args.length) {
+ patterns.add(args[i]);
+ } else {
+ System.err.println("Missing argument after " + arg);
+ error = true;
+ }
+
+ } else if (arg.equals("--min-api")) {
+ i++;
+ if (i < args.length) {
+ minApi = Integer.parseInt(args[i]);
+ } else {
+ System.err.println("Missing number >= 1 after " + arg);
+ error = true;
+ }
+ } else if (arg.length() >= 2 && arg.substring(0, 2).equals("--")) {
+ System.err.println("Unknown argument: " + arg);
+ error = true;
+
+ } else if (outPath == null) {
+ outPath = arg;
+
+ } else if (new File(arg).isDirectory()) {
+ String pattern = arg;
+ if (!pattern.endsWith(File.separator)) {
+ pattern += File.separator;
+ }
+ pattern += "platforms" + File.separator + "android-%" + File.separator + "android.jar";
+ patterns.add(pattern);
+
+ } else {
+ System.err.println("Unknown argument: " + arg);
+ error = true;
+ }
+ }
+
+ if (!error && outPath == null) {
+ System.err.println("Missing out file path");
+ error = true;
+ }
+
+ if (!error && patterns.isEmpty()) {
+ System.err.println("Missing SdkFolder or --pattern.");
+ error = true;
+ }
+
+ if (error) {
+ printUsage();
+ System.exit(1);
+ }
+
+ AndroidJarReader reader = new AndroidJarReader(patterns, minApi);
+ Map<String, ApiClass> classes = reader.getClasses();
+ if (!createApiFile(new File(outPath), classes)) {
+ System.exit(1);
+ }
+ }
+
+ private static void printUsage() {
+ System.err.println("\nGenerates a single API file from the content of an SDK.");
+ System.err.println("Usage:");
+ System.err.println("\tApiCheck [--min-api=1] OutFile [SdkFolder | --pattern sdk/%/android.jar]+");
+ System.err.println("Options:");
+ System.err.println("--min-api <int> : The first API level to consider (>=1).");
+ System.err.println("--pattern : Path pattern to find per-API android.jar files, where\n" +
+ " '%' is replacedby the API level.");
+ System.err.println("SdkFolder: if given, this adds the pattern\n" +
+ " '$SdkFolder/platforms/android-%/android.jar'");
+ System.err.println("If multiple --pattern are specified, they are tried in the order given.\n");
+ }
+
+ /**
+ * Creates the simplified diff-based API level.
+ * @param outFolder the out folder.
+ * @param classes
+ */
+ private static boolean createApiFile(File outFile, Map<String, ApiClass> classes) {
+
+ PrintStream ps = null;
+ try {
+ ps = new PrintStream(outFile, "UTF-8");
+ ps.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+ ps.println("<api version=\"1\">");
+ TreeMap<String, ApiClass> map = new TreeMap<String, ApiClass>(classes);
+ for (ApiClass theClass : map.values()) {
+ (theClass).print(ps);
+ }
+ ps.println("</api>");
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ } finally {
+ if (ps != null) {
+ ps.close();
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/misc/apigenerator/.classpath b/misc/apigenerator/.classpath
deleted file mode 100644
index d9b351a..0000000
--- a/misc/apigenerator/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/net/sf/kxml/kxml2/2.3.0/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/net/sf/kxml/kxml2/2.3.0/kxml2-2.3.0-sources.jar"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/org/ow2/asm/asm/4.0/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/org/ow2/asm/asm/4.0/asm-4.0-sources.jar"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/org/ow2/asm/asm-tree/4.0/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/org/ow2/asm/asm-tree/4.0/asm-tree-4.0-sources.jar"/>
- <classpathentry combineaccessrules="false" kind="src" path="/common"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/misc/apigenerator/.project b/misc/apigenerator/.project
deleted file mode 100644
index 2b97ec7..0000000
--- a/misc/apigenerator/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>apigenerator</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
diff --git a/misc/apigenerator/src/com/android/apigenerator/AndroidJarReader.java b/misc/apigenerator/src/com/android/apigenerator/AndroidJarReader.java
deleted file mode 100644
index 07a3ce6..0000000
--- a/misc/apigenerator/src/com/android/apigenerator/AndroidJarReader.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apigenerator;
-
-import com.android.utils.Pair;
-
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-/**
- * Reads all the android.jar files found in an SDK and generate a map of {@link ApiClass}.
- *
- */
-public class AndroidJarReader {
-
- private static final byte[] BUFFER = new byte[65535];
-
- private final String mSdkFolder;
-
- public AndroidJarReader(String sdkFolder) {
- mSdkFolder = sdkFolder;
- }
-
- public Map<String, ApiClass> getClasses() {
- HashMap<String, ApiClass> map = new HashMap<String, ApiClass>();
-
- // Get all the android.jar. They are in platforms-#
- int apiLevel = 0;
- while (true) {
- apiLevel++;
- try {
- File jar = new File(mSdkFolder, "platforms/android-" + apiLevel + "/android.jar");
- if (jar.exists() == false) {
- System.out.println("Last API level found: " + (apiLevel-1));
- break;
- }
-
- FileInputStream fis = new FileInputStream(jar);
- ZipInputStream zis = new ZipInputStream(fis);
- ZipEntry entry = zis.getNextEntry();
- while (entry != null) {
- String name = entry.getName();
-
- if (name.endsWith(".class")) {
-
- int index = 0;
- do {
- int size = zis.read(BUFFER, index, BUFFER.length - index);
- if (size >= 0) {
- index += size;
- } else {
- break;
- }
- } while (true);
-
- byte[] b = new byte[index];
- System.arraycopy(BUFFER, 0, b, 0, index);
-
- ClassReader reader = new ClassReader(b);
- ClassNode classNode = new ClassNode();
- reader.accept(classNode, 0 /*flags*/);
-
- if (classNode != null) {
- ApiClass theClass = addClass(map, classNode.name, apiLevel);
-
- // super class
- if (classNode.superName != null) {
- theClass.addSuperClass(classNode.superName, apiLevel);
- }
-
- // interfaces
- for (Object interfaceName : classNode.interfaces) {
- theClass.addInterface((String) interfaceName, apiLevel);
- }
-
- // fields
- for (Object field : classNode.fields) {
- FieldNode fieldNode = (FieldNode) field;
- if ((fieldNode.access & Opcodes.ACC_PRIVATE) != 0) {
- continue;
- }
- if (fieldNode.name.startsWith("this$") == false &&
- fieldNode.name.equals("$VALUES") == false) {
- theClass.addField(fieldNode.name, apiLevel);
- }
- }
-
- // methods
- for (Object method : classNode.methods) {
- MethodNode methodNode = (MethodNode) method;
- if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0) {
- continue;
- }
- if (methodNode.name.equals("<clinit>") == false) {
- theClass.addMethod(methodNode.name + methodNode.desc, apiLevel);
- }
- }
- }
- }
- entry = zis.getNextEntry();
- }
-
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
-
- }
- }
-
- postProcessClasses(map);
-
- return map;
- }
-
- private void postProcessClasses(Map<String, ApiClass> classes) {
- for (ApiClass theClass : classes.values()) {
- Map<String, Integer> methods = theClass.getMethods();
- Map<String, Integer> fixedMethods = new HashMap<String, Integer>();
-
- List<Pair<String, Integer>> superClasses = theClass.getSuperClasses();
- List<Pair<String, Integer>> interfaces = theClass.getInterfaces();
-
- methodLoop: for (Entry<String, Integer> method : methods.entrySet()) {
- String methodName = method.getKey();
- int apiLevel = method.getValue();
-
- if (methodName.startsWith("<init>(") == false) {
-
- for (Pair<String, Integer> parent : superClasses) {
- // only check the parent if it was a parent class at the introduction
- // of the method.
- if (parent.getSecond() <= apiLevel) {
- ApiClass parentClass = classes.get(parent.getFirst());
- assert parentClass != null;
- if (parentClass != null &&
- checkClassContains(theClass.getName(),
- methodName, apiLevel,
- classes, parentClass)) {
- continue methodLoop;
- }
- }
- }
-
- for (Pair<String, Integer> parent : interfaces) {
- // only check the parent if it was a parent class at the introduction
- // of the method.
- if (parent.getSecond() <= apiLevel) {
- ApiClass parentClass = classes.get(parent.getFirst());
- assert parentClass != null;
- if (parentClass != null &&
- checkClassContains(theClass.getName(),
- methodName, apiLevel,
- classes, parentClass)) {
- continue methodLoop;
- }
- }
- }
- }
-
- // if we reach here. the method isn't an override
- fixedMethods.put(methodName, method.getValue());
- }
-
- theClass.replaceMethods(fixedMethods);
- }
- }
-
- private boolean checkClassContains(String className, String methodName, int apiLevel,
- Map<String, ApiClass> classMap, ApiClass parentClass) {
-
- Integer parentMethodApiLevel = parentClass.getMethods().get(methodName);
- if (parentMethodApiLevel != null && parentMethodApiLevel <= apiLevel) {
- // the parent class has the method and it was introduced in the parent at the
- // same api level as the method, or before.
- return true;
- }
-
- // check on this class parents.
- List<Pair<String, Integer>> superClasses = parentClass.getSuperClasses();
- List<Pair<String, Integer>> interfaces = parentClass.getInterfaces();
-
- for (Pair<String, Integer> parent : superClasses) {
- // only check the parent if it was a parent class at the introduction
- // of the method.
- if (parent.getSecond() <= apiLevel) {
- ApiClass superParentClass = classMap.get(parent.getFirst());
- assert superParentClass != null;
- if (superParentClass != null && checkClassContains(className, methodName, apiLevel,
- classMap, superParentClass)) {
- return true;
- }
- }
- }
-
- for (Pair<String, Integer> parent : interfaces) {
- // only check the parent if it was a parent class at the introduction
- // of the method.
- if (parent.getSecond() <= apiLevel) {
- ApiClass superParentClass = classMap.get(parent.getFirst());
- assert superParentClass != null;
- if (superParentClass != null && checkClassContains(className, methodName, apiLevel,
- classMap, superParentClass)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private ApiClass addClass(HashMap<String, ApiClass> classes, String name, int apiLevel) {
- ApiClass theClass = classes.get(name);
- if (theClass == null) {
- theClass = new ApiClass(name, apiLevel);
- classes.put(name, theClass);
- }
-
- return theClass;
- }
-}
diff --git a/misc/apigenerator/src/com/android/apigenerator/Main.java b/misc/apigenerator/src/com/android/apigenerator/Main.java
deleted file mode 100644
index 4ce7ac9..0000000
--- a/misc/apigenerator/src/com/android/apigenerator/Main.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apigenerator;
-
-
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.PrintStream;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Main class for command line command to convert the existing API XML/TXT files into diff-based
- * simple text files.
- *
- */
-public class Main {
-
- /**
- * @param args
- */
- public static void main(String[] args) {
- if (args.length != 2) {
- printUsage();
- }
-
- AndroidJarReader reader = new AndroidJarReader(args[0]);
- Map<String, ApiClass> classes = reader.getClasses();
- createApiFile(new File(args[1]), classes);
- }
-
- private static void printUsage() {
- System.err.println("Generates a single API file from the content of an SDK.\n");
- System.err.println("Usage\n");
- System.err.println("\tApiCheck SDKFOLDER OUTFILE\n");
- System.exit(1);
- }
-
- /**
- * Creates the simplified diff-based API level.
- * @param outFolder the out folder.
- * @param classes
- */
- private static void createApiFile(File outFile, Map<String, ApiClass> classes) {
-
- PrintStream ps = null;
- try {
- ps = new PrintStream(outFile);
- ps.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
- ps.println("<api version=\"1\">");
- TreeMap<String, ApiClass> map = new TreeMap<String, ApiClass>(classes);
- for (ApiClass theClass : map.values()) {
- (theClass).print(ps);
- }
- ps.println("</api>");
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } finally {
- if (ps != null) {
- ps.close();
- }
- }
- }
-}
diff --git a/misc/distrib_plugins/build.gradle b/misc/distrib_plugins/build.gradle
deleted file mode 100644
index 81e96c1..0000000
--- a/misc/distrib_plugins/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'clone-artifacts'
-
-cloneArtifacts {
- mainRepo = "$rootDir/../../../../prebuilts/tools/common/gradle-plugins/repository"
- secondaryRepo = "$rootDir/../../../../prebuilts/tools/common/gradle-plugins/repository"
-}
-
-shipping {
- isShipping = true
-}
-
-repositories {
- maven { url = uri(cloneArtifacts.mainRepo) }
- maven { url = uri(cloneArtifacts.secondaryRepo) }
-}
-
-// same dependencies as the plugin to add them to its repo.
-dependencies {
- compile "org.apache.commons:commons-io:1.3.2"
- compile "com.google.guava:guava:14.0"
-}
-
-// set it to com.android.tools to filter out the project from testCompile
-group = "com.android.tools"
diff --git a/misc/distrib_plugins/buildSrc/build.gradle b/misc/distrib_plugins/buildSrc/build.gradle
deleted file mode 100644
index 9b40956..0000000
--- a/misc/distrib_plugins/buildSrc/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-apply plugin: 'groovy'
-apply plugin: 'idea'
-apply plugin: 'maven'
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- compile gradleApi()
- groovy localGroovy()
-
- compile "org.apache.commons:commons-io:1.3.2"
- compile "com.google.guava:guava:14.0"
-
- testCompile "junit:junit:3.8.1"
-}
-
-group = "com.android.tools.internal"
-archivesBaseName = "internal-plugins"
-version="1.0"
-
-task updatePrebuilts(type: Upload) {
- configuration = configurations.archives
- repositories {
- mavenDeployer {
- repository(url: uri("$rootDir/../../../../../prebuilts/tools/common/gradle-plugins/repository"))
- }
- }
-}
-
-updatePrebuilts.dependsOn check
-
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
deleted file mode 100644
index 001873f..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/ArtifactDownloader.groovy
+++ /dev/null
@@ -1,319 +0,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.
- */
-
-package com.android.build.gradle.buildsrc
-import com.google.common.base.Charsets
-import com.google.common.collect.Sets
-import com.google.common.hash.HashCode
-import com.google.common.hash.HashFunction
-import com.google.common.hash.Hashing
-import com.google.common.io.Files
-import org.apache.commons.io.FileUtils
-import org.gradle.api.Project
-import org.gradle.api.UnknownDomainObjectException
-import org.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.api.artifacts.ModuleVersionSelector
-import org.gradle.api.artifacts.result.*
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
-
-class ArtifactDownloader {
-
- private static final String URL_MAVEN_CENTRAL = "http://repo1.maven.org/maven2"
-
- private static final String MAVEN_METADATA_XML = "maven-metadata.xml"
- private static final String DOT_MD5 = ".md5"
- private static final String DOT_SHA1 = ".sha1"
- private static final String DOT_POM = ".pom"
- private static final String DOT_JAR = ".jar"
- private static final String SOURCES_JAR = "-sources.jar"
-
- Project project
-
- File mainRepo
- File secondaryRepo
-
- ArtifactDownloader(Project project, File mainRepo, File secondaryRepo) {
- this.project = project
- this.mainRepo = mainRepo
- this.secondaryRepo = secondaryRepo
- }
-
- public void downloadArtifacts() {
-
- Set<ModuleVersionIdentifier> mainList = Sets.newHashSet()
- Set<ModuleVersionIdentifier> secondaryList = Sets.newHashSet()
- Set<ModuleVersionIdentifier> gradleRepoList = Sets.newHashSet()
-
- // gather the main and secondary dependencies for all the sub-projects.
- for (Project subProject : project.allprojects) {
- ResolutionResult resolutionResult
- try {
- resolutionResult = subProject.configurations.getByName("compile")
- .getIncoming().getResolutionResult()
- // if the sub project doesn't ship then we put it's main dependencies in
- // the secondary list.
- buildArtifactList(resolutionResult.getRoot(),
- subProject.shipping.isShipping ? mainList : secondaryList)
- } catch (UnknownDomainObjectException ignored) {
- // ignore
- }
-
- try {
- resolutionResult = subProject.configurations.getByName("testCompile")
- .getIncoming().getResolutionResult()
- buildArtifactList(resolutionResult.getRoot(), secondaryList)
- } catch (UnknownDomainObjectException ignored) {
- // ignore
- }
-
- try {
- resolutionResult = subProject.configurations.getByName("testRuntime")
- .getIncoming().getResolutionResult()
- buildArtifactList(resolutionResult.getRoot(), secondaryList)
- } catch (UnknownDomainObjectException ignored) {
- // ignore
- }
-
- // provided is for artifacts that we need to get from MavenCentral but that
- // are not copied through pushDistribution (because CopyDependenciesTask only
- // look at "compile".
- try {
- resolutionResult = subProject.configurations.getByName("provided")
- .getIncoming().getResolutionResult()
- buildArtifactList(resolutionResult.getRoot(), secondaryList)
- } catch (UnknownDomainObjectException ignored) {
- // ignore
- }
-
- // manually add some artifacts that aren't detected because they are added dynamically
- // when their task run.
- try {
- resolutionResult = subProject.configurations.getByName("hidden")
- .getIncoming().getResolutionResult()
- buildArtifactList(resolutionResult.getRoot(), secondaryList)
- } catch (UnknownDomainObjectException ignored) {
- // ignore
- }
-
- // Download some artifact from the gradle repo.
- try {
- resolutionResult = subProject.configurations.getByName("gradleRepo")
- .getIncoming().getResolutionResult()
- buildArtifactList(resolutionResult.getRoot(), gradleRepoList)
- } catch (UnknownDomainObjectException ignored) {
- // ignore
- }
- }
-
- String[] repoUrls = [ URL_MAVEN_CENTRAL, CloneArtifactsPlugin.GRADLE_RELEASES_REPO,
- CloneArtifactsPlugin.GRADLE_SNAPSHOT_REPO ]
-
- try {
- Set<ModuleVersionIdentifier> downloadedSet = Sets.newHashSet()
- for (ModuleVersionIdentifier id : mainList) {
- pullArtifact(repoUrls, id, mainRepo, downloadedSet)
- }
-
- for (ModuleVersionIdentifier id : secondaryList) {
- pullArtifact(repoUrls, id, secondaryRepo, downloadedSet)
- }
-
- for (ModuleVersionIdentifier id : gradleRepoList) {
- pullArtifact(repoUrls, id, secondaryRepo, downloadedSet)
- }
- } catch (Throwable e) {
- e.printStackTrace()
- }
- }
-
- protected void buildArtifactList(ResolvedModuleVersionResult module,
- Set<ModuleVersionIdentifier> list) {
- list.add(module.id)
-
- for (DependencyResult d : module.getDependencies()) {
- if (d instanceof UnresolvedDependencyResult) {
- UnresolvedDependencyResult dependency = (UnresolvedDependencyResult) d
- ModuleVersionSelector attempted = dependency.getAttempted()
- ModuleVersionIdentifier id = DefaultModuleVersionIdentifier.newId(
- attempted.getGroup(), attempted.getName(), attempted.getVersion())
- list.add(id)
- } else {
- buildArtifactList(((ResolvedDependencyResult) d).getSelected(), list)
- }
- }
- }
-
- 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") && !artifact.group.startsWith("com.android.tools.external.")) ||
- BaseTask.isLocalArtifact(artifact)) {
- return
- }
-
- if (downloadedSet.contains(artifact)) {
- System.out.println("DUPLCTE " + artifact)
- return
- }
-
- downloadedSet.add(artifact)
-
- String folder = getFolder(artifact)
-
- // download the artifact metadata file.
- String repoUrl = tryToDownloadFile(repoUrls, folder, MAVEN_METADATA_XML, rootDestination,
- true, false)
-
- // move to the folder of the required version
- folder = folder + "/" + artifact.getVersion()
-
- // create name base
- String baseName = artifact.getName() + "-" + artifact.getVersion()
-
- // download the pom
- File pomFile = downloadFile(repoUrl ,folder, baseName + DOT_POM, rootDestination,
- false, true)
-
- // read the pom to figure out parents, relocation and packaging
- if (!handlePom(repoUrls, pomFile, rootDestination, downloadedSet)) {
- // pom said there's no jar to download: abort
- return
- }
-
- // download the jar artifact
- downloadFile(repoUrl, folder, baseName + DOT_JAR, rootDestination, false, false)
-
- // download the source if available
- try {
- downloadFile(repoUrl, folder, baseName + SOURCES_JAR, rootDestination, false, false)
- } catch (IOException ignored) {
- // ignore if missing
- }
- }
-
- private static String getFolder(ModuleVersionIdentifier artifact) {
- return artifact.getGroup().replaceAll("\\.", "/") + "/" + artifact.getName()
-
- }
-
- private String tryToDownloadFile(String[] repoUrls, String folder,
- String name, File rootDestination,
- boolean force, boolean printDownload) throws IOException {
- for (String repoUrl : repoUrls) {
- try {
- downloadFile(repoUrl, folder, name, rootDestination, force, printDownload)
- return repoUrl
- } catch (IOException ignored) {
- // ignore
- }
- }
-
- // if we get here, the file was not found in any repo.
- throw new IOException(String.format("Failed to find %s/%s in any repo", folder, name))
- }
-
- private File downloadFile(String repoUrl, String folder, String name, File rootDestination,
- boolean force, boolean printDownload) throws IOException {
- File destinationFolder = new File(rootDestination, folder)
- destinationFolder.mkdirs()
-
- URL fileURL = new URL(repoUrl + "/" + folder + "/" + name)
- File destinationFile = new File(destinationFolder, name)
-
- if (force || !destinationFile.isFile()) {
- if (printDownload) {
- System.out.println("DWNLOAD " + destinationFile.absolutePath)
- }
- FileUtils.copyURLToFile(fileURL, destinationFile)
-
- // get the checksums
- URL md5URL = new URL(repoUrl + "/" + folder + "/" + name + DOT_MD5)
- File md5File = new File(destinationFolder, name + DOT_MD5)
- FileUtils.copyURLToFile(md5URL, md5File)
-
- checksum(destinationFile, md5File, Hashing.md5())
-
- URL sha15URL = new URL(repoUrl + "/" + folder + "/" + name + DOT_SHA1)
- File sha1File = new File(destinationFolder, name + DOT_SHA1)
- FileUtils.copyURLToFile(sha15URL, sha1File)
-
- checksum(destinationFile, sha1File, Hashing.sha1())
- } else if (printDownload) {
- System.out.println("SKIPPED " + destinationFile.absolutePath)
- }
-
- return destinationFile
- }
-
- /**
- * Handles a pom and return true if there is a jar package to download.
- *
- * @param pomFile the pom file
- * @param rootDestination where the download happens, in case parent pom must be downloaded.
- *
- * @return true if jar packaging must be downloaded
- */
- private boolean handlePom(String[] repoUrls, File pomFile, File rootDestination,
- Set<ModuleVersionIdentifier> downloadedSet) {
- PomHandler pomHandler = new PomHandler(pomFile)
-
- ModuleVersionIdentifier relocation = pomHandler.getRelocation()
- if (relocation != null) {
- pullArtifact(repoUrls, relocation, rootDestination, downloadedSet)
- return false
- }
-
- ModuleVersionIdentifier parentPom = pomHandler.getParentPom()
- if (parentPom != null) {
- pullArtifact(repoUrls, parentPom, rootDestination, downloadedSet)
- }
-
- String packaging = pomHandler.getPackaging()
-
- // default packaging is jar so missing data is ok.
- return packaging == null || "jar".equals(packaging) || "bundle".equals(packaging)
- }
-
- private void checksum(File file, File checksumFile, HashFunction hashFunction)
- throws IOException {
- // get the checksum value
- List<String> lines = Files.readLines(checksumFile, Charsets.UTF_8)
- if (lines.isEmpty()) {
- throw new IOException("Empty file: " + checksumFile)
- }
-
- // read the first line only.
- String checksum = lines.get(0).trim()
- // it is possible that the line also contains the file for which this checksum applies:
- // <checksum> <file>
- // remove it
- int pos = checksum.indexOf(' ')
- if (pos != -1) {
- checksum = checksum.substring(0, pos)
- }
-
- // hash the file.
- HashCode hashCode = Files.asByteSource(file).hash(hashFunction)
- String hashCodeString = hashCode.toString()
-
- if (!checksum.equals(hashCodeString)) {
- project.logger.warn(String.format(
- "Wrong checksum!\n\t%s computed for %s\n\t%s read from %s",
- hashCodeString, file,
- checksum, checksumFile))
- }
- }
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/BaseTask.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/BaseTask.groovy
deleted file mode 100644
index 563908c..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/BaseTask.groovy
+++ /dev/null
@@ -1,27 +0,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.
- */
-
-package com.android.build.gradle.buildsrc
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.artifacts.ModuleVersionIdentifier
-
-abstract class BaseTask extends DefaultTask {
-
- public static boolean isLocalArtifact(ModuleVersionIdentifier id) {
- return id.group == "base" || id.group == "swt"
- }
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/CloneArtifactsExtension.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/CloneArtifactsExtension.groovy
deleted file mode 100644
index 822f800..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/CloneArtifactsExtension.groovy
+++ /dev/null
@@ -1,25 +0,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.
- */
-
-package com.android.build.gradle.buildsrc;
-
-/**
- * Extension for the clone-artifact plugin that is applied to the root project.
- */
-public class CloneArtifactsExtension {
- String mainRepo
- String secondaryRepo
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/CloneArtifactsPlugin.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/CloneArtifactsPlugin.groovy
deleted file mode 100644
index 685e400..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/CloneArtifactsPlugin.groovy
+++ /dev/null
@@ -1,78 +0,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.
- */
-
-package com.android.build.gradle.buildsrc
-
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.Task
-import org.gradle.api.UnknownTaskException
-
-class CloneArtifactsPlugin implements Plugin<Project> {
-
- final static String GRADLE_SNAPSHOT_REPO = 'http://repo.gradle.org/gradle/libs-snapshots-local';
- final static String GRADLE_RELEASES_REPO = "http://repo.gradle.org/gradle/libs-releases-local";
-
- @Override
- void apply(Project project) {
- // put some tasks on the project.
- Task cloneArtifacts = project.tasks.create("cloneArtifacts")
- cloneArtifacts.setDescription("Clone dependencies")
-
- ShippingExtension shippingExtension = project.extensions.create('shipping', ShippingExtension)
-
- Task setupTask = project.tasks.create("setupMaven")
- setupTask << {
- project.repositories {
- mavenCentral()
- maven { url GRADLE_SNAPSHOT_REPO }
- maven { url GRADLE_RELEASES_REPO }
- }
- }
-
- cloneArtifacts.dependsOn setupTask
-
- // if this is the top project.
- if (project.rootProject == project) {
- def extension = project.extensions.create('cloneArtifacts', CloneArtifactsExtension)
-
- // default shipping for root project is false
- shippingExtension.isShipping = false
-
- DownloadArtifactsTask downloadArtifactsTask = project.tasks.create("downloadArtifacts",
- DownloadArtifactsTask)
- downloadArtifactsTask.project = project
- downloadArtifactsTask.conventionMapping.mainRepo = { project.file(extension.mainRepo) }
- downloadArtifactsTask.conventionMapping.secondaryRepo = {
- project.file(extension.secondaryRepo)
- }
-
- downloadArtifactsTask.dependsOn setupTask
- cloneArtifacts.dependsOn downloadArtifactsTask
-
- project.afterEvaluate {
- for (Project subProject : project.subprojects) {
- try {
- Task task = subProject.tasks.getByName("cloneArtifacts")
- downloadArtifactsTask.dependsOn task
- } catch (UnknownTaskException e) {
- // ignore
- }
- }
- }
- }
- }
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/CopyDependenciesTask.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/CopyDependenciesTask.groovy
deleted file mode 100644
index d09f8a3..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/CopyDependenciesTask.groovy
+++ /dev/null
@@ -1,46 +0,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.
- */
-
-package com.android.build.gradle.buildsrc
-import com.google.common.io.Files
-import org.gradle.api.Project
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ResolvedArtifact
-import org.gradle.api.tasks.TaskAction
-
-class CopyDependenciesTask extends BaseTask {
-
- Project project
-
- File distributionDir
-
- @TaskAction
- public void copyDependencies() {
-
- Configuration configuration = project.configurations.compile
- Set<ResolvedArtifact> artifacts = configuration.resolvedConfiguration.resolvedArtifacts
- File dir = getDistributionDir()
- for (ResolvedArtifact artifact : artifacts) {
- // check it's not an android artifact
- if (!artifact.moduleVersion.id.group.startsWith("com.android.tools") &&
- !isLocalArtifact(artifact.moduleVersion.id)) {
- if (artifact.type == "jar") {
- Files.copy(artifact.file, new File(dir, artifact.file.name))
- }
- }
- }
- }
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/DistributionExtension.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/DistributionExtension.groovy
deleted file mode 100644
index 03dbf58..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/DistributionExtension.groovy
+++ /dev/null
@@ -1,25 +0,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.
- */
-
-package com.android.build.gradle.buildsrc;
-
-/**
- * Basic extension for the distribution plugin
- */
-public class DistributionExtension {
- String destinationPath
- String dependenciesRepo
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/DistributionPlugin.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/DistributionPlugin.groovy
deleted file mode 100644
index 119c20a..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/DistributionPlugin.groovy
+++ /dev/null
@@ -1,151 +0,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.
- */
-
-package com.android.build.gradle.buildsrc
-
-import com.google.common.collect.Sets
-import org.gradle.api.Project
-import org.gradle.api.Task
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ResolvedArtifact
-import org.gradle.api.tasks.Copy
-import org.gradle.api.tasks.bundling.Jar
-
-class DistributionPlugin implements org.gradle.api.Plugin<Project> {
-
- private Project project
-
- @Override
- void apply(Project project) {
- this.project = project
-
- // put some tasks on the project.
- Task pushDistribution = project.tasks.create("pushDistribution")
- pushDistribution.group = "Upload"
- pushDistribution.description = "Push the distribution artifacts into the prebuilt folder"
-
- // if this is the top project.
- if (project.rootProject == project) {
- DistributionExtension extension = project.extensions.create('distribution',
- DistributionExtension)
-
- // deal with NOTICE files from all the sub projects
- GatherNoticesTask gatherNoticesTask = project.tasks.create(
- "gatherNotices", GatherNoticesTask)
- gatherNoticesTask.project = project
- gatherNoticesTask.conventionMapping.distributionDir = {
- project.file(extension.destinationPath + "/notices")
- }
- gatherNoticesTask.conventionMapping.repoDir = {
- project.file(extension.dependenciesRepo)
- }
-
- pushDistribution.dependsOn gatherNoticesTask
- } else {
- Jar buildTask = project.tasks.create("buildDistributionJar", Jar)
- buildTask.from(project.sourceSets.main.output)
- buildTask.conventionMapping.destinationDir = {
- project.file(project.rootProject.distribution.destinationPath + "/tools/lib")
- }
- buildTask.conventionMapping.archiveName = {project.archivesBaseName + ".jar" }
-
- pushDistribution.dependsOn buildTask
-
- // delay computing the manifest classpath only if the
- // prebuiltJar task is set to run.
- project.gradle.taskGraph.whenReady { taskGraph ->
- if (taskGraph.hasTask(project.tasks.buildDistributionJar)) {
- project.tasks.buildDistributionJar.manifest.attributes(
- "Class-Path": getClassPath())
- }
- }
-
- Copy copyTask = project.tasks.create("copyLauncherScripts", Copy)
- copyTask.from {
- if (project.shipping.launcherScripts != null) {
- return project.files(project.shipping.launcherScripts.toArray())
- }
- return null
- }
- copyTask.conventionMapping.destinationDir = {
- project.file(project.rootProject.distribution.destinationPath + "/tools")
- }
- pushDistribution.dependsOn copyTask
-
- // also copy the dependencies
- CopyDependenciesTask copyDependenciesTask = project.tasks.create(
- "copyDependencies", CopyDependenciesTask)
- copyDependenciesTask.project = project
- copyDependenciesTask.conventionMapping.distributionDir = {
- project.file(project.rootProject.distribution.destinationPath + "/tools/lib")
- }
-
- copyDependenciesTask.onlyIf { project.shipping.isShipping }
-
- pushDistribution.dependsOn copyDependenciesTask
-
- // only push distribution of projects that are shipped.
- // When the task are created the project is not fully evaluated.
- project.afterEvaluate {
- if (!project.shipping.isShipping) {
- buildTask.enabled = false
- copyDependenciesTask.enabled = false
- copyTask.enabled = false
- }
- }
- }
- }
-
- private String getClassPath() {
- StringBuilder sb = new StringBuilder()
-
- Configuration configuration = project.configurations.runtime
- getClassPathFromConfiguration(configuration, sb)
-
- return sb.toString()
- }
-
- protected void getClassPathFromConfiguration(Configuration configuration, StringBuilder sb) {
- // need to detect local files, so we first do a search by artifacts.
- Set<String> processedFiles = Sets.newHashSet()
-
- Set<ResolvedArtifact> artifacts = configuration.resolvedConfiguration.resolvedArtifacts
- for (ResolvedArtifact artifact : artifacts) {
- def group = artifact.moduleVersion.id.group
- if (group.startsWith('com.android.tools') || group == 'base' || group == 'swt') {
- // add the shorter name for the android dependencies
- sb.append(' ').append(artifact.moduleVersion.id.name + ".jar")
- } else {
- // add the full name
- sb.append(' ').append(artifact.file.name)
- }
- processedFiles << artifact.file.name
- }
-
- // for local file, go through the file list, and look at non processed files yet.
- for (File file : configuration.files) {
- String name = file.name
- if (processedFiles.contains(name)) {
- continue
- }
- String suffix = "-" + project.version + ".jar"
- if (name.endsWith(suffix)) {
- name = name.substring(0, name.size() - suffix.size()) + ".jar"
- }
- sb.append(' ').append(name)
- }
- }
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/DownloadArtifactsTask.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/DownloadArtifactsTask.groovy
deleted file mode 100644
index e7636c0..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/DownloadArtifactsTask.groovy
+++ /dev/null
@@ -1,31 +0,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.
- */
-
-package com.android.build.gradle.buildsrc
-import org.gradle.api.Project
-import org.gradle.api.tasks.TaskAction
-
-class DownloadArtifactsTask extends BaseTask {
-
- Project project
- File mainRepo
- File secondaryRepo
-
- @TaskAction
- public void downloadArtifacts() {
- new ArtifactDownloader(getProject(), getMainRepo(), getSecondaryRepo()).downloadArtifacts()
- }
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/GatherNoticesTask.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/GatherNoticesTask.groovy
deleted file mode 100644
index e6ae60f..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/GatherNoticesTask.groovy
+++ /dev/null
@@ -1,162 +0,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.
- */
-
-package com.android.build.gradle.buildsrc
-import com.google.common.base.Charsets
-import com.google.common.base.Joiner
-import com.google.common.collect.Lists
-import com.google.common.collect.Sets
-import com.google.common.io.Files
-import org.gradle.api.GradleException
-import org.gradle.api.Project
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.api.artifacts.ResolvedArtifact
-import org.gradle.api.tasks.TaskAction
-
-class GatherNoticesTask extends BaseTask {
-
- Project project
-
- File distributionDir
-
- File repoDir
-
- @TaskAction
- public void gatherNotices() {
-
- Set<ModuleVersionIdentifier> dependencyCache = Sets.newHashSet()
-
- File mainDir = getDistributionDir()
- File noticeDir = new File(mainDir, project.name)
- noticeDir.deleteDir()
- noticeDir.mkdirs()
- File repo = getRepoDir()
-
- // gather the notice files into the output folder
- for (Project subProject : project.subprojects) {
-
- if (!subProject.shipping.isShipping) {
- continue
- }
-
- File fromFile = new File(subProject.projectDir, "NOTICE")
- if (!fromFile.isFile()) {
- throw new GradleException("Missing NOTICE file: " + fromFile.absolutePath)
- }
-
- // copy to the noticeDir folder, adding a header
- File toFile = new File(noticeDir, "NOTICE_" + subProject.name + ".jar.txt")
- copyNoticeAndAddHeader(fromFile, toFile, subProject.name + ".jar")
-
- Configuration configuration = subProject.configurations.compile
- gatherFromConfiguration(configuration, dependencyCache, repo, noticeDir)
- }
-
- // merge all the files together.
- // First gather them so that they can be sorted before merging them. This will limit
- // the amount of diff in git when there is an update.
- File[] folders = mainDir.listFiles()
- Set<String> noticeCache = Sets.newTreeSet();
- List<File> notices = Lists.newArrayList();
- if (folders != null) {
- for (File folder : folders) {
- if (folder.isDirectory()) {
- gatherNotices(folder, noticeCache, notices)
- }
- }
- }
-
- // sort the notices
- Collections.sort(notices);
-
- // merge and write the result
- File mainNoticeFile = new File(mainDir, "NOTICE.txt")
- BufferedWriter writer = Files.newWriter(mainNoticeFile, Charsets.UTF_8)
- mergeNotices(notices, writer);
- writer.close()
- }
-
- private static void gatherFromConfiguration(Configuration configuration,
- Set<ModuleVersionIdentifier> dependencyCache,
- File repo, File noticeDir) {
- File toFile, fromFile
- Set<ResolvedArtifact> artifacts = configuration.resolvedConfiguration.resolvedArtifacts
- for (ResolvedArtifact artifact : artifacts) {
- // check it's not an android artifact
- if (!artifact.moduleVersion.id.group.startsWith("com.android.tools") &&
- !isLocalArtifact(artifact.moduleVersion.id)) {
- if (artifact.type == "jar") {
- ModuleVersionIdentifier id = artifact.moduleVersion.id
- // manually look for the NOTICE file in the repo
- if (!dependencyCache.contains(id)) {
- dependencyCache.add(id)
-
- fromFile = new File(repo,
- id.group.replace('.', '/') +
- '/' + id.name + '/' + id.version + '/NOTICE')
- if (!fromFile.isFile()) {
- throw new GradleException(
- "Missing NOTICE file: " + fromFile.absolutePath)
- }
-
- toFile = new File(noticeDir, "NOTICE_" + artifact.file.name + ".txt")
- copyNoticeAndAddHeader(fromFile, toFile, artifact.file.name)
- }
- }
- }
- }
- }
-
- private static void gatherNotices(File folder, Set<String> filenameCache,
- List<File> noticeList) {
- File[] files = folder.listFiles();
- if (files != null) {
- for (File file : files) {
- if (file.isFile() && file.name.startsWith("NOTICE_") &&
- !filenameCache.contains(file.name)) {
- filenameCache.add(file.name)
- noticeList.add(file)
- }
- }
- }
- }
-
- private static void mergeNotices(List<File> notices, BufferedWriter noticeWriter) {
- for (File file : notices) {
- List<String> lines = Files.readLines(file, Charsets.UTF_8)
- for (String line : lines) {
- noticeWriter.write(line, 0, line.length())
- noticeWriter.newLine()
- }
- noticeWriter.newLine()
- }
- }
-
- private static void copyNoticeAndAddHeader(File from, File to, String name) {
- List<String> lines = Files.readLines(from, Charsets.UTF_8)
- List<String> noticeLines = Lists.newArrayListWithCapacity(lines.size() + 4)
- noticeLines.addAll([
- "============================================================",
- "Notices for file(s):",
- name,
- "------------------------------------------------------------"
- ]);
- noticeLines.addAll(lines);
-
- Files.write(Joiner.on("\n").join(noticeLines.iterator()), to, Charsets.UTF_8)
- }
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/ShippingExtension.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/ShippingExtension.groovy
deleted file mode 100644
index 7a1f671..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/ShippingExtension.groovy
+++ /dev/null
@@ -1,25 +0,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.
- */
-
-package com.android.build.gradle.buildsrc;
-
-/**
- * Extension for the project to know if the project is shipping or not.
- */
-public class ShippingExtension {
- boolean isShipping = true
- List<String> launcherScripts
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/java/com/android/build/gradle/buildsrc/PomHandler.java b/misc/distrib_plugins/buildSrc/src/main/java/com/android/build/gradle/buildsrc/PomHandler.java
deleted file mode 100644
index 78502db..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/java/com/android/build/gradle/buildsrc/PomHandler.java
+++ /dev/null
@@ -1,242 +0,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.
- */
-
-package com.android.build.gradle.buildsrc;
-
-import com.google.common.io.Closeables;
-import org.gradle.api.artifacts.ModuleIdentifier;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-
-/**
- * Class able to parse a POM file, and provide some information from it:
- * - Is the artifact relocated (and what's the relocation artifact)
- * - parent POM
- * - packaging
- */
-class PomHandler {
-
- private final File pomFile;
- private Document document = null;
-
- private final static class FakeModuleVersionIdentifier implements ModuleVersionIdentifier {
-
- private final String group;
- private final String name;
- private final String version;
-
- FakeModuleVersionIdentifier(String group, String name, String version) {
-
- this.group = group;
- this.name = name;
- this.version = version;
- }
-
- @Override
- public String getVersion() {
- return version;
- }
-
- @Override
- public String getGroup() {
- return group;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public ModuleIdentifier getModule() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String toString() {
- return getGroup() + ":" + getName() + ":" + getVersion();
- }
- }
-
- PomHandler(File pomFile) {
- this.pomFile = pomFile;
- }
-
- ModuleVersionIdentifier getRelocation() throws IOException {
- Document document = getDocument();
- Node rootNode = document.getDocumentElement();
-
- Node node = findNode(rootNode, "distributionManagement");
- if (node == null) {
- return null;
- }
-
- node = findNode(node, "relocation");
- if (node == null) {
- return null;
- }
-
- // ok there is a relocation. Let's find out the current artifact address
- ModuleVersionIdentifier original = readArtifactAddress(rootNode);
-
- // now read the relocation info
- ModuleVersionIdentifier relocation = readArtifactAddress(node);
-
- // merge them in case only part of the address is changed
- return new FakeModuleVersionIdentifier(
- relocation.getGroup() != null ? relocation.getGroup() : original.getGroup(),
- relocation.getName() != null ? relocation.getName() : original.getName(),
- relocation.getVersion() != null ? relocation.getVersion() : original.getVersion());
- }
-
- private ModuleVersionIdentifier readArtifactAddress(Node parentNode) {
- String group = null;
- String name = null;
- String version = null;
-
- Node node = findNode(parentNode, "groupId");
- if (node != null) {
- group = getTextNode(node);
- }
-
- node = findNode(parentNode, "artifactId");
- if (node != null) {
- name = getTextNode(node);
- }
-
- node = findNode(parentNode, "version");
- if (node != null) {
- version = getTextNode(node);
- }
-
- return new FakeModuleVersionIdentifier(group, name, version);
- }
-
- ModuleVersionIdentifier getParentPom() throws IOException {
- Document document = getDocument();
- Node rootNode = document.getDocumentElement();
-
- Node node = findNode(rootNode, "parent");
- if (node == null) {
- return null;
- }
-
- // there is a parent. Look for the rest of the nodes.
- final Node groupId = findNode(node, "groupId");
- final Node artifactId = findNode(node, "artifactId");
- final Node version = findNode(node, "version");
-
- if (groupId == null || artifactId == null || version == null) {
- return null;
- }
-
- return new FakeModuleVersionIdentifier(
- getTextNode(groupId),
- getTextNode(artifactId),
- getTextNode(version));
- }
-
- public String getPackaging() throws IOException {
- Document document = getDocument();
-
- Node rootNode = document.getDocumentElement();
-
- Node node = findNode(rootNode, "packaging");
- if (node == null) {
- return null;
- }
-
- return getTextNode(node);
- }
-
- private Document getDocument() throws IOException {
- if (document == null) {
- document = parseDocument(pomFile);
- }
-
- return document;
- }
-
- private Node findNode(Node rootNode, String name) {
- NodeList nodes = rootNode.getChildNodes();
-
- for (int i = 0, n = nodes.getLength(); i < n; i++) {
- Node node = nodes.item(i);
-
- if (node.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
-
- if (name.equals(node.getLocalName())) {
- return node;
- }
- }
-
- return null;
- }
-
- private String getTextNode(Node rootNode) {
- NodeList nodes = rootNode.getChildNodes();
-
- for (int i = 0, n = nodes.getLength(); i < n; i++) {
- Node node = nodes.item(i);
-
- if (node.getNodeType() != Node.TEXT_NODE) {
- continue;
- }
-
- return node.getNodeValue();
- }
-
- return null;
- }
-
- /**
- * Loads the DOM for a given file and returns a {@link org.w3c.dom.Document} object.
- * @param file the file to parse
- * @return a Document object.
- * @throws java.io.IOException
- */
- static Document parseDocument(File file) throws IOException {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- BufferedInputStream stream = new BufferedInputStream(new FileInputStream(file));
- InputSource is = new InputSource(stream);
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- try {
- DocumentBuilder builder = factory.newDocumentBuilder();
- return builder.parse(is);
- } catch (ParserConfigurationException e) {
- throw new IOException(e);
- } catch (SAXException e) {
- throw new IOException(e);
- } finally {
- Closeables.closeQuietly(stream);
- }
- }
-}
diff --git a/misc/distrib_plugins/buildSrc/src/main/resources/META-INF/gradle-plugins/clone-artifacts.properties b/misc/distrib_plugins/buildSrc/src/main/resources/META-INF/gradle-plugins/clone-artifacts.properties
deleted file mode 100644
index 69baed7..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/resources/META-INF/gradle-plugins/clone-artifacts.properties
+++ /dev/null
@@ -1 +0,0 @@
-implementation-class=com.android.build.gradle.buildsrc.CloneArtifactsPlugin
diff --git a/misc/distrib_plugins/buildSrc/src/main/resources/META-INF/gradle-plugins/distrib.properties b/misc/distrib_plugins/buildSrc/src/main/resources/META-INF/gradle-plugins/distrib.properties
deleted file mode 100644
index 92eb7a2..0000000
--- a/misc/distrib_plugins/buildSrc/src/main/resources/META-INF/gradle-plugins/distrib.properties
+++ /dev/null
@@ -1 +0,0 @@
-implementation-class=com.android.build.gradle.buildsrc.DistributionPlugin
diff --git a/misc/distrib_plugins/buildSrc/src/test/java/com/android/build/gradle/buildsrc/PomHandlerTest.java b/misc/distrib_plugins/buildSrc/src/test/java/com/android/build/gradle/buildsrc/PomHandlerTest.java
deleted file mode 100644
index 0e91e2b..0000000
--- a/misc/distrib_plugins/buildSrc/src/test/java/com/android/build/gradle/buildsrc/PomHandlerTest.java
+++ /dev/null
@@ -1,73 +0,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.
- */
-package com.android.build.gradle.buildsrc;
-
-import com.google.common.io.ByteStreams;
-import junit.framework.TestCase;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class PomHandlerTest extends TestCase {
-
- public void testIsRelocated() throws IOException {
- PomHandler pomHandler = new PomHandler(
- getFile(getClass().getResourceAsStream("/kxml2-2.3.0.pom")));
- assertNotNull(pomHandler.getRelocation());
-
- pomHandler = new PomHandler(
- getFile(getClass().getResourceAsStream("/guava-13.0.1.pom")));
- assertNull(pomHandler.getRelocation());
- }
-
- public void testFindParent() throws IOException {
- PomHandler pomHandler = new PomHandler(
- getFile(getClass().getResourceAsStream("/guava-13.0.1.pom")));
- ModuleVersionIdentifier id = pomHandler.getParentPom();
- assertNotNull(id);
- assertEquals("com.google.guava", id.getGroup());
- assertEquals("guava-parent", id.getName());
- assertEquals("13.0.1", id.getVersion());
-
- pomHandler = new PomHandler(
- getFile(getClass().getResourceAsStream("/kxml2-2.3.0.pom")));
- assertNull(pomHandler.getParentPom());
- }
-
- public void testPackaging() throws IOException {
- PomHandler pomHandler = new PomHandler(
- getFile(getClass().getResourceAsStream("/oss-parent-7.pom")));
- assertEquals("pom", pomHandler.getPackaging());
-
- pomHandler = new PomHandler(
- getFile(getClass().getResourceAsStream("/guava-13.0.1.pom")));
- assertNull(pomHandler.getPackaging());
- }
-
- private File getFile(InputStream inputStream) throws IOException {
- File tmpFile = File.createTempFile("pomhandler","");
- tmpFile.deleteOnExit();
-
- FileOutputStream fos = new FileOutputStream(tmpFile);
- ByteStreams.copy(inputStream, fos);
- fos.close();
-
- return tmpFile;
- }
-}
diff --git a/misc/distrib_plugins/buildSrc/src/test/resources/guava-13.0.1.pom b/misc/distrib_plugins/buildSrc/src/test/resources/guava-13.0.1.pom
deleted file mode 100644
index acb6b48..0000000
--- a/misc/distrib_plugins/buildSrc/src/test/resources/guava-13.0.1.pom
+++ /dev/null
@@ -1,156 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>com.google.guava</groupId>
- <artifactId>guava-parent</artifactId>
- <version>13.0.1</version>
- </parent>
- <artifactId>guava</artifactId>
- <name>Guava: Google Core Libraries for Java</name>
- <description>
- Guava is a suite of core and expanded libraries that include
- utility classes, google's collections, io classes, and much
- much more.
-
- Guava has only one code dependency - javax.annotation,
- per the JSR-305 spec.
- </description>
- <dependencies>
- <dependency>
- <groupId>com.google.code.findbugs</groupId>
- <artifactId>jsr305</artifactId>
- <version>1.3.9</version>
- <scope>provided</scope>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <artifactId>maven-jar-plugin</artifactId>
- <version>2.4</version>
- <configuration>
- <archive>
- <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
- </archive>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <version>2.3.7</version>
- <executions>
- <execution>
- <id>bundle-manifest</id>
- <phase>process-classes</phase>
- <goals>
- <goal>manifest</goal>
- </goals>
- </execution>
- </executions>
- <configuration>
- <instructions>
- <Export-Package>!com.google.common.base.internal,com.google.common.*</Export-Package>
- <Import-Package>
- javax.annotation;resolution:=optional,
- sun.misc.*;resolution:=optional
- </Import-Package>
- </instructions>
- </configuration>
- </plugin>
- <plugin>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.3.2</version>
- <configuration>
- <source>1.6</source>
- <target>1.6</target>
- </configuration>
- </plugin>
- <plugin>
- <artifactId>maven-source-plugin</artifactId>
- <version>2.1.2</version>
- <executions>
- <execution>
- <id>attach-sources</id>
- <phase>post-integration-test</phase>
- <goals><goal>jar</goal></goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <artifactId>maven-javadoc-plugin</artifactId>
- <version>2.8</version>
- <executions>
- <execution>
- <id>attach-docs</id>
- <phase>post-integration-test</phase>
- <goals><goal>jar</goal></goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>animal-sniffer-maven-plugin</artifactId>
- <version>1.7</version>
- <configuration>
- <signature>
- <groupId>org.codehaus.mojo.signature</groupId>
- <artifactId>java16-sun</artifactId>
- <version>1.0</version>
- </signature>
- </configuration>
- <executions>
- <execution>
- <id>check-java16-sun</id>
- <phase>test</phase>
- <goals>
- <goal>check</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
- <version>2.8</version>
- <configuration>
- <encoding>UTF-8</encoding>
- <docencoding>UTF-8</docencoding>
- <charset>UTF-8</charset>
- <additionalparam>-XDignore.symbol.file</additionalparam>
- <excludePackageNames>com.google.common.base.internal</excludePackageNames>
- <linksource>true</linksource>
- <links>
- <link>http://jsr-305.googlecode.com/svn/trunk/javadoc</link>
- </links>
- </configuration>
- <executions>
- <execution>
- <id>generate-javadoc-site-report</id>
- <phase>site</phase>
- <goals><goal>javadoc</goal></goals>
- </execution>
- <execution>
- <id>generate-jdiff-site-report</id>
- <phase>site</phase>
- <goals><goal>javadoc</goal></goals>
- <configuration>
- <doclet>jdiff.JDiff</doclet>
- <docletPath>${project.basedir}/lib/jdiff.jar</docletPath>
- <additionalparam>
- -XDignore.symbol.file -apiname 'Guava ${project.version}'
- </additionalparam>
- <useStandardDocletOptions>false</useStandardDocletOptions>
- <reportOutputDirectory>${project.reporting.outputDirectory}</reportOutputDirectory>
- <destDir>jdiff</destDir>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- <finalName>${project.artifactId}-${project.version}</finalName>
- <sourceDirectory>src</sourceDirectory>
- <testSourceDirectory>disabled</testSourceDirectory>
- </build>
-</project>
diff --git a/misc/distrib_plugins/buildSrc/src/test/resources/kxml2-2.3.0.pom b/misc/distrib_plugins/buildSrc/src/test/resources/kxml2-2.3.0.pom
deleted file mode 100644
index db3f945..0000000
--- a/misc/distrib_plugins/buildSrc/src/test/resources/kxml2-2.3.0.pom
+++ /dev/null
@@ -1,13 +0,0 @@
-<project>
- <modelVersion>4.0.0</modelVersion>
- <groupId>kxml2</groupId>
- <artifactId>kxml2</artifactId>
- <version>2.3.0</version>
-
- <distributionManagement>
- <relocation>
- <groupId>net.sf.kxml</groupId>
- </relocation>
- </distributionManagement>
-
-</project>
diff --git a/misc/distrib_plugins/buildSrc/src/test/resources/oss-parent-7.pom b/misc/distrib_plugins/buildSrc/src/test/resources/oss-parent-7.pom
deleted file mode 100644
index 3963952..0000000
--- a/misc/distrib_plugins/buildSrc/src/test/resources/oss-parent-7.pom
+++ /dev/null
@@ -1,155 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- ~ Copyright (c) 2007-2011 Sonatype, Inc. All rights reserved.
- ~
- ~ This program is licensed to you under the Apache License Version 2.0,
- ~ and you may not use this file except in compliance with the Apache License Version 2.0.
- ~ You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
- ~
- ~ Unless required by applicable law or agreed to in writing,
- ~ software distributed under the Apache License Version 2.0 is distributed on an
- ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
- -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>org.sonatype.oss</groupId>
- <artifactId>oss-parent</artifactId>
- <version>7</version>
- <packaging>pom</packaging>
-
- <name>Sonatype OSS Parent</name>
- <url>http://nexus.sonatype.org/oss-repository-hosting.html</url>
- <description>Sonatype helps open source projects to set up Maven repositories on https://oss.sonatype.org/ </description>
-
- <scm>
- <connection>scm:svn:http://svn.sonatype.org/spice/tags/oss-parent-7</connection>
- <developerConnection>scm:svn:https://svn.sonatype.org/spice/tags/oss-parent-7</developerConnection>
- <url>http://svn.sonatype.org/spice/tags/oss-parent-7</url>
- </scm>
-
- <repositories>
- <repository>
- <id>sonatype-nexus-snapshots</id>
- <name>Sonatype Nexus Snapshots</name>
- <url>https://oss.sonatype.org/content/repositories/snapshots</url>
- <releases>
- <enabled>false</enabled>
- </releases>
- <snapshots>
- <enabled>true</enabled>
- </snapshots>
- </repository>
- </repositories>
-
-
- <distributionManagement>
- <snapshotRepository>
- <id>sonatype-nexus-snapshots</id>
- <name>Sonatype Nexus Snapshots</name>
- <url>${sonatypeOssDistMgmtSnapshotsUrl}</url>
- </snapshotRepository>
- <repository>
- <id>sonatype-nexus-staging</id>
- <name>Nexus Release Repository</name>
- <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
- </repository>
- </distributionManagement>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-enforcer-plugin</artifactId>
- <version>1.0</version>
- <executions>
- <execution>
- <id>enforce-maven</id>
- <goals>
- <goal>enforce</goal>
- </goals>
- <configuration>
- <rules>
- <requireMavenVersion>
- <version>(,2.1.0),(2.1.0,2.2.0),(2.2.0,)</version>
- <message>Maven 2.1.0 and 2.2.0 produce incorrect GPG signatures and checksums respectively.</message>
- </requireMavenVersion>
- </rules>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- <pluginManagement>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-release-plugin</artifactId>
- <version>2.1</version>
- <configuration>
- <mavenExecutorId>forked-path</mavenExecutorId>
- <useReleaseProfile>false</useReleaseProfile>
- <arguments>-Psonatype-oss-release</arguments>
- </configuration>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
-
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <sonatypeOssDistMgmtSnapshotsUrl>https://oss.sonatype.org/content/repositories/snapshots/</sonatypeOssDistMgmtSnapshotsUrl>
- </properties>
-
- <profiles>
- <profile>
- <id>sonatype-oss-release</id>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- <version>2.1.2</version>
- <executions>
- <execution>
- <id>attach-sources</id>
- <goals>
- <goal>jar-no-fork</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
- <version>2.7</version>
- <executions>
- <execution>
- <id>attach-javadocs</id>
- <goals>
- <goal>jar</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-gpg-plugin</artifactId>
- <version>1.1</version>
- <executions>
- <execution>
- <id>sign-artifacts</id>
- <phase>verify</phase>
- <goals>
- <goal>sign</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </profile>
- </profiles>
-
-</project>
diff --git a/misc/screenshot2/build.gradle b/misc/screenshot2/build.gradle
index 7e597c4..51a060f 100644
--- a/misc/screenshot2/build.gradle
+++ b/misc/screenshot2/build.gradle
@@ -1,19 +1,22 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools'
archivesBaseName = 'screenshot2'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':ddmlib')
+ compile project(':base:ddmlib')
}
-shipping {
- launcherScripts = ['etc/screenshot2']
- isShipping = true
+sdk {
+ linux {
+ item('etc/screenshot2') { executable true }
+ }
+ mac {
+ item('etc/screenshot2') { executable true }
+ }
}
-// 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
+// configure the manifest of the sdkJar task.
+sdkJar.manifest.attributes("Main-Class": "com.android.screenshot.Screenshot")
diff --git a/ninepatch/build.gradle b/ninepatch/build.gradle
index c1109ff..951548c 100644
--- a/ninepatch/build.gradle
+++ b/ninepatch/build.gradle
@@ -1,8 +1,9 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools'
archivesBaseName = 'ninepatch'
+version = rootProject.ext.baseVersion
dependencies {
testCompile 'junit:junit:3.8.1'
@@ -12,5 +13,3 @@
main.resources.srcDir 'src/main/java'
test.resources.srcDir 'src/test/java'
}
-
-apply from: '../baseVersion.gradle'
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 4ed03e4..c174cdb 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
@@ -18,6 +18,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
+import com.android.ddmlib.ByteBufferUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
@@ -28,13 +29,10 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
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'
@@ -64,7 +62,7 @@
public void parse() throws IOException {
long headerLength = parseHeader(mTraceFile);
- MappedByteBuffer buffer = mapFile(mTraceFile, headerLength);
+ ByteBuffer buffer = ByteBufferUtil.mapFile(mTraceFile, headerLength, ByteOrder.LITTLE_ENDIAN);
parseData(buffer);
computeTimingStatistics();
}
@@ -138,7 +136,11 @@
}
} finally {
if (in != null) {
- Closeables.closeQuietly(in);
+ try {
+ Closeables.close(in, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
}
@@ -233,7 +235,7 @@
*
* All values are stored in little-endian order.
*/
- private void parseData(MappedByteBuffer buffer) {
+ private void parseData(ByteBuffer buffer) {
int recordSize = readDataFileHeader(buffer);
parseMethodTraceData(buffer, recordSize);
}
@@ -258,7 +260,7 @@
*
* 32 bits of microseconds is 70 minutes.
*/
- private void parseMethodTraceData(MappedByteBuffer buffer, int recordSize) {
+ private void parseMethodTraceData(ByteBuffer buffer, int recordSize) {
int methodId;
int threadId;
int version = mTraceDataBuilder.getVersion();
@@ -329,7 +331,7 @@
* @param buffer byte buffer pointing to the header
* @return record size for each data entry following the header
*/
- private int readDataFileHeader(MappedByteBuffer buffer) {
+ private int readDataFileHeader(ByteBuffer buffer) {
int magic = buffer.getInt();
if (magic != TRACE_MAGIC) {
String msg = String.format("Error: magic number mismatch; got 0x%x, expected 0x%x\n",
@@ -382,19 +384,6 @@
return recordSize;
}
- private MappedByteBuffer mapFile(File f, long offset) throws IOException {
- FileInputStream dataFile = new FileInputStream(f);
- try {
- FileChannel fc = dataFile.getChannel();
- MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, offset,
- f.length() - offset);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- return buffer;
- } finally {
- dataFile.close(); // this *also* closes the associated channel, fc
- }
- }
-
private void computeTimingStatistics() {
VmTraceData data = getTraceData();
diff --git a/publish.gradle b/publish.gradle
deleted file mode 100644
index 7dbe36d..0000000
--- a/publish.gradle
+++ /dev/null
@@ -1,78 +0,0 @@
-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/release.gradle b/release.gradle
deleted file mode 100644
index 97da7d6..0000000
--- a/release.gradle
+++ /dev/null
@@ -1,5 +0,0 @@
-allprojects {
- project.ext {
- release = "true"
- }
-}
\ No newline at end of file
diff --git a/rule-api/.classpath b/rule-api/.classpath
index 81a1b1f..9533f60 100644
--- a/rule-api/.classpath
+++ b/rule-api/.classpath
@@ -3,7 +3,7 @@
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
- <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"/>
+ <classpathentry exported="true" kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/layoutlib-api"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/rule-api/build.gradle b/rule-api/build.gradle
index 824e698..ed2f763 100644
--- a/rule-api/build.gradle
+++ b/rule-api/build.gradle
@@ -1,12 +1,11 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools'
archivesBaseName = 'rule-api'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':common')
- compile project(':layoutlib-api')
+ compile project(':base:common')
+ compile project(':base: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 b9e0c94..30dc2e0 100644
--- a/sdk-common/build.gradle
+++ b/sdk-common/build.gradle
@@ -1,25 +1,21 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
group = 'com.android.tools'
archivesBaseName = 'sdk-common'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':sdklib')
+ compile project(':base:sdklib')
testCompile 'junit:junit:3.8.1'
- testCompile project(':sdklib').sourceSets.test.output
- testCompile project(':testutils')
-}
-
-jar {
- from 'NOTICE'
+ testCompile project(':base:sdklib').sourceSets.test.output
+ testCompile project(':base:testutils')
}
project.ext.pomName = 'Android Tools sdk-common library'
project.ext.pomDesc = 'sdk-common library used by other Android tools libraries.'
-apply from: '../baseVersion.gradle'
-apply from: '../publish.gradle'
-apply from: '../javadoc.gradle'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/sdk-common/src/main/java/com/android/ide/common/internal/AaptCruncher.java b/sdk-common/src/main/java/com/android/ide/common/internal/AaptCruncher.java
new file mode 100644
index 0000000..c413136
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/internal/AaptCruncher.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.ide.common.internal;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Implementation of the PngCruncher using aapt underneath.
+ */
+public class AaptCruncher implements PngCruncher {
+
+ private final String mAaptLocation;
+ private final CommandLineRunner mCommandLineRunner;
+
+ public AaptCruncher(@NonNull String aaptLocation, @NonNull CommandLineRunner commandLineRunner) {
+ mAaptLocation = aaptLocation;
+ mCommandLineRunner = commandLineRunner;
+ }
+
+ /**
+ * Runs the aapt crunch command on a single file
+ *
+ * @param from the file to crunch
+ * @param to the output file
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ @Override
+ public void crunchPng(@NonNull File from, @NonNull File to)
+ throws InterruptedException, LoggedErrorException, IOException {
+ String[] command = new String[] {
+ mAaptLocation,
+ "s",
+ "-i",
+ from.getAbsolutePath(),
+ "-o",
+ to.getAbsolutePath()
+ };
+
+ mCommandLineRunner.runCmdLine(command, null);
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/internal/AaptRunner.java b/sdk-common/src/main/java/com/android/ide/common/internal/AaptRunner.java
deleted file mode 100644
index 5813bae..0000000
--- a/sdk-common/src/main/java/com/android/ide/common/internal/AaptRunner.java
+++ /dev/null
@@ -1,73 +0,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.
- */
-
-package com.android.ide.common.internal;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-
-/**
- * Instances of this class are able to run aapt command.
- */
-public class AaptRunner {
-
- private final String mAaptLocation;
- private final CommandLineRunner mCommandLineRunner;
-
- public AaptRunner(@NonNull String aaptLocation, @NonNull CommandLineRunner commandLineRunner) {
- mAaptLocation = aaptLocation;
- mCommandLineRunner = commandLineRunner;
- }
-
- /**
- * Runs the aapt crunch command on a single file
- * @param from the file to crunch
- * @param to the output file
- * @throws IOException
- * @throws InterruptedException
- * @throws LoggedErrorException
- */
- public void crunchPng(File from, File to)
- throws InterruptedException, LoggedErrorException, IOException {
- crunchPng(from, to, null);
- }
-
- /**
- * Runs the aapt crunch command on a single file
- * @param from the file to crunch
- * @param to the output file
- * @throws IOException
- * @throws InterruptedException
- * @throws LoggedErrorException
- */
- public void crunchPng(File from, File to, @Nullable Map<String, String> envVariableMap)
- throws IOException, InterruptedException, LoggedErrorException {
- String[] command = new String[] {
- mAaptLocation,
- "s",
- "-i",
- from.getAbsolutePath(),
- "-o",
- to.getAbsolutePath()
- };
-
- mCommandLineRunner.runCmdLine(command, envVariableMap);
- }
-}
diff --git a/sdk-common/src/main/java/com/android/ide/common/internal/CommandLineRunner.java b/sdk-common/src/main/java/com/android/ide/common/internal/CommandLineRunner.java
index 95e0ae8..145b10f 100644
--- a/sdk-common/src/main/java/com/android/ide/common/internal/CommandLineRunner.java
+++ b/sdk-common/src/main/java/com/android/ide/common/internal/CommandLineRunner.java
@@ -18,7 +18,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.sdklib.util.GrabProcessOutput;
+import com.android.utils.GrabProcessOutput;
import com.android.utils.ILogger;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
@@ -31,11 +31,23 @@
private final ILogger mLogger;
- private class OutputGrabber implements GrabProcessOutput.IProcessOutput {
+ public abstract static class CommandLineOutput implements GrabProcessOutput.IProcessOutput {
+ private final List<String> mErrors = Lists.newArrayList();
- private boolean mFoundError = false;
- private List<String> mErrors = Lists.newArrayList();
+ @Override
+ public void err(@Nullable String line) {
+ if (line != null) {
+ mErrors.add(line);
+ }
+ }
+ @NonNull
+ public final List<String> getErrors() {
+ return mErrors;
+ }
+ }
+
+ private class OutputGrabber extends CommandLineOutput {
@Override
public void out(@Nullable String line) {
if (line != null) {
@@ -45,16 +57,11 @@
@Override
public void err(@Nullable String line) {
+ super.err(line);
if (line != null) {
mLogger.error(null /*throwable*/, line);
- mErrors.add(line);
- mFoundError = true;
}
}
-
- private boolean foundError() {
- return mFoundError;
- }
}
public CommandLineRunner(ILogger logger) {
@@ -70,9 +77,30 @@
}
public void runCmdLine(
+ @NonNull List<String> command,
+ @NonNull CommandLineOutput commandLineOutput,
+ @Nullable Map<String, String> envVariableMap)
+ throws IOException, InterruptedException, LoggedErrorException {
+ String[] cmdArray = command.toArray(new String[command.size()]);
+ runCmdLine(cmdArray, commandLineOutput, envVariableMap);
+ }
+
+ public void runCmdLine(
@NonNull String[] command,
@Nullable Map<String, String> envVariableMap)
throws IOException, InterruptedException, LoggedErrorException {
+
+ // create a default CommandLineOutput
+ OutputGrabber grabber = new OutputGrabber();
+
+ runCmdLine(command, grabber, envVariableMap);
+ }
+
+ public void runCmdLine(
+ @NonNull String[] command,
+ @NonNull CommandLineOutput commandLineOutput,
+ @Nullable Map<String, String> envVariableMap)
+ throws IOException, InterruptedException, LoggedErrorException {
printCommand(command);
// launch the command line process
@@ -86,18 +114,15 @@
Process process = processBuilder.start();
- // get the output and return code from the process
- OutputGrabber grabber = new OutputGrabber();
-
int returnCode = GrabProcessOutput.grabProcessOutput(
process,
GrabProcessOutput.Wait.WAIT_FOR_READERS, // we really want to make sure we get all the output!
- grabber);
+ commandLineOutput);
if (returnCode != 0) {
throw new LoggedErrorException(
returnCode,
- grabber.mErrors,
+ commandLineOutput.getErrors(),
Joiner.on(' ').join(command));
}
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/internal/ExecutorSingleton.java b/sdk-common/src/main/java/com/android/ide/common/internal/ExecutorSingleton.java
new file mode 100644
index 0000000..f239320
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/internal/ExecutorSingleton.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Singleton executor service.
+ */
+public class ExecutorSingleton {
+
+ private static ExecutorService sExecutorService = create();
+
+ public static synchronized ExecutorService getExecutor() {
+ checkExecutor();
+ return sExecutorService;
+ }
+
+ public static synchronized void shutdown() {
+ if (sExecutorService != null) {
+ sExecutorService.shutdown();
+ sExecutorService = null;
+ }
+ }
+
+ public static synchronized void restart() {
+ shutdown();
+ sExecutorService = create();
+ }
+
+ private static void checkExecutor() {
+ if (sExecutorService == null) {
+ throw new RuntimeException("Executor Singleton not started");
+ }
+ }
+
+ private static ExecutorService create() {
+ return Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/internal/PngCruncher.java b/sdk-common/src/main/java/com/android/ide/common/internal/PngCruncher.java
new file mode 100644
index 0000000..9d31296
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/internal/PngCruncher.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * An object able to crunch a png.
+ */
+public interface PngCruncher {
+
+ /**
+ * Crunch a given file into another given file.
+ *
+ *
+ * @param from the file to crunch
+ * @param to the output file
+ *
+ * @throws java.io.IOException
+ * @throws InterruptedException
+ * @throws com.android.ide.common.internal.LoggedErrorException
+ */
+ void crunchPng(@NonNull File from, @NonNull File to)
+ throws InterruptedException, LoggedErrorException, IOException;
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java b/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java
index b2cdd19..9d578e4 100644
--- a/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java
+++ b/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java
@@ -24,6 +24,7 @@
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -40,21 +41,29 @@
*/
public class WaitableExecutor<T> {
+ private final ExecutorService mExecutorService;
private final CompletionService<T> mCompletionService;
private final Set<Future<T>> mFutureSet = Sets.newHashSet();
+ /**
+ * Creates an executor that will use at most <var>nThreads</var> threads.
+ * @param nThreads the number of threads, or zero for default count (which is number of core)
+ */
public WaitableExecutor(int nThreads) {
if (nThreads < 1) {
- mCompletionService = new ExecutorCompletionService<T>(
- Executors.newCachedThreadPool());
- } else {
- mCompletionService = new ExecutorCompletionService<T>(
- Executors.newFixedThreadPool(nThreads));
+ nThreads = Runtime.getRuntime().availableProcessors();
}
+
+ mExecutorService = Executors.newFixedThreadPool(nThreads);
+ mCompletionService = new ExecutorCompletionService<T>(mExecutorService);
}
+ /**
+ * Creates an executor that will use at most 1 thread per core.
+ */
public WaitableExecutor() {
- this(0);
+ mExecutorService = null;
+ mCompletionService = new ExecutorCompletionService<T>(ExecutorSingleton.getExecutor());
}
/**
@@ -106,6 +115,10 @@
} else {
throw new RuntimeException(cause);
}
+ } finally {
+ if (mExecutorService != null) {
+ mExecutorService.shutdownNow();
+ }
}
return results;
@@ -139,27 +152,33 @@
*/
public List<TaskResult<T>> waitForAllTasks() throws InterruptedException {
List<TaskResult<T>> results = Lists.newArrayListWithCapacity(mFutureSet.size());
- while (!mFutureSet.isEmpty()) {
- Future<T> future = mCompletionService.take();
+ try {
+ while (!mFutureSet.isEmpty()) {
+ Future<T> future = mCompletionService.take();
- assert mFutureSet.contains(future);
- mFutureSet.remove(future);
+ assert mFutureSet.contains(future);
+ mFutureSet.remove(future);
- // Get the result from the task.
- try {
- results.add(TaskResult.withValue(future.get()));
- } catch (ExecutionException e) {
- // the original exception thrown by the task is the cause of this one.
- Throwable cause = e.getCause();
+ // Get the result from the task.
+ try {
+ results.add(TaskResult.withValue(future.get()));
+ } catch (ExecutionException e) {
+ // the original exception thrown by the task is the cause of this one.
+ Throwable cause = e.getCause();
- //noinspection StatementWithEmptyBody
- if (cause instanceof InterruptedException) {
- // if the task was cancelled we probably don't care about its result.
- } else {
- // there was an error.
- results.add(new TaskResult<T>(cause));
+ //noinspection StatementWithEmptyBody
+ if (cause instanceof InterruptedException) {
+ // if the task was cancelled we probably don't care about its result.
+ } else {
+ // there was an error.
+ results.add(new TaskResult<T>(cause));
+ }
}
}
+ } finally {
+ if (mExecutorService != null) {
+ mExecutorService.shutdownNow();
+ }
}
return results;
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
index 6f74c12..d537bd3 100644
--- 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
@@ -25,6 +25,7 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.FilePermission;
+import java.io.IOException;
import java.lang.reflect.Member;
import java.net.InetAddress;
import java.security.Permission;
@@ -73,6 +74,8 @@
/** Secret which must be provided by callers wishing to deactivate the security manager */
private static Object sCredential;
+ /** For debugging purposes */
+ private static String sLastFailedPath;
private boolean mAllowSetSecurityManager;
private boolean mDisabled;
@@ -82,6 +85,8 @@
private String mProjectPath;
private String mTempDir;
private String mNormalizedTempDir;
+ private String mCanonicalTempDir;
+ private String mAppTempDir;
private SecurityManager myPreviousSecurityManager;
private ILogger mLogger;
@@ -127,6 +132,7 @@
mProjectPath = projectPath;
mTempDir = System.getProperty("java.io.tmpdir");
mNormalizedTempDir = new File(mTempDir).getPath(); // will call fs.normalize() on the path
+ sLastFailedPath = null;
}
/** Sets an optional logger. Returns this for constructor chaining. */
@@ -135,6 +141,12 @@
return this;
}
+ /** Sets an optional application temp directory. Returns this for constructor chaining. */
+ public RenderSecurityManager setAppTempDir(@Nullable String appTempDir) {
+ mAppTempDir = appTempDir;
+ return this;
+ }
+
/**
* Sets whether the {@linkplain RenderSecurityManager} is active or not.
* If it is being set as active, the passed in credential is remembered
@@ -234,6 +246,16 @@
sEnabled = token;
}
+ /**
+ * Returns the most recently denied path.
+ *
+ * @return the most recently denied path
+ */
+ @Nullable
+ public static String getLastFailedPath() {
+ return sLastFailedPath;
+ }
+
// Permitted by custom views: access any package or member, read properties
@Override
@@ -317,7 +339,7 @@
}
// Allow reading files in temp
- if (path.startsWith(mTempDir) || path.startsWith(mNormalizedTempDir)) {
+ if (isTempDirPath(path)) {
return true;
}
@@ -341,10 +363,34 @@
@SuppressWarnings("RedundantIfStatement")
private boolean isWritingAllowed(String path) {
+ return isTempDirPath(path);
+ }
+
+ private boolean isTempDirPath(String path) {
if (path.startsWith(mTempDir) || path.startsWith(mNormalizedTempDir)) {
return true;
}
+ if (mAppTempDir != null && path.startsWith(mAppTempDir)) {
+ return true;
+ }
+
+ // Work around weird temp directories
+ try {
+ if (mCanonicalTempDir == null) {
+ mCanonicalTempDir = new File(mNormalizedTempDir).getCanonicalPath();
+ }
+
+ if (path.startsWith(mCanonicalTempDir)
+ || new File(path).getCanonicalPath().startsWith(mCanonicalTempDir)) {
+ return true;
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+
+ sLastFailedPath = path;
+
return false;
}
@@ -362,6 +408,10 @@
return true;
}
+ if (name.equals("user.timezone")) {
+ return true;
+ }
+
return false;
}
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
index ae74be4..1b507b0 100644
--- 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
@@ -17,256 +17,592 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.google.common.base.Joiner;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class represents a maven coordinate and allows for comparison at any level.
+ * <p>
+ * This class does not directly implement {@link java.lang.Comparable}; instead,
+ * you should use one of the specific {@link java.util.Comparator} constants based
+ * on what type of ordering you need.
*/
-public class GradleCoordinate implements Comparable<GradleCoordinate> {
+public class 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.
- */
+ /**
+ * 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");
+ /**
+ * 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;
+ private final String mId;
- ArtifactType(String id) {
- myId = id;
+ ArtifactType(String id) {
+ mId = id;
+ }
+
+ @Nullable
+ public static ArtifactType getArtifactType(@Nullable String name) {
+ if (name != null) {
+ for (ArtifactType type : ArtifactType.values()) {
+ if (type.mId.equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mId;
+ }
}
- @Nullable
- public static ArtifactType getArtifactType(@Nullable String name) {
- if (name != null) {
- for (ArtifactType type : ArtifactType.values()) {
- if (type.myId.equalsIgnoreCase(name)) {
- return type;
- }
+ /**
+ * A single component of a revision number: either a number, a string or a list of
+ * components separated by dashes.
+ */
+ public abstract static class RevisionComponent implements Comparable<RevisionComponent> {
+ public abstract int asInteger();
+ }
+
+ public static class NumberComponent extends RevisionComponent {
+ private final int mNumber;
+
+ public NumberComponent(int number) {
+ mNumber = number;
}
- }
- return null;
+
+ @Override
+ public String toString() {
+ return Integer.toString(mNumber);
+ }
+
+ @Override
+ public int asInteger() {
+ return mNumber;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof NumberComponent && ((NumberComponent) o).mNumber == mNumber;
+ }
+
+ @Override
+ public int hashCode() {
+ return mNumber;
+ }
+
+ @Override
+ public int compareTo(RevisionComponent o) {
+ if (o instanceof NumberComponent) {
+ return mNumber - ((NumberComponent) o).mNumber;
+ }
+ if (o instanceof StringComponent) {
+ return 1;
+ }
+ if (o instanceof ListComponent) {
+ return 1; // 1.0.x > 1-1
+ }
+ return 0;
+ }
+ }
+
+ public static class StringComponent extends RevisionComponent {
+ private final String mString;
+
+ public StringComponent(String string) {
+ this.mString = string;
+ }
+
+ @Override
+ public String toString() {
+ return mString;
+ }
+
+ @Override
+ public int asInteger() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof StringComponent && ((StringComponent) o).mString.equals(mString);
+ }
+
+ @Override
+ public int hashCode() {
+ return mString.hashCode();
+ }
+
+ @Override
+ public int compareTo(RevisionComponent o) {
+ if (o instanceof NumberComponent) {
+ return -1;
+ }
+ if (o instanceof StringComponent) {
+ return mString.compareTo(((StringComponent) o).mString);
+ }
+ if (o instanceof ListComponent) {
+ return -1; // 1-sp < 1-1
+ }
+ return 0;
+ }
+ }
+
+ private static class PlusComponent extends RevisionComponent {
+ @Override
+ public String toString() {
+ return "+";
+ }
+
+ @Override
+ public int asInteger() {
+ return PLUS_REV_VALUE;
+ }
+
+ @Override
+ public int compareTo(RevisionComponent o) {
+ throw new UnsupportedOperationException(
+ "Please use a specific comparator that knows how to handle +");
+ }
+ }
+
+ /**
+ * A list of components separated by dashes.
+ */
+ public static class ListComponent extends RevisionComponent {
+ private final List<RevisionComponent> mItems = new ArrayList<RevisionComponent>();
+ private boolean mClosed = false;
+
+ public static ListComponent of(RevisionComponent... components) {
+ ListComponent result = new ListComponent();
+ for (RevisionComponent component : components) {
+ result.add(component);
+ }
+ return result;
+ }
+
+ public void add(RevisionComponent component) {
+ mItems.add(component);
+ }
+
+ @Override
+ public int asInteger() {
+ return 0;
+ }
+
+ @Override
+ public int compareTo(RevisionComponent o) {
+ if (o instanceof NumberComponent) {
+ return -1; // 1-1 < 1.0.x
+ }
+ if (o instanceof StringComponent) {
+ return 1; // 1-1 > 1-sp
+ }
+ if (o instanceof ListComponent) {
+ ListComponent rhs = (ListComponent) o;
+ for (int i = 0; i < mItems.size() && i < rhs.mItems.size(); i++) {
+ int rc = mItems.get(i).compareTo(rhs.mItems.get(i));
+ if (rc != 0) return rc;
+ }
+ return mItems.size() - rhs.mItems.size();
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof ListComponent && ((ListComponent) o).mItems.equals(mItems);
+ }
+
+ @Override
+ public int hashCode() {
+ return mItems.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return Joiner.on("-").join(mItems);
+ }
+ }
+
+ public static final PlusComponent PLUS_REV = new PlusComponent();
+ public static final int PLUS_REV_VALUE = -1;
+
+ private final String mGroupId;
+
+ private final String mArtifactId;
+
+ private final ArtifactType mArtifactType;
+
+ private final List<RevisionComponent> mRevisions = new ArrayList<RevisionComponent>(3);
+
+ private static final Pattern MAVEN_PATTERN =
+ Pattern.compile("([\\w\\d\\.-]+):([\\w\\d\\.-]+):([^:@]+)(@[\\w-]+)?");
+
+ /**
+ * Constructor
+ */
+ public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId,
+ @NonNull RevisionComponent... revisions) {
+ this(groupId, artifactId, Arrays.asList(revisions), null);
+ }
+
+ /**
+ * Constructor
+ */
+ public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId,
+ @NonNull int... revisions) {
+ this(groupId, artifactId, createComponents(revisions), null);
+ }
+
+ private static List<RevisionComponent> createComponents(int[] revisions) {
+ List<RevisionComponent> result = new ArrayList<RevisionComponent>(revisions.length);
+ for (int revision : revisions) {
+ if (revision == PLUS_REV_VALUE) {
+ result.add(PLUS_REV);
+ } else {
+ result.add(new NumberComponent(revision));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Constructor
+ */
+ public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId,
+ @NonNull List<RevisionComponent> revisions, @Nullable ArtifactType type) {
+ mGroupId = groupId;
+ mArtifactId = artifactId;
+ mRevisions.addAll(revisions);
+
+ mArtifactType = 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));
+ }
+
+ List<RevisionComponent> revisions = parseRevisionNumber(revision);
+
+ return new GradleCoordinate(groupId, artifactId, revisions, type);
+ }
+
+ private static List<RevisionComponent> parseRevisionNumber(String revision) {
+ List<RevisionComponent> components = new ArrayList<RevisionComponent>();
+ StringBuilder buffer = new StringBuilder();
+ for (int i = 0; i < revision.length(); i++) {
+ char c = revision.charAt(i);
+ if (c == '.') {
+ flushBuffer(components, buffer, true);
+ } else if (c == '+') {
+ if (buffer.length() > 0) {
+ flushBuffer(components, buffer, true);
+ }
+ components.add(PLUS_REV);
+ break;
+ } else if (c == '-') {
+ flushBuffer(components, buffer, false);
+ int last = components.size() - 1;
+ if (last == -1) {
+ components.add(ListComponent.of(new NumberComponent(0)));
+ } else if (!(components.get(last) instanceof ListComponent)) {
+ components.set(last, ListComponent.of(components.get(last)));
+ }
+ } else {
+ buffer.append(c);
+ }
+ }
+ if (buffer.length() > 0 || components.size() == 0) {
+ flushBuffer(components, buffer, true);
+ }
+ return components;
+ }
+
+ private static void flushBuffer(List<RevisionComponent> components, StringBuilder buffer,
+ boolean closeList) {
+ RevisionComponent newComponent;
+ if (buffer.length() == 0) {
+ newComponent = new NumberComponent(0);
+ } else {
+ try {
+ newComponent = new NumberComponent(Integer.parseInt(buffer.toString()));
+ } catch(NumberFormatException e) {
+ newComponent = new StringComponent(buffer.toString());
+ }
+ }
+ buffer.setLength(0);
+ if (components.size() > 0 &&
+ components.get(components.size() - 1) instanceof ListComponent) {
+ ListComponent component = (ListComponent) components.get(components.size() - 1);
+ if (!component.mClosed) {
+ component.add(newComponent);
+ if (closeList) {
+ component.mClosed = true;
+ }
+ return;
+ }
+ }
+ components.add(newComponent);
}
@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;
+ String s = String.format(Locale.US, "%s:%s:%s", mGroupId, mArtifactId, getFullRevision());
+ if (mArtifactType != null) {
+ s += "@" + mArtifactType.toString();
+ }
+ return s;
}
- Matcher matcher = MAVEN_PATTERN.matcher(coordinateString);
- if (!matcher.matches()) {
- return null;
+ @Nullable
+ public String getGroupId() {
+ return mGroupId;
}
- 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));
+ @Nullable
+ public String getArtifactId() {
+ return mArtifactId;
}
- matcher = REVISION_PATTERN.matcher(revision);
+ @Nullable
+ public String getId() {
+ if (mGroupId == null || mArtifactId == null) {
+ return null;
+ }
- 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 String.format("%s:%s", mGroupId, mArtifactId);
}
- 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;
+ @Nullable
+ public ArtifactType getType() {
+ return mArtifactType;
}
- 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);
+ public boolean acceptsGreaterRevisions() {
+ return mRevisions.get(mRevisions.size() - 1) == PLUS_REV;
}
- return revision.toString();
- }
+ public String getFullRevision() {
+ StringBuilder revision = new StringBuilder();
+ for (RevisionComponent component : mRevisions) {
+ if (revision.length() > 0) {
+ revision.append('.');
+ }
+ revision.append(component.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);
+ return revision.toString();
}
- // Specific version should beat "any version"
- if (myIsAnyRevision) {
- return -1;
- } else if (that.myIsAnyRevision) {
- return 1;
+ /**
+ * Returns the major version (X in X.2.3), which can be {@link #PLUS_REV}, or Integer.MIN_VALUE
+ * if it is not available
+ */
+ public int getMajorVersion() {
+ return mRevisions.isEmpty() ? Integer.MIN_VALUE : mRevisions.get(0).asInteger();
}
- for (int i = 0; i < myRevisions.size(); ++i) {
- int delta = myRevisions.get(i) - that.myRevisions.get(i);
- if (delta != 0) {
- return delta;
- }
+ /**
+ * Returns the minor version (X in 1.X.3), which can be {@link #PLUS_REV}, or Integer.MIN_VALUE
+ * if it is not available
+ */
+ public int getMinorVersion() {
+ return mRevisions.size() < 2 ? Integer.MIN_VALUE : mRevisions.get(1).asInteger();
}
- return 0;
- }
+
+ /**
+ * Returns the major version (X in 1.2.X), which can be {@link #PLUS_REV}, or Integer.MIN_VALUE
+ * if it is not available
+ */
+ public int getMicroVersion() {
+ return mRevisions.size() < 3 ? Integer.MIN_VALUE : mRevisions.get(2).asInteger();
+ }
+
+ /**
+ * 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.mGroupId.equals(mGroupId) && o.mArtifactId.equals(mArtifactId);
+ }
+
+ @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 (!mRevisions.equals(that.mRevisions)) {
+ return false;
+ }
+ if (!mArtifactId.equals(that.mArtifactId)) {
+ return false;
+ }
+ if (!mGroupId.equals(that.mGroupId)) {
+ return false;
+ }
+ if ((mArtifactType == null) != (that.mArtifactType == null)) {
+ return false;
+ }
+ if (mArtifactType != null && !mArtifactType.equals(that.mArtifactType)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mGroupId.hashCode();
+ result = 31 * result + mArtifactId.hashCode();
+ for (RevisionComponent component : mRevisions) {
+ result = 31 * result + component.hashCode();
+ }
+ if (mArtifactType != null) {
+ result = 31 * result + mArtifactType.hashCode();
+ }
+ return result;
+ }
+
+ /**
+ * Comparator which compares Gradle versions - and treats a + version as lower
+ * than a specific number in the same place. This is typically useful when trying
+ * to for example order coordinates by "most specific".
+ */
+ public static final Comparator<GradleCoordinate> COMPARE_PLUS_LOWER =
+ new GradleCoordinateComparator(-1);
+
+ /**
+ * Comparator which compares Gradle versions - and treats a + version as higher
+ * than a specific number. This is typically useful when seeing if a dependency
+ * is met, e.g. if you require version 0.7.3, comparing it with 0.7.+ would consider
+ * 0.7.+ higher and therefore satisfying the version requirement.
+ */
+ public static final Comparator<GradleCoordinate> COMPARE_PLUS_HIGHER =
+ new GradleCoordinateComparator(1);
+
+ private static class GradleCoordinateComparator implements Comparator<GradleCoordinate> {
+ private final int mPlusResult;
+
+ private GradleCoordinateComparator(int plusResult) {
+ mPlusResult = plusResult;
+ }
+
+ @Override
+ public int compare(@NonNull GradleCoordinate a, @NonNull GradleCoordinate b) {
+ // Make sure we're comparing apples to apples. If not, compare artifactIds
+ if (!a.isSameArtifact(b)) {
+ return a.mArtifactId.compareTo(b.mArtifactId);
+ }
+
+ int sizeA = a.mRevisions.size();
+ int sizeB = b.mRevisions.size();
+ int common = Math.min(sizeA, sizeB);
+ for (int i = 0; i < common; ++i) {
+ RevisionComponent revision1 = a.mRevisions.get(i);
+ if (revision1 instanceof PlusComponent) return mPlusResult;
+ RevisionComponent revision2 = b.mRevisions.get(i);
+ if (revision2 instanceof PlusComponent) return -mPlusResult;
+ int delta = revision1.compareTo(revision2);
+ if (delta != 0) {
+ return delta;
+ }
+ }
+ if (sizeA == sizeB) {
+ return 0;
+ } else {
+ // Treat X.0 and X.0.0 as equal
+ List<RevisionComponent> revisionList;
+ int returnValueIfNonZero;
+ int from;
+ int to;
+ if (sizeA < sizeB) {
+ revisionList = b.mRevisions;
+ from = sizeA;
+ to = sizeB;
+ returnValueIfNonZero = -1;
+ } else {
+ revisionList = a.mRevisions;
+ from = sizeB;
+ to = sizeA;
+ returnValueIfNonZero = 1;
+ }
+ for (int i = from; i < to; ++i) {
+ RevisionComponent revision = revisionList.get(i);
+ if (revision instanceof NumberComponent) {
+ if (revision.asInteger() != 0) {
+ return returnValueIfNonZero;
+ }
+ } else {
+ return returnValueIfNonZero;
+ }
+ }
+ 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 d27dfff..33c7b7a 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
@@ -20,10 +20,12 @@
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.resources.ResourceResolver.MAX_RESOURCE_INDIRECTION;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ResourceUrl;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.resources.ResourceType;
import com.google.common.base.Splitter;
@@ -260,15 +262,31 @@
return null;
}
- List<ResourceItem> matchingItems = typeItems.get(name);
- if (matchingItems == null || matchingItems.isEmpty()) {
- return null;
- }
+ for (int depth = 0; depth < MAX_RESOURCE_INDIRECTION; depth++) {
+ List<ResourceItem> matchingItems = typeItems.get(name);
+ if (matchingItems == null || matchingItems.isEmpty()) {
+ return null;
+ }
- ResourceItem match = (ResourceItem) config.findMatchingConfigurable(matchingItems);
+ ResourceItem match = (ResourceItem) config.findMatchingConfigurable(matchingItems);
+ if (match != null) {
+ ResourceValue resourceValue = match.getResourceValue(isFramework());
+ if (resourceValue != null) {
+ String value = resourceValue.getValue();
+ if (value != null && value.startsWith(PREFIX_RESOURCE_REF)) {
+ ResourceUrl url = ResourceUrl.parse(value);
+ if (url != null && url.type == type
+ && url.framework == isFramework()) {
+ name = url.name;
+ continue;
+ }
+ }
+ }
- if (match != null) {
- return match.getSource();
+ return match.getSource();
+ } else {
+ return null;
+ }
}
}
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 ad3a8b8..0403275 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
@@ -34,13 +34,13 @@
*/
public abstract class DataFile<I extends DataItem> {
- static enum FileType {
+ enum FileType {
SINGLE, MULTI
}
private final FileType mType;
protected File mFile;
- private final Map<String, I> mItems = Maps.newHashMap();
+ protected final Map<String, I> mItems = Maps.newHashMap();
/**
* Creates a data file with a list of data items.
@@ -61,19 +61,15 @@
* @param item the item
*/
protected final void init(@NonNull I item) {
- item.setSource(this);
- mItems.put(item.getKey(), item);
+ addItem(item);
}
/**
* This must be called from the constructor of the children classes.
* @param items the items
*/
- protected void init(@NonNull Iterable<I> items) {
- for (I item : items) {
- item.setSource(this);
- mItems.put(item.getKey(), item);
- }
+ protected final void init(@NonNull Iterable<I> items) {
+ addItems(items);
}
@NonNull
@@ -111,16 +107,24 @@
return mItems;
}
- public void addItems(@NonNull Collection<I> items) {
+ public void addItem(@NonNull I item) {
+ //noinspection unchecked
+ item.setSource(this);
+ mItems.put(item.getKey(), item);
+ }
+
+ public void addItems(@NonNull Iterable<I> items) {
for (I item : items) {
+ //noinspection unchecked
item.setSource(this);
mItems.put(item.getKey(), item);
}
}
- public void removeItems(@NonNull Collection<I> items) {
+ public void removeItems(@NonNull Iterable<I> items) {
for (I item : items) {
mItems.remove(item.getKey());
+ //noinspection unchecked
item.setSource(null);
}
}
@@ -136,7 +140,9 @@
public void replace(@NonNull I oldItem, @NonNull I newItem) {
mItems.remove(oldItem.getKey());
+ //noinspection unchecked
oldItem.setSource(null);
+ //noinspection unchecked
newItem.setSource(this);
mItems.put(newItem.getKey(), newItem);
}
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 e9ac33f..1aa7b6f 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
@@ -20,6 +20,7 @@
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.utils.XmlUtils;
import com.google.common.base.Charsets;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
@@ -27,16 +28,14 @@
import com.google.common.collect.Sets;
import com.google.common.io.Files;
+import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
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.IOException;
import java.util.Arrays;
import java.util.Collection;
@@ -60,6 +59,9 @@
private static final String NODE_MERGER = "merger";
private static final String NODE_DATA_SET = "dataSet";
+ private static final String ATTR_VERSION = "version";
+ private static final String MERGE_BLOB_VERSION = "2";
+
/**
* All the DataSets.
*/
@@ -281,6 +283,9 @@
Document document = builder.newDocument();
Node rootNode = document.createElement(NODE_MERGER);
+ // add the version code.
+ NodeUtils.addAttribute(document, rootNode, null, ATTR_VERSION, MERGE_BLOB_VERSION);
+
document.appendChild(rootNode);
for (S dataSet : mDataSets) {
@@ -335,16 +340,8 @@
return false;
}
- BufferedInputStream stream = null;
try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- stream = new BufferedInputStream(new FileInputStream(file));
- InputSource is = new InputSource(stream);
- factory.setNamespaceAware(true);
- factory.setValidating(false);
-
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document document = builder.parse(is);
+ Document document = XmlUtils.parseUtfXmlFile(file, true /*namespaceAware*/);
// get the root node
Node rootNode = document.getDocumentElement();
@@ -352,6 +349,16 @@
return false;
}
+ // get the version code.
+ String version = null;
+ Attr versionAttr = (Attr) rootNode.getAttributes().getNamedItem(ATTR_VERSION);
+ if (versionAttr != null) {
+ version = versionAttr.getValue();
+ }
+ if (!MERGE_BLOB_VERSION.equals(version)) {
+ return false;
+ }
+
NodeList nodes = rootNode.getChildNodes();
for (int i = 0, n = nodes.getLength(); i < n; i++) {
@@ -390,14 +397,6 @@
throw new MergingException(e).setFile(file);
} catch (SAXException e) {
throw new MergingException(e).setFile(file);
- } finally {
- try {
- if (stream != null) {
- stream.close();
- }
- } catch (IOException e) {
- // ignore
- }
}
}
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 f3a82f0..65b0543 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
@@ -34,7 +34,7 @@
public MergeWriter(@NonNull File rootFolder) {
mRootFolder = rootFolder;
- mExecutor = new WaitableExecutor<Void>(Runtime.getRuntime().availableProcessors());
+ mExecutor = new WaitableExecutor<Void>();
}
@Override
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 092d169..0952eff 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
@@ -25,7 +25,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.ide.common.internal.AaptRunner;
+import com.android.ide.common.internal.PngCruncher;
import com.android.ide.common.xml.XmlPrettyPrinter;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
@@ -60,7 +60,7 @@
public static final String FILENAME_PREFIX = "From: ";
@Nullable
- private final AaptRunner mAaptRunner;
+ private final PngCruncher mCruncher;
private boolean mInsertSourceMarkers = true;
@@ -76,9 +76,9 @@
*/
private Set<String> mQualifierWithDeletedValues;
- public MergedResourceWriter(@NonNull File rootFolder, @Nullable AaptRunner aaptRunner) {
+ public MergedResourceWriter(@NonNull File rootFolder, @Nullable PngCruncher pngRunner) {
super(rootFolder);
- mAaptRunner = aaptRunner;
+ mCruncher = pngRunner;
}
/**
@@ -180,10 +180,9 @@
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 (mCruncher != null && filename.endsWith(DOT_PNG)) {
+ // Crunch the the PNG file.
+ mCruncher.crunchPng(file, outFile);
} else if (mInsertSourceMarkers && filename.endsWith(DOT_XML)) {
SdkUtils.copyXmlWithSourceReference(file, outFile);
} else {
@@ -303,7 +302,7 @@
try {
content = XmlPrettyPrinter.prettyPrint(document, true);
} catch (Throwable t) {
- content = XmlUtils.toXml(document, false);
+ content = XmlUtils.toXml(document, true);
}
Files.write(content, outFile, Charsets.UTF_8);
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java
index 14ba07e..f2e47b5 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java
@@ -23,7 +23,6 @@
import java.io.File;
import java.util.List;
-import java.util.Map;
/**
* Represents a file in a resource folders.
@@ -81,6 +80,7 @@
return mQualifiers;
}
+ // Used in Studio
public void setQualifiers(@NonNull String qualifiers) {
mQualifiers = qualifiers;
}
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 f89abcd..ecc8523 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
@@ -42,11 +42,13 @@
import com.android.ide.common.rendering.api.PluralsResourceValue;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.rendering.api.TextResourceValue;
import com.android.ide.common.resources.configuration.Configurable;
import com.android.ide.common.resources.configuration.DensityQualifier;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.resources.Density;
import com.android.resources.ResourceType;
+import com.android.utils.XmlUtils;
import com.google.common.base.Splitter;
import org.w3c.dom.Attr;
@@ -323,6 +325,9 @@
case ATTR:
value = parseAttrValue(new AttrResourceValue(type, name, isFrameworks));
break;
+ case STRING:
+ value = parseTextValue(new TextResourceValue(type, name, isFrameworks));
+ break;
default:
value = parseValue(new ResourceValue(type, name, isFrameworks));
break;
@@ -376,8 +381,8 @@
ResourceValue resValue = new ResourceValue(null, name,
styleValue.isFramework());
- resValue.setValue(
- ValueXmlHelper.unescapeResourceString(getTextNode(child), false, true));
+ String text = getTextNode(child.getChildNodes());
+ resValue.setValue(ValueXmlHelper.unescapeResourceString(text, false, true));
styleValue.addValue(resValue, isFrameworkAttr);
}
}
@@ -425,7 +430,7 @@
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
- String text = getTextNode(child);
+ String text = getTextNode(child.getChildNodes());
text = ValueXmlHelper.unescapeResourceString(text, false, true);
arrayValue.addElement(text);
}
@@ -443,7 +448,7 @@
NamedNodeMap attributes = child.getAttributes();
String quantity = getAttributeValue(attributes, ATTR_QUANTITY);
if (quantity != null) {
- String text = getTextNode(child);
+ String text = getTextNode(child.getChildNodes());
text = ValueXmlHelper.unescapeResourceString(text, false, true);
value.addPlural(quantity, text);
}
@@ -484,15 +489,16 @@
@NonNull
private ResourceValue parseValue(@NonNull ResourceValue value) {
- value.setValue(ValueXmlHelper.unescapeResourceString(getTextNode(mValue), false, true));
+ String text = getTextNode(mValue.getChildNodes());
+ value.setValue(ValueXmlHelper.unescapeResourceString(text, false, true));
+
return value;
}
@NonNull
- private static String getTextNode(@NonNull Node node) {
+ private static String getTextNode(@NonNull NodeList children) {
StringBuilder sb = new StringBuilder();
- NodeList children = node.getChildNodes();
for (int i = 0, n = children.getLength(); i < n; i++) {
Node child = children.item(i);
@@ -519,7 +525,89 @@
}
}
- sb.append(getTextNode(child));
+ NodeList childNodes = child.getChildNodes();
+ if (childNodes.getLength() > 0) {
+ sb.append(getTextNode(childNodes));
+ }
+ break;
+ }
+ case Node.TEXT_NODE:
+ sb.append(child.getNodeValue());
+ break;
+ case Node.CDATA_SECTION_NODE:
+ sb.append(child.getNodeValue());
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+
+ @NonNull
+ private TextResourceValue parseTextValue(@NonNull TextResourceValue value) {
+ NodeList children = mValue.getChildNodes();
+ String text = getTextNode(children);
+ value.setValue(ValueXmlHelper.unescapeResourceString(text, false, true));
+
+ int length = children.getLength();
+ if (length > 1) {
+ boolean haveElementChildren = false;
+ for (int i = 0; i < length; i++) {
+ if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
+ haveElementChildren = true;
+ break;
+ }
+ }
+
+ if (haveElementChildren) {
+ String markupText = getMarkupText(children);
+ value.setRawXmlValue(markupText);
+ }
+ }
+
+ return value;
+ }
+
+ @NonNull
+ private static String getMarkupText(@NonNull NodeList children) {
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+
+ short nodeType = child.getNodeType();
+
+ switch (nodeType) {
+ case Node.ELEMENT_NODE: {
+ Element element = (Element) child;
+ String tagName = element.getTagName();
+ sb.append('<');
+ sb.append(tagName);
+
+ NamedNodeMap attributes = element.getAttributes();
+ int attributeCount = attributes.getLength();
+ if (attributeCount > 0) {
+ for (int j = 0; j < attributeCount; j++) {
+ Node attribute = attributes.item(j);
+ sb.append(' ');
+ sb.append(attribute.getNodeName());
+ sb.append('=').append('"');
+ XmlUtils.appendXmlAttributeValue(sb, attribute.getNodeValue());
+ sb.append('"');
+ }
+ }
+ sb.append('>');
+
+ NodeList childNodes = child.getChildNodes();
+ if (childNodes.getLength() > 0) {
+ sb.append(getMarkupText(childNodes));
+ }
+
+ sb.append('<');
+ sb.append('/');
+ sb.append(tagName);
+ sb.append('>');
+
break;
}
case Node.TEXT_NODE:
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 1ee3a5e..caa3785 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
@@ -20,7 +20,9 @@
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.packaging.PackagingUtils;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.resources.FolderTypeRelationship;
import com.android.resources.ResourceConstants;
import com.android.resources.ResourceFolderType;
@@ -32,7 +34,6 @@
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.util.List;
@@ -61,7 +62,7 @@
// get the type.
FolderData folderData = getFolderData(file.getParentFile());
- if (folderData.folderType == null) {
+ if (folderData == null) {
return null;
}
@@ -87,13 +88,13 @@
continue;
}
- ResourceItem r = ValueResourceParser2.getResource(resNode);
+ ResourceItem r = ValueResourceParser2.getResource(resNode, file);
if (r != null) {
resourceList.add(r);
if (r.getType() == ResourceType.DECLARE_STYLEABLE) {
// Need to also create ATTR items for its children
try {
- ValueResourceParser2.addStyleableItems(resNode, resourceList, null);
+ ValueResourceParser2.addStyleableItems(resNode, resourceList, null, file);
} catch (MergingException ignored) {
// since we are not passing a dup map, this will never be thrown
assert false : file + ": " + ignored.getMessage();
@@ -131,7 +132,7 @@
if (folder.isDirectory() &&
PackagingUtils.checkFolderForPackaging(folder.getName())) {
FolderData folderData = getFolderData(folder);
- if (folderData.folderType != null) {
+ if (folderData != null) {
parseFolder(sourceFolder, folder, folderData, logger);
}
}
@@ -157,7 +158,7 @@
throws MergingException {
FolderData folderData = getFolderData(changedFile.getParentFile());
- if (folderData.folderType == null) {
+ if (folderData == null) {
return true;
}
@@ -299,7 +300,7 @@
* @param folder the folder.
* @return the FolderData object.
*/
- @NonNull
+ @Nullable
private static FolderData getFolderData(File folder) {
FolderData fd = new FolderData();
@@ -307,7 +308,22 @@
int pos = folderName.indexOf(ResourceConstants.RES_QUALIFIER_SEP);
if (pos != -1) {
fd.folderType = ResourceFolderType.getTypeByName(folderName.substring(0, pos));
- fd.qualifiers = folderName.substring(pos + 1);
+ if (fd.folderType == null) {
+ return null;
+ }
+
+ FolderConfiguration folderConfiguration = FolderConfiguration.getConfigForFolder(folderName);
+ if (folderConfiguration == null) {
+ return null;
+ }
+
+ // normalize it
+ folderConfiguration.normalize();
+
+ // get the qualifier portion from the folder config.
+ // the returned string starts with "-" so we remove that.
+ fd.qualifiers = folderConfiguration.getUniqueKey().substring(1);
+
} else {
fd.folderType = ResourceFolderType.getTypeByName(folderName);
}
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 a55e1d4..bf4b6f9 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
@@ -20,7 +20,9 @@
import static com.android.SdkConstants.ATTR_FORMAT;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.TAG_EAT_COMMENT;
import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_SKIP;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -29,27 +31,21 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
-import com.google.common.io.Closeables;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
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.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
@@ -59,13 +55,14 @@
*/
class ValueResourceParser2 {
+ @NonNull
private final File mFile;
/**
* Creates the parser for a given file.
* @param file the file to parse.
*/
- ValueResourceParser2(File file) {
+ ValueResourceParser2(@NonNull File file) {
mFile = file;
}
@@ -99,7 +96,7 @@
continue;
}
- ResourceItem resource = getResource(node);
+ ResourceItem resource = getResource(node, mFile);
if (resource != null) {
// check this is not a dup
checkDuplicate(resource, map);
@@ -109,7 +106,7 @@
if (resource.getType() == ResourceType.DECLARE_STYLEABLE) {
// Need to also create ATTR items for its children
try {
- ValueResourceParser2.addStyleableItems(node, resources, map);
+ addStyleableItems(node, resources, map, mFile);
} catch (MergingException e) {
e.setFile(mFile);
throw e;
@@ -126,12 +123,14 @@
* @param node the node representing the resource.
* @return a ResourceItem object or null.
*/
- static ResourceItem getResource(Node node) {
- ResourceType type = getType(node);
+ static ResourceItem getResource(@NonNull Node node, @Nullable File from) {
+ ResourceType type = getType(node, from);
String name = getName(node);
- if (type != null && name != null) {
- return new ResourceItem(name, type, node);
+ if (name != null) {
+ if (type != null) {
+ return new ResourceItem(name, type, node);
+ }
}
return null;
@@ -142,7 +141,7 @@
* @param node the node
* @return the ResourceType or null if it could not be inferred.
*/
- static ResourceType getType(Node node) {
+ static ResourceType getType(@NonNull Node node, @Nullable File from) {
String nodeName = node.getLocalName();
String typeString = null;
@@ -151,16 +150,29 @@
if (attribute != null) {
typeString = attribute.getValue();
}
+ } else if (TAG_EAT_COMMENT.equals(nodeName) || TAG_SKIP.equals(nodeName)) {
+ return null;
} else {
// the type is the name of the node.
typeString = nodeName;
}
if (typeString != null) {
- return ResourceType.getEnum(typeString);
+ ResourceType type = ResourceType.getEnum(typeString);
+ if (type != null) {
+ return type;
+ }
+
+ if (from != null) {
+ throw new RuntimeException(String.format("Unsupported type '%s' in file %s", typeString, from));
+ }
+ throw new RuntimeException(String.format("Unsupported type '%s'", typeString));
}
- return null;
+ if (from != null) {
+ throw new RuntimeException(String.format("Unsupported node '%s' in file %s", nodeName, from));
+ }
+ throw new RuntimeException(String.format("Unsupported node '%s'", nodeName));
}
/**
@@ -186,15 +198,8 @@
*/
@NonNull
static Document parseDocument(File file) throws MergingException {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- 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);
+ return XmlUtils.parseUtfXmlFile(file, true /*namespaceAware*/);
} catch (SAXParseException e) {
String message = e.getLocalizedMessage();
MergingException exception = new MergingException(message, e);
@@ -211,8 +216,6 @@
throw new MergingException(e).setFile(file);
} catch (IOException e) {
throw new MergingException(e).setFile(file);
- } finally {
- Closeables.closeQuietly(stream);
}
}
@@ -226,7 +229,8 @@
*/
static void addStyleableItems(@NonNull Node styleableNode,
@NonNull List<ResourceItem> list,
- @Nullable Map<ResourceType, Set<String>> map)
+ @Nullable Map<ResourceType, Set<String>> map,
+ @Nullable File from)
throws MergingException {
assert styleableNode.getNodeName().equals(ResourceType.DECLARE_STYLEABLE.getName());
NodeList nodes = styleableNode.getChildNodes();
@@ -238,7 +242,7 @@
continue;
}
- ResourceItem resource = getResource(node);
+ ResourceItem resource = getResource(node, from);
if (resource != null) {
assert resource.getType() == ResourceType.ATTR;
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 f41e115..0eb7607 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
@@ -69,7 +69,7 @@
* {@link ResourceItem} matching a given {@link ResourceType}.
*
* @param type the type of the resources to return
- * @return a collection of items, possible empty.
+ * @return a collection of items, possibly empty.
*/
@Override
@NonNull
@@ -84,7 +84,7 @@
*/
@Override
public boolean hasResourcesOfType(@NonNull ResourceType type) {
- return mPublicResourceMap.get(type).size() > 0;
+ return !mPublicResourceMap.get(type).isEmpty();
}
@Override
@@ -104,7 +104,7 @@
*/
public void loadPublicResources(@Nullable ILogger logger) {
IAbstractFolder valueFolder = getResFolder().getFolder(SdkConstants.FD_RES_VALUES);
- if (valueFolder.exists() == false) {
+ if (!valueFolder.exists()) {
return;
}
@@ -172,7 +172,7 @@
int size;
switch (type) {
case STYLE: size = 500; break;
- case ATTR: size = 1000; break;
+ case ATTR: size = 1050; break;
case DRAWABLE: size = 200; break;
case ID: size = 50; break;
case LAYOUT:
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/IdGeneratingResourceFile.java b/sdk-common/src/main/java/com/android/ide/common/resources/IdGeneratingResourceFile.java
index 9ff1748..3d59c6d 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/IdGeneratingResourceFile.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/IdGeneratingResourceFile.java
@@ -26,6 +26,7 @@
import java.io.IOException;
import java.util.Collection;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -56,9 +57,7 @@
mFileType = type;
// Set up our resource types
- mResourceTypeList = new HashSet<ResourceType>();
- mResourceTypeList.add(mFileType);
- mResourceTypeList.add(ResourceType.ID);
+ mResourceTypeList = EnumSet.of(mFileType, ResourceType.ID);
// compute the resource name
mFileName = getFileName(type);
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/IdResourceParser.java b/sdk-common/src/main/java/com/android/ide/common/resources/IdResourceParser.java
index 60c1725..57bd3cf 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/IdResourceParser.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/IdResourceParser.java
@@ -16,6 +16,7 @@
package com.android.ide.common.resources;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
@@ -78,7 +79,7 @@
if (input instanceof FileInputStream) {
input = new BufferedInputStream(input);
}
- parser.setInput(input, "UTF-8"); //$NON-NLS-1$
+ parser.setInput(input, SdkConstants.UTF_8);
return parse(type, path, parser);
} catch (XmlPullParserException e) {
@@ -106,7 +107,11 @@
mContext.addError(error);
return false;
} finally {
- Closeables.closeQuietly(input);
+ try {
+ Closeables.close(input, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
}
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 c470aa4..706cbf8 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
@@ -32,6 +32,7 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -53,7 +54,9 @@
private final Map<ResourceType, Map<String, ResourceValue>> mFrameworkResources;
private final Map<StyleResourceValue, StyleResourceValue> mStyleInheritanceMap =
new HashMap<StyleResourceValue, StyleResourceValue>();
- private StyleResourceValue mTheme;
+ private StyleResourceValue mDefaultTheme;
+ // The resources should be searched in all the themes in the list in order.
+ private final List<StyleResourceValue> mThemes;
private FrameworkResourceIdProvider mFrameworkProvider;
private LayoutLog mLogger;
private String mThemeName;
@@ -67,6 +70,7 @@
mFrameworkResources = frameworkResources;
mThemeName = themeName;
mIsProjectTheme = isProjectTheme;
+ mThemes = new LinkedList<StyleResourceValue>();
}
/**
@@ -90,6 +94,34 @@
return resolver;
}
+ /**
+ * Sets up the light and dark default styles with the given concrete styles. This is used if we
+ * want to override the defaults configured in the framework for this particular platform.
+ */
+ public void setDeviceDefaults(@Nullable String lightStyle, @Nullable String darkStyle) {
+ if (darkStyle != null) {
+ replace("Theme.DeviceDefault", darkStyle);
+ }
+ if (lightStyle != null && replace("Theme.DeviceDefault.Light", lightStyle)) {
+ replace("Theme.DeviceDefault.Light.DarkActionBar", lightStyle + ".DarkActionBar");
+ }
+ }
+
+ private boolean replace(String fromStyleName, String toStyleName) {
+ Map<String, ResourceValue> map = mFrameworkResources.get(ResourceType.STYLE);
+ if (map != null) {
+ ResourceValue from = map.get(fromStyleName);
+ if (from instanceof StyleResourceValue) {
+ ResourceValue to = map.get(toStyleName);
+ if (to instanceof StyleResourceValue) {
+ mStyleInheritanceMap.put((StyleResourceValue)from, (StyleResourceValue)to);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
// ---- Methods to help dealing with older LayoutLibs.
public String getThemeName() {
@@ -121,11 +153,34 @@
}
@Override
- public StyleResourceValue getCurrentTheme() {
- return mTheme;
+ public StyleResourceValue getDefaultTheme() {
+ return mDefaultTheme;
}
@Override
+ public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
+ if (theme == null) {
+ return;
+ }
+ if (useAsPrimary) {
+ mThemes.add(0, theme);
+ } else {
+ mThemes.add(theme);
+ }
+ }
+
+ @Override
+ public void clearStyles() {
+ mThemes.clear();
+ mThemes.add(mDefaultTheme);
+ }
+
+ @Override
+ public List<StyleResourceValue> getAllThemes() {
+ return mThemes;
+ }
+
+ @Override
public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
ResourceValue theme;
@@ -264,7 +319,7 @@
if (resource != null && resource.hasValidName()) {
if (resource.theme) {
// no theme? no need to go further!
- if (mTheme == null) {
+ if (mDefaultTheme == null) {
return null;
}
@@ -274,8 +329,7 @@
}
// Now look for the item in the theme, starting with the current one.
- ResourceValue item = findItemInStyle(mTheme, resource.name,
- forceFrameworkOnly || resource.framework);
+ ResourceValue item = findItemInTheme(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",
@@ -453,7 +507,9 @@
frameworkStyleMap);
}
- mTheme = (StyleResourceValue) theme;
+ mDefaultTheme = (StyleResourceValue) theme;
+ mThemes.clear();
+ mThemes.add(mDefaultTheme);
}
}
@@ -608,7 +664,8 @@
* {@code parentStyle}
*/
public boolean themeExtends(@NonNull String parentStyle, @NonNull String themeStyle) {
- ResourceValue parentValue = findResValue(parentStyle, parentStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
+ ResourceValue parentValue = findResValue(parentStyle,
+ parentStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
if (parentValue instanceof StyleResourceValue) {
ResourceValue themeValue = findResValue(themeStyle,
themeStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
@@ -637,8 +694,9 @@
lookupChain, mProjectResources, mFrameworkResources, mThemeName, mIsProjectTheme);
resolver.mFrameworkProvider = mFrameworkProvider;
resolver.mLogger = mLogger;
- resolver.mTheme = mTheme;
+ resolver.mDefaultTheme = mDefaultTheme;
resolver.mStyleInheritanceMap.putAll(mStyleInheritanceMap);
+ resolver.mThemes.addAll(mThemes);
return resolver;
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ValidatingResourceParser.java b/sdk-common/src/main/java/com/android/ide/common/resources/ValidatingResourceParser.java
index b477b8f..ef8ac5d 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/ValidatingResourceParser.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ValidatingResourceParser.java
@@ -16,6 +16,7 @@
package com.android.ide.common.resources;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.google.common.io.Closeables;
@@ -64,11 +65,19 @@
throws IOException {
// No need to validate framework files
if (mIsFramework) {
- Closeables.closeQuietly(input);
+ try {
+ Closeables.close(input, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
return true;
}
if (mContext.needsFullAapt()) {
- Closeables.closeQuietly(input);
+ try {
+ Closeables.close(input, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
return false;
}
@@ -79,7 +88,7 @@
if (input instanceof FileInputStream) {
input = new BufferedInputStream(input);
}
- parser.setInput(input, "UTF-8"); //$NON-NLS-1$
+ parser.setInput(input, SdkConstants.UTF_8);
return parse(path, parser);
} catch (XmlPullParserException e) {
@@ -107,7 +116,11 @@
mContext.addError(error);
return false;
} finally {
- Closeables.closeQuietly(input);
+ try {
+ Closeables.close(input, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ValueResourceParser.java b/sdk-common/src/main/java/com/android/ide/common/resources/ValueResourceParser.java
index 327a0e9..2d5d642 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/ValueResourceParser.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ValueResourceParser.java
@@ -65,8 +65,9 @@
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (mCurrentValue != null) {
- mCurrentValue.setValue(
- ValueXmlHelper.unescapeResourceString(mCurrentValue.getValue(), false, true));
+ String value = mCurrentValue.getValue();
+ value = value == null ? "" : ValueXmlHelper.unescapeResourceString(value, false, true);
+ mCurrentValue.setValue(value);
}
if (inResources && qName.equals(NODE_RESOURCES)) {
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/CountryCodeQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
index 1d5abc3..cef0994 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
@@ -43,7 +43,7 @@
if (m.matches()) {
String v = m.group(1);
- int code = -1;
+ int code;
try {
code = Integer.parseInt(v);
} catch (NumberFormatException e) {
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 fced184..73d03ea 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
@@ -28,6 +28,7 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
/**
@@ -36,6 +37,7 @@
*/
public final class FolderConfiguration implements Comparable<FolderConfiguration> {
+ @NonNull
private static final ResourceQualifier[] DEFAULT_QUALIFIERS;
static {
@@ -52,7 +54,7 @@
private static final int INDEX_NETWORK_CODE = 1;
private static final int INDEX_LANGUAGE = 2;
private static final int INDEX_REGION = 3;
- private static final int INDEX_LAYOUTDIR = 4;
+ private static final int INDEX_LAYOUT_DIR = 4;
private static final int INDEX_SMALLEST_SCREEN_WIDTH = 5;
private static final int INDEX_SCREEN_WIDTH = 6;
private static final int INDEX_SCREEN_HEIGHT = 7;
@@ -145,6 +147,7 @@
while (qualifiers.hasNext()) {
String seg = qualifiers.next();
if (!seg.isEmpty()) {
+ seg = seg.toLowerCase(Locale.US); // no-op if string is already in lower case
while (qualifierIndex < qualifierCount &&
!DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
qualifierIndex++;
@@ -153,6 +156,8 @@
// if we reached the end of the qualifier we didn't find a matching qualifier.
if (qualifierIndex == qualifierCount) {
return null;
+ } else {
+ qualifierIndex++; // already processed this one
}
} else {
@@ -188,7 +193,7 @@
*
* @see #set(FolderConfiguration, boolean)
*/
- public void set(FolderConfiguration config) {
+ public void set(@Nullable FolderConfiguration config) {
set(config, false /*nonFakeValuesOnly*/);
}
@@ -200,7 +205,7 @@
*
* @see ResourceQualifier#hasFakeValue()
*/
- public void set(FolderConfiguration config, boolean nonFakeValuesOnly) {
+ public void set(@Nullable FolderConfiguration config, boolean nonFakeValuesOnly) {
if (config != null) {
for (int i = 0 ; i < INDEX_COUNT ; i++) {
ResourceQualifier q = config.mQualifiers[i];
@@ -225,7 +230,7 @@
* Removes the qualifiers from the receiver if they are present (and valid)
* in the given configuration.
*/
- public void substract(FolderConfiguration config) {
+ public void substract(@NonNull FolderConfiguration config) {
for (int i = 0 ; i < INDEX_COUNT ; i++) {
if (config.mQualifiers[i] != null && config.mQualifiers[i].isValid()) {
mQualifiers[i] = null;
@@ -237,7 +242,7 @@
* Adds the non-qualifiers from the given config.
* Qualifiers that are null in the given config do not change in the receiver.
*/
- public void add(FolderConfiguration config) {
+ public void add(@NonNull FolderConfiguration config) {
for (int i = 0 ; i < INDEX_COUNT ; i++) {
if (config.mQualifiers[i] != null) {
mQualifiers[i] = config.mQualifiers[i];
@@ -249,6 +254,7 @@
* Returns the first invalid qualifier, or <code>null<code> if they are all valid (or if none
* exists).
*/
+ @Nullable
public ResourceQualifier getInvalidQualifier() {
for (int i = 0 ; i < INDEX_COUNT ; i++) {
if (mQualifiers[i] != null && !mQualifiers[i].isValid()) {
@@ -277,7 +283,7 @@
* Adds a qualifier to the {@link FolderConfiguration}
* @param qualifier the {@link ResourceQualifier} to add.
*/
- public void addQualifier(ResourceQualifier qualifier) {
+ public void addQualifier(@Nullable ResourceQualifier qualifier) {
if (qualifier instanceof CountryCodeQualifier) {
mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
@@ -291,7 +297,7 @@
mQualifiers[INDEX_REGION] = qualifier;
} else if (qualifier instanceof LayoutDirectionQualifier) {
- mQualifiers[INDEX_LAYOUTDIR] = qualifier;
+ mQualifiers[INDEX_LAYOUT_DIR] = qualifier;
} else if (qualifier instanceof SmallestScreenWidthQualifier) {
mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = qualifier;
@@ -348,7 +354,7 @@
* Removes a given qualifier from the {@link FolderConfiguration}.
* @param qualifier the {@link ResourceQualifier} to remove.
*/
- public void removeQualifier(ResourceQualifier qualifier) {
+ public void removeQualifier(@NonNull ResourceQualifier qualifier) {
for (int i = 0 ; i < INDEX_COUNT ; i++) {
if (mQualifiers[i] == qualifier) {
mQualifiers[i] = null;
@@ -363,6 +369,7 @@
* @param index the index of the qualifier to return.
* @return the qualifier or null if there are none at the index.
*/
+ @Nullable
public ResourceQualifier getQualifier(int index) {
return mQualifiers[index];
}
@@ -371,6 +378,7 @@
mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
}
+ @Nullable
public CountryCodeQualifier getCountryCodeQualifier() {
return (CountryCodeQualifier)mQualifiers[INDEX_COUNTRY_CODE];
}
@@ -379,6 +387,7 @@
mQualifiers[INDEX_NETWORK_CODE] = qualifier;
}
+ @Nullable
public NetworkCodeQualifier getNetworkCodeQualifier() {
return (NetworkCodeQualifier)mQualifiers[INDEX_NETWORK_CODE];
}
@@ -387,6 +396,7 @@
mQualifiers[INDEX_LANGUAGE] = qualifier;
}
+ @Nullable
public LanguageQualifier getLanguageQualifier() {
return (LanguageQualifier)mQualifiers[INDEX_LANGUAGE];
}
@@ -395,22 +405,25 @@
mQualifiers[INDEX_REGION] = qualifier;
}
+ @Nullable
public RegionQualifier getRegionQualifier() {
return (RegionQualifier)mQualifiers[INDEX_REGION];
}
public void setLayoutDirectionQualifier(LayoutDirectionQualifier qualifier) {
- mQualifiers[INDEX_LAYOUTDIR] = qualifier;
+ mQualifiers[INDEX_LAYOUT_DIR] = qualifier;
}
+ @Nullable
public LayoutDirectionQualifier getLayoutDirectionQualifier() {
- return (LayoutDirectionQualifier)mQualifiers[INDEX_LAYOUTDIR];
+ return (LayoutDirectionQualifier)mQualifiers[INDEX_LAYOUT_DIR];
}
public void setSmallestScreenWidthQualifier(SmallestScreenWidthQualifier qualifier) {
mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = qualifier;
}
+ @Nullable
public SmallestScreenWidthQualifier getSmallestScreenWidthQualifier() {
return (SmallestScreenWidthQualifier) mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH];
}
@@ -419,6 +432,7 @@
mQualifiers[INDEX_SCREEN_WIDTH] = qualifier;
}
+ @Nullable
public ScreenWidthQualifier getScreenWidthQualifier() {
return (ScreenWidthQualifier) mQualifiers[INDEX_SCREEN_WIDTH];
}
@@ -427,6 +441,7 @@
mQualifiers[INDEX_SCREEN_HEIGHT] = qualifier;
}
+ @Nullable
public ScreenHeightQualifier getScreenHeightQualifier() {
return (ScreenHeightQualifier) mQualifiers[INDEX_SCREEN_HEIGHT];
}
@@ -435,6 +450,7 @@
mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = qualifier;
}
+ @Nullable
public ScreenSizeQualifier getScreenSizeQualifier() {
return (ScreenSizeQualifier)mQualifiers[INDEX_SCREEN_LAYOUT_SIZE];
}
@@ -443,6 +459,7 @@
mQualifiers[INDEX_SCREEN_RATIO] = qualifier;
}
+ @Nullable
public ScreenRatioQualifier getScreenRatioQualifier() {
return (ScreenRatioQualifier)mQualifiers[INDEX_SCREEN_RATIO];
}
@@ -451,6 +468,7 @@
mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier;
}
+ @Nullable
public ScreenOrientationQualifier getScreenOrientationQualifier() {
return (ScreenOrientationQualifier)mQualifiers[INDEX_SCREEN_ORIENTATION];
}
@@ -459,6 +477,7 @@
mQualifiers[INDEX_UI_MODE] = qualifier;
}
+ @Nullable
public UiModeQualifier getUiModeQualifier() {
return (UiModeQualifier)mQualifiers[INDEX_UI_MODE];
}
@@ -467,6 +486,7 @@
mQualifiers[INDEX_NIGHT_MODE] = qualifier;
}
+ @Nullable
public NightModeQualifier getNightModeQualifier() {
return (NightModeQualifier)mQualifiers[INDEX_NIGHT_MODE];
}
@@ -475,6 +495,7 @@
mQualifiers[INDEX_PIXEL_DENSITY] = qualifier;
}
+ @Nullable
public DensityQualifier getDensityQualifier() {
return (DensityQualifier)mQualifiers[INDEX_PIXEL_DENSITY];
}
@@ -483,6 +504,7 @@
mQualifiers[INDEX_TOUCH_TYPE] = qualifier;
}
+ @Nullable
public TouchScreenQualifier getTouchTypeQualifier() {
return (TouchScreenQualifier)mQualifiers[INDEX_TOUCH_TYPE];
}
@@ -491,6 +513,7 @@
mQualifiers[INDEX_KEYBOARD_STATE] = qualifier;
}
+ @Nullable
public KeyboardStateQualifier getKeyboardStateQualifier() {
return (KeyboardStateQualifier)mQualifiers[INDEX_KEYBOARD_STATE];
}
@@ -499,6 +522,7 @@
mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier;
}
+ @Nullable
public TextInputMethodQualifier getTextInputMethodQualifier() {
return (TextInputMethodQualifier)mQualifiers[INDEX_TEXT_INPUT_METHOD];
}
@@ -507,6 +531,7 @@
mQualifiers[INDEX_NAVIGATION_STATE] = qualifier;
}
+ @Nullable
public NavigationStateQualifier getNavigationStateQualifier() {
return (NavigationStateQualifier)mQualifiers[INDEX_NAVIGATION_STATE];
}
@@ -515,6 +540,7 @@
mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier;
}
+ @Nullable
public NavigationMethodQualifier getNavigationMethodQualifier() {
return (NavigationMethodQualifier)mQualifiers[INDEX_NAVIGATION_METHOD];
}
@@ -523,6 +549,7 @@
mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier;
}
+ @Nullable
public ScreenDimensionQualifier getScreenDimensionQualifier() {
return (ScreenDimensionQualifier)mQualifiers[INDEX_SCREEN_DIMENSION];
}
@@ -531,11 +558,36 @@
mQualifiers[INDEX_VERSION] = qualifier;
}
+ @Nullable
public VersionQualifier getVersionQualifier() {
return (VersionQualifier)mQualifiers[INDEX_VERSION];
}
/**
+ * Normalize a folder configuration based on the API level of its qualifiers
+ */
+ public void normalize() {
+ int minSdk = 1;
+ for (ResourceQualifier qualifier : mQualifiers) {
+ if (qualifier != null) {
+ int min = qualifier.since();
+ if (min > minSdk) {
+ minSdk = min;
+ }
+ }
+ }
+
+ if (minSdk == 1) {
+ return;
+ }
+
+ if (mQualifiers[INDEX_VERSION] == null ||
+ ((VersionQualifier)mQualifiers[INDEX_VERSION]).getVersion() < minSdk) {
+ mQualifiers[INDEX_VERSION] = new VersionQualifier(minSdk);
+ }
+ }
+
+ /**
* Updates the {@link SmallestScreenWidthQualifier}, {@link ScreenWidthQualifier}, and
* {@link ScreenHeightQualifier} based on the (required) values of
* {@link ScreenDimensionQualifier} {@link DensityQualifier}, and
@@ -640,7 +692,8 @@
/**
* Returns the name of a folder with the configuration.
*/
- public String getFolderName(ResourceFolderType folder) {
+ @NonNull
+ public String getFolderName(@NonNull ResourceFolderType folder) {
StringBuilder result = new StringBuilder(folder.getName());
for (ResourceQualifier qualifier : mQualifiers) {
@@ -659,6 +712,7 @@
/**
* Returns the folder configuration as a unique key
*/
+ @NonNull
public String getUniqueKey() {
StringBuilder result = new StringBuilder(100);
@@ -678,6 +732,7 @@
/**
* Returns {@link #toDisplayString()}.
*/
+ @NonNull
@Override
public String toString() {
return toDisplayString();
@@ -686,6 +741,7 @@
/**
* Returns a string valid for display purpose.
*/
+ @NonNull
public String toDisplayString() {
if (isDefault()) {
return "default";
@@ -693,7 +749,7 @@
StringBuilder result = null;
int index = 0;
- ResourceQualifier qualifier = null;
+ ResourceQualifier qualifier;
// pre- language/region qualifiers
while (index < INDEX_LANGUAGE) {
@@ -738,12 +794,13 @@
}
}
- return result == null ? null : result.toString();
+ return result == null ? "" : result.toString();
}
/**
* Returns a string for display purposes which uses only the short names of the qualifiers
*/
+ @NonNull
public String toShortDisplayString() {
if (isDefault()) {
return "default";
@@ -767,7 +824,7 @@
}
@Override
- public int compareTo(FolderConfiguration folderConfig) {
+ public int compareTo(@NonNull FolderConfiguration folderConfig) {
// default are always at the top.
if (isDefault()) {
if (folderConfig.isDefault()) {
@@ -782,9 +839,7 @@
ResourceQualifier qualifier2 = folderConfig.mQualifiers[i];
if (qualifier1 == null) {
- if (qualifier2 == null) {
- continue;
- } else {
+ if (qualifier2 != null) {
return -1;
}
} else {
@@ -816,7 +871,7 @@
* See http://d.android.com/guide/topics/resources/resources-i18n.html#best-match
*/
@Nullable
- public Configurable findMatchingConfigurable(List<? extends Configurable> configurables) {
+ public Configurable findMatchingConfigurable(@Nullable List<? extends Configurable> configurables) {
if (configurables == null) {
return null;
}
@@ -848,7 +903,7 @@
}
// 2. Loop on the qualifiers, and eliminate matches
- final int count = FolderConfiguration.getQualifierCount();
+ final int count = getQualifierCount();
for (int q = 0 ; q < count ; q++) {
// look to see if one configurable has this qualifier.
// At the same time also record the best match value for the qualifier (if applicable).
@@ -896,7 +951,8 @@
matchingConfigurables.remove(configurable);
} else {
// looks like we keep this resource, move on to the next one.
- i++;
+ //noinspection AssignmentToForLoopParameter
+ i++;
}
}
@@ -929,7 +985,7 @@
* @param referenceConfig The reference configuration to test against.
* @return true if the configuration matches.
*/
- public boolean isMatchFor(FolderConfiguration referenceConfig) {
+ public boolean isMatchFor(@Nullable FolderConfiguration referenceConfig) {
if (referenceConfig == null) {
return false;
}
@@ -973,7 +1029,7 @@
mQualifiers[INDEX_NETWORK_CODE] = new NetworkCodeQualifier();
mQualifiers[INDEX_LANGUAGE] = new LanguageQualifier();
mQualifiers[INDEX_REGION] = new RegionQualifier();
- mQualifiers[INDEX_LAYOUTDIR] = new LayoutDirectionQualifier();
+ mQualifiers[INDEX_LAYOUT_DIR] = new LayoutDirectionQualifier();
mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = new SmallestScreenWidthQualifier();
mQualifiers[INDEX_SCREEN_WIDTH] = new ScreenWidthQualifier();
mQualifiers[INDEX_SCREEN_HEIGHT] = new ScreenHeightQualifier();
@@ -995,6 +1051,7 @@
/**
* Returns an array of all the non null qualifiers.
*/
+ @NonNull
public ResourceQualifier[] getQualifiers() {
int count = 0;
for (int i = 0 ; i < INDEX_COUNT ; i++) {
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LanguageQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LanguageQualifier.java
index 128b72b..c7dd84b 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LanguageQualifier.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LanguageQualifier.java
@@ -23,7 +23,7 @@
* Resource Qualifier for Language.
*/
public final class LanguageQualifier extends ResourceQualifier {
- private static final Pattern sLanguagePattern = Pattern.compile("^[a-z]{2}$"); //$NON-NLS-1$
+ private static final Pattern sLanguagePattern = Pattern.compile("^[a-zA-Z]{2}$"); //$NON-NLS-1$
public static final String FAKE_LANG_VALUE = "__"; //$NON-NLS-1$
public static final String NAME = "Language";
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
index a1b41ba..0c99d2c 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
@@ -16,6 +16,7 @@
package com.android.ide.common.resources.configuration;
+import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -65,7 +66,7 @@
*/
public static String getFolderSegment(int code) {
if (code != DEFAULT_CODE && code >= 1 && code <= 999) { // code is 1-3 digit.
- return String.format("mnc%1$d", code); //$NON-NLS-1$
+ return String.format(Locale.US, "mnc%1$03d", code); //$NON-NLS-1$
}
return ""; //$NON-NLS-1$
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/RegionQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/RegionQualifier.java
index 7993090..32dd37e 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/RegionQualifier.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/RegionQualifier.java
@@ -24,7 +24,7 @@
* Resource Qualifier for Region.
*/
public final class RegionQualifier extends ResourceQualifier {
- private static final Pattern sRegionPattern = Pattern.compile("^r([A-Z]{2})$"); //$NON-NLS-1$
+ private static final Pattern sRegionPattern = Pattern.compile("^r([a-zA-Z]{2})$"); //$NON-NLS-1$
public static final String FAKE_REGION_VALUE = "__"; //$NON-NLS-1$
public static final String NAME = "Region";
@@ -41,7 +41,11 @@
Matcher m = sRegionPattern.matcher(segment);
if (m.matches()) {
RegionQualifier qualifier = new RegionQualifier();
- qualifier.mValue = m.group(1);
+ assert m.group(1).length() == 2;
+ qualifier.mValue = new String(new char[] {
+ Character.toUpperCase(segment.charAt(1)),
+ Character.toUpperCase(segment.charAt(2))
+ });
return qualifier;
}
@@ -70,7 +74,7 @@
}
public RegionQualifier(String value) {
- mValue = value;
+ mValue = value.toUpperCase(Locale.US);
}
public String getValue() {
@@ -108,6 +112,9 @@
@Override
public boolean checkAndSet(String value, FolderConfiguration config) {
+ if (value.length() != 3) {
+ return false;
+ }
RegionQualifier qualifier = getQualifier(value);
if (qualifier != null) {
config.setRegionQualifier(qualifier);
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
index 77193a2..e8dcd8b 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
@@ -71,4 +71,32 @@
return false;
}
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ // This is a match only if the screen size is smaller than the qualifier's screen size.
+ if (qualifier instanceof ScreenSizeQualifier) {
+ int qualifierIndex = ScreenSize.getIndex(((ScreenSizeQualifier) qualifier).mValue);
+ int index = ScreenSize.getIndex(mValue);
+ if (index <= qualifierIndex) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ ScreenSizeQualifier compareQ = (ScreenSizeQualifier) compareTo;
+ int thisIndex = ScreenSize.getIndex(mValue);
+ int compareIndex = ScreenSize.getIndex(compareQ.mValue);
+
+ // Return true if this size is larger than reference size. Since isMatchFor() is called
+ // before, it is guaranteed that the size will not be larger than the reference.
+ return thisIndex > compareIndex;
+ }
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/UiModeQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/UiModeQualifier.java
index 1e302c5..109b00a 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/UiModeQualifier.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/UiModeQualifier.java
@@ -57,6 +57,9 @@
@Override
public int since() {
+ if (mValue != null) {
+ return mValue.since();
+ }
return 8;
}
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 70b5749..0dd888e 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
@@ -16,6 +16,8 @@
package com.android.ide.common.sdk;
import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
import java.util.Locale;
@@ -31,6 +33,11 @@
public static final int HIGHEST_KNOWN_API = 19;
/**
+ * Like {@link #HIGHEST_KNOWN_API} but does not include preview platforms
+ */
+ public static final int HIGHEST_KNOWN_STABLE_API = HIGHEST_KNOWN_API;
+
+ /**
* Returns the Android version and code name of the given API level, or null
* if not known. The highest number (inclusive) that is supported
* is {@link SdkVersionInfo#HIGHEST_KNOWN_API}.
@@ -68,6 +75,22 @@
}
}
+ public static String getCodeName(int api) {
+ String s = getAndroidName(api);
+ if (s != null) {
+ int start = s.indexOf('(');
+ if (start != -1) {
+ start++;
+ int end = s.indexOf(')', start);
+ if (end != -1) {
+ return s.substring(start, end);
+ }
+ }
+ }
+
+ return null;
+ }
+
/**
* Returns the applicable build code (for
* {@code android.os.Build.VERSION_CODES}) for the corresponding API level,
@@ -202,4 +225,63 @@
return sb.toString();
}
+
+ /**
+ * Returns the {@link AndroidVersion} for a given version string, which is typically an API
+ * level number, but can also be a codename for a <b>preview</b> platform. Note: This should
+ * <b>not</b> be used to look up version names for build codes; for that, use {@link
+ * #getApiByBuildCode(String, boolean)}. The primary difference between this method is that
+ * {@link #getApiByBuildCode(String, boolean)} will return the final API number for a platform
+ * (e.g. for "KITKAT" it will return 19) whereas this method will return the API number for the
+ * codename as a preview platform (e.g. 18).
+ *
+ * @param apiOrPreviewName the version string
+ * @param targets an optional array of installed targets, if available. If the version
+ * string corresponds to a code name, this is used to search for a
+ * corresponding API level.
+ * @return an {@link com.android.sdklib.AndroidVersion}, or null if the version could not be
+ * determined (e.g. an empty or invalid API number or an unknown code name)
+ */
+ @Nullable
+ public static AndroidVersion getVersion(
+ @Nullable String apiOrPreviewName,
+ @Nullable IAndroidTarget[] targets) {
+ if (apiOrPreviewName == null || apiOrPreviewName.isEmpty()) {
+ return null;
+ }
+
+ if (Character.isDigit(apiOrPreviewName.charAt(0))) {
+ try {
+ int api = Integer.parseInt(apiOrPreviewName);
+ if (api >= 1) {
+ return new AndroidVersion(api, null);
+ }
+ return null;
+ } catch (NumberFormatException e) {
+ // Invalid version string
+ return null;
+ }
+ }
+
+ // Codename
+ if (targets != null) {
+ for (int i = targets.length - 1; i >= 0; i--) {
+ IAndroidTarget target = targets[i];
+ if (target.isPlatform()) {
+ AndroidVersion version = target.getVersion();
+ if (version.isPreview() && apiOrPreviewName.equalsIgnoreCase(version.getCodename())) {
+ return new AndroidVersion(version.getApiLevel(), version.getCodename());
+ }
+ }
+ }
+ }
+
+ int api = getApiByPreviewName(apiOrPreviewName, false);
+ if (api != -1) {
+ return new AndroidVersion(api - 1, apiOrPreviewName);
+ }
+
+ // Must be a future SDK platform
+ return new AndroidVersion(HIGHEST_KNOWN_API, apiOrPreviewName);
+ }
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/signing/CertificateInfo.java b/sdk-common/src/main/java/com/android/ide/common/signing/CertificateInfo.java
new file mode 100644
index 0000000..f328630
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/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.ide.common.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/sdk-common/src/main/java/com/android/ide/common/signing/KeystoreHelper.java b/sdk-common/src/main/java/com/android/ide/common/signing/KeystoreHelper.java
new file mode 100644
index 0000000..4da72c5
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/signing/KeystoreHelper.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.signing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.utils.GrabProcessOutput;
+import com.android.utils.GrabProcessOutput.IProcessOutput;
+import com.android.utils.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(@Nullable String storeType, @NonNull File storeFile,
+ @NonNull String storePassword, @NonNull String keyPassword,
+ @NonNull String keyAlias,
+ @NonNull ILogger logger) throws KeytoolException {
+
+ return createNewStore(storeType, storeFile, storePassword, keyPassword, keyAlias,
+ 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(
+ @Nullable String storeType,
+ @NonNull File storeFile,
+ @NonNull String storePassword,
+ @NonNull String keyPassword,
+ @NonNull String keyAlias,
+ @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(keyAlias);
+ 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(keyPassword);
+ commandList.add("-keystore");
+ commandList.add(storeFile.getAbsolutePath());
+ commandList.add("-storepass");
+ commandList.add(storePassword);
+ if (storeType != null) {
+ commandList.add("-storetype");
+ commandList.add(storeType);
+ }
+
+ 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(@Nullable String storeType, @NonNull File storeFile,
+ @NonNull String storePassword, @NonNull String keyPassword,
+ @NonNull String keyAlias)
+ throws KeytoolException, FileNotFoundException {
+
+ try {
+ KeyStore keyStore = KeyStore.getInstance(storeType != null ?
+ storeType : KeyStore.getDefaultType());
+
+ FileInputStream fis = new FileInputStream(storeFile);
+ //noinspection ConstantConditions
+ keyStore.load(fis, storePassword.toCharArray());
+ fis.close();
+
+ //noinspection ConstantConditions
+ char[] keyPasswordArray = keyPassword.toCharArray();
+ PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
+ keyAlias, new KeyStore.PasswordProtection(keyPasswordArray));
+
+ 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",
+ keyAlias, storeFile, e.getMessage()),
+ e);
+ }
+
+ return null;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/signing/KeytoolException.java b/sdk-common/src/main/java/com/android/ide/common/signing/KeytoolException.java
new file mode 100644
index 0000000..4436a78
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/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.ide.common.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/sdk-common/src/main/java/com/android/ide/common/xml/AndroidManifestParser.java b/sdk-common/src/main/java/com/android/ide/common/xml/AndroidManifestParser.java
index eafa13d..ea34226 100644
--- a/sdk-common/src/main/java/com/android/ide/common/xml/AndroidManifestParser.java
+++ b/sdk-common/src/main/java/com/android/ide/common/xml/AndroidManifestParser.java
@@ -30,6 +30,7 @@
import com.android.resources.Navigation;
import com.android.resources.TouchScreen;
import com.android.xml.AndroidManifest;
+import com.google.common.io.Closeables;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
@@ -606,7 +607,16 @@
ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
data, errorHandler);
- parser.parse(new InputSource(manifestFile.getContents()), manifestHandler);
+ InputStream is = manifestFile.getContents();
+ try {
+ parser.parse(new InputSource(is), manifestHandler);
+ } finally {
+ try {
+ Closeables.close(is, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
return data;
}
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 ff97dcc..a4defca 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
@@ -16,10 +16,15 @@
package com.android.ide.common.xml;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
import com.android.utils.SdkUtils;
import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
@@ -28,11 +33,14 @@
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
+import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.TAG_COLOR;
import static com.android.SdkConstants.TAG_DIMEN;
import static com.android.SdkConstants.TAG_ITEM;
@@ -434,12 +442,34 @@
}
}
if (lastPrefixNewline != -1 || firstSuffixNewline != -1) {
+ boolean stripSuffix;
if (firstSuffixNewline == -1) {
firstSuffixNewline = text.length();
+ stripSuffix = false;
+ } else {
+ stripSuffix = true;
}
+
int stripFrom = lastPrefixNewline + 1;
if (firstSuffixNewline >= stripFrom) {
text = text.substring(stripFrom, firstSuffixNewline);
+
+ // In markup strings we may need to preserve spacing on the left and/or
+ // right if we're next to a markup string on the given side
+ if (lastPrefixNewline != -1) {
+ Node left = node.getPreviousSibling();
+ if (left != null && left.getNodeType() == Node.ELEMENT_NODE
+ && isMarkupElement((Element) left)) {
+ text = ' ' + text;
+ }
+ }
+ if (stripSuffix) {
+ Node right = node.getNextSibling();
+ if (right != null && right.getNodeType() == Node.ELEMENT_NODE
+ && isMarkupElement((Element) right)) {
+ text += ' ';
+ }
+ }
}
}
@@ -453,6 +483,18 @@
if (mStyle != XmlFormatStyle.RESOURCE) {
mOut.append(mLineSeparator);
}
+ } else {
+ // Ensure that if we're in the middle of a markup string, we preserve spacing.
+ // In other words, "<b>first</b> <b>second</b>" - we don't want that middle
+ // space to disappear, but we do want repeated spaces to collapse into one.
+ Node left = node.getPreviousSibling();
+ Node right = node.getNextSibling();
+ if (left != null && right != null
+ && left.getNodeType() == Node.ELEMENT_NODE
+ && right.getNodeType() == Node.ELEMENT_NODE
+ && isMarkupElement((Element)left)) {
+ mOut.append(' ');
+ }
}
}
@@ -1103,4 +1145,114 @@
return true;
}
+
+ private static void printUsage() {
+ System.out.println("Usage: " + XmlPrettyPrinter.class.getSimpleName() +
+ " <options>... <files or directories...>");
+ System.out.println("OPTIONS:");
+ System.out.println("--stdout");
+ System.out.println("--removeEmptyLines");
+ System.out.println("--noAttributeOnFirstLine");
+ System.out.println("--noSpaceBeforeClose");
+ System.exit(1);
+ }
+
+ /** Command line driver */
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ printUsage();
+ }
+
+ List<File> files = Lists.newArrayList();
+
+ XmlFormatPreferences prefs = XmlFormatPreferences.defaults();
+ boolean stdout = false;
+
+ for (String arg : args) {
+ if (arg.startsWith("--")) {
+ if ("--stdout".equals(arg)) {
+ stdout = true;
+ } else if ("--removeEmptyLines".equals(arg)) {
+ prefs.removeEmptyLines = true;
+ } else if ("--noAttributeOnFirstLine".equals(arg)) {
+ prefs.oneAttributeOnFirstLine = false;
+ } else if ("--noSpaceBeforeClose".equals(arg)) {
+ prefs.spaceBeforeClose = false;
+ } else {
+ System.err.println("Unknown flag " + arg);
+ printUsage();
+ }
+ } else {
+ File file = new File(arg).getAbsoluteFile();
+ if (!file.exists()) {
+ System.err.println("Can't find file " + file);
+ System.exit(1);
+ } else {
+ files.add(file);
+ }
+ }
+ }
+
+ for (File file : files) {
+ formatFile(prefs, file, stdout);
+ }
+
+ System.exit(0);
+ }
+
+ private static void formatFile(@NonNull XmlFormatPreferences prefs, File file,
+ boolean stdout) {
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ formatFile(prefs, child, stdout);
+ }
+ }
+ } else if (file.isFile() && SdkUtils.endsWithIgnoreCase(file.getName(), DOT_XML)) {
+ XmlFormatStyle style = null;
+ if (file.getName().equals(SdkConstants.ANDROID_MANIFEST_XML)) {
+ style = XmlFormatStyle.MANIFEST;
+ } else {
+ File parent = file.getParentFile();
+ if (parent != null) {
+ String parentName = parent.getName();
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(parentName);
+ if (folderType == ResourceFolderType.LAYOUT) {
+ style = XmlFormatStyle.LAYOUT;
+ } else if (folderType == ResourceFolderType.VALUES) {
+ style = XmlFormatStyle.RESOURCE;
+ }
+ }
+ }
+
+ try {
+ String xml = Files.toString(file, Charsets.UTF_8);
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ if (document == null) {
+ System.err.println("Could not parse " + file);
+ System.exit(1);
+ return;
+ }
+
+ if (style == null) {
+ style = XmlFormatStyle.get(document);
+ }
+ boolean endWithNewline = xml.endsWith("\n");
+ int firstNewLine = xml.indexOf('\n');
+ String lineSeparator = firstNewLine > 0 && xml.charAt(firstNewLine - 1) == '\r' ?
+ "\r\n" : "\n";
+ String formatted = XmlPrettyPrinter.prettyPrint(document, prefs, style,
+ lineSeparator, endWithNewline);
+ if (stdout) {
+ System.out.println(formatted);
+ } else {
+ Files.write(formatted, file, Charsets.UTF_8);
+ }
+ } catch (IOException e) {
+ System.err.println("Could not read " + file);
+ System.exit(1);
+ }
+ }
+ }
}
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
index b4b0211..6f796f8 100644
--- 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
@@ -32,6 +32,7 @@
import java.lang.reflect.Field;
import java.security.Permission;
import java.util.Collections;
+import java.util.TimeZone;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -707,4 +708,63 @@
manager.dispose(myCredential);
}
}
+
+ public void testTempDir() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ String temp = System.getProperty("java.io.tmpdir");
+ assertNotNull(temp);
+
+ manager.checkPermission(new FilePermission(temp, "read,write"));
+ manager.checkPermission(new FilePermission(temp + File.separator, "read,write"));
+
+ temp = new File(temp).getCanonicalPath();
+ manager.checkPermission(new FilePermission(temp, "read,write"));
+
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testAppTempDir() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setAppTempDir("/random/path/");
+ manager.setActive(true, myCredential);
+ manager.checkPermission(new FilePermission("/random/path/myfile.tmp", "read,write"));
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+
+ public void testSetTimeZone() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ /* ICU needs this (needed for Calendar widget rendering)
+ at java.util.TimeZone.hasPermission(TimeZone.java:597)
+ at java.util.TimeZone.setDefault(TimeZone.java:619)
+ at com.ibm.icu.util.TimeZone.setDefault(TimeZone.java:973)
+ at libcore.icu.DateIntervalFormat_Delegate.createDateIntervalFormat(DateIntervalFormat_Delegate.java:61)
+ at libcore.icu.DateIntervalFormat.createDateIntervalFormat(DateIntervalFormat.java)
+ at libcore.icu.DateIntervalFormat.getFormatter(DateIntervalFormat.java:112)
+ at libcore.icu.DateIntervalFormat.formatDateRange(DateIntervalFormat.java:102)
+ at libcore.icu.DateIntervalFormat.formatDateRange(DateIntervalFormat.java:71)
+ at android.text.format.DateUtils.formatDateRange(DateUtils.java:826)
+ */
+ TimeZone deflt = TimeZone.getDefault();
+ String[] availableIDs = TimeZone.getAvailableIDs();
+ TimeZone timeZone = TimeZone.getTimeZone(availableIDs[0]);
+ TimeZone.setDefault(timeZone);
+ TimeZone.setDefault(deflt);
+ } 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
index 1814994..6cabebd 100644
--- 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
@@ -20,110 +20,259 @@
import java.util.List;
+import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_HIGHER;
+import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_LOWER;
+
/**
* 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);
+ 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, GradleCoordinate.PLUS_REV);
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.+");
- assertEquals(expected, actual);
+ expected = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
+ assertEquals(expected, actual);
- expected = new GradleCoordinate("a.b.c", "package", GradleCoordinate.PLUS_REV);
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+");
- assertEquals(expected, actual);
+ expected = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.+");
+ 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", GradleCoordinate.PLUS_REV);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+");
+ 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);
- }
+ List<GradleCoordinate.RevisionComponent> revisionList =
+ Lists.<GradleCoordinate.RevisionComponent>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);
- 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 = new GradleCoordinate("a.b.c", "package", revisionList,
+ GradleCoordinate.ArtifactType.AAR);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+@AAR");
+ 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 = new GradleCoordinate("a.b.c", "package",
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.StringComponent("v2"));
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:v1.v2");
+ 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 = new GradleCoordinate("a.b.c", "package",
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.NumberComponent(1)));
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:v1-1");
+ assertEquals(expected, actual);
- expected = "a.b.c:package:+";
- actual = new GradleCoordinate("a.b.c", "package", GradleCoordinate.PLUS_REV).toString();
- assertEquals(expected, actual);
+ expected = new GradleCoordinate("a.b.c", "package",
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.NumberComponent(1)),
+ new GradleCoordinate.NumberComponent(17),
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.NumberComponent(0),
+ new GradleCoordinate.StringComponent("rc"),
+ new GradleCoordinate.StringComponent("SNAPSHOT")));
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:v1-1.17.0-rc-SNAPSHOT");
+ 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);
+ 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:+@aar";
- actual = new GradleCoordinate("a.b.c", "package", revisionList, GradleCoordinate.ArtifactType.AAR).toString();
- assertEquals(expected, actual);
- }
+ expected = "a.b.c:package:5.4.+";
+ actual = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE)
+ .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));
+ expected = "a.b.c:package:5.+";
+ actual = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE)
+ .toString();
+ assertEquals(expected, actual);
- 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));
+ expected = "a.b.c:package:+";
+ actual = new GradleCoordinate("a.b.c", "package", GradleCoordinate.PLUS_REV).toString();
+ assertEquals(expected, actual);
- 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));
- }
+ expected = "a.b.c:package:+@jar";
+ List<GradleCoordinate.RevisionComponent> revisionList =
+ Lists.<GradleCoordinate.RevisionComponent>newArrayList(GradleCoordinate.PLUS_REV);
+ actual = new GradleCoordinate("a.b.c", "package", revisionList,
+ GradleCoordinate.ArtifactType.JAR).toString();
+ assertEquals(expected, actual);
- 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);
+ expected = "a.b.c:package:+@aar";
+ actual = new GradleCoordinate("a.b.c", "package", revisionList,
+ GradleCoordinate.ArtifactType.AAR).toString();
+ assertEquals(expected, actual);
- 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);
+ expected = "com.google.maps.android:android-maps-utils:0.3";
+ actual = GradleCoordinate.parseCoordinateString(expected).toString();
+ assertEquals(expected, actual);
- 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);
+ expected = "a.b.c:package:v1.v2";
+ actual = new GradleCoordinate("a.b.c", "package",
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.StringComponent("v2")).toString();
+ assertEquals(expected, actual);
- 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);
+ expected = "a.b.c:package:v1-1.17.0-rc-SNAPSHOT";
+ actual = new GradleCoordinate("a.b.c", "package",
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.NumberComponent(1)),
+ new GradleCoordinate.NumberComponent(17),
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.NumberComponent(0),
+ new GradleCoordinate.StringComponent("rc"),
+ new GradleCoordinate.StringComponent("SNAPSHOT"))).toString();
+ assertEquals(expected, actual);
+ }
- a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- b = new GradleCoordinate("a.b.c", "feature", 5, 4, 2);
+ 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));
- assertTrue( (a.compareTo(b) < 0) == ("package".compareTo("feature") < 0));
+ 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, 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, 4, 2);
+ b = new GradleCoordinate("a.b.c", "feature", 5, 5, 5);
+ assertFalse(a.isSameArtifact(b));
+ assertFalse(b.isSameArtifact(a));
+ }
- 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);
- }
-}
+ public void testCompareVersions() {
+ // Requirements order
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 10);
+ b = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
+ b = new GradleCoordinate("a.b.c", "package", 6, 0, 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) == 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ b = new GradleCoordinate("a.b.c", "feature", 5, 4, 2);
+
+ assertTrue((COMPARE_PLUS_HIGHER.compare(a, 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_VALUE);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.2");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
+ assert a != null;
+ assert b != null;
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:5");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:+");
+ assert a != null;
+ assert b != null;
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:1.any");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:1.1");
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:1-1");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:1-2");
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+ }
+
+ public void testCompareSpecificity() {
+ // Order of specificity
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(b, a) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 10);
+ b = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
+ b = new GradleCoordinate("a.b.c", "package", 6, 0, 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) < 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) == 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ b = new GradleCoordinate("a.b.c", "feature", 5, 4, 2);
+
+ assertTrue((COMPARE_PLUS_LOWER.compare(a, 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_VALUE);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.2");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
+ assert a != null;
+ assert b != null;
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(b, a) < 0);
+ }
+
+ public void testGetVersions() {
+ GradleCoordinate c = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ assertEquals(5, c.getMajorVersion());
+ assertEquals(4, c.getMinorVersion());
+ assertEquals(2, c.getMicroVersion());
+ }
+
+ public void testSameSharedDifferentLengths() {
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(b, a) > 0);
+ }
+
+ public void testSameSharedDifferentLengthsWithZeros() {
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 4, 0, 0, 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) == 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) == 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) == 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(b, a) == 0);
+ }
+}
\ No newline at end of file
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/BaseTestCase.java b/sdk-common/src/test/java/com/android/ide/common/res2/BaseTestCase.java
index 9bc1977..ea5506d 100644
--- a/sdk-common/src/test/java/com/android/ide/common/res2/BaseTestCase.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/BaseTestCase.java
@@ -93,8 +93,8 @@
* (The blobs written in the test data contains placeholders for the path root and path
* separators)
*
- * @param folder
- * @return
+ * @param folder the folder containing the merge blob
+ * @return a new file that contains the merge blob
* @throws java.io.IOException
*/
protected static File getMergedBlobFolder(File folder) throws IOException {
@@ -118,7 +118,7 @@
* from the file generated, this checks that the file replacement works and all the files are
* where they are supposed to be.
*
- * @param dataMerger
+ * @param dataMerger the data merger
*/
protected void checkSourceFolders(
DataMerger<? extends DataItem, ? extends DataFile, ? extends DataSet> dataMerger) {
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 d9c402a..9d49e77 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
@@ -49,7 +49,7 @@
public void testMergeByCount() throws Exception {
ResourceMerger merger = getResourceMerger();
- assertEquals(29, merger.size());
+ assertEquals(31, merger.size());
}
public void testMergedResourcesByName() throws Exception {
@@ -57,7 +57,7 @@
verifyResourceExists(merger,
"drawable/icon",
- "drawable-ldpi/icon",
+ "drawable-ldpi-v4/icon",
"drawable/icon2",
"drawable/patch",
"raw/foo",
@@ -70,6 +70,7 @@
"color/color",
"string/basic_string",
"string/xliff_string",
+ "string/xliff_with_carriage_return",
"string/styled_string",
"style/style",
"array/string_array",
@@ -82,6 +83,7 @@
"attr/flagAttr",
"declare-styleable/declare_styleable",
"dimen/dimen",
+ "dimen-sw600dp-v13/offset",
"id/item_id",
"integer/integer"
);
@@ -150,6 +152,82 @@
checkLogger(logger);
}
+ public void testXliffString() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ // check the result of the load
+ List<ResourceItem> values = merger.getDataMap().get("string/xliff_string");
+
+ assertEquals(1, values.size());
+ ResourceItem string = values.get(0);
+
+ // Even though the content is
+ // <xliff:g id="firstName">%1$s</xliff:g> <xliff:g id="lastName">%2$s</xliff:g>
+ // The valueText is going to skip the <g> node so we skip them from the comparison.
+ // What matters here is that the whitespaces are kept.
+ assertEquals("Loaded String in merger",
+ "%1$s %2$s",
+ string.getValueText());
+
+ File folder = getWrittenResources();
+
+ RecordingLogger logger = new RecordingLogger();
+ ResourceSet writtenSet = new ResourceSet("unused");
+ writtenSet.addSource(folder);
+ writtenSet.loadFromFiles(logger);
+
+ values = writtenSet.getDataMap().get("string/xliff_string");
+
+ assertEquals(1, values.size());
+ string = values.get(0);
+
+ // Even though the content is
+ // <xliff:g id="firstName">%1$s</xliff:g> <xliff:g id="lastName">%2$s</xliff:g>
+ // The valueText is going to skip the <g> node so we skip them from the comparison.
+ // What matters here is that the whitespaces are kept.
+ assertEquals("Rewritten String through merger",
+ "%1$s %2$s",
+ string.getValueText());
+ }
+
+ public void testXliffStringWithCarriageReturn() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ // check the result of the load
+ List<ResourceItem> values = merger.getDataMap().get("string/xliff_with_carriage_return");
+
+ assertEquals(1, values.size());
+ ResourceItem string = values.get(0);
+
+ // Even though the content has xliff nodes
+ // The valueText is going to skip the <g> node so we skip them from the comparison.
+ // What matters here is that the whitespaces are kept.
+ String value = string.getValueText();
+ assertEquals("Loaded String in merger",
+ "This is should be followed by whitespace:\n %1$s",
+ value);
+
+ File folder = getWrittenResources();
+
+ RecordingLogger logger = new RecordingLogger();
+ ResourceSet writtenSet = new ResourceSet("unused");
+ writtenSet.addSource(folder);
+ writtenSet.loadFromFiles(logger);
+
+ values = writtenSet.getDataMap().get("string/xliff_with_carriage_return");
+
+ assertEquals(1, values.size());
+ string = values.get(0);
+
+ // Even though the content has xliff nodes
+ // The valueText is going to skip the <g> node so we skip them from the comparison.
+ // What matters here is that the whitespaces are kept.
+ value = string.getValueText();
+ assertEquals("Rewritten String through merger",
+ "This is should be followed by whitespace: %1$s",
+ value);
+ }
+
public void testNotMergedAttr() throws Exception {
RecordingLogger logger = new RecordingLogger();
@@ -190,7 +268,7 @@
new MergedResourceWriter(Files.createTempDir(), null /*aaptRunner*/));
ResourceMerger loadedMerger = new ResourceMerger();
- loadedMerger.loadFromBlob(folder, true /*incrementalState*/);
+ assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
// check that attr/blah is ignoredFromDiskMerge.
List<ResourceItem> items = loadedMerger.getDataSets().get(0).getDataMap().get("attr/blah");
@@ -260,7 +338,7 @@
new MergedResourceWriter(Files.createTempDir(), null /*aaptRunner*/));
ResourceMerger loadedMerger = new ResourceMerger();
- loadedMerger.loadFromBlob(folder, true /*incrementalState*/);
+ assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
compareResourceMaps(merger, loadedMerger, true /*full compare*/);
}
@@ -274,7 +352,7 @@
File fakeRoot = getMergedBlobFolder(root);
ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
checkSourceFolders(resourceMerger);
List<ResourceSet> sets = resourceMerger.getDataSets();
@@ -294,7 +372,7 @@
File root = getIncMergeRoot("basicFiles");
File fakeRoot = getMergedBlobFolder(root);
ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
checkSourceFolders(resourceMerger);
List<ResourceSet> sets = resourceMerger.getDataSets();
@@ -378,7 +456,7 @@
assertTrue(newOverlay.isTouched());
// check new alternate: one objects, last one is TOUCHED
- List<ResourceItem> drawableHdpiNewAlternate = mergedMap.get("drawable-hdpi/new_alternate");
+ List<ResourceItem> drawableHdpiNewAlternate = mergedMap.get("drawable-hdpi-v4/new_alternate");
assertEquals(1, drawableHdpiNewAlternate.size());
ResourceItem newAlternate = drawableHdpiNewAlternate.get(0);
assertEquals(overlayDrawableHdpiNewAlternate, newAlternate.getSource().getFile());
@@ -402,16 +480,16 @@
(int) 0xFF00FF00);
checkImageColor(new File(resFolder, "drawable" + File.separator + "removed_overlay.png"),
(int) 0xFF00FF00);
- checkImageColor(new File(resFolder, "drawable-hdpi" + File.separator + "new_alternate.png"),
+ checkImageColor(new File(resFolder, "drawable-hdpi-v4" + File.separator + "new_alternate.png"),
(int) 0xFF00FF00);
- assertFalse(new File(resFolder, "drawable-ldpi" + File.separator + "removed.png").isFile());
+ assertFalse(new File(resFolder, "drawable-ldpi-v4" + File.separator + "removed.png").isFile());
}
public void testUpdateWithBasicValues() throws Exception {
File root = getIncMergeRoot("basicValues");
File fakeRoot = getMergedBlobFolder(root);
ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
checkSourceFolders(resourceMerger);
List<ResourceSet> sets = resourceMerger.getDataSets();
@@ -528,7 +606,7 @@
File root = getIncMergeRoot("basicValues2");
File fakeRoot = getMergedBlobFolder(root);
ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
checkSourceFolders(resourceMerger);
List<ResourceSet> sets = resourceMerger.getDataSets();
@@ -596,7 +674,7 @@
File root = getIncMergeRoot("filesVsValues");
File fakeRoot = getMergedBlobFolder(root);
ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
checkSourceFolders(resourceMerger);
List<ResourceSet> sets = resourceMerger.getDataSets();
@@ -720,7 +798,7 @@
// reload it
ResourceMerger loadedMerger = new ResourceMerger();
- loadedMerger.loadFromBlob(folder, true /*incrementalState*/);
+ assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
String expected = merger1.toString();
String actual = loadedMerger.toString();
@@ -818,7 +896,7 @@
File fakeBlobRoot = getMergedBlobFolder(root);
ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/);
+ assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
checkSourceFolders(resourceMerger);
List<ResourceSet> sets = resourceMerger.getDataSets();
@@ -870,7 +948,7 @@
// check the removed icon is not present.
ResourceMerger resourceMerger2 = new ResourceMerger();
- resourceMerger2.loadFromBlob(outBlobFolder, true /*incrementalState*/);
+ assertTrue(resourceMerger2.loadFromBlob(outBlobFolder, true /*incrementalState*/));
mergedMap = resourceMerger2.getDataMap();
removedIcon = mergedMap.get("drawable/removed");
@@ -884,8 +962,8 @@
*
* Each set is [ setName, folder1, folder2, ...]
*
- * @param data
- * @return
+ * @param data the data sets
+ * @return the merger
*/
private static ResourceMerger createMerger(String[][] data) {
ResourceMerger merger = new ResourceMerger();
@@ -932,7 +1010,7 @@
return folder;
}
- private File getIncMergeRoot(String name) throws IOException {
+ private static File getIncMergeRoot(String name) throws IOException {
File root = TestUtils.getCanonicalRoot("resources", "incMergeData");
return new File(root, name);
}
@@ -984,7 +1062,7 @@
continue;
}
- ResourceType type = ValueResourceParser2.getType(node);
+ ResourceType type = ValueResourceParser2.getType(node, file);
if (type != ResourceType.STRING) {
throw new IllegalArgumentException("Only String resources supported.");
}
@@ -1064,6 +1142,18 @@
fail("Exception not thrown as expected");
}
+ public void testWriteAndReadBlob() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ File folder = Files.createTempDir();
+ merger.writeBlobTo(folder,
+ new MergedResourceWriter(Files.createTempDir(), null /*aaptRunner*/));
+
+ // new merger to read the blob
+ ResourceMerger loadedMerger = new ResourceMerger();
+ assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
+ }
+
public void testInvalidFileNames() throws Exception {
File root = TestUtils.getRoot("resources", "brokenSet5");
ResourceSet resourceSet = new ResourceSet("brokenSet5");
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 611f565..49dd463 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
@@ -45,12 +45,12 @@
assertEquals(1, items.get(ResourceType.RAW).size());
assertEquals(4, items.get(ResourceType.LAYOUT).size());
assertEquals(1, items.get(ResourceType.COLOR).size());
- assertEquals(3, items.get(ResourceType.STRING).size());
+ assertEquals(4, items.get(ResourceType.STRING).size());
assertEquals(1, items.get(ResourceType.STYLE).size());
assertEquals(1, items.get(ResourceType.ARRAY).size());
assertEquals(7, items.get(ResourceType.ATTR).size());
assertEquals(1, items.get(ResourceType.DECLARE_STYLEABLE).size());
- assertEquals(1, items.get(ResourceType.DIMEN).size());
+ assertEquals(2, items.get(ResourceType.DIMEN).size());
assertEquals(1, items.get(ResourceType.ID).size());
assertEquals(1, items.get(ResourceType.INTEGER).size());
}
@@ -58,9 +58,10 @@
public void testMergedResourcesByName() throws Exception {
ResourceRepository repo = getResourceRepository();
+ // use ? between type and qualifier because of declare-styleable
verifyResourceExists(repo,
"drawable/icon",
- "drawable?ldpi/icon",
+ "drawable?ldpi-v4/icon",
"drawable/icon2",
"drawable/patch",
"drawable/color_drawable",
@@ -85,6 +86,7 @@
"attr/flagAttr",
"declare-styleable/declare_styleable",
"dimen/dimen",
+ "dimen?sw600dp-v13/offset",
"id/item_id",
"integer/integer"
);
@@ -229,7 +231,7 @@
verifyResourceExists(repo,
"drawable/new_overlay",
"drawable/removed",
- "drawable?ldpi/removed",
+ "drawable?ldpi-v4/removed",
"drawable/touched",
"drawable/removed_overlay",
"drawable/untouched");
@@ -292,7 +294,7 @@
"drawable/touched",
"drawable/removed_overlay",
"drawable/untouched",
- "drawable?hdpi/new_alternate");
+ "drawable?hdpi-v4/new_alternate");
checkRemovedItems(resourceMerger);
}
@@ -495,7 +497,14 @@
checkRemovedItems(resourceMerger);
}
- private void checkRemovedItems(DataMap<? extends DataItem> dataMap) {
+ public void testUpdateFromOldFile() throws Exception {
+ File root = getIncMergeRoot("oldMerge");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger();
+ assertFalse(resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/));
+ }
+
+ private static void checkRemovedItems(DataMap<? extends DataItem> dataMap) {
for (DataItem item : dataMap.getDataMap().values()) {
if (item.isRemoved()) {
fail("Removed item found: " + item);
@@ -574,7 +583,7 @@
return resourceMerger;
}
- private ResourceRepository getResourceRepository()
+ private static ResourceRepository getResourceRepository()
throws MergingException, IOException {
ResourceMerger merger = getBaseResourceMerger();
@@ -589,7 +598,7 @@
return new File(root, name);
}
- private void verifyResourceExists(ResourceRepository repository,
+ private static void verifyResourceExists(ResourceRepository repository,
String... dataItemKeys) {
Map<ResourceType, ListMultimap<String, ResourceItem>> items = repository.getItems();
@@ -604,6 +613,8 @@
throw new IllegalArgumentException("Invalid key " + resKey);
}
+ // use ? as a qualifier delimiter because of
+ // declare-styleable
pos = type.indexOf('?');
if (pos != -1) {
qualifier = type.substring(pos + 1);
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 6f127c5..f3a8481 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
@@ -25,6 +25,7 @@
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.RegionQualifier;
import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;
@@ -101,6 +102,17 @@
+ " <string name=\"show_all_apps\">Todo</string>\n"
+ "</resources>\n", new File(valuesEsUs, "strings.xml"), Charsets.UTF_8);
+ if ("testGetMatchingFileAliases".equals(getName())) {
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <item name=\"layout2\" type=\"layout\">@layout/indirect3</item>\n"
+ + " <item name=\"indirect3\" type=\"layout\">@layout/indirect2</item>\n"
+ + " <item name=\"indirect2\" type=\"layout\">@layout/indirect1</item>\n"
+ + " <item name=\"indirect1\" type=\"layout\">@layout/layout1</item>\n"
+ + "</resources>", new File(valuesEsUs, "refs.xml"), Charsets.UTF_8);
+ }
+
mResourceMerger = new ResourceMerger();
ResourceSet resourceSet = new ResourceSet("main");
resourceSet.addSource(mRes);
@@ -245,6 +257,34 @@
assertNotNull(file);
}
+ public void testGetMatchingFileAliases() throws Exception {
+ FolderConfiguration folderConfig = new FolderConfiguration();
+ folderConfig.setLanguageQualifier(new LanguageQualifier("es"));
+ folderConfig.setScreenOrientationQualifier(
+ new ScreenOrientationQualifier(ScreenOrientation.LANDSCAPE));
+
+ Map<ResourceType, Map<String, ResourceValue>> configuredResources =
+ mRepository.getConfiguredResources(folderConfig);
+ Map<String, ResourceValue> layouts = configuredResources.get(ResourceType.LAYOUT);
+ assertEquals(6, layouts.size());
+ assertNotNull(layouts.get("layout1"));
+
+ folderConfig.setRegionQualifier(new RegionQualifier("ES"));
+ ResourceFile file = mRepository.getMatchingFile("dialog_min_width_major",
+ ResourceType.DIMEN, folderConfig);
+ assertNotNull(file);
+ file = mRepository.getMatchingFile("layout2", ResourceType.LAYOUT, folderConfig);
+ assertNotNull(file);
+ assertEquals("layout2.xml", file.getFile().getName());
+ assertEquals("", file.getQualifiers());
+
+ folderConfig.setRegionQualifier(new RegionQualifier("US"));
+ file = mRepository.getMatchingFile("layout2", ResourceType.LAYOUT, folderConfig);
+ assertNotNull(file);
+ assertEquals("layout1.xml", file.getFile().getName());
+ assertEquals("land", file.getQualifiers());
+ }
+
public void testUpdates() throws Exception {
assertFalse(mRepository.hasResourcesOfType(ResourceType.ANIM));
assertFalse(mRepository.hasResourcesOfType(ResourceType.MENU));
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 665aa3b..481de6e 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
@@ -27,7 +27,7 @@
public void testBaseResourceSetByCount() throws Exception {
ResourceSet resourceSet = getBaseResourceSet();
- assertEquals(27, resourceSet.size());
+ assertEquals(29, resourceSet.size());
}
public void testBaseResourceSetByName() throws Exception {
@@ -58,6 +58,7 @@
"attr/flagAttr",
"declare-styleable/declare_styleable",
"dimen/dimen",
+ "dimen-sw600dp-v13/offset",
"id/item_id",
"integer/integer",
"plurals/plurals"
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 91153f3..7ef6002 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
@@ -16,10 +16,18 @@
package com.android.ide.common.res2;
+import com.android.SdkConstants;
import com.android.testutils.TestUtils;
+import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
+import org.w3c.dom.Document;
+
+import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Map;
@@ -32,7 +40,7 @@
public void testParsedResourcesByCount() throws Exception {
List<ResourceItem> resources = getParsedResources();
- assertEquals(22, resources.size());
+ assertEquals(23, resources.size());
}
public void testParsedResourcesByName() throws Exception {
@@ -87,4 +95,39 @@
return sResources;
}
+
+ public void testUtfBom() throws IOException, MergingException {
+ File file = File.createTempFile(getName(), SdkConstants.DOT_XML);
+ String xml = "" +
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n" +
+ " some text\n" +
+ "\n" +
+ "</LinearLayout>\n";
+
+ BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
+ OutputStreamWriter writer = new OutputStreamWriter(stream, Charsets.UTF_8);
+ stream.write(0xef);
+ stream.write(0xbb);
+ stream.write(0xbf);
+ writer.write(xml);
+ writer.close();
+
+ Document document = ValueResourceParser2.parseDocument(file);
+ assertNotNull(document);
+ assertNotNull(document.getDocumentElement());
+ assertEquals("LinearLayout", document.getDocumentElement().getTagName());
+
+ //noinspection ResultOfMethodCallIgnored
+ file.delete();
+ }
}
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
index d29c7eb..d1aabc9 100644
--- 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
@@ -94,7 +94,7 @@
"values/strings.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\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"
@@ -104,6 +104,7 @@
+ " <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"
+ + " <string name=\"xliff_string\">First: <xliff:g id=\"firstName\">%1$s</xliff:g> Last: <xliff:g id=\"lastName\">%2$s</xliff:g></string>"
+ "</resources>\n",
"values-es/strings.xml", ""
@@ -226,6 +227,11 @@
assertEquals("@android:color/bright_foreground_dark => @android:color/background_light",
ResourceItemResolver.getDisplayString(ResourceType.COLOR, "bright_foreground_dark",
true, chain));
+ assertEquals("First: ${firstName} Last: ${lastName}",
+ resolver.findResValue("@string/xliff_string", false).getValue());
+ assertEquals("First: <xliff:g id=\"firstName\">%1$s</xliff:g> Last: <xliff:g id=\"lastName\">%2$s</xliff:g>",
+ resolver.findResValue("@string/xliff_string", false).getRawXmlValue());
+
chain.clear();
assertEquals("#ffffffff", resolver.resolveResValue(v).getValue());
assertEquals("@android:color/bright_foreground_dark => @android:color/background_light "
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
index 29616a4..e7a0fdf 100644
--- 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
@@ -431,4 +431,70 @@
projectRepository.dispose();
}
+
+ public void testSetDeviceDefaults() throws Exception {
+ TestResourceRepository frameworkRepository = TestResourceRepository.create(true,
+ new Object[] {
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"Theme.Light\" parent=\"\">\n"
+ + " <item name=\"android:textColor\">#ff0000</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.Holo.Light\" parent=\"Theme.Light\">\n"
+ + " <item name=\"android:textColor\">#00ff00</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.DeviceDefault.Light\" parent=\"Theme.Holo.Light\"/>\n"
+ + " <style name=\"Theme\" parent=\"\">\n"
+ + " <item name=\"android:textColor\">#000000</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.Holo\" parent=\"Theme\">\n"
+ + " <item name=\"android:textColor\">#0000ff</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.DeviceDefault\" parent=\"Theme.Holo\"/>\n"
+ + "</resources>\n",
+ });
+
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[] {
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"AppTheme\" parent=\"android:Theme.DeviceDefault.Light\"/>\n"
+ + " <style name=\"AppTheme.Dark\" parent=\"android:Theme.DeviceDefault\"/>\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 lightResolver = ResourceResolver.create(projectResources,
+ frameworkResources, "AppTheme", true);
+ assertNotNull(lightResolver);
+ ResourceValue textColor = lightResolver.findItemInTheme("textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#00ff00", textColor.getValue());
+
+ lightResolver.setDeviceDefaults("Theme.Light", null);
+ textColor = lightResolver.findItemInTheme("textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#ff0000", textColor.getValue());
+
+ ResourceResolver darkResolver = ResourceResolver.create(projectResources,
+ frameworkResources, "AppTheme.Dark", true);
+ assertNotNull(darkResolver);
+ textColor = darkResolver.findItemInTheme("textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#0000ff", textColor.getValue());
+
+ darkResolver.setDeviceDefaults("Theme.Light", "Theme");
+ textColor = darkResolver.findItemInTheme("textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#000000", textColor.getValue());
+ }
}
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 085f0ff..f76a338 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
@@ -17,9 +17,14 @@
package com.android.ide.common.resources.configuration;
import com.android.resources.ResourceFolderType;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+
import junit.framework.TestCase;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
public class FolderConfigurationTest extends TestCase {
@@ -62,6 +67,7 @@
"w540dp");
}
+ @SuppressWarnings("ConstantConditions")
public void testAddQualifier() {
FolderConfiguration defaultConfig = new FolderConfiguration();
defaultConfig.createDefault();
@@ -80,6 +86,7 @@
}
}
+ @SuppressWarnings("ConstantConditions")
public void testGetConfig1() {
FolderConfiguration configForFolder =
FolderConfiguration.getConfig(new String[] { "values", "en", "rUS" });
@@ -90,6 +97,12 @@
assertNull(configForFolder.getLayoutDirectionQualifier());
}
+ @SuppressWarnings("ConstantConditions")
+ public void testInvalidRepeats() {
+ assertNull(FolderConfiguration.getConfigForFolder("values-en-rUS-rES"));
+ }
+
+ @SuppressWarnings("ConstantConditions")
public void testGetConfig2() {
FolderConfiguration configForFolder =
FolderConfiguration.getConfigForFolder("values-en-rUS");
@@ -100,6 +113,29 @@
assertNull(configForFolder.getLayoutDirectionQualifier());
}
+ @SuppressWarnings("ConstantConditions")
+ public void testGetConfigCaseInsensitive() {
+ FolderConfiguration configForFolder =
+ FolderConfiguration.getConfigForFolder("values-EN-rus");
+ assertNotNull(configForFolder);
+ assertEquals("en", configForFolder.getLanguageQualifier().getValue());
+ assertEquals("US", configForFolder.getRegionQualifier().getValue());
+ assertNull(configForFolder.getScreenDimensionQualifier());
+ assertNull(configForFolder.getLayoutDirectionQualifier());
+ assertEquals("layout-en-rUS", configForFolder.getFolderName(ResourceFolderType.LAYOUT));
+
+ runConfigMatchTest(
+ "en-rgb-Port-HDPI-notouch-12key",
+ 3,
+ "",
+ "en",
+ "fr-rCA",
+ "en-port",
+ "en-notouch-12key",
+ "port-ldpi",
+ "port-notouch-12key");
+ }
+
public void testToStrings() {
FolderConfiguration configForFolder = FolderConfiguration.getConfigForFolder("values-en-rUS");
assertNotNull(configForFolder);
@@ -109,9 +145,38 @@
assertEquals("-en-rUS", configForFolder.getUniqueKey());
}
+ public void testNormalize() {
+ // test normal qualifiers that all have the same min SDK
+ doTestNormalize(4, "large");
+ doTestNormalize(8, "notnight");
+ doTestNormalize(13, "sw42dp");
+ doTestNormalize(17, "ldrtl");
+
+ // test we take the highest qualifier
+ doTestNormalize(13, "sw42dp", "large");
+
+ // test where different values have different minSdk
+ doTestNormalize(8, "car");
+ doTestNormalize(13, "television");
+ doTestNormalize(16, "appliance");
+
+ // test case where there's already a higher -v# qualifier
+ doTestNormalize(18, "sw42dp", "v18");
+
+ // finally test that in some cases it won't add a -v# value.
+ FolderConfiguration configForFolder = FolderConfiguration.getConfigFromQualifiers(
+ Collections.singletonList("port"));
+
+ assertNotNull(configForFolder);
+
+ configForFolder.normalize();
+ VersionQualifier versionQualifier = configForFolder.getVersionQualifier();
+ assertNull(versionQualifier);
+ }
+
// --- helper methods
- private final static class MockConfigurable implements Configurable {
+ private static final class MockConfigurable implements Configurable {
private final FolderConfiguration mConfig;
@@ -130,7 +195,7 @@
}
}
- private void runConfigMatchTest(String refConfig, int resultIndex, String... configs) {
+ private static void runConfigMatchTest(String refConfig, int resultIndex, String... configs) {
FolderConfiguration reference = FolderConfiguration.getConfig(getFolderSegments(refConfig));
assertNotNull(reference);
@@ -140,7 +205,7 @@
assertEquals(resultIndex, list.indexOf(match));
}
- private List<? extends Configurable> getConfigurable(String... configs) {
+ private static List<? extends Configurable> getConfigurable(String... configs) {
ArrayList<MockConfigurable> list = new ArrayList<MockConfigurable>();
for (String config : configs) {
@@ -151,6 +216,64 @@
}
private static String[] getFolderSegments(String config) {
- return (config.length() > 0 ? "foo-" + config : "foo").split("-");
+ return (!config.isEmpty() ? "foo-" + config : "foo").split("-");
+ }
+
+ public void testSort1() {
+ List<FolderConfiguration> configs = Lists.newArrayList();
+ FolderConfiguration f1 = FolderConfiguration.getConfigForFolder("values-hdpi");
+ FolderConfiguration f2 = FolderConfiguration.getConfigForFolder("values-v11");
+ FolderConfiguration f3 = FolderConfiguration.getConfigForFolder("values-sp");
+ FolderConfiguration f4 = FolderConfiguration.getConfigForFolder("values-v4");
+ configs.add(f1);
+ configs.add(f2);
+ configs.add(f3);
+ configs.add(f4);
+ assertEquals(Arrays.asList(f1, f2, f3, f4), configs);
+ Collections.sort(configs);
+ assertEquals(Arrays.asList(f2, f4, f1, f3), configs);
+ }
+
+ public void testSort2() {
+ // Test case from
+ // http://developer.android.com/guide/topics/resources/providing-resources.html#BestMatch
+ List<FolderConfiguration> configs = Lists.newArrayList();
+ for (String name : new String[] {
+ "drawable",
+ "drawable-en",
+ "drawable-fr-rCA",
+ "drawable-en-port",
+ "drawable-en-notouch-12key",
+ "drawable-port-ldpi",
+ "drawable-port-notouch-12key"
+ }) {
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder(name);
+ assertNotNull(name, config);
+ configs.add(config);
+ }
+ Collections.sort(configs);
+ Collections.reverse(configs);
+ //assertEquals("", configs.get(0).toDisplayString());
+
+ List<String> strings = Lists.newArrayList();
+ for (FolderConfiguration config : configs) {
+ strings.add(config.getUniqueKey());
+ }
+ assertEquals("-fr-rCA,-en-port,-en-notouch-12key,-en,-port-ldpi,-port-notouch-12key,",
+ Joiner.on(",").skipNulls().join(strings));
+
+ }
+
+ private void doTestNormalize(int expectedVersion, String... segments) {
+ FolderConfiguration configForFolder = FolderConfiguration.getConfigFromQualifiers(
+ Arrays.asList(segments));
+
+ assertNotNull(configForFolder);
+
+ configForFolder.normalize();
+ VersionQualifier versionQualifier = configForFolder.getVersionQualifier();
+ assertNotNull(versionQualifier);
+ assertEquals(expectedVersion, versionQualifier.getVersion());
+
}
}
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LanguageQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LanguageQualifierTest.java
index 2dfd65f..f6794d1 100644
--- a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LanguageQualifierTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LanguageQualifierTest.java
@@ -41,12 +41,17 @@
assertEquals(true, lq.checkAndSet("en", config)); //$NON-NLS-1$
assertTrue(config.getLanguageQualifier() != null);
assertEquals("en", config.getLanguageQualifier().toString()); //$NON-NLS-1$
+ }
+ public void testCheckAndSetCaseInsensitive() {
+ assertEquals(true, lq.checkAndSet("EN", config)); //$NON-NLS-1$
+ assertTrue(config.getLanguageQualifier() != null);
+ assertEquals("en", config.getLanguageQualifier().toString()); //$NON-NLS-1$
+ assertEquals("en", LanguageQualifier.getFolderSegment("EN"));
}
public void testFailures() {
assertEquals(false, lq.checkAndSet("", config)); //$NON-NLS-1$
- assertEquals(false, lq.checkAndSet("EN", config)); //$NON-NLS-1$
assertEquals(false, lq.checkAndSet("abc", config)); //$NON-NLS-1$
}
}
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java
index 6896316..5ea03a2 100644
--- a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java
@@ -44,6 +44,13 @@
assertEquals("mnc123", config.getNetworkCodeQualifier().toString()); //$NON-NLS-1$
}
+ public void testPrint() {
+ assertEquals("mnc123", NetworkCodeQualifier.getFolderSegment(123));
+ assertEquals("mnc012", NetworkCodeQualifier.getFolderSegment(12));
+ assertEquals("mnc001", NetworkCodeQualifier.getFolderSegment(1));
+ assertEquals("", NetworkCodeQualifier.getFolderSegment(0));
+ }
+
public void testFailures() {
assertEquals(false, mncq.checkAndSet("", config));//$NON-NLS-1$
assertEquals(false, mncq.checkAndSet("mnc", config));//$NON-NLS-1$
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/RegionQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/RegionQualifierTest.java
index fc0402c..348ecd7 100644
--- a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/RegionQualifierTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/RegionQualifierTest.java
@@ -45,9 +45,17 @@
assertEquals("rUS", config.getRegionQualifier().toString()); //$NON-NLS-1$
}
+ public void testCheckCaseInsensitive() {
+ assertEquals(true, rq.checkAndSet("rus", config));//$NON-NLS-1$
+ assertTrue(config.getRegionQualifier() != null);
+ assertEquals("US", config.getRegionQualifier().getValue()); //$NON-NLS-1$
+ assertEquals("rUS", config.getRegionQualifier().toString()); //$NON-NLS-1$
+ assertEquals("rUS", config.getRegionQualifier().toString()); //$NON-NLS-1$
+ assertEquals("rUS", RegionQualifier.getFolderSegment("us"));
+ }
+
public void testFailures() {
assertEquals(false, rq.checkAndSet("", config));//$NON-NLS-1$
- assertEquals(false, rq.checkAndSet("rus", config));//$NON-NLS-1$
assertEquals(false, rq.checkAndSet("rUSA", config));//$NON-NLS-1$
assertEquals(false, rq.checkAndSet("abc", config));//$NON-NLS-1$
}
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java
index b19f125..d23d708 100644
--- a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java
@@ -66,4 +66,69 @@
assertEquals(ScreenSize.XLARGE, config.getScreenSizeQualifier().getValue());
assertEquals("xlarge", config.getScreenSizeQualifier().toString()); //$NON-NLS-1$
}
+
+ public void testIsMatchFor() {
+ // create qualifiers for small, normal, large and xlarge sizes.
+ ScreenSizeQualifier smallQ = new ScreenSizeQualifier(ScreenSize.SMALL);
+ ScreenSizeQualifier normalQ = new ScreenSizeQualifier(ScreenSize.NORMAL);
+ ScreenSizeQualifier largeQ = new ScreenSizeQualifier(ScreenSize.LARGE);
+ ScreenSizeQualifier xlargeQ = new ScreenSizeQualifier(ScreenSize.XLARGE);
+
+ // test that every qualifier is a match for itself.
+ assertTrue(smallQ.isMatchFor(smallQ));
+ assertTrue(normalQ.isMatchFor(normalQ));
+ assertTrue(largeQ.isMatchFor(largeQ));
+ assertTrue(xlargeQ.isMatchFor(xlargeQ));
+
+ // test that small screen sizes match the larger ones.
+ assertTrue(smallQ.isMatchFor(smallQ));
+ assertTrue(smallQ.isMatchFor(normalQ));
+ assertTrue(smallQ.isMatchFor(largeQ));
+ assertTrue(smallQ.isMatchFor(xlargeQ));
+ assertTrue(normalQ.isMatchFor(normalQ));
+ assertTrue(normalQ.isMatchFor(largeQ));
+ assertTrue(normalQ.isMatchFor(xlargeQ));
+ assertTrue(largeQ.isMatchFor(largeQ));
+ assertTrue(largeQ.isMatchFor(xlargeQ));
+ assertTrue(xlargeQ.isMatchFor(xlargeQ));
+
+ // test that larger screen sizes don't match the smaller ones.
+ assertFalse(normalQ.isMatchFor(smallQ));
+ assertFalse(largeQ.isMatchFor(smallQ));
+ assertFalse(largeQ.isMatchFor(normalQ));
+ assertFalse(xlargeQ.isMatchFor(smallQ));
+ assertFalse(xlargeQ.isMatchFor(normalQ));
+ assertFalse(xlargeQ.isMatchFor(largeQ));
+ }
+
+ public void testIsBetterMatchThan() {
+ // create qualifiers for small, normal, large and xlarge sizes.
+ ScreenSizeQualifier smallQ = new ScreenSizeQualifier(ScreenSize.SMALL);
+ ScreenSizeQualifier normalQ = new ScreenSizeQualifier(ScreenSize.NORMAL);
+ ScreenSizeQualifier largeQ = new ScreenSizeQualifier(ScreenSize.LARGE);
+ ScreenSizeQualifier xlargeQ = new ScreenSizeQualifier(ScreenSize.XLARGE);
+
+ // test that each Q is a better match than all other valid Qs when the ref is the same Q.
+ assertTrue(normalQ.isBetterMatchThan(smallQ, normalQ));
+
+ assertTrue(largeQ.isBetterMatchThan(smallQ, largeQ));
+ assertTrue(largeQ.isBetterMatchThan(normalQ, largeQ));
+
+ assertTrue(xlargeQ.isBetterMatchThan(smallQ, xlargeQ));
+ assertTrue(xlargeQ.isBetterMatchThan(normalQ, xlargeQ));
+ assertTrue(xlargeQ.isBetterMatchThan(largeQ, xlargeQ));
+
+ // test that higher screen size if preferable if there's no exact match.
+ assertTrue(normalQ.isBetterMatchThan(smallQ, largeQ));
+ assertFalse(smallQ.isBetterMatchThan(normalQ, largeQ));
+
+ assertTrue(normalQ.isBetterMatchThan(smallQ, xlargeQ));
+ assertTrue(largeQ.isBetterMatchThan(smallQ, xlargeQ));
+ assertTrue(largeQ.isBetterMatchThan(normalQ, xlargeQ));
+
+ assertFalse(smallQ.isBetterMatchThan(normalQ, xlargeQ));
+ assertFalse(smallQ.isBetterMatchThan(largeQ, xlargeQ));
+ assertFalse(normalQ.isBetterMatchThan(largeQ, xlargeQ));
+ }
+
}
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
index f7be54b..097a145 100644
--- 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
@@ -20,6 +20,8 @@
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.getCodeName;
+import static com.android.ide.common.sdk.SdkVersionInfo.getVersion;
import static com.android.ide.common.sdk.SdkVersionInfo.underlinesToCamelCase;
import junit.framework.TestCase;
@@ -53,6 +55,13 @@
assertEquals(HIGHEST_KNOWN_API + 1, getApiByBuildCode("K_SURPRISE_SURPRISE", true));
}
+ public void testGetCodeName() {
+ assertNull(getCodeName(1));
+ assertNull(getCodeName(2));
+ assertEquals("Cupcake", getCodeName(3));
+ assertEquals("KitKat", getCodeName(19));
+ }
+
public void testCamelCaseToUnderlines() {
assertEquals("", camelCaseToUnderlines(""));
assertEquals("foo", camelCaseToUnderlines("foo"));
@@ -72,4 +81,19 @@
assertEquals("Foo", underlinesToCamelCase("foo_"));
assertEquals("JellyBeanMr2", underlinesToCamelCase("jelly_bean_mr2"));
}
+
+ @SuppressWarnings("ConstantConditions")
+ public void testGetAndroidVersion() {
+ assertNull(getVersion("", null));
+ assertNull(getVersion("4H", null));
+ assertEquals(4, getVersion("4", null).getApiLevel());
+ assertNull(getVersion("4", null).getCodename());
+ assertEquals("4", getVersion("4", null).getApiString());
+ assertEquals(19, getVersion("19", null).getApiLevel());
+ // ICS is API 14, but when expressed as a preview platform, it's not yet 14
+ assertEquals(13, getVersion("IceCreamSandwich", null).getApiLevel());
+ assertEquals("IceCreamSandwich", getVersion("IceCreamSandwich", null).getCodename());
+ assertEquals(HIGHEST_KNOWN_API, getVersion("BackToTheFuture", null).getApiLevel());
+ assertEquals("BackToTheFuture", getVersion("BackToTheFuture", null).getCodename());
+ }
}
diff --git a/sdk-common/src/test/java/com/android/ide/common/signing/KeyStoreHelperTest.java b/sdk-common/src/test/java/com/android/ide/common/signing/KeyStoreHelperTest.java
new file mode 100755
index 0000000..4ed26b3
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/signing/KeyStoreHelperTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.ide.common.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();
+
+ // "now" is just slightly before the key was created
+ long now = System.currentTimeMillis();
+
+ // create the keystore
+ KeystoreHelper.createDebugStore(null, keystoreFile, "android", "android", "AndroidDebugKey",
+ fakeLogger);
+
+ // read the key back
+ CertificateInfo certificateInfo = KeystoreHelper.getCertificateInfo(
+ null, keystoreFile, "android", "android", "AndroidDebugKey");
+
+ 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/sdk-common/src/test/java/com/android/ide/common/xml/XmlPrettyPrinterTest.java b/sdk-common/src/test/java/com/android/ide/common/xml/XmlPrettyPrinterTest.java
index 94e7269..730fbae 100644
--- a/sdk-common/src/test/java/com/android/ide/common/xml/XmlPrettyPrinterTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/xml/XmlPrettyPrinterTest.java
@@ -19,17 +19,24 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.utils.XmlUtils;
+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 org.w3c.dom.Node;
import org.w3c.dom.NodeList;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.security.Permission;
+
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
-import junit.framework.TestCase;
-
@SuppressWarnings("javadoc")
public class XmlPrettyPrinterTest extends TestCase {
private void checkFormat(XmlFormatPreferences prefs,
@@ -61,7 +68,7 @@
assertEquals(expected, formatted);
}
- private Node findNode(Node node, String nodeName) {
+ private static Node findNode(Node node, String nodeName) {
if (node.getNodeName().equals(nodeName)) {
return node;
}
@@ -1094,6 +1101,66 @@
xml);
}
+ public void testMarkupSpacing() throws Exception {
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ + "\n"
+ + " <string name=\"basic_string\">basic_string</string>\n"
+ + " <string name=\"markup\">this is a <b>bold</b> <b>string</b> </string>\n"
+ + " <string name=\"xliff_string\"><xliff:g id=\"firstName\">%1$s</xliff:g> <xliff:g id=\"lastName\">%2$s</xliff:g></string>\n"
+ + " <string name=\"styled_string\">Forgot your username or password\\?\\nVisit <b>google.com/accounts/recovery</b>.</string>\n"
+ + "\n"
+ + " <plurals name=\"plurals\">\n"
+ + " <item quantity=\"one\">test2 <xliff:g xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" id=\"test3\">%s</xliff:g> test4</item>\n"
+ + " </plurals>\n"
+ + "</resources>\n";
+
+ Document doc = parse(xml);
+ assertNotNull(doc);
+
+ xml = XmlPrettyPrinter.prettyPrint(doc, false);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ + "\n"
+ + " <string name=\"basic_string\">basic_string</string>\n"
+ + " <string name=\"markup\">this is a <b>bold</b> <b>string</b></string>\n"
+ + " <string name=\"xliff_string\"><xliff:g id=\"firstName\">%1$s</xliff:g> <xliff:g id=\"lastName\">%2$s</xliff:g></string>\n"
+ + " <string name=\"styled_string\">Forgot your username or password\\?\\nVisit <b>google.com/accounts/recovery</b>.</string>\n"
+ + "\n"
+ + " <plurals name=\"plurals\">\n"
+ + " <item quantity=\"one\">test2 <xliff:g id=\"test3\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\">%s</xliff:g> test4</item>\n"
+ + " </plurals>\n"
+ + "\n"
+ + "</resources>",
+
+ xml);
+ }
+
+ public void testXliff() throws Exception {
+ String xml = ""
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ + " <string name=\"xliff\">Text:\n"
+ + " <xliff:g id=\"firstName\">%1$s</xliff:g></string>\n"
+ + " <string name=\"xliff2\">Name:<xliff:g id=\"firstName\"> %1$s</xliff:g></string>\n"
+ + "</resources>";
+
+ Document doc = parse(xml);
+ assertNotNull(doc);
+
+ xml = XmlPrettyPrinter.prettyPrint(doc, false);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ + "\n"
+ + " <string name=\"xliff\">Text: <xliff:g id=\"firstName\">%1$s</xliff:g></string>\n"
+ + " <string name=\"xliff2\">Name:<xliff:g id=\"firstName\"> %1$s</xliff:g></string>\n"
+ + "\n"
+ + "</resources>",
+ xml);
+ }
+
public void test52887() throws Exception {
// https://code.google.com/p/android/issues/detail?id=52887
String xml = ""
@@ -1132,6 +1199,160 @@
assertEquals(expected, after);
}
+ public void testDriver1() throws Exception {
+ checkDriver(""
+ + "Usage: XmlPrettyPrinter <options>... <files or directories...>\n"
+ + "OPTIONS:\n"
+ + "--stdout\n"
+ + "--removeEmptyLines\n"
+ + "--noAttributeOnFirstLine\n"
+ + "--noSpaceBeforeClose\n",
+ "Unknown flag --nonexistentFlag\n",
+ 1,
+ new String[]{"--nonexistentFlag"},
+ null
+ );
+ }
+
+ public void testDriver2() throws Exception {
+ String brokenXml = "<view>\n"
+ + "<notclosed>\n"
+ + "</view>";
+ File temp = File.createTempFile("mylayout", ".xml");
+ Files.write(brokenXml, temp, Charsets.UTF_8);
+ checkDriver(
+ "",
+ "[Fatal Error] :3:3: The element type \"notclosed\" must be terminated by "
+ + "the matching end-tag \"</notclosed>\".\n"
+ + "Could not parse $TESTFILE\n",
+ 1,
+ new String[]{"--stdout", temp.getPath()},
+ temp
+ );
+ //noinspection ResultOfMethodCallIgnored
+ temp.delete();
+ }
+
+ public void testDriver3() throws Exception {
+ String xml = ""
+ + "<?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=\"match_parent\" android:layout_height=\"match_parent\""
+ + " android:orientation=\"vertical\" tools:ignore=\"HardcodedText\">"
+ + "\n"
+ + " <!-- Comment -->\n"
+ + "</LinearLayout>";
+ File temp = File.createTempFile("mylayout", ".xml");
+ Files.write(xml, temp, Charsets.UTF_8);
+ checkDriver(
+ "",
+ "",
+ 0,
+ new String[]{temp.getPath()},
+ temp
+ );
+
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\"\n"
+ + " android:orientation=\"vertical\"\n"
+ + " tools:ignore=\"HardcodedText\" >\n"
+ + "\n"
+ + " <!-- Comment -->\n"
+ + "\n"
+ + "</LinearLayout>",
+ Files.toString(temp, Charsets.UTF_8));
+ //noinspection ResultOfMethodCallIgnored
+ temp.delete();
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void testDriver4() throws Exception {
+ File root = Files.createTempDir();
+ String xml = ""
+ + "<?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=\"match_parent\" android:layout_height=\"match_parent\""
+ + " android:orientation=\"vertical\" tools:ignore=\"HardcodedText\">"
+ + "\n"
+ + " <!-- Comment -->\n"
+ + "</LinearLayout>";
+ File file1 = new File(root, "layout1.xml");
+ Files.write(xml, file1, Charsets.UTF_8);
+ File dir1 = new File(root, "layout");
+ dir1.mkdirs();
+ File file2 = new File(dir1, "layout2.xml");
+ Files.write(xml, file2, Charsets.UTF_8);
+
+ checkDriver(
+ "",
+ "",
+ 0,
+ new String[]{file1.getPath(), dir1.getPath()},
+ root
+ );
+
+ String formatted = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\"\n"
+ + " android:orientation=\"vertical\"\n"
+ + " tools:ignore=\"HardcodedText\" >\n"
+ + "\n"
+ + " <!-- Comment -->\n"
+ + "\n"
+ + "</LinearLayout>";
+
+ assertEquals(formatted, Files.toString(file1, Charsets.UTF_8));
+ assertEquals(formatted, Files.toString(file2, Charsets.UTF_8));
+
+ file1.delete();
+ file2.delete();
+ dir1.delete();
+ root.delete();
+ }
+
+ public void testDriver5() throws Exception {
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + "<Button></Button>"
+ + "<Button/>"
+ + "</LinearLayout>";
+ File temp = File.createTempFile("mylayout", ".xml");
+ Files.write(xml, temp, Charsets.UTF_8);
+ checkDriver(
+ "",
+ "",
+ 0,
+ new String[]{temp.getPath()},
+ temp
+ );
+
+ // Note limitation of command line XML parser: Can't distinguish
+ // between <element></element> and <element/>
+
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
+ + "\n"
+ + " <Button />\n"
+ + "\n"
+ + " <Button />\n"
+ + "\n"
+ + "</LinearLayout>",
+ Files.toString(temp, Charsets.UTF_8));
+ //noinspection ResultOfMethodCallIgnored
+ temp.delete();
+ }
+
@Nullable
private static Document createEmptyPlainDocument() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@@ -1150,4 +1371,71 @@
assertNotNull(doc);
return doc;
}
+
+ private static void checkDriver(String expectedOutput, String expectedError,
+ int expectedExitCode, String[] args, File testFile)
+ throws Exception {
+ PrintStream previousOut = System.out;
+ PrintStream previousErr = System.err;
+ try {
+ // Trap System.exit calls:
+ System.setSecurityManager(new SecurityManager() {
+ @Override
+ public void checkPermission(Permission perm)
+ {
+ // allow anything.
+ }
+ @Override
+ public void checkPermission(Permission perm, Object context)
+ {
+ // allow anything.
+ }
+ @Override
+ public void checkExit(int status) {
+ throw new ExitException(status);
+ }
+ });
+
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(output));
+ final ByteArrayOutputStream error = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(error));
+
+ int exitCode = 0xCAFEBABE; // not set
+ try {
+ XmlPrettyPrinter.main(args);
+ } catch (ExitException e) {
+ // Allow
+ exitCode = e.getStatus();
+ }
+
+ String testPath = testFile == null
+ ? XmlPrettyPrinterTest.class.getName() : testFile.getPath();
+ String pathName = "$TESTFILE";
+ assertEquals(expectedError, error.toString().replace(testPath, pathName));
+ assertEquals(expectedOutput, output.toString().replace(testPath, pathName));
+ assertEquals(expectedExitCode, exitCode);
+ } finally {
+ // Re-enable system exit for unit test
+ System.setSecurityManager(null);
+
+ System.setOut(previousOut);
+ System.setErr(previousErr);
+ }
+ }
+
+ private static class ExitException extends SecurityException {
+ private static final long serialVersionUID = 1L;
+
+ private final int mStatus;
+
+ public ExitException(int status) {
+ super("Unit test");
+ mStatus = status;
+ }
+
+ public int getStatus() {
+ return mStatus;
+ }
+ }
}
diff --git a/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/merger.xml b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/merger.xml
index 8edb8b3..92cca4b 100644
--- a/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/merger.xml
+++ b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/merger.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<merger>
+<merger version="2">
<dataSet config="main">
<source path="$TOP$$SEP$main">
<file name="untouched.png" path="$TOP$$SEP$main$SEP$untouched.png" />
diff --git a/sdk-common/src/test/resources/testData/resources/baseMerge/merger.xml b/sdk-common/src/test/resources/testData/resources/baseMerge/merger.xml
index ca547b8..7836589 100644
--- a/sdk-common/src/test/resources/testData/resources/baseMerge/merger.xml
+++ b/sdk-common/src/test/resources/testData/resources/baseMerge/merger.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<merger xmlns:ns1="urn:oasis:names:tc:xliff:document:1.2">
+<merger version="2" xmlns:ns1="urn:oasis:names:tc:xliff:document:1.2">
<dataSet config="main">
<source path="$TOP$$SEP$..$SEP$baseSet">
<file name="icon" path="$TOP$$SEP$..$SEP$baseSet$SEP$drawable$SEP$icon.png" qualifiers="" type="drawable"/>
diff --git a/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values-sw600dp-v13/values.xml b/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values-sw600dp-v13/values.xml
new file mode 100644
index 0000000..d1b307c
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values-sw600dp-v13/values.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ * Some comment to throw off the XML parser if it doesn't properly handle
+ * Document.getDocumentElement().
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="offset">13dp</dimen>
+</resources>
diff --git a/sdk-common/src/test/resources/testData/resources/baseSet/values-sw600dp/values.xml b/sdk-common/src/test/resources/testData/resources/baseSet/values-sw600dp/values.xml
new file mode 100644
index 0000000..d8e5556
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/baseSet/values-sw600dp/values.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ * Some comment to throw off the XML parser if it doesn't properly handle
+ * Document.getDocumentElement().
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="offset">12dp</dimen>
+</resources>
diff --git a/sdk-common/src/test/resources/testData/resources/baseSet/values/empty.xml b/sdk-common/src/test/resources/testData/resources/baseSet/values/empty.xml
new file mode 100644
index 0000000..56d9f67
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/baseSet/values/empty.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+</resources>
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 9ce3842..c47cb9f 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
@@ -10,7 +10,9 @@
<color name="color">#00000000</color>
<string name="basic_string">basic_string</string>
- <string name="xliff_string"><xliff:g id="number" example="123">%1$s</xliff:g><xliff:g id="unit" example="KB">%2$s</xliff:g></string>
+ <string name="xliff_string"><xliff:g id="firstName">%1$s</xliff:g> <xliff:g id="lastName">%2$s</xliff:g></string>
+ <string name="xliff_with_carriage_return">This is should be followed by whitespace:
+ <xliff:g id="firstName">%1$s</xliff:g></string>
<string name="styled_string">Forgot your username or password\?\nVisit <b>google.com/accounts/recovery</b>.</string>
<style name="style" parent="@android:style/Holo.Light">
diff --git a/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/merger.xml
index e2e88cc..285ab00 100644
--- a/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/merger.xml
+++ b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/merger.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<merger>
+<merger version="2">
<dataSet config="main">
<source path="$TOP$$SEP$main">
<file name="untouched" path="$TOP$$SEP$main$SEP$drawable$SEP$untouched.png" qualifiers="" type="drawable"/>
<file name="touched" path="$TOP$$SEP$main$SEP$drawable$SEP$touched.png" qualifiers="" type="drawable"/>
<file name="new_overlay" path="$TOP$$SEP$main$SEP$drawable$SEP$new_overlay.png" qualifiers="" type="drawable"/>
<file name="removed" path="$TOP$$SEP$main$SEP$drawable$SEP$removed.png" qualifiers="" type="drawable"/>
- <file name="removed" path="$TOP$$SEP$main$SEP$drawable-ldpi$SEP$removed.png" qualifiers="ldpi" type="drawable"/>
+ <file name="removed" path="$TOP$$SEP$main$SEP$drawable-ldpi$SEP$removed.png" qualifiers="ldpi-v4" type="drawable"/>
<file name="removed_overlay" path="$TOP$$SEP$main$SEP$drawable$SEP$removed_overlay.png" qualifiers="" type="drawable"/>
</source>
</dataSet>
diff --git a/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/merger.xml
index 646c8a5..8c9408a 100644
--- a/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/merger.xml
+++ b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/merger.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<merger>
+<merger version="2">
<dataSet config="main">
<source path="$TOP$$SEP$main">
<file path="$TOP$$SEP$main$SEP$values$SEP$values.xml" qualifiers="">
diff --git a/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/merger.xml
index aa771aa..c9f2d7f 100644
--- a/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/merger.xml
+++ b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/merger.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<merger>
+<merger version="2">
<dataSet config="main">
<source path="$TOP$$SEP$main">
<file path="$TOP$$SEP$main$SEP$values$SEP$values.xml" qualifiers="">
diff --git a/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/merger.xml
index e42e67a..728e2a0 100644
--- a/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/merger.xml
+++ b/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/merger.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<merger>
+<merger version="2">
<dataSet config="main">
<source path="$TOP$$SEP$main">
<file name="main" path="$TOP$$SEP$main$SEP$layout$SEP$main.xml" qualifiers="" type="layout"/>
diff --git a/sdk-common/src/test/resources/testData/resources/incMergeData/oldMerge/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/oldMerge/merger.xml
new file mode 100644
index 0000000..e2e88cc
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/incMergeData/oldMerge/merger.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merger>
+ <dataSet config="main">
+ <source path="$TOP$$SEP$main">
+ <file name="untouched" path="$TOP$$SEP$main$SEP$drawable$SEP$untouched.png" qualifiers="" type="drawable"/>
+ <file name="touched" path="$TOP$$SEP$main$SEP$drawable$SEP$touched.png" qualifiers="" type="drawable"/>
+ <file name="new_overlay" path="$TOP$$SEP$main$SEP$drawable$SEP$new_overlay.png" qualifiers="" type="drawable"/>
+ <file name="removed" path="$TOP$$SEP$main$SEP$drawable$SEP$removed.png" qualifiers="" type="drawable"/>
+ <file name="removed" path="$TOP$$SEP$main$SEP$drawable-ldpi$SEP$removed.png" qualifiers="ldpi" type="drawable"/>
+ <file name="removed_overlay" path="$TOP$$SEP$main$SEP$drawable$SEP$removed_overlay.png" qualifiers="" type="drawable"/>
+ </source>
+ </dataSet>
+ <dataSet config="overlay">
+ <source path="$TOP$$SEP$overlay">
+ <file name="removed_overlay" path="$TOP$$SEP$overlay$SEP$drawable$SEP$removed_overlay.png" qualifiers="" type="drawable"/>
+ </source>
+ </dataSet>
+</merger>
diff --git a/sdk-common/src/test/resources/testData/resources/removedFile/merger.xml b/sdk-common/src/test/resources/testData/resources/removedFile/merger.xml
index ce93563..20b894e 100644
--- a/sdk-common/src/test/resources/testData/resources/removedFile/merger.xml
+++ b/sdk-common/src/test/resources/testData/resources/removedFile/merger.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<merger xmlns:ns1="urn:oasis:names:tc:xliff:document:1.2">
+<merger version="2" xmlns:ns1="urn:oasis:names:tc:xliff:document:1.2">
<dataSet config="main">
<source path="$TOP$$SEP$res">
<file name="icon" path="$TOP$$SEP$res$SEP$drawable$SEP$icon.png" qualifiers="" type="drawable"/>
diff --git a/sdklib/.classpath b/sdklib/.classpath
index a441584..7253ac2 100644
--- a/sdklib/.classpath
+++ b/sdklib/.classpath
@@ -13,7 +13,5 @@
<classpathentry exported="true" kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/org/apache/httpcomponents/httpmime/4.1/httpmime-4.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/org/apache/httpcomponents/httpmime/4.1/httpmime-4.1-sources.jar"/>
<classpathentry combineaccessrules="false" exported="true" kind="src" path="/dvlib"/>
<classpathentry combineaccessrules="false" exported="true" kind="src" path="/layoutlib-api"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/org/bouncycastle/bcpkix-jdk15on/1.48/bcpkix-jdk15on-1.48.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/org/bouncycastle/bcpkix-jdk15on/1.48/bcpkix-jdk15on-1.48-sources.jar"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/sdklib/build.gradle b/sdklib/build.gradle
index 5b78836..7e67c4f 100644
--- a/sdklib/build.gradle
+++ b/sdklib/build.gradle
@@ -1,60 +1,65 @@
apply plugin: 'java'
-apply plugin: 'distrib'
+apply plugin: 'sdk-java-lib'
-evaluationDependsOn(':dvlib')
+evaluationDependsOn(':base:dvlib')
group = 'com.android.tools'
archivesBaseName = 'sdklib'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':layoutlib-api')
- compile project(':dvlib')
+ compile project(':base:layoutlib-api')
+ compile project(':base:dvlib')
- compile 'org.apache.commons:commons-compress:1.0'
+ compile 'org.apache.commons:commons-compress:1.8.1'
compile 'org.apache.httpcomponents:httpclient:4.1.1'
compile 'org.apache.httpcomponents:httpmime:4.1'
- testCompile project(':dvlib').sourceSets.test.output
+ testCompile project(':base:dvlib').sourceSets.test.output
testCompile 'junit:junit:3.8.1'
}
+test {
+ testLogging {
+ showStandardStreams = true
+ showStackTraces = true
+ exceptionFormat = "full"
+ }
+}
+
sourceSets {
main.resources.srcDir 'src/main/java'
test.resources.srcDir 'src/test/java'
}
-jar {
- from 'NOTICE'
-}
-
task copyXsd(type: Copy) {
from sourceSets.main.resources.srcDirs
include '**/*.xsd'
- into file(rootProject.distribution.destinationPath + "/repository")
+ into new File(rootProject.buildDir, "repository-xsd")
eachFile { details ->
details.path = details.name
}
}
+
// delete the destination folder first
copyXsd.doFirst {
- File destFolder = file(rootProject.distribution.destinationPath + "/repository")
+ File destFolder = file(rootProject.buildDir + "/repository-xsd")
destFolder.deleteDir()
destFolder.mkdirs()
}
+
// clean up after the copy task which creates empty folders.
copyXsd.doLast {
- File destFolder = file(rootProject.distribution.destinationPath + "/repository/com")
+ File destFolder = file(rootProject.buildDir + "/repository-xsd/com")
destFolder.deleteDir()
}
-buildDistributionJar.dependsOn copyXsd
-
+//packageJavaLib.dependsOn copyXsd
project.ext.pomName = 'Android Tools sdklib'
project.ext.pomDesc = 'A library to parse and download the Android SDK.'
-apply from: '../baseVersion.gradle'
-apply from: '../publish.gradle'
-apply from: '../javadoc.gradle'
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java b/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
index 05d12d3..6d0427e 100755
--- a/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
+++ b/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
@@ -18,6 +18,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.sdklib.repository.descriptors.IdDisplay;
@@ -30,12 +31,15 @@
* Prefix used to build hash strings for platform targets
* @see SdkManager#getTargetFromHashString(String)
*/
- private static final String PLATFORM_HASH_PREFIX = "android-";
+ public static final String PLATFORM_HASH_PREFIX = "android-";
/**
- * String to compute hash for add-on targets.
- * Format is vendor:name:apiVersion
- * */
+ * String to compute hash for add-on targets. <br/>
+ * Format is {@code vendor:name:apiVersion}. <br/>
+ *
+ * <em>Important<em/>: the vendor and name compontents are the display strings, not the
+ * newer id strings.
+ */
public static final String ADD_ON_FORMAT = "%s:%s:%s"; //$NON-NLS-1$
/**
@@ -83,18 +87,20 @@
/**
* Returns the hash string for a given add-on.
*
- * @param addonVendor A non-null
- * @param addonName
+ * @param addonVendorDisplay A non-null vendor. When using an {@link IdDisplay} source,
+ * this parameter should be the {@link IdDisplay#getDisplay()}.
+ * @param addonNameDisplay A non-null add-on name. When using an {@link IdDisplay} source,
+ * this parameter should be the {@link IdDisplay#getDisplay()}.
* @param version A non-null platform version (the addon's base platform version)
* @return A non-null hash string uniquely representing this add-on target.
*/
public static String getAddonHashString(
- @NonNull String addonVendor,
- @NonNull String addonName,
+ @NonNull String addonVendorDisplay,
+ @NonNull String addonNameDisplay,
@NonNull AndroidVersion version) {
return String.format(ADD_ON_FORMAT,
- addonVendor,
- addonName,
+ addonVendorDisplay,
+ addonNameDisplay,
version.getApiString());
}
diff --git a/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java b/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java
index 05efe52..49dffbc 100644
--- a/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java
+++ b/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java
@@ -47,6 +47,9 @@
private final int mApiLevel;
private final String mCodename;
+ /** The default AndroidVersion for minSdkVersion and targetSdkVersion if not specified */
+ public static final AndroidVersion DEFAULT = new AndroidVersion(1, null);
+
/**
* Thrown when an {@link AndroidVersion} object could not be created.
* @see AndroidVersion#AndroidVersion(Properties)
@@ -176,6 +179,21 @@
}
/**
+ * Returns the API level as an integer. If this is a preview platform, it
+ * will return the expected final version of the API rather than the current API
+ * level. This is the "feature level" as opposed to the "release level" returned by
+ * {@link #getApiLevel()} in the sense that it is useful when you want
+ * to check the presence of a given feature from an API, and we consider the feature
+ * present in preview platforms as well.
+ *
+ * @return the API level of this version, +1 for preview platforms
+ */
+ public int getFeatureLevel() {
+ //noinspection VariableNotUsedInsideIf
+ return mCodename != null ? mApiLevel + 1 : mApiLevel;
+ }
+
+ /**
* Returns the version code name if applicable, null otherwise.
* <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.
@@ -311,7 +329,7 @@
* less than, equal to, or greater than the specified object.
*/
@Override
- public int compareTo(AndroidVersion o) {
+ public int compareTo(@NonNull AndroidVersion o) {
return compareTo(o.mApiLevel, o.mCodename);
}
@@ -362,10 +380,10 @@
* @return Null for a release version or a non-empty codename.
*/
@Nullable
- private String sanitizeCodename(@Nullable String codename) {
+ private static String sanitizeCodename(@Nullable String codename) {
if (codename != null) {
codename = codename.trim();
- if (codename.length() == 0 || SdkConstants.CODENAME_RELEASE.equals(codename)) {
+ if (codename.isEmpty() || SdkConstants.CODENAME_RELEASE.equals(codename)) {
codename = null;
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java b/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
index 68d3c6a..8256bc1 100755
--- a/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
+++ b/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
@@ -16,21 +16,57 @@
package com.android.sdklib;
-import com.android.SdkConstants;
+import static com.android.SdkConstants.FD_LIB;
+import static com.android.SdkConstants.FN_AAPT;
+import static com.android.SdkConstants.FN_AIDL;
+import static com.android.SdkConstants.FN_BCC_COMPAT;
+import static com.android.SdkConstants.FN_DX;
+import static com.android.SdkConstants.FN_DX_JAR;
+import static com.android.SdkConstants.FN_LD_ARM;
+import static com.android.SdkConstants.FN_LD_MIPS;
+import static com.android.SdkConstants.FN_LD_X86;
+import static com.android.SdkConstants.FN_RENDERSCRIPT;
+import static com.android.SdkConstants.FN_ZIPALIGN;
+import static com.android.SdkConstants.OS_FRAMEWORK_RS;
+import static com.android.SdkConstants.OS_FRAMEWORK_RS_CLANG;
+import static com.android.sdklib.BuildToolInfo.PathId.*;
+
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.io.FileOp;
import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
import com.android.utils.ILogger;
import com.google.common.collect.Maps;
import java.io.File;
import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Information on a specific build-tool folder.
+ * <p/>
+ * For unit tests, see:
+ * - sdklib/src/test/.../LocalSdkTest
+ * - sdklib/src/test/.../SdkManagerTest
+ * - sdklib/src/test/.../BuildToolInfoTest
*/
public class BuildToolInfo {
+ /** Name of the file read by {@link #getRuntimeProps()} */
+ private static final String FN_RUNTIME_PROPS = "runtime.properties";
+
+ /**
+ * Property in {@link #FN_RUNTIME_PROPS} indicating the desired runtime JVM.
+ * Type: {@link NoPreviewRevision#toShortString()}, e.g. "1.7.0"
+ */
+ private static final String PROP_RUNTIME_JVM = "Runtime.Jvm";
+
+
public enum PathId {
/** OS Path to the target's version of the aapt tool. */
AAPT("1.0.0"),
@@ -56,7 +92,10 @@
/** OS Path to the X86 linker. */
LD_X86("18.1.0"),
/** OS Path to the MIPS linker. */
- LD_MIPS("18.1.0");
+ LD_MIPS("18.1.0"),
+
+ // --- NEW IN 19.1.0 ---
+ ZIP_ALIGN("19.1.0");
/**
* min revision this element was introduced.
@@ -86,8 +125,11 @@
}
/** The build-tool revision. */
+ @NonNull
private final FullRevision mRevision;
+
/** The path to the build-tool folder specific to this revision. */
+ @NonNull
private final File mPath;
private final Map<PathId, String> mPaths = Maps.newEnumMap(PathId.class);
@@ -96,20 +138,23 @@
mRevision = revision;
mPath = path;
- add(PathId.AAPT, SdkConstants.FN_AAPT);
- add(PathId.AIDL, SdkConstants.FN_AIDL);
- add(PathId.DX, SdkConstants.FN_DX);
- add(PathId.DX_JAR, SdkConstants.FD_LIB + File.separator + SdkConstants.FN_DX_JAR);
- 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);
+ add(AAPT, FN_AAPT);
+ add(AIDL, FN_AIDL);
+ add(DX, FN_DX);
+ add(DX_JAR, FD_LIB + File.separator + FN_DX_JAR);
+ add(LLVM_RS_CC, FN_RENDERSCRIPT);
+ add(ANDROID_RS, OS_FRAMEWORK_RS);
+ add(ANDROID_RS_CLANG, OS_FRAMEWORK_RS_CLANG);
+ add(BCC_COMPAT, FN_BCC_COMPAT);
+ add(LD_ARM, FN_LD_ARM);
+ add(LD_X86, FN_LD_X86);
+ add(LD_MIPS, FN_LD_MIPS);
+ add(ZIP_ALIGN, FN_ZIPALIGN);
}
- public BuildToolInfo(FullRevision revision, @NonNull File mainPath,
+ public BuildToolInfo(
+ @NonNull FullRevision revision,
+ @NonNull File mainPath,
@NonNull File aapt,
@NonNull File aidl,
@NonNull File dx,
@@ -120,37 +165,39 @@
@Nullable File bccCompat,
@Nullable File ldArm,
@Nullable File ldX86,
- @Nullable File ldMips) {
+ @Nullable File ldMips,
+ @NonNull File zipAlign) {
mRevision = revision;
mPath = mainPath;
- add(PathId.AAPT, aapt);
- add(PathId.AIDL, aidl);
- add(PathId.DX, dx);
- add(PathId.DX_JAR, dxJar);
- add(PathId.LLVM_RS_CC, llmvRsCc);
- add(PathId.ANDROID_RS, androidRs);
- add(PathId.ANDROID_RS_CLANG, androidRsClang);
+ add(AAPT, aapt);
+ add(AIDL, aidl);
+ add(DX, dx);
+ add(DX_JAR, dxJar);
+ add(LLVM_RS_CC, llmvRsCc);
+ add(ANDROID_RS, androidRs);
+ add(ANDROID_RS_CLANG, androidRsClang);
+ add(ZIP_ALIGN, zipAlign);
if (bccCompat != null) {
- add(PathId.BCC_COMPAT, bccCompat);
- } else if (PathId.BCC_COMPAT.isPresentIn(revision)) {
+ add(BCC_COMPAT, bccCompat);
+ } else if (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)) {
+ add(LD_ARM, ldArm);
+ } else if (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)) {
+ add(LD_X86, ldX86);
+ } else if (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)) {
+ add(LD_MIPS, ldMips);
+ } else if (LD_MIPS.isPresentIn(revision)) {
throw new IllegalArgumentException("LD_MIPS required in " + revision.toString());
}
}
@@ -225,6 +272,58 @@
}
/**
+ * Parses the build-tools runtime.props file, if present.
+ *
+ * @return The properties from runtime.props if present, otherwise an empty properties set.
+ */
+ @NonNull
+ public Properties getRuntimeProps() {
+ FileOp fop = new FileOp();
+ return fop.loadProperties(new File(mPath, FN_RUNTIME_PROPS));
+ }
+
+ /**
+ * Checks whether this build-tools package can run on the current JVM.
+ *
+ * @return True if the build-tools package has a Runtime.Jvm property and it is lesser or
+ * equal to the current JVM version.
+ * False if the property is present and the requirement is not met.
+ * True if there's an error parsing either versions and the comparison cannot be made.
+ */
+ public boolean canRunOnJvm() {
+ Properties props = getRuntimeProps();
+ String required = props.getProperty(PROP_RUNTIME_JVM);
+ if (required == null) {
+ // No requirement ==> accepts.
+ return true;
+ }
+ try {
+ NoPreviewRevision requiredVersion = NoPreviewRevision.parseRevision(required);
+ NoPreviewRevision currentVersion = getCurrentJvmVersion();
+ return currentVersion.compareTo(requiredVersion) >= 0;
+
+ } catch (NumberFormatException ignore) {
+ // Either we failed to parse the property version or the running JVM version.
+ // Right now take the relaxed policy of accepting it if we can't compare.
+ return true;
+ }
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ @Nullable
+ protected NoPreviewRevision getCurrentJvmVersion() throws NumberFormatException {
+ String javav = System.getProperty("java.version"); //$NON-NLS-1$
+ // java Version is typically in the form "1.2.3_45" and we just need to keep up to "1.2.3"
+ // since our revision numbers are in 3-parts form (1.2.3).
+ Pattern p = Pattern.compile("((\\d+)(\\.\\d+)?(\\.\\d+)?).*"); //$NON-NLS-1$
+ Matcher m = p.matcher(javav);
+ if (m.matches()) {
+ return NoPreviewRevision.parseRevision(m.group(1));
+ }
+ return null;
+ }
+
+ /**
* Returns a debug representation suitable for unit-tests.
* Note that unit-tests need to clean up the paths to avoid inconsistent results.
*/
diff --git a/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java b/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java
index bd3aa5e..710a6ba 100644
--- a/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java
+++ b/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java
@@ -17,7 +17,10 @@
package com.android.sdklib;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import java.io.File;
import java.util.List;
import java.util.Map;
@@ -126,6 +129,7 @@
/**
* Returns the version of the target. This is guaranteed to be non-null.
*/
+ @NonNull
AndroidVersion getVersion();
/**
@@ -149,14 +153,24 @@
/**
* Returns the path of a platform component.
- * @param pathId the id representing the path to return. Any of the constants defined in the
- * {@link IAndroidTarget} interface can be used.
+ * @param pathId the id representing the path to return.
+ * Any of the constants defined in the {@link IAndroidTarget} interface can be used.
*/
String getPath(int pathId);
/**
+ * Returns the path of a platform component.
+ * <p/>
+ * This is like the legacy {@link #getPath(int)} method except it returns a {@link File}.
+ *
+ * @param pathId the id representing the path to return.
+ * Any of the constants defined in the {@link IAndroidTarget} interface can be used.
+ */
+ File getFile(int pathId);
+
+ /**
* Returns a BuildToolInfo for backward compatibility. If an older SDK is used this will return
- * paths located in the platform-tools, otherwise it'll return paths located in the lastest
+ * paths located in the platform-tools, otherwise it'll return paths located in the latest
* build-tools.
* @return a BuildToolInfo or null if none are available.
*/
@@ -178,14 +192,27 @@
boolean hasRenderingLibrary();
/**
- * Returns the available skins for this target.
+ * Returns the available skin folders for this target.
+ * <p/>
+ * To get the skin names, use {@link File#getName()}. <br/>
+ * Skins come either from:
+ * <ul>
+ * <li>a platform ({@code sdk/platforms/N/skins/name})</li>
+ * <li>an add-on ({@code sdk/addons/name/skins/name})</li>
+ * <li>a tagged system-image ({@code sdk/system-images/platform-N/tag/abi/skins/name}.)</li>
+ * </ul>
+ * The array can be empty but not null.
*/
- String[] getSkins();
+ @NonNull
+ File[] getSkins();
/**
- * Returns the default skin for this target.
+ * Returns the default skin folder for this target.
+ * <p/>
+ * To get the skin name, use {@link File#getName()}.
*/
- String getDefaultSkin();
+ @Nullable
+ File getDefaultSkin();
/**
* Returns the available optional libraries for this target.
@@ -249,13 +276,15 @@
public ISystemImage[] getSystemImages();
/**
- * Returns the system image information for the given {@code abiType}.
+ * Returns the system image information for the given {@code tag} and {@code abiType}.
*
+ * @param tag A tag id-display.
* @param abiType An ABI type string.
* @return An existing {@link ISystemImage} for the requested {@code abiType}
* or null if none exists for this type.
*/
- public ISystemImage getSystemImage(String abiType);
+ @Nullable
+ public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType);
/**
* Returns whether the given target is compatible with the receiver.
diff --git a/sdklib/src/main/java/com/android/sdklib/ISystemImage.java b/sdklib/src/main/java/com/android/sdklib/ISystemImage.java
index 7a69030..a41f1bb 100755
--- a/sdklib/src/main/java/com/android/sdklib/ISystemImage.java
+++ b/sdklib/src/main/java/com/android/sdklib/ISystemImage.java
@@ -17,6 +17,10 @@
package com.android.sdklib;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.devices.Abi;
+import com.android.sdklib.repository.descriptors.IdDisplay;
import java.io.File;
@@ -35,7 +39,7 @@
* <p/>
* Used by both platform and add-ons.
*/
- IN_PLATFORM_LEGACY,
+ IN_LEGACY_FOLDER,
/**
* The system image is located in a sub-directory of the platform's
@@ -44,28 +48,48 @@
* <p/>
* Used by both platform and add-ons.
*/
- IN_PLATFORM_SUBFOLDER,
+ IN_IMAGES_SUBFOLDER,
/**
* The system image is located in the new SDK's {@link SdkConstants#FD_SYSTEM_IMAGES}
* folder. Supported as of Tools R14 and Repository XSD version 5.
* <p/>
- * Used <em>only</em> by both platform. This is not supported for add-ons yet.
+ * Used <em>only</em> by both platform up to Tools R22.6.
+ * Supported for add-ons as of Tools R22.8.
*/
IN_SYSTEM_IMAGE,
}
/** Returns the actual location of an installed system image. */
- public abstract File getLocation();
+ @NonNull
+ public File getLocation();
/** Indicates the location strategy for this system image in the SDK. */
- public abstract LocationType getLocationType();
+ @NonNull
+ public LocationType getLocationType();
+
+ /** Returns the tag of the system image. */
+ @NonNull
+ public IdDisplay getTag();
+
+ /** Returns the vendor for an add-on's system image, or null for a platform system-image. */
+ @Nullable
+ public IdDisplay getAddonVendor();
/**
- * Returns the ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
- * {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
- * {@link SdkConstants#ABI_MIPS}.
+ * Returns the ABI type.
+ * See {@link Abi} for a full list.
* Cannot be null nor empty.
*/
- public abstract String getAbiType();
+ @NonNull
+ public String getAbiType();
+
+ /**
+ * Returns the skins embedded in the system image. <br/>
+ * Only supported by system images using {@link LocationType#IN_SYSTEM_IMAGE}. <br/>
+ * The skins listed here are merged in the {@link IAndroidTarget#getSkins()} list.
+ * @return A non-null skin list, possibly empty.
+ */
+ @NonNull
+ public File[] getSkins();
}
diff --git a/sdklib/src/main/java/com/android/sdklib/SdkManager.java b/sdklib/src/main/java/com/android/sdklib/SdkManager.java
old mode 100644
new mode 100755
index 018098f..523ea20
--- a/sdklib/src/main/java/com/android/sdklib/SdkManager.java
+++ b/sdklib/src/main/java/com/android/sdklib/SdkManager.java
@@ -25,21 +25,21 @@
import com.android.prefs.AndroidLocation.AndroidLocationException;
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.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repository.local.LocalExtraPkgInfo;
+import com.android.sdklib.repository.local.LocalPkgInfo;
+import com.android.sdklib.repository.local.LocalSdk;
import com.android.utils.ILogger;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -52,6 +52,7 @@
*/
public class SdkManager {
+ @SuppressWarnings("unused")
private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG") != null; //$NON-NLS-1$
/** Preference file containing the usb ids for adb */
@@ -65,9 +66,6 @@
/** 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.
* External users should use {@link #createManager(String, ILogger)}.
@@ -112,8 +110,7 @@
* @param log the ILogger object receiving warning/error from the parsing.
*/
public void reloadSdk(@NonNull ILogger log) {
- mCachedTargets = null;
- mLocalSdk.clearLocalPkg(LocalSdk.PKG_ALL);
+ mLocalSdk.clearLocalPkg(PkgType.PKG_ALL);
}
/**
@@ -136,9 +133,9 @@
* @return True if at least one directory or source.prop has changed.
*/
public boolean hasChanged(@Nullable ILogger log) {
- return mLocalSdk.hasChanged(LocalSdk.PKG_PLATFORMS |
- LocalSdk.PKG_ADDONS |
- LocalSdk.PKG_BUILD_TOOLS);
+ return mLocalSdk.hasChanged(EnumSet.of(PkgType.PKG_PLATFORM,
+ PkgType.PKG_ADDON,
+ PkgType.PKG_BUILD_TOOLS));
}
/**
@@ -161,24 +158,7 @@
*/
@NonNull
public IAndroidTarget[] getTargets() {
- 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;
+ return mLocalSdk.getTargets();
}
/**
@@ -188,11 +168,12 @@
@Deprecated
@NonNull
public Set<FullRevision> getBuildTools() {
- LocalPkgInfo[] pkgs = mLocalSdk.getPkgsInfos(LocalSdk.PKG_BUILD_TOOLS);
+ LocalPkgInfo[] pkgs = mLocalSdk.getPkgsInfos(PkgType.PKG_BUILD_TOOLS);
TreeSet<FullRevision> bt = new TreeSet<FullRevision>();
for (LocalPkgInfo pkg : pkgs) {
- if (pkg.hasFullRevision()) {
- bt.add(pkg.getFullRevision());
+ IPkgDesc d = pkg.getDesc();
+ if (d.hasFullRevision()) {
+ bt.add(d.getFullRevision());
}
}
return Collections.unmodifiableSet(bt);
@@ -308,7 +289,7 @@
@NonNull
public Map<File, String> getExtraSamples() {
- LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(LocalSdk.PKG_EXTRAS);
+ LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(PkgType.PKG_EXTRA);
Map<File, String> samples = new HashMap<File, String>();
for (LocalPkgInfo info : pkgsInfos) {
@@ -341,21 +322,22 @@
* @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.
+ * to use the new {code LocalSdk.getPkgInfo(PkgType.PKG_EXTRAS)} API instead.
*/
@Deprecated
@NonNull
public Map<String, Integer> getExtrasVersions() {
- LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(LocalSdk.PKG_EXTRAS);
+ LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(PkgType.PKG_EXTRA);
Map<String, Integer> extraVersions = new TreeMap<String, Integer>();
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();
+ IPkgDesc d = ei.getDesc();
+ String vendor = d.getVendor().getId();
+ String path = d.getPath();
+ int majorRev = d.getFullRevision().getMajor();
extraVersions.put(vendor + '/' + path, majorRev);
}
@@ -367,9 +349,10 @@
/** Returns the platform tools version if installed, null otherwise. */
@Nullable
public String getPlatformToolsVersion() {
- LocalPkgInfo info = mLocalSdk.getPkgInfo(LocalSdk.PKG_PLATFORM_TOOLS);
- if (info != null && info.hasFullRevision()) {
- return info.getFullRevision().toShortString();
+ LocalPkgInfo info = mLocalSdk.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
+ IPkgDesc d = info == null ? null : info.getDesc();
+ if (d != null && d.hasFullRevision()) {
+ return d.getFullRevision().toShortString();
}
return null;
diff --git a/sdklib/src/main/java/com/android/sdklib/SystemImage.java b/sdklib/src/main/java/com/android/sdklib/SystemImage.java
index fadb44e..f25ab57 100755
--- a/sdklib/src/main/java/com/android/sdklib/SystemImage.java
+++ b/sdklib/src/main/java/com/android/sdklib/SystemImage.java
@@ -17,8 +17,12 @@
package com.android.sdklib;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.devices.Abi;
import com.android.sdklib.internal.androidTarget.PlatformTarget;
import com.android.sdklib.io.FileOp;
+import com.android.sdklib.repository.descriptors.IdDisplay;
import java.io.File;
import java.util.Locale;
@@ -26,68 +30,140 @@
/**
* Describes a system image as used by an {@link IAndroidTarget}.
- * A system image has an installation path, a location type and an ABI type.
+ * A system image has an installation path, a location type, a tag and an ABI type.
*/
public class SystemImage implements ISystemImage {
- public static final String ANDROID_PREFIX = "android-"; //$NON-NLS-1$
+ public static final IdDisplay DEFAULT_TAG = new IdDisplay("default", //$NON-NLS-1$
+ "Default"); //$NON-NLS-1$
private final LocationType mLocationtype;
+ private final IdDisplay mTag;
+ private final IdDisplay mAddonVendor;
private final String mAbiType;
private final File mLocation;
+ private final File[] mSkins;
/**
- * Creates a {@link SystemImage} description for an existing system image folder.
+ * Creates a {@link SystemImage} description for an existing platform system image folder.
*
* @param location The location of an installed system image.
* @param locationType Where the system image folder is located for this ABI.
+ * @param tag The tag of the system-image. Use {@link #DEFAULT_TAG} for backward compatibility.
* @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
* {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
* {@link SdkConstants#ABI_MIPS}.
+ * @param skins A non-null, possibly empty list of skins specific to this system image.
*/
- public SystemImage(File location, LocationType locationType, String abiType) {
- mLocation = location;
- mLocationtype = locationType;
- mAbiType = abiType;
+ public SystemImage(
+ @NonNull File location,
+ @NonNull LocationType locationType,
+ @NonNull IdDisplay tag,
+ @NonNull String abiType,
+ @NonNull File[] skins) {
+ this(location, locationType, tag, null /*addonVendor*/, abiType, skins);
}
/**
- * Creates a {@link SystemImage} description for a non-existing system image folder.
- * The actual location is computed based on the {@code locationtype}.
+ * Creates a {@link SystemImage} description for an existing system image folder,
+ * for either platform or add-on.
*
- * @param sdkManager The current SDK manager.
+ * @param location The location of an installed system image.
* @param locationType Where the system image folder is located for this ABI.
+ * @param tagName The tag of the system-image.
+ * For an add-on, the tag-id must match the add-on's name-id.
+ * @param addonVendor Non-null add-on vendor name. Null for platforms.
* @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
* {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
* {@link SdkConstants#ABI_MIPS}.
+ * @param skins A non-null, possibly empty list of skins specific to this system image.
+ */
+ public SystemImage(
+ @NonNull File location,
+ @NonNull LocationType locationType,
+ @NonNull IdDisplay tagName,
+ @Nullable IdDisplay addonVendor,
+ @NonNull String abiType,
+ @NonNull File[] skins) {
+ mLocation = location;
+ mLocationtype = locationType;
+ mTag = tagName;
+ mAddonVendor = addonVendor;
+ mAbiType = abiType;
+ mSkins = skins;
+ }
+
+ /**
+ * Creates a {@link SystemImage} description for a non-existing platform system image folder.
+ * The actual location is computed based on the {@code locationType}.
+ *
+ * @param sdkManager The current SDK manager.
+ * @param locationType Where the system image folder is located for this ABI.
+ * @param tag The tag of the system-image. Use {@link #DEFAULT_TAG} for backward compatibility.
+ * @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
+ * {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
+ * {@link SdkConstants#ABI_MIPS}.
+ * @param skins A non-null, possibly empty list of skins specific to this system image.
* @throws IllegalArgumentException if the {@code target} used for
* {@link ISystemImage.LocationType#IN_SYSTEM_IMAGE} is not a {@link PlatformTarget}.
*/
public SystemImage(
- SdkManager sdkManager,
- IAndroidTarget target,
- LocationType locationType,
- String abiType) {
+ @NonNull SdkManager sdkManager,
+ @NonNull IAndroidTarget target,
+ @NonNull LocationType locationType,
+ @NonNull IdDisplay tag,
+ @NonNull String abiType,
+ @NonNull File[] skins) {
+ this(sdkManager, target, locationType, tag, null /*addonVendor*/, abiType, skins);
+ }
+
+
+ /**
+ * Creates a {@link SystemImage} description for a non-existing system image folder,
+ * for either platform or add-on.
+ * The actual location is computed based on the {@code locationType}.
+ *
+ * @param sdkManager The current SDK manager.
+ * @param locationType Where the system image folder is located for this ABI.
+ * @param tag The tag of the system-image. Use {@link #DEFAULT_TAG} for backward compatibility.
+ * @param addonVendor Non-null add-on vendor name. Null for platforms.
+ * @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
+ * {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
+ * {@link SdkConstants#ABI_MIPS}.
+ * @param skins A non-null, possibly empty list of skins specific to this system image.
+ * @throws IllegalArgumentException if the {@code target} used for
+ * {@link ISystemImage.LocationType#IN_SYSTEM_IMAGE} is not a {@link PlatformTarget}.
+ */
+ public SystemImage(
+ @NonNull SdkManager sdkManager,
+ @NonNull IAndroidTarget target,
+ @NonNull LocationType locationType,
+ @NonNull IdDisplay tag,
+ @Nullable IdDisplay addonVendor,
+ @NonNull String abiType,
+ @NonNull File[] skins) {
mLocationtype = locationType;
+ mTag = tag;
+ mAddonVendor = addonVendor;
mAbiType = abiType;
+ mSkins = skins;
File location = null;
switch(locationType) {
- case IN_PLATFORM_LEGACY:
+ case IN_LEGACY_FOLDER:
location = new File(target.getLocation(), SdkConstants.OS_IMAGES_FOLDER);
break;
- case IN_PLATFORM_SUBFOLDER:
+ case IN_IMAGES_SUBFOLDER:
location = FileOp.append(target.getLocation(), SdkConstants.OS_IMAGES_FOLDER, abiType);
break;
case IN_SYSTEM_IMAGE:
- if (!target.isPlatform()) {
- throw new IllegalArgumentException(
- "Add-ons do not support the system-image location type"); //$NON-NLS-1$
- }
-
- location = getCanonicalFolder(sdkManager.getLocation(), target.getVersion(), abiType);
+ location = getCanonicalFolder(sdkManager.getLocation(),
+ target.getVersion(),
+ tag.getId(),
+ addonVendor == null ? null : addonVendor.getId(),
+ abiType);
break;
default:
// This is not supposed to happen unless LocationType is
@@ -101,23 +177,40 @@
* Static helper method that returns the canonical path for a system-image that uses
* the {@link ISystemImage.LocationType#IN_SYSTEM_IMAGE} location type.
* <p/>
- * Such an image is located in {@code SDK/system-images/android-N/abiType}.
- * For this reason this method requires the root SDK as well as the platform and the ABI type.
+ * Such an image is located in {@code SDK/system-images/android-N/tag/abiType}.
+ * For this reason this method requires the root SDK as well as the platform, tag abd ABI type.
*
* @param sdkOsPath The OS path to the SDK.
* @param platformVersion The platform version.
+ * @param tagId An optional tag. If null, not tag folder is used.
+ * For legacy, use {@code SystemImage.DEFAULT_TAG.getId()}.
+ * @param addonVendorId An optional vendor-id for an add-on. If null, it's a platform sys-img.
* @param abiType An optional ABI type. If null, the parent directory is returned.
* @return A file that represents the location of the canonical system-image folder
* for this configuration.
*/
- public static File getCanonicalFolder(
- String sdkOsPath,
- AndroidVersion platformVersion,
- String abiType) {
- File root = FileOp.append(
- sdkOsPath,
- SdkConstants.FD_SYSTEM_IMAGES,
- ANDROID_PREFIX + platformVersion.getApiString());
+ @NonNull
+ private static File getCanonicalFolder(
+ @NonNull String sdkOsPath,
+ @NonNull AndroidVersion platformVersion,
+ @Nullable String tagId,
+ @Nullable String addonVendorId,
+ @Nullable String abiType) {
+ File root;
+ if (addonVendorId == null) {
+ root = FileOp.append(
+ sdkOsPath,
+ SdkConstants.FD_SYSTEM_IMAGES,
+ AndroidTargetHash.getPlatformHashString(platformVersion));
+ if (tagId != null) {
+ root = FileOp.append(root, tagId);
+ }
+ } else {
+ root = FileOp.append(
+ sdkOsPath,
+ SdkConstants.FD_SYSTEM_IMAGES,
+ AndroidTargetHash.getAddonHashString(addonVendorId, tagId, platformVersion));
+ }
if (abiType == null) {
return root;
} else {
@@ -126,31 +219,59 @@
}
/** Returns the actual location of an installed system image. */
+ @NonNull
@Override
public File getLocation() {
return mLocation;
}
/** Indicates the location strategy for this system image in the SDK. */
+ @NonNull
@Override
public LocationType getLocationType() {
return mLocationtype;
}
+ /** Returns the tag of the system image. */
+ @NonNull
+ @Override
+ public IdDisplay getTag() {
+ return mTag;
+ }
+
+ /** Returns the vendor for an add-on's system image, or null for a platform system-image. */
+ @Nullable
+ @Override
+ public IdDisplay getAddonVendor() {
+ return mAddonVendor;
+ }
+
/**
- * Returns the ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
- * {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
- * {@link SdkConstants#ABI_MIPS}.
+ * Returns the ABI type.
+ * See {@link Abi} for a full list.
* Cannot be null nor empty.
*/
+ @NonNull
@Override
public String getAbiType() {
return mAbiType;
}
+ @NonNull
+ @Override
+ public File[] getSkins() {
+ return mSkins;
+ }
+
+ /**
+ * Sort by tag & ABI name only. This is what matters from a user point of view.
+ */
@Override
public int compareTo(ISystemImage other) {
- // Sort by ABI name only. This is what matters from a user point of view.
+ int t = this.getTag().compareTo(other.getTag());
+ if (t != 0) {
+ return t;
+ }
return this.getAbiType().compareToIgnoreCase(other.getAbiType());
}
@@ -160,13 +281,22 @@
*
* {@inheritDoc}
*/
+ @NonNull
@Override
public String toString() {
- return String.format("SystemImage ABI=%s, location %s='%s'", //$NON-NLS-1$
- mAbiType,
- mLocationtype.toString().replace('_', ' ').toLowerCase(Locale.US),
- mLocation
- );
+ StringBuilder sb = new StringBuilder();
+ sb.append("SystemImage");
+ if (mAddonVendor != null) {
+ sb.append(" addon-vendor=").append(mAddonVendor.getId()).append(',');
+ }
+ sb.append(" tag=").append(mTag.getId());
+ sb.append(", ABI=").append(mAbiType);
+ sb.append(", location ")
+ .append(mLocationtype.toString().replace('_', ' ').toLowerCase(Locale.US))
+ .append("='")
+ .append(mLocation)
+ .append("'");
+ return sb.toString();
}
diff --git a/sdklib/src/main/java/com/android/sdklib/build/JarListSanitizer.java b/sdklib/src/main/java/com/android/sdklib/build/JarListSanitizer.java
index b91c015..9e95f41 100644
--- a/sdklib/src/main/java/com/android/sdklib/build/JarListSanitizer.java
+++ b/sdklib/src/main/java/com/android/sdklib/build/JarListSanitizer.java
@@ -16,6 +16,8 @@
package com.android.sdklib.build;
+import com.android.SdkConstants;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@@ -320,7 +322,7 @@
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(cacheFile),
- "UTF-8"));
+ SdkConstants.UTF_8));
String line = null;
while ((line = reader.readLine()) != null) {
@@ -367,7 +369,7 @@
OutputStreamWriter writer = null;
try {
writer = new OutputStreamWriter(
- new FileOutputStream(cacheFile), "UTF-8");
+ new FileOutputStream(cacheFile), SdkConstants.UTF_8);
writer.write("# cache for current jar dependency. DO NOT EDIT.\n");
writer.write("# format is <lastModified> <length> <SHA-1> <path>\n");
diff --git a/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java b/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java
index b5219b9..22d550a 100644
--- a/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java
+++ b/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java
@@ -105,7 +105,7 @@
private final boolean mSupportMode;
private final File mRsLib;
- private final File mLibClCore;
+ private final Map<String, File> mLibClCore = Maps.newHashMap();
public interface CommandLineLauncher {
void launch(
@@ -144,9 +144,12 @@
if (supportMode) {
File rs = new File(mBuildToolInfo.getLocation(), "renderscript");
mRsLib = new File(rs, "lib");
- mLibClCore = new File(mRsLib, "libclcore.bc");
+ File bcFolder = new File(mRsLib, "bc");
+ for (Abi abi : ABIS) {
+ mLibClCore.put(abi.mDevice,
+ new File(bcFolder, abi.mDevice + File.separatorChar + "libclcore.bc"));
+ }
} else {
- mLibClCore = null;
mRsLib = null;
}
}
@@ -317,7 +320,7 @@
args.add("-shared");
args.add("-rt-path");
- args.add(mLibClCore.getAbsolutePath());
+ args.add(mLibClCore.get(abi.mDevice).getAbsolutePath());
args.add("-mtriple");
args.add(abi.mToolchain);
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/Abi.java b/sdklib/src/main/java/com/android/sdklib/devices/Abi.java
index 9184dd5..c3ec95d 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/Abi.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Abi.java
@@ -20,30 +20,109 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+/**
+ * ABI values that can appear in a device's xml <abi> field <em>and</em>
+ * in a system-image abi.
+ * <p/>
+ * The CPU arch and model values are used to configure an AVD using a given ABI.
+ */
public enum Abi {
- ARMEABI(SdkConstants.ABI_ARMEABI),
- ARMEABI_V7A(SdkConstants.ABI_ARMEABI_V7A),
- X86(SdkConstants.ABI_INTEL_ATOM),
- MIPS(SdkConstants.ABI_MIPS);
+ // // ABI string // Display // CPU arch
+ ARMEABI (SdkConstants.ABI_ARMEABI, "ARM", SdkConstants.CPU_ARCH_ARM),
+ ARMEABI_V7A(SdkConstants.ABI_ARMEABI_V7A, "ARM", SdkConstants.CPU_ARCH_ARM, SdkConstants.CPU_MODEL_CORTEX_A8),
+ ARM64_V8A (SdkConstants.ABI_ARM64_V8A, "ARM", SdkConstants.CPU_ARCH_ARM64),
+ X86 (SdkConstants.ABI_INTEL_ATOM, "Intel Atom", SdkConstants.CPU_ARCH_INTEL_ATOM),
+ X86_64 (SdkConstants.ABI_INTEL_ATOM64,"Intel Atom", SdkConstants.CPU_ARCH_INTEL_ATOM64),
+ MIPS (SdkConstants.ABI_MIPS, "MIPS", SdkConstants.CPU_ARCH_MIPS),
+ MIPS64 (SdkConstants.ABI_MIPS64, "MIPS", SdkConstants.CPU_ARCH_MIPS64);
- @NonNull private final String mValue;
+ @NonNull private final String mAbi;
+ @NonNull private final String mCpuArch;
+ @Nullable private final String mCpuModel;
+ @NonNull private final String mDisplayName;
- Abi(@NonNull String value) {
- mValue = value;
+ /**
+ * Define an ABI with a given ABI code name, a display name and a CPU architecture.
+ *
+ * @param abi The ABI code name, used in the system-images and device definitions.
+ * @param displayName The ABI "family" name. Typically used in the UI combined with the
+ * code name, for example "ARM (armeabi-v7a)".
+ * @param cpuArch The CPU architecture, used in the AVD configuration files.
+ */
+ Abi(@NonNull String abi, @NonNull String displayName, @NonNull String cpuArch) {
+ this(abi, displayName, cpuArch, null);
}
+
+ /**
+ * Define an ABI with a given ABI code name, a display name, a CPU architecture
+ * and an optional CPU model.
+ *
+ * @param abi The ABI code name, used in the system-images and device definitions.
+ * @param displayName The ABI "family" name. Typically used in the UI combined with the
+ * code name, for example "ARM (armeabi-v7a)".
+ * @param cpuArch The CPU architecture, used in the AVD configuration files.
+ * @param cpuModel An optional CPU model, used in the AVD configuration files.
+ * The current strategy is to leave this field out. The emulator, which uses the
+ * AVD configuration files, doesn't seem to use it.
+ */
+ Abi(@NonNull String abi, @NonNull String displayName,
+ @NonNull String cpuArch, @Nullable String cpuModel) {
+ mAbi = abi;
+ mDisplayName = displayName;
+ mCpuArch = cpuArch;
+ mCpuModel = cpuModel;
+ }
+
+ /**
+ * Returns the ABI definition matching the given ABI code name.
+ *
+ * @param abi The ABI code name, used in the system-images and device definitions.
+ * @return An existing {@link Abi} description or null.
+ */
@Nullable
- public static Abi getEnum(@NonNull String value) {
+ public static Abi getEnum(@NonNull String abi) {
for (Abi a : values()) {
- if (a.mValue.equals(value)) {
+ if (a.mAbi.equals(abi)) {
return a;
}
}
return null;
}
+ /**
+ * Returns the ABI code name, as used in the system-images and device definitions
+ */
+ @NonNull
@Override
public String toString() {
- return mValue;
+ return mAbi;
+ }
+
+ /**
+ * Returns the CPU architecture, as used in the AVD configuration files
+ */
+ @NonNull
+ public String getCpuArch() {
+ return mCpuArch;
+ }
+
+ /**
+ * Returns the optional CPU model, used in the AVD configuration files.
+ * This is often null.
+ */
+ @Nullable
+ public String getCpuModel() {
+ return mCpuModel;
+ }
+
+ /**
+ * Return the ABI "family" name for display.
+ * Clients should typically display that combined with the code name,
+ * for example "ARM (armeabi-v7a)".
+ */
+ @NonNull
+ public String getDisplayName() {
+ return mDisplayName;
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/Camera.java b/sdklib/src/main/java/com/android/sdklib/devices/Camera.java
index add14da..c1480fb 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/Camera.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Camera.java
@@ -26,7 +26,7 @@
/**
* Creates a {@link Camera} with reasonable defaults.
- *
+ *
* The resulting {@link Camera} with be on the {@link CameraLocation#BACK} with both autofocus
* and flash.
*/
@@ -109,4 +109,19 @@
hash = 31 * hash + (mFlash ? 1 : 0);
return hash;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Camera <mLocation=");
+ sb.append(mLocation);
+ sb.append(", mAutofocus=");
+ sb.append(mAutofocus);
+ sb.append(", mFlash=");
+ sb.append(mFlash);
+ sb.append(">");
+ return sb.toString();
+ }
+
+
}
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 8078d20..067b5e4 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/Device.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Device.java
@@ -25,7 +25,12 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Instances of this class contain the specifications for a device. Use the
@@ -62,6 +67,14 @@
@NonNull
private final State mDefaultState;
+ /** Optional tag-id of the device. */
+ @Nullable
+ private String mTagId;
+
+ /** Optional boot.props of the device. */
+ @NonNull
+ private Map<String, String> mBootProps;
+
/**
* 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.
@@ -234,6 +247,25 @@
return new Dimension(screenWidth, screenHeight);
}
+ /**
+ * Returns the optional tag-id of the device.
+ *
+ * @return the optional tag-id of the device. Can be null.
+ */
+ @Nullable
+ public String getTagId() {
+ return mTagId;
+ }
+
+ /**
+ * Returns the optional boot.props of the device.
+ *
+ * @return the optional boot.props of the device. Can be null or empty.
+ */
+ public Map<String, String> getBootProps() {
+ return mBootProps;
+ }
+
public static class Builder {
private String mName;
private String mId;
@@ -242,10 +274,13 @@
private final List<State> mState = new ArrayList<State>();
private Meta mMeta;
private State mDefaultState;
+ private String mTagId;
+ private final Map<String, String> mBootProps = new TreeMap<String, String>();
public Builder() { }
public Builder(Device d) {
+ mTagId = null;
mName = d.getDisplayName();
mId = d.getId();
mManufacturer = d.getManufacturer();
@@ -269,6 +304,14 @@
mId = id;
}
+ public void setTagId(@Nullable String tagId) {
+ mTagId = tagId;
+ }
+
+ public void addBootProp(@NonNull String propName, @NonNull String propValue) {
+ mBootProps.put(propName, propValue);
+ }
+
public void setManufacturer(@NonNull String manufacturer) {
mManufacturer = manufacturer;
}
@@ -361,6 +404,8 @@
mState = Collections.unmodifiableList(b.mState);
mMeta = b.mMeta;
mDefaultState = b.mDefaultState;
+ mTagId = b.mTagId;
+ mBootProps = Collections.unmodifiableMap(b.mBootProps);
}
@Override
@@ -372,16 +417,31 @@
return false;
}
Device d = (Device) o;
- return mName.equals(d.getDisplayName())
+ boolean ok = mName.equals(d.getDisplayName())
&& mManufacturer.equals(d.getManufacturer())
&& mSoftware.equals(d.getAllSoftware())
&& mState.equals(d.getAllStates())
&& mMeta.equals(d.getMeta())
&& mDefaultState.equals(d.getDefaultState());
+ if (!ok) {
+ return false;
+ }
+
+ ok = (mTagId == null && d.mTagId == null) ||
+ (mTagId != null && mTagId.equals(d.mTagId));
+ if (!ok) {
+ return false;
+ }
+
+ ok = (mBootProps == null && d.mBootProps == null) ||
+ (mBootProps != null && mBootProps.equals(d.mBootProps));
+ return ok;
}
+ /**
+ * For *internal* usage only. Must not be serialized to disk.
+ */
@Override
- /** A hash that's stable across JVM instances */
public int hashCode() {
int hash = 17;
hash = 31 * hash + mName.hashCode();
@@ -390,11 +450,109 @@
hash = 31 * hash + mState.hashCode();
hash = 31 * hash + mMeta.hashCode();
hash = 31 * hash + mDefaultState.hashCode();
+
+ // tag-id and boot-props are optional and should not change a device's hashcode
+ // which did not have them before.
+ if (mTagId != null) {
+ hash = 31 * hash + mTagId.hashCode();
+ }
+ if (mBootProps != null && !mBootProps.isEmpty()) {
+ hash = 31 * hash + mBootProps.hashCode();
+ }
return hash;
}
+ /** toString value suitable for debugging only. */
@Override
public String toString() {
- return mName;
+ StringBuilder sb = new StringBuilder();
+ sb.append("Device [mName=");
+ sb.append(mName);
+ sb.append(", mId=");
+ sb.append(mId);
+ sb.append(", mManufacturer=");
+ sb.append(mManufacturer);
+ sb.append(", mSoftware=");
+ sb.append(mSoftware);
+ sb.append(", mState=");
+ sb.append(mState);
+ sb.append(", mMeta=");
+ sb.append(mMeta);
+ sb.append(", mDefaultState=");
+ sb.append(mDefaultState);
+ sb.append(", mTagId=");
+ sb.append(mTagId);
+ sb.append(", mBootProps=");
+ sb.append(mBootProps);
+ sb.append("]");
+ return sb.toString();
}
+
+ private static Pattern PATTERN = Pattern.compile(
+ "(\\d+\\.?\\d*)(?:in|\") (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$
+
+ /**
+ * Returns a "sortable" name for the device -- if a device list is sorted
+ * using this sort-aware display name, it will be displayed in an order that
+ * is user friendly with devices using names first sorted alphabetically
+ * followed by all devices that use a numeric screen size sorted by actual
+ * size.
+ * <p/>
+ * Note that although the name results in a proper sort, it is not a name
+ * that you actually want to display to the user.
+ * <p/>
+ * Extracted from DeviceMenuListener. Modified to remove the leading space
+ * insertion as it doesn't render neatly in the avd manager. Instead added
+ * the option to add leading zeroes to make the string names sort properly.
+ *
+ * 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).
+ */
+ private String getSortableName() {
+ String sortableName = mName;
+ Matcher matcher = PATTERN.matcher(sortableName);
+ if (matcher.matches()) {
+ String size = matcher.group(1);
+ String n = matcher.group(2);
+ int dot = size.indexOf('.');
+ if (dot == -1) {
+ size = size + ".0";
+ dot = size.length() - 2;
+ }
+ if (dot < 3) {
+ // Pad to have at least 3 digits before the dot, for sorting
+ // purposes.
+ // We can revisit this once we get devices that are more than
+ // 999 inches wide.
+ size = "000".substring(dot) + size;
+ }
+ sortableName = size + "\" " + n;
+ }
+
+ return sortableName;
+ }
+
+ /**
+ * Returns a comparator suitable to sort a device list using a sort-aware display name.
+ * The list is displayed in an order that is user friendly with devices using names
+ * first sorted alphabetically followed by all devices that use a numeric screen size
+ * sorted by actual size.
+ */
+ public static Comparator<Device> getDisplayComparator() {
+ return new Comparator<Device>() {
+ @Override
+ public int compare(Device d1, Device d2) {
+ String s1 = d1.getSortableName();
+ String s2 = d2.getSortableName();
+ if (s1.length() > 1 && s2.length() > 1) {
+ int i1 = Character.isDigit(s1.charAt(0)) ? 1 : 0;
+ int i2 = Character.isDigit(s2.charAt(0)) ? 1 : 0;
+ if (i1 != i2) {
+ return i1 - i2;
+ }
+ }
+ return s1.compareTo(s2);
+ }};
+ }
+
}
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 61fa17a..68d4ac5 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java
@@ -26,8 +26,13 @@
import com.android.resources.Navigation;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.avd.HardwareProperties;
+import com.android.sdklib.io.FileOp;
import com.android.sdklib.repository.PkgProps;
import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
import org.xml.sax.SAXException;
@@ -41,6 +46,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -63,20 +69,27 @@
Pattern.compile('^' + PkgProps.EXTRA_PATH + '=' + DEVICE_PROFILES_PROP + '$');
private ILogger mLog;
private List<Device> mVendorDevices;
+ private List<Device> mSysImgDevices;
private List<Device> mUserDevices;
private List<Device> mDefaultDevices;
private final Object mLock = new Object();
private final List<DevicesChangedListener> sListeners = new ArrayList<DevicesChangedListener>();
private final String mOsSdkPath;
- /** getDevices() flag to list user devices. */
- public static final int USER_DEVICES = 1;
- /** getDevices() flag to list default devices. */
- public static final int DEFAULT_DEVICES = 2;
- /** getDevices() flag to list vendor devices. */
- public static final int VENDOR_DEVICES = 4;
+ public enum DeviceFilter {
+ /** getDevices() flag to list default devices from the bundled devices.xml definitions. */
+ DEFAULT,
+ /** getDevices() flag to list user devices saved in the .android home folder. */
+ USER,
+ /** getDevices() flag to list vendor devices -- the bundled nexus.xml devices
+ * as well as all those coming from extra packages. */
+ VENDOR,
+ /** getDevices() flag to list devices from system-images/platform-N/tag/abi/devices.xml */
+ SYSTEM_IMAGES,
+ }
+
/** getDevices() flag to list all devices. */
- public static final int ALL_DEVICES = USER_DEVICES | DEFAULT_DEVICES | VENDOR_DEVICES;
+ public static final EnumSet<DeviceFilter> ALL_DEVICES = EnumSet.allOf(DeviceFilter.class);
public enum DeviceStatus {
/**
@@ -96,13 +109,14 @@
/**
* Creates a new instance of DeviceManager.
*
- * @param osSdkPath Path to the current SDK. If null or invalid, vendor devices are ignored.
+ * @param sdkLocation Path to the current SDK. If null or invalid, vendor and system images
+ * devices are ignored.
* @param log SDK logger instance. Should be non-null.
*/
- public static DeviceManager createInstance(@Nullable String osSdkPath, @NonNull ILogger log) {
+ public static DeviceManager createInstance(@Nullable File sdkLocation, @NonNull ILogger log) {
// TODO consider using a cache and reusing the same instance of the device manager
// for the same manager/log combo.
- return new DeviceManager(osSdkPath, log);
+ return new DeviceManager(sdkLocation == null ? null : sdkLocation.getPath(), log);
}
/**
@@ -153,28 +167,41 @@
}
@NonNull
- public DeviceStatus getDeviceStatus(@NonNull String name, @NonNull String manufacturer,
- int hashCode) {
+ public DeviceStatus getDeviceStatus(@NonNull String name, @NonNull String manufacturer) {
Device d = getDevice(name, manufacturer);
if (d == null) {
return DeviceStatus.MISSING;
- } else {
- return d.hashCode() == hashCode ? DeviceStatus.EXISTS : DeviceStatus.CHANGED;
}
+
+ return DeviceStatus.EXISTS;
}
@Nullable
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.getId().equals(id) && d.getManufacturer().equals(manufacturer)) {
- return d;
- }
- }
+ Device d = getDeviceImpl(mUserDevices, id, manufacturer);
+ if (d != null) {
+ return d;
+ }
+ d = getDeviceImpl(mSysImgDevices, id, manufacturer);
+ if (d != null) {
+ return d;
+ }
+ d = getDeviceImpl(mDefaultDevices, id, manufacturer);
+ if (d != null) {
+ return d;
+ }
+ d = getDeviceImpl(mVendorDevices, id, manufacturer);
+ return d;
+ }
+
+ @Nullable
+ private Device getDeviceImpl(@NonNull List<Device> devicesList,
+ @NonNull String id,
+ @NonNull String manufacturer) {
+ for (Device d : devicesList) {
+ if (d.getId().equals(id) && d.getManufacturer().equals(manufacturer)) {
+ return d;
}
}
return null;
@@ -183,29 +210,44 @@
/**
* Returns the known {@link Device} list.
*
- * @param deviceFilter A combination of USER_DEVICES, VENDOR_DEVICES and DEFAULT_DEVICES
- * or the constant ALL_DEVICES.
+ * @param deviceFilter One of the {@link DeviceFilter} constants.
* @return A copy of the list of {@link Device}s. Can be empty but not null.
*/
@NonNull
- public List<Device> getDevices(int deviceFilter) {
+ public List<Device> getDevices(@NonNull DeviceFilter deviceFilter) {
+ return getDevices(EnumSet.of(deviceFilter));
+ }
+
+ /**
+ * Returns the known {@link Device} list.
+ *
+ * @param deviceFilter A combination of the {@link DeviceFilter} constants
+ * or the constant {@link DeviceManager#ALL_DEVICES}.
+ * @return A copy of the list of {@link Device}s. Can be empty but not null.
+ */
+ @NonNull
+ public List<Device> getDevices(@NonNull EnumSet<DeviceFilter> deviceFilter) {
initDevicesLists();
List<Device> devices = new ArrayList<Device>();
- if (mUserDevices != null && (deviceFilter & USER_DEVICES) != 0) {
+ if (mUserDevices != null && (deviceFilter.contains(DeviceFilter.USER))) {
devices.addAll(mUserDevices);
}
- if (mDefaultDevices != null && (deviceFilter & DEFAULT_DEVICES) != 0) {
+ if (mDefaultDevices != null && (deviceFilter.contains(DeviceFilter.DEFAULT))) {
devices.addAll(mDefaultDevices);
}
- if (mVendorDevices != null && (deviceFilter & VENDOR_DEVICES) != 0) {
+ if (mVendorDevices != null && (deviceFilter.contains(DeviceFilter.VENDOR))) {
devices.addAll(mVendorDevices);
}
+ if (mSysImgDevices != null && (deviceFilter.contains(DeviceFilter.SYSTEM_IMAGES))) {
+ devices.addAll(mSysImgDevices);
+ }
return Collections.unmodifiableList(devices);
}
private void initDevicesLists() {
boolean changed = initDefaultDevices();
changed |= initVendorDevices();
+ changed |= initSysImgDevices();
changed |= initUserDevices();
if (changed) {
notifyListeners();
@@ -218,19 +260,27 @@
*/
private boolean initDefaultDevices() {
synchronized (mLock) {
- if (mDefaultDevices == null) {
- try {
- mDefaultDevices = DeviceParser.parse(
- DeviceManager.class.getResourceAsStream(SdkConstants.FN_DEVICES_XML));
- return true;
- } catch (IllegalStateException e) {
- // The device builders can throw IllegalStateExceptions if
- // build gets called before everything is properly setup
- mLog.error(e, null);
- mDefaultDevices = new ArrayList<Device>();
- } catch (Exception e) {
- mLog.error(e, "Error reading default devices");
- mDefaultDevices = new ArrayList<Device>();
+ if (mDefaultDevices != null) {
+ return false;
+ }
+ InputStream stream = null;
+ try {
+ stream = DeviceManager.class.getResourceAsStream(SdkConstants.FN_DEVICES_XML);
+ mDefaultDevices = DeviceParser.parse(stream);
+ return true;
+ } catch (IllegalStateException e) {
+ // The device builders can throw IllegalStateExceptions if
+ // build gets called before everything is properly setup
+ mLog.error(e, null);
+ mDefaultDevices = new ArrayList<Device>();
+ } catch (Exception e) {
+ mLog.error(e, "Error reading default devices");
+ mDefaultDevices = new ArrayList<Device>();
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException ignore) {}
}
}
}
@@ -238,81 +288,139 @@
}
/**
- * Initializes all vendor-provided {@link Device}s.
+ * Initializes all vendor-provided {@link Device}s: the bundled nexus.xml devices
+ * as well as all those coming from extra packages.
* @return True if the list has changed.
*/
private boolean initVendorDevices() {
synchronized (mLock) {
- if (mVendorDevices == null) {
- mVendorDevices = new ArrayList<Device>();
+ if (mVendorDevices != null) {
+ return false;
+ }
- // 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");
+ mVendorDevices = new ArrayList<Device>();
+
+ // Load builtin devices
+ InputStream stream = null;
+ try {
+ stream = DeviceManager.class.getResourceAsStream("nexus.xml");
+ mVendorDevices.addAll(DeviceParser.parse(stream));
+ } catch (Exception e) {
+ mLog.error(e, null, "Could not load devices");
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException ignore) {}
}
+ }
- if (mOsSdkPath != null) {
- // Load devices from vendor extras
- File extrasFolder = new File(mOsSdkPath, SdkConstants.FD_EXTRAS);
- List<File> deviceDirs = getExtraDirs(extrasFolder);
- for (File deviceDir : deviceDirs) {
- File deviceXml = new File(deviceDir, SdkConstants.FN_DEVICES_XML);
- if (deviceXml.isFile()) {
- mVendorDevices.addAll(loadDevices(deviceXml));
- }
+ if (mOsSdkPath != null) {
+ // Load devices from vendor extras
+ File extrasFolder = new File(mOsSdkPath, SdkConstants.FD_EXTRAS);
+ List<File> deviceDirs = getExtraDirs(extrasFolder);
+ for (File deviceDir : deviceDirs) {
+ File deviceXml = new File(deviceDir, SdkConstants.FN_DEVICES_XML);
+ if (deviceXml.isFile()) {
+ mVendorDevices.addAll(loadDevices(deviceXml));
}
- return true;
}
+ return true;
}
}
return false;
}
/**
+ * Initializes all system-image provided {@link Device}s.
+ * @return True if the list has changed.
+ */
+ private boolean initSysImgDevices() {
+ synchronized (mLock) {
+ if (mSysImgDevices != null) {
+ return false;
+ }
+ mSysImgDevices = new ArrayList<Device>();
+
+ if (mOsSdkPath == null) {
+ return false;
+ }
+
+ // Load devices from tagged system-images
+ // Path pattern is /sdk/system-images/<platform-N>/<tag>/<abi>/devices.xml
+
+ FileOp fop = new FileOp();
+ File sysImgFolder = new File(mOsSdkPath, SdkConstants.FD_SYSTEM_IMAGES);
+
+ for (File platformFolder : fop.listFiles(sysImgFolder)) {
+ if (!fop.isDirectory(platformFolder)) {
+ continue;
+ }
+
+ for (File tagFolder : fop.listFiles(platformFolder)) {
+ if (!fop.isDirectory(tagFolder)) {
+ continue;
+ }
+
+ for (File abiFolder : fop.listFiles(tagFolder)) {
+ if (!fop.isDirectory(abiFolder)) {
+ continue;
+ }
+
+ File deviceXml = new File(abiFolder, SdkConstants.FN_DEVICES_XML);
+ if (fop.isFile(deviceXml)) {
+ mSysImgDevices.addAll(loadDevices(deviceXml));
+ }
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
* Initializes all user-created {@link Device}s
* @return True if the list has changed.
*/
private boolean initUserDevices() {
synchronized (mLock) {
- if (mUserDevices == null) {
- // User devices should be saved out to
- // $HOME/.android/devices.xml
- mUserDevices = new ArrayList<Device>();
- File userDevicesFile = null;
- try {
- userDevicesFile = new File(
- AndroidLocation.getFolder(),
- SdkConstants.FN_DEVICES_XML);
- if (userDevicesFile.exists()) {
- mUserDevices.addAll(DeviceParser.parse(userDevicesFile));
- return true;
- }
- } catch (AndroidLocationException e) {
- mLog.warning("Couldn't load user devices: %1$s", e.getMessage());
- } catch (SAXException e) {
- // Probably an old config file which we don't want to overwrite.
- if (userDevicesFile != null) {
- String base = userDevicesFile.getAbsoluteFile() + ".old";
- File renamedConfig = new File(base);
- int i = 0;
- while (renamedConfig.exists()) {
- renamedConfig = new File(base + '.' + (i++));
- }
- mLog.error(e, "Error parsing %1$s, backing up to %2$s",
- userDevicesFile.getAbsolutePath(),
- renamedConfig.getAbsolutePath());
- userDevicesFile.renameTo(renamedConfig);
- }
- } catch (ParserConfigurationException e) {
- mLog.error(e, "Error parsing %1$s",
- userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
- } catch (IOException e) {
- mLog.error(e, "Error parsing %1$s",
- userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
+ if (mUserDevices != null) {
+ return false;
+ }
+ // User devices should be saved out to
+ // $HOME/.android/devices.xml
+ mUserDevices = new ArrayList<Device>();
+ File userDevicesFile = null;
+ try {
+ userDevicesFile = new File(
+ AndroidLocation.getFolder(),
+ SdkConstants.FN_DEVICES_XML);
+ if (userDevicesFile.exists()) {
+ mUserDevices.addAll(DeviceParser.parse(userDevicesFile));
+ return true;
}
+ } catch (AndroidLocationException e) {
+ mLog.warning("Couldn't load user devices: %1$s", e.getMessage());
+ } catch (SAXException e) {
+ // Probably an old config file which we don't want to overwrite.
+ if (userDevicesFile != null) {
+ String base = userDevicesFile.getAbsoluteFile() + ".old";
+ File renamedConfig = new File(base);
+ int i = 0;
+ while (renamedConfig.exists()) {
+ renamedConfig = new File(base + '.' + (i++));
+ }
+ mLog.error(e, "Error parsing %1$s, backing up to %2$s",
+ userDevicesFile.getAbsolutePath(),
+ renamedConfig.getAbsolutePath());
+ userDevicesFile.renameTo(renamedConfig);
+ }
+ } catch (ParserConfigurationException e) {
+ mLog.error(e, "Error parsing %1$s",
+ userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
+ } catch (IOException e) {
+ mLog.error(e, "Error parsing %1$s",
+ userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
}
}
return false;
@@ -447,6 +555,9 @@
* Returns the hardware properties defined in
* {@link AvdManager#HARDWARE_INI} as a {@link Map}.
*
+ * This is intended to be dumped in the config.ini and already contains
+ * the device name, manufacturer and device hash.
+ *
* @param d The {@link Device} from which to derive the hardware properties.
* @return A {@link Map} of hardware properties.
*/
@@ -458,26 +569,72 @@
props.put("hw.keyboard.lid", getBooleanVal(true));
}
}
- props.put(AvdManager.AVD_INI_DEVICE_HASH, Integer.toString(d.hashCode()));
+
+ HashFunction md5 = Hashing.md5();
+ Hasher hasher = md5.newHasher();
+
+ ArrayList<String> keys = new ArrayList<String>(props.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ if (key != null) {
+ hasher.putString(key, Charsets.UTF_8);
+ String value = props.get(key);
+ hasher.putString(value == null ? "null" : value, Charsets.UTF_8);
+ }
+ }
+ // store the hash method for potential future compatibility
+ String hash = "MD5:" + hasher.hash().toString();
+ props.put(AvdManager.AVD_INI_DEVICE_HASH_V2, hash);
+ props.remove(AvdManager.AVD_INI_DEVICE_HASH_V1);
+
props.put(AvdManager.AVD_INI_DEVICE_NAME, d.getId());
props.put(AvdManager.AVD_INI_DEVICE_MANUFACTURER, d.getManufacturer());
return props;
}
/**
+ * Checks whether the the hardware props have changed.
+ * If the hash is the same, returns null for success.
+ * If the hash is not the same or there's not enough information to indicate it's
+ * the same (e.g. if in the future we change the digest method), simply return the
+ * new hash, indicating it would be best to update it.
+ *
+ * @param d The device.
+ * @param hashV2 The previous saved AvdManager.AVD_INI_DEVICE_HASH_V2 property.
+ * @return Null if the same, otherwise returns the new and different hash.
+ */
+ @Nullable
+ public static String hasHardwarePropHashChanged(@NonNull Device d, @NonNull String hashV2) {
+ Map<String, String> props = getHardwareProperties(d);
+ String newHash = props.get(AvdManager.AVD_INI_DEVICE_HASH_V2);
+
+ // Implementation detail: don't just return the hash and let the caller decide whether
+ // the hash is the same. That's because the hash contains the digest method so if in
+ // the future we decide to change it, we could potentially recompute the hash here
+ // using an older digest method here and still determine its validity, whereas the
+ // caller cannot determine that.
+
+ if (newHash != null && newHash.equals(hashV2)) {
+ return null;
+ }
+ return newHash;
+ }
+
+
+ /**
* Takes a boolean and returns the appropriate value for
* {@link HardwareProperties}
*
* @param bool The boolean value to turn into the appropriate
* {@link HardwareProperties} value.
- * @return {@code HardwareProperties#BOOLEAN_VALUES[0]} if true,
- * {@code HardwareProperties#BOOLEAN_VALUES[1]} otherwise.
+ * @return {@code HardwareProperties#BOOLEAN_YES} if true,
+ * {@code HardwareProperties#BOOLEAN_NO} otherwise.
*/
private static String getBooleanVal(boolean bool) {
if (bool) {
- return HardwareProperties.BOOLEAN_VALUES[0];
+ return HardwareProperties.BOOLEAN_YES;
}
- return HardwareProperties.BOOLEAN_VALUES[1];
+ return HardwareProperties.BOOLEAN_NO;
}
@NonNull
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 7c39a97..945d610 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java
@@ -36,7 +36,9 @@
import org.xml.sax.helpers.DefaultHandler;
import java.awt.Point;
+import java.io.BufferedInputStream;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -61,11 +63,13 @@
private Device.Builder mBuilder;
private Camera mCamera;
private Storage.Unit mUnit;
+ private String[] mBootProp;
public DeviceHandler(@Nullable File parentFolder) {
mParentFolder = parentFolder;
}
+ @NonNull
public List<Device> getDevices() {
return mDevices;
}
@@ -108,6 +112,8 @@
mMeta.setFrameOffsetPortrait(new Point());
} else if (DeviceSchema.NODE_SCREEN.equals(localName)) {
mHardware.setScreen(new Screen());
+ } else if (DeviceSchema.NODE_BOOT_PROP.equals(localName)) {
+ mBootProp = new String[2];
}
mStringAccumulator.setLength(0);
}
@@ -319,6 +325,20 @@
}
} else if (DeviceSchema.NODE_STATUS_BAR.equals(localName)) {
mSoftware.setStatusBar(getBool(mStringAccumulator));
+
+ } else if (DeviceSchema.NODE_TAG_ID.equals(localName)) {
+ mBuilder.setTagId(getString(mStringAccumulator));
+ } else if (DeviceSchema.NODE_PROP_NAME.equals(localName)) {
+ assert mBootProp != null && mBootProp.length == 2;
+ mBootProp[0] = getString(mStringAccumulator);
+ } else if (DeviceSchema.NODE_PROP_VALUE.equals(localName)) {
+ assert mBootProp != null && mBootProp.length == 2;
+ mBootProp[1] = mStringAccumulator.toString();
+ } else if (DeviceSchema.NODE_BOOT_PROP.equals(localName)) {
+ assert mBootProp != null && mBootProp.length == 2 &&
+ mBootProp[0] != null && mBootProp[1] != null;
+ mBuilder.addBootProp(mBootProp[0], mBootProp[1]);
+ mBootProp = null;
}
}
@@ -389,25 +409,46 @@
sParserFactory.setNamespaceAware(true);
}
- public static List<Device> parse(File devicesFile) throws SAXException,
- ParserConfigurationException, IOException {
- SAXParser parser = getParser();
- DeviceHandler dHandler = new DeviceHandler(devicesFile.getAbsoluteFile().getParentFile());
- parser.parse(devicesFile, dHandler);
- return dHandler.getDevices();
+ @NonNull
+ public static List<Device> parse(@NonNull File devicesFile)
+ throws SAXException, ParserConfigurationException, IOException {
+ InputStream stream = null;
+ try {
+ stream = new FileInputStream(devicesFile);
+ return parseImpl(stream, devicesFile.getAbsoluteFile().getParentFile());
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException ignore) {}
+ }
+ }
}
- public static List<Device> parse(InputStream devices) throws SAXException, IOException,
- ParserConfigurationException {
- SAXParser parser = getParser();
- DeviceHandler dHandler = new DeviceHandler(null);
+ @NonNull
+ public static List<Device> parse(@NonNull InputStream devices)
+ throws SAXException, IOException, ParserConfigurationException {
+ return parseImpl(devices, null);
+ }
+
+ @NonNull
+ private static List<Device> parseImpl(@NonNull InputStream devices, @Nullable File parentDir)
+ throws SAXException, IOException, ParserConfigurationException {
+ if (!devices.markSupported()) {
+ devices = new BufferedInputStream(devices);
+ }
+ devices.mark(500000);
+ int version = DeviceSchema.getXmlSchemaVersion(devices);
+ SAXParser parser = getParser(version);
+ DeviceHandler dHandler = new DeviceHandler(parentDir);
+ devices.reset();
parser.parse(devices, dHandler);
return dHandler.getDevices();
}
@NonNull
- private static SAXParser getParser() throws ParserConfigurationException, SAXException {
- Schema schema = DeviceSchema.getSchema();
+ private static SAXParser getParser(int version) throws ParserConfigurationException, SAXException {
+ Schema schema = DeviceSchema.getSchema(version);
if (schema != null) {
sParserFactory.setSchema(schema);
}
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 df70c9c..7a94ff9 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java
@@ -16,7 +16,6 @@
package com.android.sdklib.devices;
-import com.android.SdkConstants;
import com.android.dvlib.DeviceSchema;
import com.android.resources.UiMode;
@@ -29,6 +28,7 @@
import java.io.OutputStream;
import java.util.Collection;
import java.util.Locale;
+import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -51,7 +51,7 @@
/**
* Writes the XML definition of the given {@link Collection} of {@link Device}s according to
- * {@link SdkConstants#NS_DEVICES_XSD} to the {@link OutputStream}.
+ * {@link DeviceSchema#NS_DEVICES_URI} to the {@link OutputStream}.
* Note that it is up to the caller to close the {@link OutputStream}.
* @param out The {@link OutputStream} to write the resulting XML to.
* @param devices The {@link Device}s from which to generate the XML.
@@ -63,11 +63,12 @@
ParserConfigurationException,
TransformerFactoryConfigurationError,
TransformerException {
+
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element root = doc.createElement(PREFIX + DeviceSchema.NODE_DEVICES);
root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":xsi",
XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
- root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + LOCAL_NS, SdkConstants.NS_DEVICES_XSD);
+ root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + LOCAL_NS, DeviceSchema.NS_DEVICES_URI);
doc.appendChild(root);
for (Device device : devices) {
@@ -98,6 +99,29 @@
for (State s : device.getAllStates()) {
deviceNode.appendChild(generateStateNode(s, doc, device.getDefaultHardware()));
}
+
+ String tagId = device.getTagId();
+ if (tagId != null) {
+ Element e = doc.createElement(PREFIX + DeviceSchema.NODE_TAG_ID);
+ e.appendChild(doc.createTextNode(tagId));
+ deviceNode.appendChild(e);
+ }
+
+ Map<String, String> bootProps = device.getBootProps();
+ if (bootProps != null && !bootProps.isEmpty()) {
+ Element props = doc.createElement(PREFIX + DeviceSchema.NODE_BOOT_PROPS);
+ for (Map.Entry<String, String> bootProp : bootProps.entrySet()) {
+ Element prop = doc.createElement(PREFIX + DeviceSchema.NODE_BOOT_PROP);
+ Element propName = doc.createElement(PREFIX + DeviceSchema.NODE_PROP_NAME);
+ propName.appendChild(doc.createTextNode(bootProp.getKey()));
+ Element propValue = doc.createElement(PREFIX + DeviceSchema.NODE_PROP_VALUE);
+ propValue.appendChild(doc.createTextNode(bootProp.getValue()));
+ prop.appendChild(propName);
+ prop.appendChild(propValue);
+ props.appendChild(prop);
+ }
+ deviceNode.appendChild(props);
+ }
}
Transformer tf = TransformerFactory.newInstance().newTransformer();
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/Hardware.java b/sdklib/src/main/java/com/android/sdklib/devices/Hardware.java
index 2a25755..2750d51 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/Hardware.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Hardware.java
@@ -339,4 +339,43 @@
return hash;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Hardware <mScreen=");
+ sb.append(mScreen);
+ sb.append(", mNetworking=");
+ sb.append(mNetworking);
+ sb.append(", mSensors=");
+ sb.append(mSensors);
+ sb.append(", mMic=");
+ sb.append(mMic);
+ sb.append(", mCameras=");
+ sb.append(mCameras);
+ sb.append(", mKeyboard=");
+ sb.append(mKeyboard);
+ sb.append(", mNav=");
+ sb.append(mNav);
+ sb.append(", mRam=");
+ sb.append(mRam);
+ sb.append(", mButtons=");
+ sb.append(mButtons);
+ sb.append(", mInternalStorage=");
+ sb.append(mInternalStorage);
+ sb.append(", mRemovableStorage=");
+ sb.append(mRemovableStorage);
+ sb.append(", mCpu=");
+ sb.append(mCpu);
+ sb.append(", mGpu=");
+ sb.append(mGpu);
+ sb.append(", mAbis=");
+ sb.append(mAbis);
+ sb.append(", mUiModes=");
+ sb.append(mUiModes);
+ sb.append(", mPluggedIn=");
+ sb.append(mPluggedIn);
+ sb.append(">");
+ return sb.toString();
+ }
}
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/Screen.java b/sdklib/src/main/java/com/android/sdklib/devices/Screen.java
index a7f4334..cd665c2 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/Screen.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Screen.java
@@ -186,4 +186,33 @@
hash = 31 * hash + mScreenType.ordinal();
return hash;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Screen [mScreenSize=");
+ sb.append(mScreenSize);
+ sb.append(", mDiagonalLength=");
+ sb.append(mDiagonalLength);
+ sb.append(", mPixelDensity=");
+ sb.append(mPixelDensity);
+ sb.append(", mScreenRatio=");
+ sb.append(mScreenRatio);
+ sb.append(", mXDimension=");
+ sb.append(mXDimension);
+ sb.append(", mYDimension=");
+ sb.append(mYDimension);
+ sb.append(", mXdpi=");
+ sb.append(mXdpi);
+ sb.append(", mYdpi=");
+ sb.append(mYdpi);
+ sb.append(", mMultitouch=");
+ sb.append(mMultitouch);
+ sb.append(", mMechanism=");
+ sb.append(mMechanism);
+ sb.append(", mScreenType=");
+ sb.append(mScreenType);
+ sb.append("]");
+ return sb.toString();
+ }
}
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/Software.java b/sdklib/src/main/java/com/android/sdklib/devices/Software.java
index 45e4d6a..98ca9c1 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/Software.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Software.java
@@ -150,4 +150,26 @@
hash = 31 * hash + (mStatusBar ? 1 : 0);
return hash;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Software <mMinSdkLevel=");
+ sb.append(mMinSdkLevel);
+ sb.append(", mMaxSdkLevel=");
+ sb.append(mMaxSdkLevel);
+ sb.append(", mLiveWallpaperSupport=");
+ sb.append(mLiveWallpaperSupport);
+ sb.append(", mBluetoothProfiles=");
+ sb.append(mBluetoothProfiles);
+ sb.append(", mGlVersion=");
+ sb.append(mGlVersion);
+ sb.append(", mGlExtensions=");
+ sb.append(mGlExtensions);
+ sb.append(", mStatusBar=");
+ sb.append(mStatusBar);
+ sb.append(">");
+ return sb.toString();
+ }
+
}
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/State.java b/sdklib/src/main/java/com/android/sdklib/devices/State.java
index abc74eb..0d27169 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/State.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/State.java
@@ -143,6 +143,22 @@
@Override
public String toString() {
- return mName;
+ StringBuilder sb = new StringBuilder();
+ sb.append("State <mDefaultState=");
+ sb.append(mDefaultState);
+ sb.append(", mName=");
+ sb.append(mName);
+ sb.append(", mDescription=");
+ sb.append(mDescription);
+ sb.append(", mOrientation=");
+ sb.append(mOrientation);
+ sb.append(", mKeyState=");
+ sb.append(mKeyState);
+ sb.append(", mNavState=");
+ sb.append(mNavState);
+ sb.append(", mHardwareOverride=");
+ sb.append(mHardwareOverride);
+ sb.append(">");
+ return sb.toString();
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/Storage.java b/sdklib/src/main/java/com/android/sdklib/devices/Storage.java
index 187c42a..c5c96d5 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/Storage.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Storage.java
@@ -128,4 +128,12 @@
}
return optimalUnit;
}
+
+ @Override
+ public String toString() {
+ Unit u = getAppropriateUnits();
+ return String.format("%d %s", getSizeAsUnit(u), u);
+ }
+
+
}
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 f2f75c7..1102888 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/devices.xml
+++ b/sdklib/src/main/java/com/android/sdklib/devices/devices.xml
@@ -455,8 +455,8 @@
</d:state>
</d:device>
<d:device>
- <d:name>3.4in WQVGA</d:name>
- <d:id>3.4" WQVGA</d:id>
+ <d:name>3.4" WQVGA</d:name>
+ <d:id>3.4in WQVGA</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
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
index 83c3555..d65c382 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/AddOnTarget.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/AddOnTarget.java
@@ -18,11 +18,13 @@
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.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISystemImage;
+import com.android.sdklib.repository.descriptors.IdDisplay;
import java.io.File;
import java.io.FileFilter;
@@ -83,8 +85,8 @@
private final boolean mHasRenderingLibrary;
private final boolean mHasRenderingResources;
- private String[] mSkins;
- private String mDefaultSkin;
+ private File[] mSkins;
+ private File mDefaultSkin;
private IOptionalLibrary[] mLibraries;
private int mVendorId = NO_USB_ID;
@@ -156,9 +158,10 @@
}
@Override
- public ISystemImage getSystemImage(String abiType) {
+ @Nullable
+ public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType) {
for (ISystemImage sysImg : mSystemImages) {
- if (sysImg.getAbiType().equals(abiType)) {
+ if (sysImg.getTag().equals(tag) && sysImg.getAbiType().equals(abiType)) {
return sysImg;
}
}
@@ -195,6 +198,7 @@
return mDescription;
}
+ @NonNull
@Override
public AndroidVersion getVersion() {
// this is always defined by the base platform
@@ -273,6 +277,11 @@
}
@Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+ @Override
public BuildToolInfo getBuildToolInfo() {
return mBasePlatform.getBuildToolInfo();
}
@@ -287,13 +296,15 @@
return mHasRenderingLibrary || mHasRenderingResources;
}
+ @NonNull
@Override
- public String[] getSkins() {
+ public File[] getSkins() {
return mSkins;
}
+ @Nullable
@Override
- public String getDefaultSkin() {
+ public File getDefaultSkin() {
return mDefaultSkin;
}
@@ -359,8 +370,7 @@
// 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) {
+ if (!mVendor.equals(target.getVendor()) || !mName.equals(target.getName())) {
return false;
}
@@ -451,15 +461,15 @@
// ---- local methods.
- public void setSkins(String[] skins, String defaultSkin) {
+ public void setSkins(@NonNull File[] skins, @NonNull File defaultSkin) {
mDefaultSkin = defaultSkin;
// we mix the add-on and base platform skins
- HashSet<String> skinSet = new HashSet<String>();
+ HashSet<File> skinSet = new HashSet<File>();
skinSet.addAll(Arrays.asList(skins));
skinSet.addAll(Arrays.asList(mBasePlatform.getSkins()));
- mSkins = skinSet.toArray(new String[skinSet.size()]);
+ mSkins = skinSet.toArray(new File[skinSet.size()]);
}
/**
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
index 0fc2227..7e0290a 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/PlatformTarget.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/PlatformTarget.java
@@ -18,12 +18,14 @@
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.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISystemImage;
import com.android.sdklib.SdkManager.LayoutlibVersion;
+import com.android.sdklib.repository.descriptors.IdDisplay;
import com.android.utils.SparseArray;
import java.io.File;
@@ -50,7 +52,7 @@
private final int mRevision;
private final Map<String, String> mProperties;
private final SparseArray<String> mPaths = new SparseArray<String>();
- private String[] mSkins;
+ private File[] mSkins;
private final ISystemImage[] mSystemImages;
private final LayoutlibVersion mLayoutlibVersion;
private final BuildToolInfo mBuildToolInfo;
@@ -67,7 +69,6 @@
* @param systemImages list of supported system images
* @param properties the platform properties
*/
- @SuppressWarnings("deprecation")
public PlatformTarget(
String sdkOsPath,
String platformOSPath,
@@ -134,9 +135,10 @@
}
@Override
- public ISystemImage getSystemImage(String abiType) {
+ @Nullable
+ public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType) {
for (ISystemImage sysImg : mSystemImages) {
- if (sysImg.getAbiType().equals(abiType)) {
+ if (sysImg.getTag().equals(tag) && sysImg.getAbiType().equals(abiType)) {
return sysImg;
}
}
@@ -197,6 +199,7 @@
return String.format("Standard Android platform %s", mVersionName);
}
+ @NonNull
@Override
public AndroidVersion getVersion() {
return mVersion;
@@ -228,6 +231,12 @@
}
@Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+
+ @Override
public BuildToolInfo getBuildToolInfo() {
return mBuildToolInfo;
}
@@ -245,14 +254,15 @@
return true;
}
-
+ @NonNull
@Override
- public String[] getSkins() {
+ public File[] getSkins() {
return mSkins;
}
+ @Nullable
@Override
- public String getDefaultSkin() {
+ public File getDefaultSkin() {
// only one skin? easy.
if (mSkins.length == 1) {
return mSkins[0];
@@ -260,17 +270,17 @@
// look for the skin name in the platform props
String skinName = mProperties.get(SdkConstants.PROP_SDK_DEFAULT_SKIN);
- if (skinName != null) {
- return skinName;
+ if (skinName == null) {
+ // 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.
+ skinName = "WVGA800"; //$NON-NLS-1$
+ } else {
+ skinName = "HVGA"; // this is for 1.5 and earlier. //$NON-NLS-1$
+ }
}
- // 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.
+ return new File(getFile(IAndroidTarget.SKINS), skinName);
}
/**
@@ -421,7 +431,7 @@
// ---- platform only methods.
- public void setSkins(String[] skins) {
+ public void setSkins(@NonNull File[] skins) {
mSkins = skins;
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java
index c3b6329..29bb9bd 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java
@@ -17,9 +17,15 @@
package com.android.sdklib.internal.avd;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.SystemImage;
+import com.android.sdklib.devices.Abi;
import com.android.sdklib.devices.Device;
+import com.android.sdklib.repository.descriptors.IdDisplay;
import java.io.File;
import java.util.Collections;
@@ -63,6 +69,7 @@
/** An immutable map of properties. This must not be modified. Map can be empty. Never null. */
private final Map<String, String> mProperties;
private final AvdStatus mStatus;
+ private final IdDisplay mTag;
/**
* Creates a new valid AVD info. Values are immutable.
@@ -75,17 +82,21 @@
* @param folderPath The path to the data directory
* @param targetHash the target hash
* @param target The target. Can be null, if the target was not resolved.
+ * @param tag The tag id/display.
* @param abiType Name of the abi.
* @param properties The property map. If null, an empty map will be created.
*/
- public AvdInfo(String name,
- File iniFile,
- String folderPath,
- String targetHash,
- IAndroidTarget target,
- String abiType,
- Map<String, String> properties) {
- this(name, iniFile, folderPath, targetHash, target, abiType, properties, AvdStatus.OK);
+ public AvdInfo(@NonNull String name,
+ @NonNull File iniFile,
+ @NonNull String folderPath,
+ @NonNull String targetHash,
+ @Nullable IAndroidTarget target,
+ @NonNull IdDisplay tag,
+ @NonNull String abiType,
+ @Nullable Map<String, String> properties) {
+ this(name, iniFile, folderPath,
+ targetHash, target, tag, abiType,
+ properties, AvdStatus.OK);
}
/**
@@ -99,44 +110,57 @@
* @param folderPath The path to the data directory
* @param targetHash the target hash
* @param target The target. Can be null, if the target was not resolved.
+ * @param tag The tag id/display.
* @param abiType Name of the abi.
* @param properties The property map. If null, an empty map will be created.
* @param status The {@link AvdStatus} of this AVD. Cannot be null.
*/
- public AvdInfo(String name,
- File iniFile,
- String folderPath,
- String targetHash,
- IAndroidTarget target,
- String abiType,
- Map<String, String> properties,
- AvdStatus status) {
- mName = name;
- mIniFile = iniFile;
+ public AvdInfo(@NonNull String name,
+ @NonNull File iniFile,
+ @NonNull String folderPath,
+ @NonNull String targetHash,
+ @Nullable IAndroidTarget target,
+ @NonNull IdDisplay tag,
+ @NonNull String abiType,
+ @Nullable Map<String, String> properties,
+ @NonNull AvdStatus status) {
+ mName = name;
+ mIniFile = iniFile;
mFolderPath = folderPath;
mTargetHash = targetHash;
- mTarget = target;
- mAbiType = abiType;
+ mTarget = target;
+ mTag = tag;
+ mAbiType = abiType;
mProperties = properties == null ? Collections.<String, String>emptyMap()
: Collections.unmodifiableMap(properties);
- mStatus = status;
+ mStatus = status;
}
/** Returns the name of the AVD. */
+ @NonNull
public String getName() {
return mName;
}
/** Returns the path of the AVD data directory. */
+ @NonNull
public String getDataFolderPath() {
return mFolderPath;
}
+ /** Returns the tag id/display of the AVD. */
+ @NonNull
+ public IdDisplay getTag() {
+ return mTag;
+ }
+
/** Returns the processor type of the AVD. */
+ @NonNull
public String getAbiType() {
return mAbiType;
}
+ @NonNull
public String getCpuArch() {
String cpuArch = mProperties.get(AvdManager.AVD_INI_CPU_ARCH);
if (cpuArch != null) {
@@ -147,58 +171,69 @@
return SdkConstants.CPU_ARCH_ARM;
}
+ @NonNull
public String getDeviceManufacturer() {
String deviceManufacturer = mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER);
if (deviceManufacturer != null && !deviceManufacturer.isEmpty()) {
return deviceManufacturer;
}
- return "";
+ return ""; // $NON-NLS-1$
}
+ @NonNull
public String getDeviceName() {
String deviceName = mProperties.get(AvdManager.AVD_INI_DEVICE_NAME);
if (deviceName != null && !deviceName.isEmpty()) {
return deviceName;
}
- return "";
+ return ""; // $NON-NLS-1$
}
/** Convenience function to return a more user friendly name of the abi type. */
- public static String getPrettyAbiType(String raw) {
- String s = null;
- if (raw.equalsIgnoreCase(SdkConstants.ABI_ARMEABI)) {
- s = "ARM (" + SdkConstants.ABI_ARMEABI + ")";
+ @NonNull
+ public static String getPrettyAbiType(@NonNull AvdInfo avdInfo) {
+ return getPrettyAbiType(avdInfo.getTag(), avdInfo.getAbiType());
+ }
- } else if (raw.equalsIgnoreCase(SdkConstants.ABI_ARMEABI_V7A)) {
- s = "ARM (" + SdkConstants.ABI_ARMEABI_V7A + ")";
+ /** Convenience function to return a more user friendly name of the abi type. */
+ @NonNull
+ public static String getPrettyAbiType(@NonNull ISystemImage sysImg) {
+ return getPrettyAbiType(sysImg.getTag(), sysImg.getAbiType());
+ }
- } else if (raw.equalsIgnoreCase(SdkConstants.ABI_INTEL_ATOM)) {
- s = "Intel Atom (" + SdkConstants.ABI_INTEL_ATOM + ")";
+ /** Convenience function to return a more user friendly name of the abi type. */
+ @NonNull
+ public static String getPrettyAbiType(@NonNull IdDisplay tag, @NonNull String rawAbi) {
+ String s = ""; // $NON-NLS-1$
- } else if (raw.equalsIgnoreCase(SdkConstants.ABI_MIPS)) {
- s = "MIPS (" + SdkConstants.ABI_MIPS + ")";
-
- } else {
- s = raw + " (" + raw + ")";
+ if (!SystemImage.DEFAULT_TAG.equals(tag)) {
+ s = tag.getDisplay() + ' ';
}
+
+ Abi abi = Abi.getEnum(rawAbi);
+ s += (abi == null ? rawAbi : abi.getDisplayName()) + " (" + rawAbi + ')';
+
return s;
}
/**
* Returns the target hash string.
*/
+ @NonNull
public String getTargetHash() {
return mTargetHash;
}
/** Returns the target of the AVD, or <code>null</code> if it has not been resolved. */
+ @Nullable
public IAndroidTarget getTarget() {
return mTarget;
}
/** Returns the {@link AvdStatus} of the receiver. */
+ @NonNull
public AvdStatus getStatus() {
return mStatus;
}
@@ -220,7 +255,8 @@
* @param avdName The name of the desired AVD.
* @throws AndroidLocationException if there's a problem getting android root directory.
*/
- public static File getDefaultAvdFolder(AvdManager manager, String avdName)
+ @NonNull
+ public static File getDefaultAvdFolder(@NonNull AvdManager manager, @NonNull String avdName)
throws AndroidLocationException {
return new File(manager.getBaseAvdFolder(),
avdName + AvdManager.AVD_FOLDER_EXTENSION);
@@ -235,7 +271,8 @@
* @param avdName The name of the desired AVD.
* @throws AndroidLocationException if there's a problem getting android root directory.
*/
- public static File getDefaultIniFile(AvdManager manager, String avdName)
+ @NonNull
+ public static File getDefaultIniFile(@NonNull AvdManager manager, @NonNull String avdName)
throws AndroidLocationException {
String avdRoot = manager.getBaseAvdFolder();
return new File(avdRoot, avdName + AvdManager.INI_EXTENSION);
@@ -244,6 +281,7 @@
/**
* Returns the .ini {@link File} for this AVD.
*/
+ @NonNull
public File getIniFile() {
return mIniFile;
}
@@ -251,13 +289,15 @@
/**
* Helper method that returns the Config {@link File} for a given AVD name.
*/
- public static File getConfigFile(String path) {
+ @NonNull
+ public static File getConfigFile(@NonNull String path) {
return new File(path, AvdManager.CONFIG_INI);
}
/**
* Returns the Config {@link File} for this AVD.
*/
+ @NonNull
public File getConfigFile() {
return getConfigFile(mFolderPath);
}
@@ -267,6 +307,7 @@
* This can be empty but not null.
* Callers must NOT try to modify this immutable map.
*/
+ @NonNull
public Map<String, String> getProperties() {
return mProperties;
}
@@ -275,6 +316,7 @@
* Returns the error message for the AVD or <code>null</code> if {@link #getStatus()}
* returns {@link AvdStatus#OK}
*/
+ @Nullable
public String getErrorMessage() {
switch (mStatus) {
case ERROR_PATH:
@@ -291,7 +333,10 @@
getConfigFile());
case ERROR_IMAGE_DIR:
return String.format(
- "Invalid value in image.sysdir. Run 'android update avd -n %1$s'",
+ "Missing system image for %1$s%2$s %3$s. Run 'android update avd -n %4$s'",
+ SystemImage.DEFAULT_TAG.equals(mTag) ? "" : (mTag.getDisplay() + " "),
+ mAbiType,
+ mTarget.getFullName(),
mName);
case ERROR_DEVICE_CHANGED:
return String.format("%1$s %2$s configuration has changed since AVD creation",
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
index 6fead16..04a4488 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
@@ -19,6 +19,8 @@
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.io.FileWrapper;
import com.android.io.IAbstractFile;
import com.android.io.StreamException;
@@ -27,14 +29,22 @@
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISystemImage;
import com.android.sdklib.SdkManager;
+import com.android.sdklib.SystemImage;
+import com.android.sdklib.devices.Abi;
+import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.DeviceManager;
import com.android.sdklib.devices.DeviceManager.DeviceStatus;
import com.android.sdklib.internal.avd.AvdInfo.AvdStatus;
import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.util.GrabProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.Wait;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.local.LocalSdk;
+import com.android.sdklib.repository.local.LocalSysImgPkgInfo;
+import com.android.utils.GrabProcessOutput;
+import com.android.utils.GrabProcessOutput.IProcessOutput;
+import com.android.utils.GrabProcessOutput.Wait;
import com.android.utils.ILogger;
+import com.android.utils.NullLogger;
import com.android.utils.Pair;
import com.google.common.base.Charsets;
import com.google.common.io.Closeables;
@@ -53,8 +63,8 @@
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;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -102,20 +112,27 @@
public static final String AVD_INFO_TARGET = "target"; //$NON-NLS-1$
/**
+ * AVD/config.ini key name representing the tag id of the specific avd
+ */
+ public static final String AVD_INI_TAG_ID = "tag.id"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the tag display of the specific avd
+ */
+ public static final String AVD_INI_TAG_DISPLAY = "tag.display"; //$NON-NLS-1$
+
+ /**
* AVD/config.ini key name representing the abi type of the specific avd
- *
*/
public static final String AVD_INI_ABI_TYPE = "abi.type"; //$NON-NLS-1$
/**
* AVD/config.ini key name representing the CPU architecture of the specific avd
- *
*/
public static final String AVD_INI_CPU_ARCH = "hw.cpu.arch"; //$NON-NLS-1$
/**
* AVD/config.ini key name representing the CPU architecture of the specific avd
- *
*/
public static final String AVD_INI_CPU_MODEL = "hw.cpu.model"; //$NON-NLS-1$
@@ -219,9 +236,21 @@
public static final String AVD_INI_DATA_PARTITION_SIZE = "disk.dataPartition.size";
/**
- * AVD/config.ini key name representing the hash of the device this AVD is based on
+ * AVD/config.ini key name representing the hash of the device this AVD is based on. <br/>
+ * This old hash is deprecated and shouldn't be used anymore.
+ * It represents the Device.hashCode() and is not stable accross implementations.
+ * @see #AVD_INI_DEVICE_HASH_V2
*/
- public static final String AVD_INI_DEVICE_HASH = "hw.device.hash";
+ public static final String AVD_INI_DEVICE_HASH_V1 = "hw.device.hash";
+
+ /**
+ * AVD/config.ini key name representing the hash of the device hardware properties
+ * actually present in the config.ini. This replaces {@link #AVD_INI_DEVICE_HASH_V1}.
+ * <p/>
+ * To find this hash, use
+ * {@code DeviceManager.getHardwareProperties(device).get(AVD_INI_DEVICE_HASH_V2)}.
+ */
+ public static final String AVD_INI_DEVICE_HASH_V2 = "hw.device.hash2";
/**
* Pattern to match pixel-sized skin "names", e.g. "320x480".
@@ -229,6 +258,7 @@
public static final Pattern NUMERIC_SKIN_SIZE = Pattern.compile("([0-9]{2,})x([0-9]{2,})"); //$NON-NLS-1$
private static final String USERDATA_IMG = "userdata.img"; //$NON-NLS-1$
+ private static final String BOOT_PROP = "boot.prop"; //$NON-NLS-1$
static final String CONFIG_INI = "config.ini"; //$NON-NLS-1$
private static final String SDCARD_IMG = "sdcard.img"; //$NON-NLS-1$
private static final String SNAPSHOTS_IMG = "snapshots.img"; //$NON-NLS-1$
@@ -298,11 +328,11 @@
private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<AvdInfo>();
private AvdInfo[] mValidAvdList;
private AvdInfo[] mBrokenAvdList;
- private final SdkManager mSdkManager;
+ private final LocalSdk myLocalSdk;
/**
- * Creates an AVD Manager for a given SDK represented by a {@link SdkManager}.
- * @param sdkManager The SDK.
+ * Creates an AVD Manager for a given SDK represented by a {@link LocalSdk}.
+ * @param localSdk The SDK.
* @param log The log object to receive the log of the initial loading of the AVDs.
* This log object is not kept by this instance of AvdManager and each
* method takes its own logger. The rationale is that the AvdManager
@@ -310,20 +340,35 @@
* logging needs. Cannot be null.
* @throws AndroidLocationException
*/
- protected AvdManager(SdkManager sdkManager, ILogger log) throws AndroidLocationException {
- mSdkManager = sdkManager;
+ protected AvdManager(@NonNull LocalSdk localSdk, @NonNull ILogger log)
+ throws AndroidLocationException {
+ myLocalSdk = localSdk;
buildAvdList(mAllAvdList, log);
}
- public static AvdManager getInstance(SdkManager sdkManager, ILogger log)
+ /**
+ * Returns an AVD Manager for a given SDK represented by a {@link LocalSdk}.
+ * One AVD Manager instance is created by SDK location and then cached and reused.
+ *
+ * @param localSdk The SDK.
+ * @param log The log object to receive the log of the initial loading of the AVDs.
+ * This log object is not kept by this instance of AvdManager and each
+ * method takes its own logger. The rationale is that the AvdManager
+ * might be called from a variety of context, each with different
+ * logging needs. Cannot be null.
+ * @throws AndroidLocationException
+ */
+ @NonNull
+ public static AvdManager getInstance(@NonNull LocalSdk localSdk, @NonNull ILogger log)
throws AndroidLocationException {
synchronized(mManagers) {
AvdManager manager;
- if ((manager = mManagers.get(sdkManager.getLocation())) != null) {
+ if ((manager = mManagers.get(localSdk.getLocation().getPath())) != null) {
return manager;
}
- manager = new AvdManager(sdkManager, log);
- mManagers.put(sdkManager.getLocation(), manager);
+ manager = new AvdManager(localSdk, log);
+
+ mManagers.put(localSdk.getLocation().getPath(), manager);
return manager;
}
}
@@ -333,16 +378,29 @@
*
* @throws AndroidLocationException
*/
+ @NonNull
public String getBaseAvdFolder() throws AndroidLocationException {
assert AndroidLocation.getFolder().endsWith(File.separator);
return AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
}
/**
- * Returns the {@link SdkManager} associated with the {@link AvdManager}.
+ * Returns the {@link LocalSdk} associated with the {@link AvdManager}.
*/
+ @NonNull
+ public LocalSdk getLocalSdk() {
+ return myLocalSdk;
+ }
+
+ /**
+ * Returns the {@link SdkManager} associated with the {@link AvdManager}.
+ * Note: This is temporary and will be removed as SdkManager is phased out.
+ * TODO: Remove this when SdkManager is removed
+ */
+ @NonNull
+ @Deprecated
public SdkManager getSdkManager() {
- return mSdkManager;
+ return SdkManager.createManager(myLocalSdk.getPath(), NullLogger.getLogger());
}
/**
@@ -366,7 +424,7 @@
* @return A size in byte if > 0, or {@link #SDCARD_SIZE_NOT_IN_RANGE},
* {@link #SDCARD_SIZE_INVALID} or {@link #SDCARD_NOT_SIZE_PATTERN} as error codes.
*/
- public static long parseSdcardSize(String sdcard, String[] parsedStrings) {
+ public static long parseSdcardSize(@NonNull String sdcard, @Nullable String[] parsedStrings) {
if (parsedStrings != null) {
assert parsedStrings.length == 2;
@@ -414,6 +472,7 @@
* Returns all the existing AVDs.
* @return a newly allocated array containing all the AVDs.
*/
+ @NonNull
public AvdInfo[] getAllAvds() {
synchronized (mAllAvdList) {
return mAllAvdList.toArray(new AvdInfo[mAllAvdList.size()]);
@@ -424,6 +483,7 @@
* Returns all the valid AVDs.
* @return a newly allocated array containing all valid the AVDs.
*/
+ @NonNull
public AvdInfo[] getValidAvds() {
synchronized (mAllAvdList) {
if (mValidAvdList == null) {
@@ -444,6 +504,7 @@
* Returns all the broken AVDs.
* @return a newly allocated array containing all the broken AVDs.
*/
+ @NonNull
public AvdInfo[] getBrokenAvds() {
synchronized (mAllAvdList) {
if (mBrokenAvdList == null) {
@@ -468,7 +529,8 @@
* @param validAvdOnly if <code>true</code>, only look through the list of valid AVDs.
* @return the matching AvdInfo or <code>null</code> if none were found.
*/
- public AvdInfo getAvd(String name, boolean validAvdOnly) {
+ @Nullable
+ public AvdInfo getAvd(@Nullable String name, boolean validAvdOnly) {
boolean ignoreCase = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS;
@@ -499,7 +561,8 @@
* @param name the name of the AVD to return
* @return A pair of {@link AvdConflict} and the path or AVD name that conflicts.
*/
- public Pair<AvdConflict, String> isAvdNameConflicting(String name) {
+ @NonNull
+ public Pair<AvdConflict, String> isAvdNameConflicting(@Nullable String name) {
boolean ignoreCase = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS;
@@ -546,7 +609,7 @@
* @throws AndroidLocationException if there was an error finding the location of the
* AVD folder.
*/
- public void reloadAvds(ILogger log) throws AndroidLocationException {
+ public void reloadAvds(@NonNull ILogger log) throws AndroidLocationException {
// build the list in a temp list first, in case the method throws an exception.
// It's better than deleting the whole list before reading the new one.
ArrayList<AvdInfo> allList = new ArrayList<AvdInfo>();
@@ -567,11 +630,15 @@
* {@code AvdManager.AvdInfo.getAvdFolder}.
* @param avdName the name of the AVD
* @param target the target of the AVD
+ * @param tag the tag of the AVD
* @param abiType the abi type of the AVD
+ * @param skinFolder the skin folder path to use, if specified. Can be null.
* @param skinName the name of the skin. Can be null. Must have been verified by caller.
+ * Can be a size in the form "NNNxMMM" or a directory name matching skinFolder.
* @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
* an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
* @param hardwareConfig the hardware setup for the AVD. Can be null to use defaults.
+ * @param bootProps the optional boot properties for the AVD. Can be null.
* @param createSnapshot If true copy a blank snapshot image into the AVD.
* @param removePrevious If true remove any previous files.
* @param editExisting If true, edit an existing AVD, changing only the minimum required.
@@ -580,18 +647,22 @@
* @return The new {@link AvdInfo} in case of success (which has just been added to the
* internal list) or null in case of failure.
*/
+ @Nullable
public AvdInfo createAvd(
- File avdFolder,
- String avdName,
- IAndroidTarget target,
- String abiType,
- String skinName,
- String sdcard,
- Map<String,String> hardwareConfig,
+ @NonNull File avdFolder,
+ @NonNull String avdName,
+ @NonNull IAndroidTarget target,
+ @NonNull IdDisplay tag,
+ @NonNull String abiType,
+ @Nullable File skinFolder,
+ @Nullable String skinName,
+ @Nullable String sdcard,
+ @Nullable Map<String,String> hardwareConfig,
+ @Nullable Map<String,String> bootProps,
boolean createSnapshot,
boolean removePrevious,
boolean editExisting,
- ILogger log) {
+ @NonNull ILogger log) {
if (log == null) {
throw new IllegalArgumentException("log cannot be null");
}
@@ -632,7 +703,7 @@
// Look for a system image in the add-on.
// If we don't find one there, look in the base platform.
- ISystemImage systemImage = target.getSystemImage(abiType);
+ ISystemImage systemImage = target.getSystemImage(tag, abiType);
if (systemImage != null) {
File imageFolder = systemImage.getLocation();
@@ -642,7 +713,7 @@
if ((userdataSrc == null || !userdataSrc.exists()) && !target.isPlatform()) {
// If we don't find a system-image in the add-on, look into the platform.
- systemImage = target.getParent().getSystemImage(abiType);
+ systemImage = target.getParent().getSystemImage(tag, abiType);
if (systemImage != null) {
File imageFolder = systemImage.getLocation();
userdataSrc = new File(imageFolder, USERDATA_IMG);
@@ -672,7 +743,7 @@
// Config file.
HashMap<String, String> values = new HashMap<String, String>();
- if (setImagePathProperties(target, abiType, values, log) == false) {
+ if (setImagePathProperties(target, tag, abiType, values, log) == false) {
log.error(null, "Failed to set image path properties in the AVD folder.");
needCleanup = true;
return null;
@@ -685,8 +756,8 @@
log.info("Snapshot image already present, was not changed.\n");
} else {
- String toolsLib = mSdkManager.getLocation() + File.separator
- + SdkConstants.OS_SDK_TOOLS_LIB_EMULATOR_FOLDER;
+ File toolsLib = new File(myLocalSdk.getLocation(),
+ SdkConstants.OS_SDK_TOOLS_LIB_EMULATOR_FOLDER);
File snapshotBlank = new File(toolsLib, SNAPSHOTS_IMG);
if (snapshotBlank.exists() == false) {
log.error(null,
@@ -701,19 +772,20 @@
values.put(AVD_INI_SNAPSHOT_PRESENT, "true");
}
- // Now the abi type
- values.put(AVD_INI_ABI_TYPE, abiType);
+ // Now the tag & abi type
+ values.put(AVD_INI_TAG_ID, tag.getId());
+ values.put(AVD_INI_TAG_DISPLAY, tag.getDisplay());
+ values.put(AVD_INI_ABI_TYPE, abiType);
// and the cpu arch.
- if (SdkConstants.ABI_ARMEABI.equals(abiType)) {
- values.put(AVD_INI_CPU_ARCH, SdkConstants.CPU_ARCH_ARM);
- } else if (SdkConstants.ABI_ARMEABI_V7A.equals(abiType)) {
- values.put(AVD_INI_CPU_ARCH, SdkConstants.CPU_ARCH_ARM);
- values.put(AVD_INI_CPU_MODEL, SdkConstants.CPU_MODEL_CORTEX_A8);
- } else if (SdkConstants.ABI_INTEL_ATOM.equals(abiType)) {
- values.put(AVD_INI_CPU_ARCH, SdkConstants.CPU_ARCH_INTEL_ATOM);
- } else if (SdkConstants.ABI_MIPS.equals(abiType)) {
- values.put(AVD_INI_CPU_ARCH, SdkConstants.CPU_ARCH_MIPS);
+ Abi abi = Abi.getEnum(abiType);
+ if (abi != null) {
+ values.put(AVD_INI_CPU_ARCH, abi.getCpuArch());
+
+ String model = abi.getCpuModel();
+ if (model != null) {
+ values.put(AVD_INI_CPU_MODEL, model);
+ }
} else {
log.error(null,
"ABI %1$s is not supported by this version of the SDK Tools", abiType);
@@ -722,29 +794,47 @@
}
// Now the skin.
- if (skinName == null || skinName.length() == 0) {
- skinName = target.getDefaultSkin();
+ String skinPath = null;
+
+ if (skinFolder == null && skinName == null) {
+ // Nothing specified. Use the default from the target.
+ skinFolder = target.getDefaultSkin();
}
- if (NUMERIC_SKIN_SIZE.matcher(skinName).matches()) {
- // Skin name is an actual screen resolution.
- // Set skin.name for display purposes in the AVD manager and
- // set skin.path for use by the emulator.
- values.put(AVD_INI_SKIN_NAME, skinName);
- values.put(AVD_INI_SKIN_PATH, skinName);
- } else {
- // get the path of the skin (relative to the SDK)
- // assume skin name is valid
- String skinPath = getSkinRelativePath(skinName, target, log);
- if (skinPath == null) {
- log.error(null, "Missing skinpath in the AVD folder.");
- needCleanup = true;
+ if (skinFolder == null && skinName != null &&
+ NUMERIC_SKIN_SIZE.matcher(skinName).matches()) {
+ // Numeric skin size. Set both skinPath and skinName to the same size.
+ skinPath = skinName;
+
+ } else if (skinFolder != null && skinName == null) {
+ // Skin folder is specified, but not skin name. Adjust it.
+ skinName = skinFolder.getName();
+
+ } else if (skinFolder == null && skinName != null) {
+ // skin folder is not specified, but there's a non-numeric skin name.
+ // check whether the skin is in the target.
+ skinFolder = getSkinFolder(skinName, target);
+ }
+
+ if (skinFolder != null) {
+ // skin does not exist!
+ if (!skinFolder.exists()) {
+ log.error(null, "Skin '%1$s' does not exist.", skinName);
return null;
}
- values.put(AVD_INI_SKIN_PATH, skinPath);
+ // skinPath is the skin folder path relative to the SDK directory
+ skinPath = FileOp.makeRelative(myLocalSdk.getLocation(), skinFolder);
+ }
+
+ // Set skin.name for display purposes in the AVD manager and
+ // set skin.path for use by the emulator.
+ if (skinName != null) {
values.put(AVD_INI_SKIN_NAME, skinName);
}
+ if (skinPath != null) {
+ values.put(AVD_INI_SKIN_PATH, skinPath);
+ }
if (sdcard != null && sdcard.length() > 0) {
// Sdcard is possibly a size. In that case we create a file called 'sdcard.img'
@@ -792,7 +882,7 @@
String path = sdcardFile.getAbsolutePath();
// execute mksdcard with the proper parameters.
- File toolsFolder = new File(mSdkManager.getLocation(),
+ File toolsFolder = new File(myLocalSdk.getLocation(),
SdkConstants.FD_TOOLS);
File mkSdCard = new File(toolsFolder, SdkConstants.mkSdCardCmdName());
@@ -822,6 +912,7 @@
// - values provided by the user
// - values provided by the skin
// - values provided by the target (add-on only).
+ // - values provided by the sys img
// In order to follow this priority, we'll add the lowest priority values first and then
// override by higher priority values.
// In the case of a platform with override values from the user, the skin value might
@@ -829,6 +920,18 @@
HashMap<String, String> finalHardwareValues = new HashMap<String, String>();
+ FileWrapper sysImgHardwareFile = new FileWrapper(systemImage.getLocation(),
+ AvdManager.HARDWARE_INI);
+ if (sysImgHardwareFile.isFile()) {
+ Map<String, String> targetHardwareConfig = ProjectProperties.parsePropertyFile(
+ sysImgHardwareFile, log);
+
+ if (targetHardwareConfig != null) {
+ finalHardwareValues.putAll(targetHardwareConfig);
+ values.putAll(targetHardwareConfig);
+ }
+ }
+
FileWrapper targetHardwareFile = new FileWrapper(target.getLocation(),
AvdManager.HARDWARE_INI);
if (targetHardwareFile.isFile()) {
@@ -842,15 +945,16 @@
}
// get the hardware properties for this skin
- File skinFolder = getSkinPath(skinName, target);
- FileWrapper skinHardwareFile = new FileWrapper(skinFolder, AvdManager.HARDWARE_INI);
- if (skinHardwareFile.isFile()) {
- Map<String, String> skinHardwareConfig = ProjectProperties.parsePropertyFile(
- skinHardwareFile, log);
+ if (skinFolder != null) {
+ FileWrapper skinHardwareFile = new FileWrapper(skinFolder, AvdManager.HARDWARE_INI);
+ if (skinHardwareFile.isFile()) {
+ Map<String, String> skinHardwareConfig =
+ ProjectProperties.parsePropertyFile(skinHardwareFile, log);
- if (skinHardwareConfig != null) {
- finalHardwareValues.putAll(skinHardwareConfig);
- values.putAll(skinHardwareConfig);
+ if (skinHardwareConfig != null) {
+ finalHardwareValues.putAll(skinHardwareConfig);
+ values.putAll(skinHardwareConfig);
+ }
}
}
@@ -861,7 +965,12 @@
}
File configIniFile = new File(avdFolder, CONFIG_INI);
- writeIniFile(configIniFile, values);
+ writeIniFile(configIniFile, values, true);
+
+ if (bootProps != null && !bootProps.isEmpty()) {
+ File bootPropsFile = new File(avdFolder, BOOT_PROP);
+ writeIniFile(bootPropsFile, bootProps, false);
+ }
// Generate the log report first because we want to control where line breaks
// are located when generating the hardware config list.
@@ -884,13 +993,16 @@
target.getName(), target.getVendor()));
}
}
- report.append(String.format(", %s processor", AvdInfo.getPrettyAbiType(abiType)));
+ report.append(String.format(", %s processor", AvdInfo.getPrettyAbiType(tag, abiType)));
// display the chosen hardware config
if (finalHardwareValues.size() > 0) {
report.append(",\nwith the following hardware config:\n");
- for (Entry<String, String> entry : finalHardwareValues.entrySet()) {
- report.append(String.format("%s=%s\n",entry.getKey(), entry.getValue()));
+ List<String> keys = new ArrayList<String>(finalHardwareValues.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ String value = finalHardwareValues.get(key);
+ report.append(String.format("%s=%s\n", key, value));
}
} else {
report.append("\n");
@@ -904,7 +1016,9 @@
iniFile,
avdFolder.getAbsolutePath(),
target.hashString(),
- target, abiType, values);
+ target,
+ tag, abiType,
+ values);
AvdInfo oldAvdInfo = getAvd(avdName, false /*validAvdOnly*/);
@@ -963,7 +1077,7 @@
* @throws FileNotFoundException
* @throws IOException
*/
- private void copyImageFile(File source, File destination)
+ private void copyImageFile(@NonNull File source, @NonNull File destination)
throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(destination);
@@ -983,10 +1097,12 @@
* is not empty. If the image folder is empty or does not exist, <code>null</code> is returned.
* @throws InvalidTargetPathException if the target image folder is not in the current SDK.
*/
- private String getImageRelativePath(IAndroidTarget target, String abiType)
+ private String getImageRelativePath(@NonNull IAndroidTarget target,
+ @NonNull IdDisplay tag,
+ @NonNull String abiType)
throws InvalidTargetPathException {
- ISystemImage systemImage = target.getSystemImage(abiType);
+ ISystemImage systemImage = target.getSystemImage(tag, abiType);
if (systemImage == null) {
// ABI Type is unknown for target
return null;
@@ -996,7 +1112,7 @@
String imageFullPath = folder.getAbsolutePath();
// make this path relative to the SDK location
- String sdkLocation = mSdkManager.getLocation();
+ String sdkLocation = myLocalSdk.getPath();
if (!imageFullPath.startsWith(sdkLocation)) {
// this really really should not happen.
assert false;
@@ -1037,13 +1153,16 @@
* @param target The target where to find the skin.
* @param log the log object to receive action logs. Cannot be null.
*/
- public String getSkinRelativePath(String skinName, IAndroidTarget target, ILogger log) {
+ @Deprecated
+ private String getSkinRelativePath(@NonNull String skinName,
+ @NonNull IAndroidTarget target,
+ @NonNull ILogger log) {
if (log == null) {
throw new IllegalArgumentException("log cannot be null");
}
// first look to see if the skin is in the target
- File skin = getSkinPath(skinName, target);
+ File skin = getSkinFolder(skinName, target);
// skin really does not exist!
if (skin.exists() == false) {
@@ -1055,7 +1174,8 @@
String path = skin.getAbsolutePath();
// make this path relative to the SDK location
- String sdkLocation = mSdkManager.getLocation();
+
+ String sdkLocation = myLocalSdk.getPath();
if (path.startsWith(sdkLocation) == false) {
// this really really should not happen.
log.error(null, "Target location is not inside the SDK.");
@@ -1076,7 +1196,7 @@
* @param target The target where to find the skin.
* @return a {@link File} that may or may not actually exist.
*/
- public File getSkinPath(String skinName, IAndroidTarget target) {
+ private File getSkinFolder(@NonNull String skinName, @NonNull IAndroidTarget target) {
String path = target.getPath(IAndroidTarget.SKINS);
File skin = new File(path, skinName);
@@ -1100,9 +1220,9 @@
* @throws AndroidLocationException if there's a problem getting android root directory.
* @throws IOException if {@link File#getAbsolutePath()} fails.
*/
- private File createAvdIniFile(String name,
- File avdFolder,
- IAndroidTarget target,
+ private File createAvdIniFile(@NonNull String name,
+ @NonNull File avdFolder,
+ @NonNull IAndroidTarget target,
boolean removePrevious)
throws AndroidLocationException, IOException {
File iniFile = AvdInfo.getDefaultIniFile(this, name);
@@ -1131,7 +1251,7 @@
}
values.put(AVD_INFO_ABS_PATH, absPath);
values.put(AVD_INFO_TARGET, target.hashString());
- writeIniFile(iniFile, values);
+ writeIniFile(iniFile, values, true);
return iniFile;
}
@@ -1143,7 +1263,8 @@
* @throws AndroidLocationException if there's a problem getting android root directory.
* @throws IOException if {@link File#getAbsolutePath()} fails.
*/
- private File createAvdIniFile(AvdInfo info) throws AndroidLocationException, IOException {
+ private File createAvdIniFile(@NonNull AvdInfo info)
+ throws AndroidLocationException, IOException {
return createAvdIniFile(info.getName(),
new File(info.getDataFolderPath()),
info.getTarget(),
@@ -1165,7 +1286,7 @@
* @param log the log object to receive action logs. Cannot be null.
* @return True if the AVD was deleted with no error.
*/
- public boolean deleteAvd(AvdInfo avdInfo, ILogger log) {
+ public boolean deleteAvd(@NonNull AvdInfo avdInfo, @NonNull ILogger log) {
try {
boolean error = false;
@@ -1222,7 +1343,10 @@
* @return True if the move succeeded or there was nothing to do.
* If false, this method will have had already output error in the log.
*/
- public boolean moveAvd(AvdInfo avdInfo, String newName, String paramFolderPath, ILogger log) {
+ public boolean moveAvd(@NonNull AvdInfo avdInfo,
+ @Nullable String newName,
+ @Nullable String paramFolderPath,
+ @NonNull ILogger log) {
try {
if (paramFolderPath != null) {
@@ -1243,6 +1367,7 @@
paramFolderPath,
avdInfo.getTargetHash(),
avdInfo.getTarget(),
+ avdInfo.getTag(),
avdInfo.getAbiType(),
avdInfo.getProperties());
replaceAvd(avdInfo, info);
@@ -1269,6 +1394,7 @@
avdInfo.getDataFolderPath(),
avdInfo.getTargetHash(),
avdInfo.getTarget(),
+ avdInfo.getTag(),
avdInfo.getAbiType(),
avdInfo.getProperties());
replaceAvd(avdInfo, info);
@@ -1364,7 +1490,7 @@
if (avds != null) {
for (File avd : avds) {
AvdInfo info = parseAvdInfo(avd, log);
- if (info != null) {
+ if (info != null && !allList.contains(info)) {
allList.add(info);
}
}
@@ -1406,7 +1532,7 @@
Map<String, String> properties = null;
if (targetHash != null) {
- target = mSdkManager.getTargetFromHashString(targetHash);
+ target = myLocalSdk.getTargetFromHashString(targetHash);
}
// load the AVD properties.
@@ -1429,6 +1555,17 @@
name = matcher.group(1);
}
+ // get tag
+ IdDisplay tag = SystemImage.DEFAULT_TAG;
+ String tagId = properties == null ? null : properties.get(AVD_INI_TAG_ID);
+ if (tagId != null) {
+ String tagDisp = properties == null ? null : properties.get(AVD_INI_TAG_DISPLAY);
+ if (tagDisp == null || tagDisp.isEmpty()) {
+ tagDisp = LocalSysImgPkgInfo.tagIdToDisplay(tagId);
+ }
+ tag = new IdDisplay(tagId, tagDisp);
+ }
+
// get abi type
String abiType = properties == null ? null : properties.get(AVD_INI_ABI_TYPE);
// for the avds created previously without enhancement, i.e. They are created based
@@ -1442,13 +1579,13 @@
if (properties != null) {
String imageSysDir = properties.get(AVD_INI_IMAGES_1);
if (imageSysDir != null) {
- File f = new File(mSdkManager.getLocation() + File.separator + imageSysDir);
+ File f = new File(myLocalSdk.getLocation(), imageSysDir);
if (f.isDirectory() == false) {
validImageSysdir = false;
} else {
imageSysDir = properties.get(AVD_INI_IMAGES_2);
if (imageSysDir != null) {
- f = new File(mSdkManager.getLocation() + File.separator + imageSysDir);
+ f = new File(myLocalSdk.getLocation(), imageSysDir);
if (f.isDirectory() == false) {
validImageSysdir = false;
}
@@ -1459,14 +1596,36 @@
// Get the device status if this AVD is associated with a device
DeviceStatus deviceStatus = null;
+ boolean updateHashV2 = false;
if (properties != null) {
String deviceName = properties.get(AVD_INI_DEVICE_NAME);
String deviceMfctr = properties.get(AVD_INI_DEVICE_MANUFACTURER);
- String hash = properties.get(AVD_INI_DEVICE_HASH);
- if (deviceName != null && deviceMfctr != null && hash != null) {
- int deviceHash = Integer.parseInt(hash);
- DeviceManager devMan = DeviceManager.createInstance(mSdkManager.getLocation(), log);
- deviceStatus = devMan.getDeviceStatus(deviceName, deviceMfctr, deviceHash);
+
+ Device d = null;
+
+ if (deviceName != null && deviceMfctr != null) {
+ DeviceManager devMan = DeviceManager.createInstance(myLocalSdk.getLocation(), log);
+ d = devMan.getDevice(deviceName, deviceMfctr);
+ deviceStatus = d == null ? DeviceStatus.MISSING : DeviceStatus.EXISTS;
+
+ if (d != null) {
+ updateHashV2 = true;
+ String hashV2 = properties.get(AVD_INI_DEVICE_HASH_V2);
+ if (hashV2 != null) {
+ String newHashV2 = DeviceManager.hasHardwarePropHashChanged(d, hashV2);
+ if (newHashV2 == null) {
+ updateHashV2 = false;
+ } else {
+ properties.put(AVD_INI_DEVICE_HASH_V2, newHashV2);
+ }
+ }
+
+ String hashV1 = properties.get(AVD_INI_DEVICE_HASH_V1);
+ if (hashV1 != null) {
+ // will recompute a hash v2 and save it below
+ properties.remove(AVD_INI_DEVICE_HASH_V1);
+ }
+ }
}
}
@@ -1501,32 +1660,48 @@
avdPath,
targetHash,
target,
+ tag,
abiType,
properties,
status);
+ if (updateHashV2) {
+ try {
+ return updateDeviceChanged(info, log);
+ } catch (IOException ignore) {}
+ }
+
return info;
}
/**
* Writes a .ini file from a set of properties, using UTF-8 encoding.
+ * The keys are sorted.
* The file should be read back later by {@link #parseIniFile(IAbstractFile, ILogger)}.
*
* @param iniFile The file to generate.
- * @param values THe properties to place in the ini file.
+ * @param values The properties to place in the ini file.
+ * @param addEncoding When true, add a property {@link #AVD_INI_ENCODING} indicating the
+ * encoding used to write the file.
* @throws IOException if {@link FileWriter} fails to open, write or close the file.
*/
- private static void writeIniFile(File iniFile, Map<String, String> values)
+ private static void writeIniFile(File iniFile, Map<String, String> values, boolean addEncoding)
throws IOException {
Charset charset = Charsets.ISO_8859_1;
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(iniFile), charset);
- // Write down the charset used in case we want to use it later.
- writer.write(String.format("%1$s=%2$s\n", AVD_INI_ENCODING, charset.name()));
+ if (addEncoding) {
+ // Write down the charset used in case we want to use it later.
+ writer.write(String.format("%1$s=%2$s\n", AVD_INI_ENCODING, charset.name()));
+ }
- for (Entry<String, String> entry : values.entrySet()) {
- writer.write(String.format("%1$s=%2$s\n", entry.getKey(), entry.getValue()));
+ ArrayList<String> keys = new ArrayList<String>(values.keySet());
+ Collections.sort(keys);
+
+ for (String key : keys) {
+ String value = values.get(key);
+ writer.write(String.format("%1$s=%2$s\n", key, value));
}
writer.close();
}
@@ -1627,7 +1802,11 @@
e.getMessage());
}
} finally {
- Closeables.closeQuietly(reader);
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen.
+ }
}
return null;
@@ -1642,7 +1821,8 @@
* @param log the log object to receive action logs. Cannot be null.
* @return True if the sdcard could be created.
*/
- private boolean createSdCard(String toolLocation, String size, String location, ILogger log) {
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected boolean createSdCard(String toolLocation, String size, String location, ILogger log) {
try {
String[] command = new String[3];
command[0] = toolLocation;
@@ -1741,7 +1921,7 @@
* @param log the log object to receive action logs. Cannot be null.
* @throws IOException
*/
- public void updateAvd(AvdInfo avd, ILogger log) throws IOException {
+ public AvdInfo updateAvd(AvdInfo avd, ILogger log) throws IOException {
// get the properties. This is a unmodifiable Map.
Map<String, String> oldProperties = avd.getProperties();
@@ -1754,7 +1934,11 @@
AvdStatus status;
// create the path to the new system images.
- if (setImagePathProperties(avd.getTarget(), avd.getAbiType(), properties, log)) {
+ if (setImagePathProperties(avd.getTarget(),
+ avd.getTag(),
+ avd.getAbiType(),
+ properties,
+ log)) {
if (properties.containsKey(AVD_INI_IMAGES_1)) {
log.info("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_1,
properties.get(AVD_INI_IMAGES_1));
@@ -1772,16 +1956,17 @@
//FIXME: display paths to empty image folders?
status = AvdStatus.ERROR_IMAGE_DIR;
}
- updateAvd(avd, properties, status, log);
+
+ return updateAvd(avd, properties, status, log);
}
- public void updateAvd(AvdInfo avd,
+ public AvdInfo updateAvd(AvdInfo avd,
Map<String, String> newProperties,
AvdStatus status,
ILogger log) throws IOException {
// now write the config file
File configIniFile = new File(avd.getDataFolderPath(), CONFIG_INI);
- writeIniFile(configIniFile, newProperties);
+ writeIniFile(configIniFile, newProperties, true);
// finally create a new AvdInfo for this unbroken avd and add it to the list.
// instead of creating the AvdInfo object directly we reparse it, to detect other possible
@@ -1793,10 +1978,47 @@
avd.getDataFolderPath(),
avd.getTargetHash(),
avd.getTarget(),
+ avd.getTag(),
avd.getAbiType(),
newProperties);
replaceAvd(avd, newAvd);
+
+ return newAvd;
+ }
+
+ /**
+ * Updates the device-specific part of an AVD ini.
+ * @param avd the AVD to update.
+ * @param log the log object to receive action logs. Cannot be null.
+ * @return The new AVD on success.
+ * @throws IOException
+ */
+ public AvdInfo updateDeviceChanged(AvdInfo avd, ILogger log) throws IOException {
+
+ // Overwrite the properties derived from the device and nothing else
+ Map<String, String> properties = new HashMap<String, String>(avd.getProperties());
+
+ DeviceManager devMan = DeviceManager.createInstance(myLocalSdk.getLocation(), log);
+ List<Device> devices = devMan.getDevices(DeviceManager.ALL_DEVICES);
+ String name = properties.get(AvdManager.AVD_INI_DEVICE_NAME);
+ String manufacturer = properties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER);
+
+ if (properties != null && devices != null && name != null && manufacturer != null) {
+ for (Device d : devices) {
+ if (d.getId().equals(name) && d.getManufacturer().equals(manufacturer)) {
+ properties.putAll(DeviceManager.getHardwareProperties(d));
+ try {
+ return updateAvd(avd, properties, AvdStatus.OK, log);
+ } catch (IOException e) {
+ log.error(e, null);
+ }
+ }
+ }
+ } else {
+ log.error(null, "Base device information incomplete or missing.");
+ }
+ return null;
}
/**
@@ -1810,6 +2032,7 @@
* @return true if success, false if some path are missing.
*/
private boolean setImagePathProperties(IAndroidTarget target,
+ IdDisplay tag,
String abiType,
Map<String, String> properties,
ILogger log) {
@@ -1820,7 +2043,7 @@
String property = AVD_INI_IMAGES_1;
// First the image folders of the target itself
- String imagePath = getImageRelativePath(target, abiType);
+ String imagePath = getImageRelativePath(target, tag, abiType);
if (imagePath != null) {
properties.put(property, imagePath);
property = AVD_INI_IMAGES_2;
@@ -1829,7 +2052,7 @@
// If the target is an add-on we need to add the Platform image as a backup.
IAndroidTarget parent = target.getParent();
if (parent != null) {
- imagePath = getImageRelativePath(parent, abiType);
+ imagePath = getImageRelativePath(parent, tag, abiType);
if (imagePath != null) {
properties.put(property, imagePath);
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/avd/HardwareProperties.java b/sdklib/src/main/java/com/android/sdklib/internal/avd/HardwareProperties.java
index efe1f19..6cff91d 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/avd/HardwareProperties.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/avd/HardwareProperties.java
@@ -17,6 +17,7 @@
package com.android.sdklib.internal.avd;
import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
import java.io.BufferedReader;
import java.io.File;
@@ -85,7 +86,6 @@
public static final String BOOLEAN_YES = "yes";
public static final String BOOLEAN_NO = "no";
- public static final String[] BOOLEAN_VALUES = new String[] { BOOLEAN_YES, BOOLEAN_NO };
public static final Pattern DISKSIZE_PATTERN = Pattern.compile("\\d+[MK]B"); //$NON-NLS-1$
/** Represents the type of a hardware property value. */
@@ -197,7 +197,7 @@
BufferedReader reader = null;
try {
FileInputStream fis = new FileInputStream(file);
- reader = new BufferedReader(new InputStreamReader(fis));
+ reader = new BufferedReader(new InputStreamReader(fis, Charsets.UTF_8));
Map<String, HardwareProperty> map = new TreeMap<String, HardwareProperty>();
@@ -289,7 +289,28 @@
}
/**
- * Returns the index of <var>value</var> in {@link #BOOLEAN_VALUES}.
+ * Returns the boolean value matching the given index.
+ * This is the reverse of {@link #getBooleanValueIndex(String)}.
+ *
+ * @param index 0 or 1.
+ * @return {@link #BOOLEAN_YES} for 0 or {@link #BOOLEAN_NO} for 1.
+ * @throws IndexOutOfBoundsException if index is neither 0 nor 1.
+ */
+ public static String getBooleanValue(int index) {
+ if (index == 0) {
+ return BOOLEAN_YES;
+ } else if (index == 1) {
+ return BOOLEAN_NO;
+ }
+ throw new IndexOutOfBoundsException("HardwareProperty boolean index must 0 (true) or 1 (false) but was " + index);
+ }
+
+ /**
+ * Returns the index of a boolean <var>value</var>.
+ * This if the reverse of {@link #getBooleanValue(int)}.
+ *
+ * @param value Either {@link #BOOLEAN_YES} or {@link #BOOLEAN_NO}.
+ * @return 0 for {@link #BOOLEAN_YES}, 1 for {@link #BOOLEAN_NO} or -1 for anything else.
*/
public static int getBooleanValueIndex(String value) {
if (BOOLEAN_YES.equals(value)) {
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfigGenerator.java b/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfigGenerator.java
index 0183b7f..96fa976 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfigGenerator.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfigGenerator.java
@@ -16,6 +16,8 @@
package com.android.sdklib.internal.build;
+import com.android.SdkConstants;
+
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -120,7 +122,7 @@
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
- InputStream source = new ByteArrayInputStream(content.getBytes("UTF-8"));
+ InputStream source = new ByteArrayInputStream(content.getBytes(SdkConstants.UTF_8));
byte[] buffer = new byte[1024];
int count = 0;
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/build/KeystoreHelper.java b/sdklib/src/main/java/com/android/sdklib/internal/build/KeystoreHelper.java
index 1f3f6e3..d339ea5 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/build/KeystoreHelper.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/build/KeystoreHelper.java
@@ -19,9 +19,9 @@
import com.android.annotations.Nullable;
import com.android.sdklib.internal.build.DebugKeyProvider.IKeyGenOutput;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
-import com.android.sdklib.util.GrabProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.Wait;
+import com.android.utils.GrabProcessOutput;
+import com.android.utils.GrabProcessOutput.IProcessOutput;
+import com.android.utils.GrabProcessOutput.Wait;
import java.io.File;
import java.io.IOException;
@@ -33,7 +33,7 @@
/**
* A Helper to create new keystore/key.
- *
+ *
* @deprecated Use Android-Builder instead
*/
@Deprecated
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
index 2dd0a26..2faba6a 100644
--- 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,7 @@
package com.android.sdklib.internal.build;
+import com.android.SdkConstants;
import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
import sun.misc.BASE64Encoder;
@@ -342,7 +343,7 @@
MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM);
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
- true, "UTF-8");
+ true, SdkConstants.UTF_8);
// Digest of the entire manifest
mManifest.write(print);
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolWriter.java b/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolWriter.java
index 6146b02..3dfaa6f 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolWriter.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolWriter.java
@@ -23,7 +23,7 @@
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.Closer;
import com.google.common.io.Files;
import java.io.BufferedWriter;
@@ -79,9 +79,9 @@
file.mkdirs();
file = new File(file, SdkConstants.FN_RESOURCE_CLASS);
- BufferedWriter writer = null;
+ Closer closer = Closer.create();
try {
- writer = Files.newWriter(file, Charsets.UTF_8);
+ BufferedWriter writer = closer.register(Files.newWriter(file, Charsets.UTF_8));
writer.write("/* AUTO-GENERATED FILE. DO NOT MODIFY.\n");
writer.write(" *\n");
@@ -129,8 +129,10 @@
}
writer.write("}\n");
+ } catch (Throwable e) {
+ throw closer.rethrow(e);
} finally {
- Closeables.closeQuietly(writer);
+ closer.close();
}
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectCreator.java b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectCreator.java
index 7f8cacb..9588de2 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectCreator.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectCreator.java
@@ -592,12 +592,16 @@
keywords);
// Create the gradle wrapper files
- createDirs(projectFolder, "gradle/wrapper");
- installGradleWrapperFile("gradle/wrapper/gradle-wrapper.jar", projectFolder);
- installGradleWrapperFile("gradle/wrapper/gradle-wrapper.properties", projectFolder);
- installGradleWrapperFile("gradlew.bat", projectFolder);
- installGradleWrapperFile("gradlew", projectFolder);
- new File(projectFolder, "gradlew").setExecutable(true, false);
+ createDirs(projectFolder, SdkConstants.FD_GRADLE_WRAPPER);
+ installGradleWrapperFile(SdkConstants.FD_GRADLE_WRAPPER + File.separator
+ + SdkConstants.FN_GRADLE_WRAPPER_JAR,
+ projectFolder);
+ installGradleWrapperFile(SdkConstants.FD_GRADLE_WRAPPER + File.separator
+ + SdkConstants.FN_GRADLE_WRAPPER_PROPERTIES,
+ projectFolder);
+ installGradleWrapperFile(SdkConstants.FN_GRADLE_WRAPPER_WIN, projectFolder);
+ installGradleWrapperFile(SdkConstants.FN_GRADLE_WRAPPER_UNIX, projectFolder);
+ new File(projectFolder, SdkConstants.FN_GRADLE_WRAPPER_UNIX).setExecutable(true, false);
} catch (Exception e) {
mLog.error(e, null);
}
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 e578747..c685e1d 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
@@ -455,8 +455,10 @@
public static Map<String, String> parsePropertyFile(
@NonNull IAbstractFile propFile,
@Nullable ILogger log) {
+ InputStream is = null;
try {
- return parsePropertyStream(propFile.getContents(),
+ is = propFile.getContents();
+ return parsePropertyStream(is,
propFile.getOsLocation(),
log);
} catch (StreamException e) {
@@ -465,8 +467,15 @@
propFile.getOsLocation(),
e.getMessage());
}
+ } finally {
+ try {
+ Closeables.close(is, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
+
return null;
}
@@ -497,6 +506,7 @@
@Nullable ILogger log) {
BufferedReader reader = null;
try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
reader = new BufferedReader(
new InputStreamReader(propStream, SdkConstants.INI_CHARSET));
@@ -504,7 +514,7 @@
Map<String, String> map = new HashMap<String, String>();
while ((line = reader.readLine()) != null) {
line = line.trim();
- if (line.length() > 0 && line.charAt(0) != '#') {
+ if (!line.isEmpty() && line.charAt(0) != '#') {
Matcher m = PATTERN_PROP.matcher(line);
if (m.matches()) {
@@ -532,8 +542,16 @@
e.getMessage());
}
} finally {
- Closeables.closeQuietly(reader);
- Closeables.closeQuietly(propStream);
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(propStream, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
return null;
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
index 4fd1eeb..4fac8f9 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
@@ -26,6 +26,7 @@
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
@@ -136,67 +137,90 @@
OutputStreamWriter writer = new OutputStreamWriter(baos, SdkConstants.INI_CHARSET);
if (toSave.exists()) {
- BufferedReader reader = new BufferedReader(new InputStreamReader(toSave.getContents(),
- SdkConstants.INI_CHARSET));
+ InputStream contentStream = toSave.getContents();
+ InputStreamReader isr = null;
+ BufferedReader reader = null;
- // since we're reading the existing file and replacing values with new ones, or skipping
- // removed values, we need to record what properties have been visited, so that
- // we can figure later what new properties need to be added at the end of the file.
- Set<String> visitedProps = new HashSet<String>();
+ try {
+ contentStream = toSave.getContents();
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ isr = new InputStreamReader(contentStream, SdkConstants.INI_CHARSET);
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ reader = new BufferedReader(isr);
- String line = null;
- while ((line = reader.readLine()) != null) {
- // check if this is a line containing a property.
- if (line.length() > 0 && line.charAt(0) != '#') {
+ // since we're reading the existing file and replacing values with new ones, or skipping
+ // removed values, we need to record what properties have been visited, so that
+ // we can figure later what new properties need to be added at the end of the file.
+ Set<String> visitedProps = new HashSet<String>();
- Matcher m = PATTERN_PROP.matcher(line);
- if (m.matches()) {
- String key = m.group(1);
- String value = m.group(2);
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ // check if this is a line containing a property.
+ if (!line.isEmpty() && line.charAt(0) != '#') {
- // record the prop
- visitedProps.add(key);
+ Matcher m = PATTERN_PROP.matcher(line);
+ if (m.matches()) {
+ String key = m.group(1);
+ String value = m.group(2);
- // check if this property must be removed.
- if (mType.isRemovedProperty(key)) {
- value = null;
- } else if (mProperties.containsKey(key)) { // if the property still exists.
- // put the new value.
- value = mProperties.get(key);
- } else {
- // property doesn't exist. Check if it's a known property.
- // if it's a known one, we'll remove it, otherwise, leave it untouched.
- if (mType.isKnownProperty(key)) {
+ // record the prop
+ visitedProps.add(key);
+
+ // check if this property must be removed.
+ if (mType.isRemovedProperty(key)) {
value = null;
+ } else if (mProperties.containsKey(key)) { // if the property still exists.
+ // put the new value.
+ value = mProperties.get(key);
+ } else {
+ // property doesn't exist. Check if it's a known property.
+ // if it's a known one, we'll remove it, otherwise, leave it untouched.
+ if (mType.isKnownProperty(key)) {
+ value = null;
+ }
}
- }
- // if the value is still valid, write it down.
+ // if the value is still valid, write it down.
+ if (value != null) {
+ writeValue(writer, key, value, false /*addComment*/);
+ }
+ } else {
+ // the line was wrong, let's just ignore it so that it's removed from the
+ // file.
+ }
+ } else {
+ // non-property line: just write the line in the output as-is.
+ writer.append(line).append('\n');
+ }
+ }
+
+ // now add the new properties.
+ for (Entry<String, String> entry : mProperties.entrySet()) {
+ if (!visitedProps.contains(entry.getKey())) {
+ String value = entry.getValue();
if (value != null) {
- writeValue(writer, key, value, false /*addComment*/);
+ writeValue(writer, entry.getKey(), value, true /*addComment*/);
}
- } else {
- // the line was wrong, let's just ignore it so that it's removed from the
- // file.
- }
- } else {
- // non-property line: just write the line in the output as-is.
- writer.append(line).append('\n');
- }
- }
-
- // now add the new properties.
- for (Entry<String, String> entry : mProperties.entrySet()) {
- if (visitedProps.contains(entry.getKey()) == false) {
- String value = entry.getValue();
- if (value != null) {
- writeValue(writer, entry.getKey(), value, true /*addComment*/);
}
}
+ } finally {
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(isr, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(contentStream, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
}
- Closeables.closeQuietly(reader);
-
} else {
// new file, just write it all
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/AddonsListFetcher.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/AddonsListFetcher.java
index ac4309e..ab9cea4 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/AddonsListFetcher.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/AddonsListFetcher.java
@@ -18,8 +18,8 @@
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.io.NonClosingInputStream;
-import com.android.sdklib.io.NonClosingInputStream.CloseBehavior;
+import com.android.io.NonClosingInputStream;
+import com.android.io.NonClosingInputStream.CloseBehavior;
import com.android.sdklib.repository.SdkAddonsListConstants;
import com.android.sdklib.repository.SdkRepoConstants;
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java
index b37a1e8..46ad78a 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java
@@ -23,6 +23,8 @@
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.io.IFileOp;
import com.android.utils.Pair;
import org.apache.http.Header;
@@ -33,9 +35,7 @@
import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -131,8 +131,9 @@
HttpHeaders.DATE
};
- private final Strategy mStrategy;
+ private final IFileOp mFileOp;
private final File mCacheRoot;
+ private final Strategy mStrategy;
public enum Strategy {
/**
@@ -162,7 +163,13 @@
}
/** Creates a default instance of the URL cache */
- public DownloadCache(Strategy strategy) {
+ public DownloadCache(@NonNull Strategy strategy) {
+ this(new FileOp(), strategy);
+ }
+
+ /** Creates a default instance of the URL cache */
+ public DownloadCache(@NonNull IFileOp fileOp, @NonNull Strategy strategy) {
+ mFileOp = fileOp;
mCacheRoot = initCacheRoot();
// If this is defined in the environment, never use the cache. Useful for testing.
@@ -173,10 +180,12 @@
mStrategy = mCacheRoot == null ? Strategy.DIRECT : strategy;
}
+ @NonNull
public Strategy getStrategy() {
return mStrategy;
}
+ @Nullable
public File getCacheRoot() {
return mCacheRoot;
}
@@ -190,15 +199,12 @@
long size = 0;
if (mCacheRoot != null) {
- File[] files = mCacheRoot.listFiles();
- if (files != null) {
- for (File f : files) {
- if (f.isFile()) {
- String name = f.getName();
- if (name.startsWith(BIN_FILE_PREFIX) ||
- name.startsWith(INFO_FILE_PREFIX)) {
- size += f.length();
- }
+ File[] files = mFileOp.listFiles(mCacheRoot);
+ for (File f : files) {
+ if (mFileOp.isFile(f)) {
+ String name = f.getName();
+ if (name.startsWith(BIN_FILE_PREFIX) || name.startsWith(INFO_FILE_PREFIX)) {
+ size += f.length();
}
}
}
@@ -212,15 +218,12 @@
*/
public void clearCache() {
if (mCacheRoot != null) {
- File[] files = mCacheRoot.listFiles();
- if (files != null) {
- for (File f : files) {
- if (f.isFile()) {
- String name = f.getName();
- if (name.startsWith(BIN_FILE_PREFIX) ||
- name.startsWith(INFO_FILE_PREFIX)) {
- f.delete();
- }
+ File[] files = mFileOp.listFiles(mCacheRoot);
+ for (File f : files) {
+ if (mFileOp.isFile(f)) {
+ String name = f.getName();
+ if (name.startsWith(BIN_FILE_PREFIX) || name.startsWith(INFO_FILE_PREFIX)) {
+ mFileOp.delete(f);
}
}
}
@@ -235,16 +238,14 @@
String prefix1 = BIN_FILE_PREFIX + REV_FILE_PREFIX;
String prefix2 = INFO_FILE_PREFIX + REV_FILE_PREFIX;
if (mCacheRoot != null) {
- File[] files = mCacheRoot.listFiles();
- if (files != null) {
- for (File f : files) {
- if (f.isFile()) {
- String name = f.getName();
- if (name.startsWith(BIN_FILE_PREFIX) ||
- name.startsWith(INFO_FILE_PREFIX)) {
- if (!name.startsWith(prefix1) && !name.startsWith(prefix2)) {
- f.delete();
- }
+ File[] files = mFileOp.listFiles(mCacheRoot);
+ for (File f : files) {
+ if (mFileOp.isFile(f)) {
+ String name = f.getName();
+ if (name.startsWith(BIN_FILE_PREFIX) ||
+ name.startsWith(INFO_FILE_PREFIX)) {
+ if (!name.startsWith(prefix1) && !name.startsWith(prefix2)) {
+ mFileOp.delete(f);
}
}
}
@@ -261,12 +262,13 @@
* or null in case of error in which case the cache will be disabled.
*/
@VisibleForTesting(visibility=Visibility.PRIVATE)
+ @Nullable
protected File initCacheRoot() {
try {
File root = new File(AndroidLocation.getFolder());
root = new File(root, SdkConstants.FD_CACHE);
- if (!root.exists()) {
- root.mkdirs();
+ if (!mFileOp.exists(root)) {
+ mFileOp.mkdirs(root);
}
return root;
} catch (AndroidLocationException e) {
@@ -276,6 +278,23 @@
}
/**
+ * Calls {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
+ * to actually perform a download.
+ * <p/>
+ * Isolated so that it can be overridden by unit tests.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ @NonNull
+ protected Pair<InputStream, HttpResponse> openUrl(
+ @NonNull String url,
+ boolean needsMarkResetSupport,
+ @NonNull ITaskMonitor monitor,
+ @Nullable Header[] headers) throws IOException, CanceledByUserException {
+ return UrlOpener.openUrl(url, needsMarkResetSupport, monitor, headers);
+ }
+
+
+ /**
* Does a direct download of the given URL using {@link UrlOpener}.
* This does not check the download cache and does not attempt to cache the file.
* Instead the HttpClient library returns a progressive download stream.
@@ -301,6 +320,7 @@
* @throws CanceledByUserException Exception thrown if the user cancels the
* authentication dialog.
*/
+ @NonNull
public Pair<InputStream, HttpResponse> openDirectUrl(
@NonNull String urlString,
@Nullable Header[] headers,
@@ -309,7 +329,7 @@
if (DEBUG) {
System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$
}
- return UrlOpener.openUrl(
+ return openUrl(
urlString,
false /*needsMarkResetSupport*/,
monitor,
@@ -344,6 +364,7 @@
* authentication dialog.
* @see #openDirectUrl(String, Header[], ITaskMonitor)
*/
+ @NonNull
public Pair<InputStream, Integer> openDirectUrl(
@NonNull String urlString,
@NonNull ITaskMonitor monitor)
@@ -351,7 +372,7 @@
if (DEBUG) {
System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$
}
- Pair<InputStream, HttpResponse> result = UrlOpener.openUrl(
+ Pair<InputStream, HttpResponse> result = openUrl(
urlString,
false /*needsMarkResetSupport*/,
monitor,
@@ -382,11 +403,12 @@
* @throws CanceledByUserException Exception thrown if the user cancels the
* authentication dialog.
*/
- public InputStream openCachedUrl(String urlString, ITaskMonitor monitor)
+ @NonNull
+ public InputStream openCachedUrl(@NonNull String urlString, @NonNull ITaskMonitor monitor)
throws IOException, CanceledByUserException {
// Don't cache in direct mode.
if (mStrategy == Strategy.DIRECT) {
- Pair<InputStream, HttpResponse> result = UrlOpener.openUrl(
+ Pair<InputStream, HttpResponse> result = openUrl(
urlString,
true /*needsMarkResetSupport*/,
monitor,
@@ -397,13 +419,13 @@
File cached = new File(mCacheRoot, getCacheFilename(urlString));
File info = new File(mCacheRoot, getInfoFilename(cached.getName()));
- boolean useCached = cached.exists();
+ boolean useCached = mFileOp.exists(cached);
if (useCached && mStrategy == Strategy.FRESH_CACHE) {
// Check whether the file should be served from the cache or
// refreshed first.
- long cacheModifiedMs = cached.lastModified(); /* last mod time in epoch/millis */
+ long cacheModifiedMs = mFileOp.lastModified(cached); /* last mod time in epoch/millis */
boolean checkCache = true;
Properties props = readInfo(info);
@@ -440,7 +462,7 @@
long length = Long.parseLong(props.getProperty(HttpHeaders.CONTENT_LENGTH,
"-1")); //$NON-NLS-1$
if (length >= 0) {
- useCached = length == cached.length();
+ useCached = length == mFileOp.length(cached);
if (!useCached && DEBUG) {
System.out.println(String.format(
@@ -569,8 +591,8 @@
// If we're not using the cache, try to remove the cache and download again.
try {
- cached.delete();
- info.delete();
+ mFileOp.delete(cached);
+ mFileOp.delete(info);
} catch (SecurityException ignore) {}
return downloadAndCache(urlString, monitor, cached, info,
@@ -581,7 +603,8 @@
// --------------
- private InputStream readCachedFile(File cached) throws IOException {
+ @Nullable
+ private InputStream readCachedFile(@NonNull File cached) throws IOException {
InputStream is = null;
int inc = 65536;
@@ -595,7 +618,7 @@
byte[] result = new byte[(int) (len > 0 ? len : inc)];
try {
- is = new FileInputStream(cached);
+ is = mFileOp.newFileInputStream(cached);
int n;
while ((n = is.read(result, curr, result.length - curr)) != -1) {
@@ -633,11 +656,12 @@
* and locally cached file, or null if nothing was downloaded
* (including if it was a 304 Not-Modified status code.)
*/
+ @Nullable
private InputStream downloadAndCache(
- String urlString,
- ITaskMonitor monitor,
- File cached,
- File info,
+ @NonNull String urlString,
+ @NonNull ITaskMonitor monitor,
+ @NonNull File cached,
+ @NonNull File info,
@Nullable Header[] headers,
@Nullable AtomicInteger outStatusCode)
throws FileNotFoundException, IOException, CanceledByUserException {
@@ -650,7 +674,7 @@
try {
Pair<InputStream, HttpResponse> r =
- UrlOpener.openUrl(urlString, true /*needsMarkResetSupport*/, monitor, headers);
+ openUrl(urlString, true /*needsMarkResetSupport*/, monitor, headers);
is = r.getFirst();
HttpResponse response = r.getSecond();
@@ -676,7 +700,7 @@
return null;
}
- os = new FileOutputStream(cached);
+ os = mFileOp.newFileOutputStream(cached);
int n;
while ((n = is.read(result, curr, result.length - curr)) != -1) {
@@ -731,8 +755,8 @@
// was an issue and we don't want to keep that file. We'll try to
// delete it.
try {
- cached.delete();
- info.delete();
+ mFileOp.delete(cached);
+ mFileOp.delete(info);
} catch (SecurityException ignore) {}
}
}
@@ -741,7 +765,10 @@
/**
* Saves part of the HTTP Response to the info file.
*/
- private void saveInfo(String urlString, HttpResponse response, File info) throws IOException {
+ private void saveInfo(
+ @NonNull String urlString,
+ @NonNull HttpResponse response,
+ @NonNull File info) throws IOException {
Properties props = new Properties();
// we don't need the status code & URL right now.
@@ -757,38 +784,17 @@
}
}
- FileOutputStream os = null;
- try {
- os = new FileOutputStream(info);
- props.store(os, "## Meta data for SDK Manager cache. Do not modify."); //$NON-NLS-1$
- } finally {
- if (os != null) {
- os.close();
- }
- }
+ mFileOp.saveProperties(info, props, "## Meta data for SDK Manager cache. Do not modify."); //$NON-NLS-1$
}
/**
* Reads the info properties file.
* @return The properties found or null if there's no file or it can't be read.
*/
- private Properties readInfo(File info) {
- if (info.exists()) {
- Properties props = new Properties();
-
- InputStream is = null;
- try {
- is = new FileInputStream(info);
- props.load(is);
- return props;
- } catch (IOException ignore) {
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException ignore) {}
- }
- }
+ @Nullable
+ private Properties readInfo(@NonNull File info) {
+ if (mFileOp.exists(info)) {
+ return mFileOp.loadProperties(info);
}
return null;
}
@@ -802,8 +808,14 @@
* @param urlString The download URL.
* @return A leaf filename for the cached download file.
*/
- private String getCacheFilename(String urlString) {
- String hash = String.format("%08x", urlString.hashCode());
+ @NonNull
+ private String getCacheFilename(@NonNull String urlString) {
+
+ int code = 0;
+ for (int i = 0, j = urlString.length(); i < j; i++) {
+ code = code * 31 + urlString.charAt(i);
+ }
+ String hash = String.format("%08x", code);
String leaf = urlString.toLowerCase(Locale.US);
if (leaf.length() >= 2) {
@@ -824,7 +836,8 @@
return prefix + leaf;
}
- private String getInfoFilename(String cacheFilename) {
+ @NonNull
+ private String getInfoFilename(@NonNull String cacheFilename) {
return cacheFilename.replaceFirst(BIN_FILE_PREFIX, INFO_FILE_PREFIX);
}
}
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 ff87bd7..a8ace5d 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
@@ -25,8 +25,6 @@
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;
import com.android.sdklib.internal.repository.packages.BuildToolPackage;
import com.android.sdklib.internal.repository.packages.DocPackage;
@@ -38,10 +36,12 @@
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.sdklib.io.FileOp;
+import com.android.sdklib.repository.AddonManifestIniProps;
+import com.android.sdklib.repository.descriptors.PkgType;
import com.android.utils.ILogger;
import com.android.utils.Pair;
+import com.google.common.collect.Lists;
import java.io.File;
import java.io.FileInputStream;
@@ -49,6 +49,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -60,33 +61,33 @@
private Package[] mPackages;
/** Parse all SDK folders. */
- public static final int PARSE_ALL = LocalSdk.PKG_ALL;
+ public static final int PARSE_ALL = PkgType.PKG_ALL_INT;
/** Parse the SDK/tools folder. */
- public static final int PARSE_TOOLS = LocalSdk.PKG_TOOLS;
+ public static final int PARSE_TOOLS = PkgType.PKG_TOOLS.getIntValue();
/** Parse the SDK/platform-tools folder */
- public static final int PARSE_PLATFORM_TOOLS = LocalSdk.PKG_PLATFORM_TOOLS;
+ public static final int PARSE_PLATFORM_TOOLS = PkgType.PKG_PLATFORM_TOOLS.getIntValue();
/** Parse the SDK/docs folder. */
- public static final int PARSE_DOCS = LocalSdk.PKG_DOCS;
+ public static final int PARSE_DOCS = PkgType.PKG_DOC.getIntValue();
/**
* 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 = LocalSdk.PKG_PLATFORMS;
+ public static final int PARSE_PLATFORMS = PkgType.PKG_PLATFORM.getIntValue();
/**
* 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 = LocalSdk.PKG_ADDONS;
+ public static final int PARSE_ADDONS = PkgType.PKG_ADDON.getIntValue();
/** Parse the SDK/samples folder.
* Note: this will not detect samples located in the SDK/extras packages. */
- public static final int PARSE_SAMPLES = LocalSdk.PKG_SAMPLES;
+ public static final int PARSE_SAMPLES = PkgType.PKG_SAMPLE.getIntValue();
/** Parse the SDK/sources folder. */
- public static final int PARSE_SOURCES = LocalSdk.PKG_SOURCES;
+ public static final int PARSE_SOURCES = PkgType.PKG_SOURCE.getIntValue();
/** Parse the SDK/extras folder. */
- public static final int PARSE_EXTRAS = LocalSdk.PKG_EXTRAS;
+ public static final int PARSE_EXTRAS = PkgType.PKG_EXTRA.getIntValue();
/** Parse the SDK/build-tools folder. */
- public static final int PARSE_BUILD_TOOLS = LocalSdk.PKG_BUILD_TOOLS;
+ public static final int PARSE_BUILD_TOOLS = PkgType.PKG_BUILD_TOOLS.getIntValue();
public LocalSdkParser() {
// pass
@@ -235,7 +236,7 @@
new File(siDir, SdkConstants.FN_SOURCE_PROP));
Package pkg2 = new SystemImagePackage(
target.getVersion(),
- 0 /*rev*/, // this will use the one from siProps
+ 0 /*rev*/, // use the one from siProps
systemImage.getAbiType(),
siProps,
siDir.getAbsolutePath());
@@ -299,12 +300,7 @@
ILogger log) {
File root = new File(sdkManager.getLocation(), SdkConstants.FD_EXTRAS);
- if (!root.isDirectory()) {
- // This should not happen. It makes listFiles() return null so let's avoid it.
- return;
- }
-
- for (File vendor : root.listFiles()) {
+ for (File vendor : listFilesNonNull(root)) {
if (vendor.isDirectory()) {
scanExtrasDirectory(vendor.getAbsolutePath(), visited, packages, log);
}
@@ -321,12 +317,7 @@
ILogger log) {
File root = new File(extrasRoot);
- if (!root.isDirectory()) {
- // This should not happen. It makes listFiles() return null so let's avoid it.
- return;
- }
-
- for (File dir : root.listFiles()) {
+ for (File dir : listFilesNonNull(root)) {
if (dir.isDirectory() && !visited.contains(dir)) {
Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
if (props != null) {
@@ -340,8 +331,6 @@
null, //license
null, //description
null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
dir.getPath() //archiveOsPath
);
@@ -368,12 +357,7 @@
File root = new File(sdkManager.getLocation());
root = new File(root, SdkConstants.FD_SAMPLES);
- if (!root.isDirectory()) {
- // It makes listFiles() return null so let's avoid it.
- return;
- }
-
- for (File dir : root.listFiles()) {
+ for (File dir : listFilesNonNull(root)) {
if (dir.isDirectory() && !visited.contains(dir)) {
Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
if (props != null) {
@@ -402,12 +386,7 @@
ILogger log) {
File addons = new File(new File(sdkManager.getLocation()), SdkConstants.FD_ADDONS);
- File[] files = addons.listFiles();
- if (files == null) {
- return;
- }
-
- for (File dir : files) {
+ for (File dir : listFilesNonNull(addons)) {
if (dir.isDirectory() && !visited.contains(dir)) {
Pair<Map<String, String>, String> infos =
parseAddonProperties(dir, sdkManager.getTargets(), log);
@@ -471,27 +450,26 @@
// look for some specific values in the map.
// we require name, vendor, and api
- String name = propertyMap.get(LocalAddonPkgInfo.ADDON_NAME);
+ String name = propertyMap.get(AddonManifestIniProps.ADDON_NAME);
if (name == null) {
error = String.format("'%1$s' is missing from %2$s.",
- LocalAddonPkgInfo.ADDON_NAME,
+ AddonManifestIniProps.ADDON_NAME,
SdkConstants.FN_MANIFEST_INI);
break;
}
- String vendor = propertyMap.get(LocalAddonPkgInfo.ADDON_VENDOR);
+ String vendor = propertyMap.get(AddonManifestIniProps.ADDON_VENDOR);
if (vendor == null) {
error = String.format("'%1$s' is missing from %2$s.",
- LocalAddonPkgInfo.ADDON_VENDOR,
+ AddonManifestIniProps.ADDON_VENDOR,
SdkConstants.FN_MANIFEST_INI);
break;
}
- String api = propertyMap.get(LocalAddonPkgInfo.ADDON_API);
- PlatformTarget plat = null;
+ String api = propertyMap.get(AddonManifestIniProps.ADDON_API);
if (api == null) {
error = String.format("'%1$s' is missing from %2$s.",
- LocalAddonPkgInfo.ADDON_API,
+ AddonManifestIniProps.ADDON_API,
SdkConstants.FN_MANIFEST_INI);
break;
}
@@ -513,9 +491,9 @@
}
// get the add-on revision
- String revision = propertyMap.get(LocalAddonPkgInfo.ADDON_REVISION);
+ String revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION);
if (revision == null) {
- revision = propertyMap.get(LocalAddonPkgInfo.ADDON_REVISION_OLD);
+ revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION_OLD);
}
if (revision != null) {
try {
@@ -524,7 +502,7 @@
// 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,
+ AddonManifestIniProps.ADDON_REVISION,
SdkConstants.FN_BUILD_PROP);
break;
}
@@ -547,39 +525,46 @@
ILogger log) {
File siRoot = new File(sdkManager.getLocation(), SdkConstants.FD_SYSTEM_IMAGES);
- File[] files = siRoot.listFiles();
- if (files == null) {
- return;
- }
-
// The system-images folder contains a list of platform folders.
- for (File platformDir : files) {
+ for (File platformDir : listFilesNonNull(siRoot)) {
if (platformDir.isDirectory() && !visited.contains(platformDir)) {
visited.add(platformDir);
// In the platform directory, we expect a list of abi folders
- File[] platformFiles = platformDir.listFiles();
- if (platformFiles != null) {
- for (File abiDir : platformFiles) {
- if (abiDir.isDirectory() && !visited.contains(abiDir)) {
- visited.add(abiDir);
+ // or a list of tag/abi folders. Basically parse any folder that has
+ // a source.prop file within 2 levels.
+ List<File> propFiles = Lists.newArrayList();
- // Ignore empty directories
- File[] abiFiles = abiDir.listFiles();
- if (abiFiles != null && abiFiles.length > 0) {
- Properties props =
- parseProperties(new File(abiDir, SdkConstants.FN_SOURCE_PROP));
-
- try {
- Package pkg = SystemImagePackage.createBroken(abiDir, props);
- packages.add(pkg);
- } catch (Exception e) {
- log.error(e, null);
+ for (File dir1 : listFilesNonNull(platformDir)) {
+ if (dir1.isDirectory() && !visited.contains(dir1)) {
+ visited.add(dir1);
+ File prop1 = new File(dir1, SdkConstants.FN_SOURCE_PROP);
+ if (prop1.isFile()) {
+ propFiles.add(prop1);
+ } else {
+ for (File dir2 : listFilesNonNull(dir1)) {
+ if (dir2.isDirectory() && !visited.contains(dir2)) {
+ visited.add(dir2);
+ File prop2 = new File(dir2, SdkConstants.FN_SOURCE_PROP);
+ if (prop2.isFile()) {
+ propFiles.add(prop2);
+ }
}
}
}
}
}
+
+ for (File propFile : propFiles) {
+ Properties props = parseProperties(propFile);
+ try {
+ Package pkg = SystemImagePackage.createBroken(propFile.getParentFile(),
+ props);
+ packages.add(pkg);
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ }
}
}
}
@@ -593,13 +578,8 @@
ILogger log) {
File srcRoot = new File(sdkManager.getLocation(), SdkConstants.FD_PKG_SOURCES);
- File[] subDirs = srcRoot.listFiles();
- if (subDirs == null) {
- return;
- }
-
// The sources folder contains a list of platform folders.
- for (File platformDir : subDirs) {
+ for (File platformDir : listFilesNonNull(srcRoot)) {
if (platformDir.isDirectory() && !visited.contains(platformDir)) {
visited.add(platformDir);
@@ -634,16 +614,13 @@
boolean hasAndroid = false;
String android1 = SdkConstants.androidCmdName().replace(".bat", ".exe");
String android2 = android1.indexOf('.') == -1 ? null : android1.replace(".exe", ".bat");
- File[] files = toolFolder.listFiles();
- if (files != null) {
- 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;
- }
+ for (File file : listFilesNonNull(toolFolder)) {
+ String name = file.getName();
+ if (SdkConstants.FN_EMULATOR.equals(name)) {
+ hasEmulator = true;
+ }
+ if (android1.equals(name) || (android2 != null && android2.equals(name))) {
+ hasAndroid = true;
}
}
@@ -660,8 +637,6 @@
null, //license
"Tools", //description
null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
toolFolder.getPath() //archiveOsPath
);
@@ -699,8 +674,6 @@
null, //license
"Platform Tools", //description
null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
platformToolsFolder.getPath() //archiveOsPath
);
@@ -721,13 +694,8 @@
ILogger log) {
File buildToolRoot = new File(sdkManager.getLocation(), SdkConstants.FD_BUILD_TOOLS);
- File[] subDirs = buildToolRoot.listFiles();
- if (subDirs == null) {
- return;
- }
-
// The build-tool root folder contains a list of revisioned folders.
- for (File buildToolDir : subDirs) {
+ for (File buildToolDir : listFilesNonNull(buildToolRoot)) {
if (buildToolDir.isDirectory() && !visited.contains(buildToolDir)) {
visited.add(buildToolDir);
@@ -769,8 +737,6 @@
null, //license
null, //description
null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
docFolder.getPath() //archiveOsPath
);
@@ -814,4 +780,20 @@
}
return null;
}
+
+ /**
+ * Helper method that calls {@link File#listFiles()} and returns
+ * a non-null empty list if the input is not a directory or has
+ * no files.
+ */
+ @NonNull
+ private static File[] listFilesNonNull(@NonNull File dir) {
+ if (dir.isDirectory()) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ return files;
+ }
+ }
+ return FileOp.EMPTY_FILE_ARRAY;
+ }
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/SdkStats.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/SdkStats.java
index e07613d..6a19750 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/SdkStats.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/SdkStats.java
@@ -18,8 +18,8 @@
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.io.NonClosingInputStream;
-import com.android.sdklib.io.NonClosingInputStream.CloseBehavior;
+import com.android.io.NonClosingInputStream;
+import com.android.io.NonClosingInputStream.CloseBehavior;
import com.android.sdklib.repository.SdkStatsConstants;
import com.android.utils.SparseArray;
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java
new file mode 100755
index 0000000..e2d261f
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archives;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.repository.NoPreviewRevision;
+
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ArchFilter {
+
+ private static final String PROP_HOST_OS = "Archive.HostOs"; //$NON-NLS-1$
+ private static final String PROP_HOST_BITS = "Archive.HostBits"; //$NON-NLS-1$
+ private static final String PROP_JVM_BITS = "Archive.JvmBits"; //$NON-NLS-1$
+ private static final String PROP_MIN_JVM_VERSION = "Archive.MinJvmVers"; //$NON-NLS-1$
+
+ /**
+ * The legacy property used to serialize {@link LegacyOs} in source.properties files.
+ * <p/>
+ * Replaced by {@code ArchFilter.PROP_HOST_OS}.
+ */
+ public static final String LEGACY_PROP_OS = "Archive.Os"; //$NON-NLS-1$
+
+ /**
+ * The legacy property used to serialize {@link LegacyArch} in source.properties files.
+ * <p/>
+ * Replaced by {@code ArchFilter.PROP_HOST_BITS} and {@code ArchFilter.PROP_JVM_BITS}.
+ */
+ public static final String LEGACY_PROP_ARCH = "Archive.Arch"; //$NON-NLS-1$
+
+ private final HostOs mHostOs;
+ private final BitSize mHostBits;
+ private final BitSize mJvmBits;
+ private final NoPreviewRevision mMinJvmVersion;
+
+ /**
+ * Creates a new {@link ArchFilter} with the specified filter attributes.
+ * <p/>
+ * This filters represents the attributes requires for a package's {@link Archive} to
+ * be installable on the current architecture. Not all fields are required -- those that
+ * are not specified imply there is no limitation on that particular attribute.
+ *
+ *
+ * @param hostOs The host OS or null if there's no limitation for this package.
+ * @param hostBits The host bit size or null if there's no limitation for this package.
+ * @param jvmBits The JVM bit size or null if there's no limitation for this package.
+ * @param minJvmVersion The minimal JVM version required by this package
+ * or null if there's no limitation for this package.
+ */
+ public ArchFilter(@Nullable HostOs hostOs,
+ @Nullable BitSize hostBits,
+ @Nullable BitSize jvmBits,
+ @Nullable NoPreviewRevision minJvmVersion) {
+ mHostOs = hostOs;
+ mHostBits = hostBits;
+ mJvmBits = jvmBits;
+ mMinJvmVersion = minJvmVersion;
+ }
+
+ /**
+ * Creates an {@link ArchFilter} using properties previously saved in a {@link Properties}
+ * object, typically by the {@link ArchFilter#saveProperties(Properties)} method.
+ * <p/>
+ * Missing properties are set to null and will not filter.
+ *
+ * @param props A properties object previously filled by {@link #saveProperties(Properties)}.
+ * If null, a default empty {@link ArchFilter} is created.
+ */
+ public ArchFilter(@Nullable Properties props) {
+ HostOs hostOs = null;
+ BitSize hostBits = null;
+ BitSize jvmBits = null;
+ NoPreviewRevision minJvmVers = null;
+
+ if (props != null) {
+ hostOs = HostOs .fromXmlName(props.getProperty(PROP_HOST_OS));
+ hostBits = BitSize.fromXmlName(props.getProperty(PROP_HOST_BITS));
+ jvmBits = BitSize.fromXmlName(props.getProperty(PROP_JVM_BITS));
+
+ try {
+ minJvmVers = NoPreviewRevision.parseRevision(props.getProperty(PROP_MIN_JVM_VERSION));
+ } catch (NumberFormatException ignore) {}
+
+ // Backward compatibility with older PROP_OS and PROP_ARCH values
+ if (!props.containsKey(PROP_HOST_OS) && props.containsKey(LEGACY_PROP_OS)) {
+ hostOs = HostOs.fromXmlName(props.getProperty(LEGACY_PROP_OS));
+ }
+ if (!props.containsKey(PROP_HOST_BITS) &&
+ !props.containsKey(PROP_HOST_BITS) &&
+ props.containsKey(LEGACY_PROP_ARCH)) {
+ // We'll only handle the typical x86 and x86_64 values of the old PROP_ARCH
+ // value and ignore the PPC value. "Any" is equivalent to keeping the new
+ // attributes to null.
+ String v = props.getProperty(LEGACY_PROP_ARCH).toLowerCase();
+
+ if (v.indexOf("x86_64") > 0) {
+ // JVM in 64-bit x86_64 mode so host-bits should be 64 too.
+ hostBits = jvmBits = BitSize._64;
+ } else if (v.indexOf("x86") > 0) {
+ // JVM in 32-bit x86 mode, but host-bits could be either 32 or 64
+ // so we don't set this one.
+ jvmBits = BitSize._32;
+ }
+ }
+ }
+
+ mHostOs = hostOs;
+ mHostBits = hostBits;
+ mJvmBits = jvmBits;
+ mMinJvmVersion = minJvmVers;
+ }
+
+ /** @return the host OS or null if there's no limitation for this package. */
+ @Nullable
+ public HostOs getHostOS() {
+ return mHostOs;
+ }
+
+ /** @return the host bit size or null if there's no limitation for this package. */
+ @Nullable
+ public BitSize getHostBits() {
+ return mHostBits;
+ }
+
+ /** @return the JVM bit size or null if there's no limitation for this package. */
+ @Nullable
+ public BitSize getJvmBits() {
+ return mJvmBits;
+ }
+
+ /** @return the minimal JVM version required by this package
+ * or null if there's no limitation for this package. */
+ @Nullable
+ public NoPreviewRevision getMinJvmVersion() {
+ return mMinJvmVersion;
+ }
+
+ /**
+ * Checks whether {@code this} {@link ArchFilter} is compatible with the right-hand side one.
+ * <p/>
+ * Typically this is used to check whether "this downloaded package is compatible with the
+ * current architecture", which would be expressed as:
+ * <pre>
+ * DownloadedArchive.filter.isCompatibleWith(ArhFilter.getCurrent())
+ * </pre>
+ * For the host OS & bit size attribute, if the attributes are non-null they must be equal.
+ * For the min-jvm-version, "this" version (the package we want to install) needs to be lower
+ * or equal to the "required" (current host) version.
+ *
+ * @param required The requirements to meet.
+ * @return True if this filter meets or exceeds the given requirements.
+ */
+ public boolean isCompatibleWith(@NonNull ArchFilter required) {
+ if (mHostOs != null
+ && required.mHostOs != null
+ && !mHostOs.equals(required.mHostOs)) {
+ return false;
+ }
+
+ if (mHostBits != null
+ && required.mHostBits != null
+ && !mHostBits.equals(required.mHostBits)) {
+ return false;
+ }
+
+ if (mJvmBits != null
+ && required.mJvmBits != null
+ && !mJvmBits.equals(required.mJvmBits)) {
+ return false;
+ }
+
+ if (mMinJvmVersion != null
+ && required.mMinJvmVersion != null
+ && mMinJvmVersion.compareTo(required.mMinJvmVersion) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns an {@link ArchFilter} that represents the current host platform.
+ * @return an {@link ArchFilter} that represents the current host platform.
+ */
+ @NonNull
+ public static ArchFilter getCurrent() {
+ String os = System.getProperty("os.name"); //$NON-NLS-1$
+ HostOs hostOS = null;
+ if (os.startsWith("Mac")) { //$NON-NLS-1$
+ hostOS = HostOs.MACOSX;
+ } else if (os.startsWith("Windows")) { //$NON-NLS-1$
+ hostOS = HostOs.WINDOWS;
+ } else if (os.startsWith("Linux")) { //$NON-NLS-1$
+ hostOS = HostOs.LINUX;
+ }
+
+ BitSize jvmBits;
+ String arch = System.getProperty("os.arch"); //$NON-NLS-1$
+
+ if (arch.equalsIgnoreCase("x86_64") || //$NON-NLS-1$
+ arch.equalsIgnoreCase("ia64") || //$NON-NLS-1$
+ arch.equalsIgnoreCase("amd64")) { //$NON-NLS-1$
+ jvmBits = BitSize._64;
+ } else {
+ jvmBits = BitSize._32;
+ }
+
+ // TODO figure out the host bit size.
+ // When jvmBits is 64 we know it's surely 64
+ // but that's not necessarily obvious when jvmBits is 32.
+ BitSize hostBits = jvmBits;
+
+ NoPreviewRevision minJvmVersion = null;
+ String javav = System.getProperty("java.version"); //$NON-NLS-1$
+ // java Version is typically in the form "1.2.3_45" and we just need to keep up to "1.2.3"
+ // since our revision numbers are in 3-parts form (1.2.3).
+ Pattern p = Pattern.compile("((\\d+)(\\.\\d+)?(\\.\\d+)?).*"); //$NON-NLS-1$
+ Matcher m = p.matcher(javav);
+ if (m.matches()) {
+ minJvmVersion = NoPreviewRevision.parseRevision(m.group(1));
+ }
+
+ return new ArchFilter(hostOS, hostBits, jvmBits, minJvmVersion);
+ }
+
+ /**
+ * Save this {@link ArchFilter} attributes into the the given {@link Properties} object.
+ * These properties can later be given to the constructor that takes a {@link Properties} object.
+ * <p/>
+ * Null attributes are not saved in the properties.
+ *
+ * @param props A non-null properties object to fill with non-null attributes.
+ */
+ void saveProperties(@NonNull Properties props) {
+ if (mHostOs != null) {
+ props.setProperty(PROP_HOST_OS, mHostOs.getXmlName());
+ }
+ if (mHostBits != null) {
+ props.setProperty(PROP_HOST_BITS, mHostBits.getXmlName());
+ }
+ if (mJvmBits != null) {
+ props.setProperty(PROP_JVM_BITS, mJvmBits.getXmlName());
+ }
+ if (mMinJvmVersion != null) {
+ props.setProperty(PROP_MIN_JVM_VERSION, mMinJvmVersion.toShortString());
+ }
+ }
+
+ /** String for debug purposes. */
+ @Override
+ public String toString() {
+ return "<ArchFilter mHostOs=" + mHostOs +
+ ", mHostBits=" + mHostBits
+ + ", mJvmBits=" + mJvmBits +
+ ", mMinJvmVersion=" + mMinJvmVersion + ">";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mHostOs == null) ? 0 : mHostOs.hashCode());
+ result = prime * result + ((mHostBits == null) ? 0 : mHostBits.hashCode());
+ result = prime * result + ((mJvmBits == null) ? 0 : mJvmBits.hashCode());
+ result = prime * result + ((mMinJvmVersion == null) ? 0 : mMinJvmVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ArchFilter other = (ArchFilter) obj;
+ if (mHostBits != other.mHostBits) {
+ return false;
+ }
+ if (mHostOs != other.mHostOs) {
+ return false;
+ }
+ if (mJvmBits != other.mJvmBits) {
+ return false;
+ }
+ if (mMinJvmVersion == null) {
+ if (other.mMinJvmVersion != null) {
+ return false;
+ }
+ } else if (!mMinJvmVersion.equals(other.mMinJvmVersion)) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/Archive.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/Archive.java
index 7ddba1a..aac43eb 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/Archive.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/Archive.java
@@ -16,6 +16,8 @@
package com.android.sdklib.internal.repository.archives;
+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.internal.repository.IDescription;
@@ -24,9 +26,6 @@
import com.android.sdklib.io.FileOp;
import java.io.File;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Locale;
import java.util.Properties;
@@ -42,146 +41,6 @@
*/
public class Archive implements IDescription, Comparable<Archive> {
- private static final String PROP_OS = "Archive.Os"; //$NON-NLS-1$
- private static final String PROP_ARCH = "Archive.Arch"; //$NON-NLS-1$
-
- /** The checksum type. */
- public enum ChecksumType {
- /** A SHA1 checksum, represented as a 40-hex string. */
- SHA1("SHA-1"); //$NON-NLS-1$
-
- private final String mAlgorithmName;
-
- /**
- * Constructs a {@link ChecksumType} with the algorithm name
- * suitable for {@link MessageDigest#getInstance(String)}.
- * <p/>
- * These names are officially documented at
- * http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest
- */
- private ChecksumType(String algorithmName) {
- mAlgorithmName = algorithmName;
- }
-
- /**
- * Returns a new {@link MessageDigest} instance for this checksum type.
- * @throws NoSuchAlgorithmException if this algorithm is not available.
- */
- public MessageDigest getMessageDigest() throws NoSuchAlgorithmException {
- return MessageDigest.getInstance(mAlgorithmName);
- }
- }
-
- /** The OS that this archive can be downloaded on. */
- public enum Os {
- ANY("Any"),
- LINUX("Linux"),
- MACOSX("MacOS X"),
- WINDOWS("Windows");
-
- private final String mUiName;
-
- private Os(String uiName) {
- mUiName = uiName;
- }
-
- /** Returns the UI name of the OS. */
- public String getUiName() {
- return mUiName;
- }
-
- /** Returns the XML name of the OS. */
- public String getXmlName() {
- return toString().toLowerCase(Locale.US);
- }
-
- /**
- * Returns the current OS as one of the {@link Os} enum values or null.
- */
- public static Os getCurrentOs() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- if (os.startsWith("Mac")) { //$NON-NLS-1$
- return Os.MACOSX;
-
- } else if (os.startsWith("Windows")) { //$NON-NLS-1$
- return Os.WINDOWS;
-
- } else if (os.startsWith("Linux")) { //$NON-NLS-1$
- return Os.LINUX;
- }
-
- return null;
- }
-
- /** Returns true if this OS is compatible with the current one. */
- public boolean isCompatible() {
- if (this == ANY) {
- return true;
- }
-
- Os os = getCurrentOs();
- return this == os;
- }
- }
-
- /** The Architecture that this archive can be downloaded on. */
- public enum Arch {
- ANY("Any"),
- PPC("PowerPC"),
- X86("x86"),
- X86_64("x86_64");
-
- private final String mUiName;
-
- private Arch(String uiName) {
- mUiName = uiName;
- }
-
- /** Returns the UI name of the architecture. */
- public String getUiName() {
- return mUiName;
- }
-
- /** Returns the XML name of the architecture. */
- public String getXmlName() {
- return toString().toLowerCase(Locale.US);
- }
-
- /**
- * Returns the current architecture as one of the {@link Arch} enum values or null.
- */
- public static Arch getCurrentArch() {
- // Values listed from http://lopica.sourceforge.net/os.html
- String arch = System.getProperty("os.arch");
-
- if (arch.equalsIgnoreCase("x86_64") || arch.equalsIgnoreCase("amd64")) {
- return Arch.X86_64;
-
- } else if (arch.equalsIgnoreCase("x86")
- || arch.equalsIgnoreCase("i386")
- || arch.equalsIgnoreCase("i686")) {
- return Arch.X86;
-
- } else if (arch.equalsIgnoreCase("ppc") || arch.equalsIgnoreCase("PowerPC")) {
- return Arch.PPC;
- }
-
- return null;
- }
-
- /** Returns true if this architecture is compatible with the current one. */
- public boolean isCompatible() {
- if (this == ANY) {
- return true;
- }
-
- Arch arch = getCurrentArch();
- return this == arch;
- }
- }
-
- private final Os mOs;
- private final Arch mArch;
private final String mUrl;
private final long mSize;
private final String mChecksum;
@@ -189,14 +48,27 @@
private final Package mPackage;
private final String mLocalOsPath;
private final boolean mIsLocal;
+ private final ArchFilter mArchFilter;
/**
* Creates a new remote archive.
+ * This is typically called when inflating a remote-package info from XML meta-data.
+ *
+ * @param pkg The package that contains this archive. Typically not null.
+ * @param archFilter The {@link ArchFilter} for the archive. Typically not null.
+ * @param url The URL where the archive is available.
+ * Typically not null but code should be able to handles both.
+ * @param size The expected size in bytes of the archive to download.
+ * @param checksum The expected checksum string of the archive. Currently only the
+ * {@link ChecksumType#SHA1} format is supported.
*/
- public Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) {
+ public Archive(@Nullable Package pkg,
+ @Nullable ArchFilter archFilter,
+ @Nullable String url,
+ long size,
+ @NonNull String checksum) {
mPackage = pkg;
- mOs = os;
- mArch = arch;
+ mArchFilter = archFilter != null ? archFilter : new ArchFilter(null);
mUrl = url == null ? null : url.trim();
mLocalOsPath = null;
mSize = size;
@@ -206,15 +78,21 @@
/**
* Creates a new local archive.
- * Uses the properties from props first, if possible. Props can be null.
+ * This is typically called when inflating a local-package info by reading a local
+ * source.properties file. In this case a few properties like the URL, checksum and
+ * size are not defined.
+ *
+ * @param pkg The package that contains this archive. Cannot be null.
+ * @param props A set of properties. Can be null.
+ * @param localOsPath The OS path where the archive is installed if this represents a
+ * local package. Null for a remote package.
*/
@VisibleForTesting(visibility=Visibility.PACKAGE)
- public Archive(Package pkg, Properties props, Os os, Arch arch, String localOsPath) {
+ public Archive(@NonNull Package pkg,
+ @Nullable Properties props,
+ @Nullable String localOsPath) {
mPackage = pkg;
-
- mOs = props == null ? os : Os.valueOf( props.getProperty(PROP_OS, os.toString()));
- mArch = props == null ? arch : Arch.valueOf(props.getProperty(PROP_ARCH, arch.toString()));
-
+ mArchFilter = new ArchFilter(props);
mUrl = null;
mLocalOsPath = localOsPath;
mSize = 0;
@@ -226,9 +104,8 @@
* Save the properties of the current archive in the give {@link Properties} object.
* These properties will later be give the constructor that takes a {@link Properties} object.
*/
- void saveProperties(Properties props) {
- props.setProperty(PROP_OS, mOs.toString());
- props.setProperty(PROP_ARCH, mArch.toString());
+ void saveProperties(@NonNull Properties props) {
+ mArchFilter.saveProperties(props);
}
/**
@@ -243,6 +120,7 @@
* Returns the package that created and owns this archive.
* It should generally not be null.
*/
+ @Nullable
public Package getParentPackage() {
return mPackage;
}
@@ -259,6 +137,7 @@
* Returns the SHA1 archive checksum, as a 40-char hex.
* Can be empty but not null for local installed folders.
*/
+ @NonNull
public String getChecksum() {
return mChecksum;
}
@@ -266,6 +145,7 @@
/**
* Returns the checksum type, always {@link ChecksumType#SHA1} right now.
*/
+ @NonNull
public ChecksumType getChecksumType() {
return mChecksumType;
}
@@ -275,6 +155,7 @@
* Always return null for a local installed folder.
* @see #getLocalOsPath()
*/
+ @Nullable
public String getUrl() {
return mUrl;
}
@@ -284,48 +165,40 @@
* Always return null for remote archives.
* @see #getUrl()
*/
+ @Nullable
public String getLocalOsPath() {
return mLocalOsPath;
}
/**
- * Returns the archive {@link Os} enum.
- * Can be null for a local installed folder on an unknown OS.
+ * Returns the architecture filter.
+ * This non-null filter indicates which host/jvm this archive is compatible with.
*/
- public Os getOs() {
- return mOs;
+ @NonNull
+ public ArchFilter getArchFilter() {
+ return mArchFilter;
}
/**
- * Returns the archive {@link Arch} enum.
- * Can be null for a local installed folder on an unknown architecture.
- */
- public Arch getArch() {
- return mArch;
- }
-
- /**
- * Generates a description for this archive of the OS/Arch supported by this archive.
+ * Generates a description of the {@link ArchFilter} supported by this archive.
*/
public String getOsDescription() {
- String os;
- if (mOs == null) {
- os = "unknown OS";
- } else if (mOs == Os.ANY) {
- os = "any OS";
- } else {
- os = mOs.getUiName();
+ StringBuilder sb = new StringBuilder();
+
+ HostOs hos = mArchFilter.getHostOS();
+ sb.append(hos == null ? "any OS" : hos.getUiName());
+
+ BitSize jvmBits = mArchFilter.getJvmBits();
+ if (jvmBits != null) {
+ sb.append(", JVM ").append(jvmBits.getSize()).append("-bits");
}
- String arch = ""; //$NON-NLS-1$
- if (mArch != null && mArch != Arch.ANY) {
- arch = mArch.getUiName();
+ BitSize hostBits = mArchFilter.getJvmBits();
+ if (hostBits != null) {
+ sb.append(", Host ").append(hostBits.getSize()).append("-bits");
}
- return String.format("%1$s%2$s%3$s",
- os,
- arch.length() > 0 ? " " : "", //$NON-NLS-2$
- arch);
+ return sb.toString();
}
/**
@@ -389,7 +262,8 @@
* Returns true if this archive can be installed on the current platform.
*/
public boolean isCompatible() {
- return getOs().isCompatible() && getArch().isCompatible();
+ ArchFilter current = ArchFilter.getCurrent();
+ return mArchFilter.isCompatibleWith(current);
}
/**
@@ -423,12 +297,11 @@
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + ((mArch == null) ? 0 : mArch.hashCode());
+ result = prime * result + ((mArchFilter == null) ? 0 : mArchFilter.hashCode());
result = prime * result + ((mChecksum == null) ? 0 : mChecksum.hashCode());
result = prime * result + ((mChecksumType == null) ? 0 : mChecksumType.hashCode());
result = prime * result + (mIsLocal ? 1231 : 1237);
result = prime * result + ((mLocalOsPath == null) ? 0 : mLocalOsPath.hashCode());
- result = prime * result + ((mOs == null) ? 0 : mOs.hashCode());
result = prime * result + (int) (mSize ^ (mSize >>> 32));
result = prime * result + ((mUrl == null) ? 0 : mUrl.hashCode());
return result;
@@ -451,11 +324,11 @@
return false;
}
Archive other = (Archive) obj;
- if (mArch == null) {
- if (other.mArch != null) {
+ if (mArchFilter == null) {
+ if (other.mArchFilter != null) {
return false;
}
- } else if (!mArch.equals(other.mArch)) {
+ } else if (!mArchFilter.equals(other.mArchFilter)) {
return false;
}
if (mChecksum == null) {
@@ -482,13 +355,6 @@
} else if (!mLocalOsPath.equals(other.mLocalOsPath)) {
return false;
}
- if (mOs == null) {
- if (other.mOs != null) {
- return false;
- }
- } else if (!mOs.equals(other.mOs)) {
- return false;
- }
if (mSize != other.mSize) {
return false;
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java
index 6d9802f..5760ee7 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java
@@ -29,9 +29,9 @@
import com.android.sdklib.io.FileOp;
import com.android.sdklib.io.IFileOp;
import com.android.sdklib.repository.RepoConstants;
-import com.android.sdklib.util.GrabProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.Wait;
+import com.android.utils.GrabProcessOutput;
+import com.android.utils.GrabProcessOutput.IProcessOutput;
+import com.android.utils.GrabProcessOutput.Wait;
import com.android.utils.Pair;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
@@ -55,6 +55,7 @@
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
@@ -421,7 +422,9 @@
resp.getFirstHeader(HttpHeaders.LAST_MODIFIED).getValue());
}
- mFileOp.saveProperties(propsFile, props, "## Android SDK Download."); //$NON-NLS-1$
+ try {
+ mFileOp.saveProperties(propsFile, props, "## Android SDK Download."); //$NON-NLS-1$
+ } catch (IOException ignore) {}
// On success, status can be:
// - 206 (Partial content), if resumeHeaders is not null (we asked for a partial
@@ -1085,7 +1088,30 @@
*/
@VisibleForTesting(visibility=Visibility.PRIVATE)
protected boolean generateSourceProperties(Archive archive, File unzipDestFolder) {
- Properties props = new Properties();
+
+ // Create a version of Properties that returns a sorted key set.
+ // This is used by Properties#saveProperties and should ensure the
+ // properties are in a stable order. Unit tests rely on this fact.
+ @SuppressWarnings("serial")
+ Properties props = new Properties() {
+ @Override
+ public synchronized Enumeration<Object> keys() {
+ Set<Object> sortedSet = new TreeSet<Object>(keySet());
+ final Iterator<Object> it = sortedSet.iterator();
+ return new Enumeration<Object>() {
+ @Override
+ public boolean hasMoreElements() {
+ return it.hasNext();
+ }
+
+ @Override
+ public Object nextElement() {
+ return it.next();
+ }
+
+ };
+ }
+ };
archive.saveProperties(props);
@@ -1094,10 +1120,15 @@
pkg.saveProperties(props);
}
- return mFileOp.saveProperties(
+ try {
+ mFileOp.saveProperties(
new File(unzipDestFolder, SdkConstants.FN_SOURCE_PROP),
props,
"## Android Tool: Source of this archive."); //$NON-NLS-1$
+ return true;
+ } catch (IOException ignore) {
+ return false;
+ }
}
/**
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/BitSize.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/BitSize.java
new file mode 100755
index 0000000..b2e8ba4
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/BitSize.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archives;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/** The Architecture that this archive can be downloaded on. */
+public enum BitSize {
+ _32(32),
+ _64(64);
+
+ private final int mSize;
+
+ private BitSize(int size) {
+ mSize = size;
+ }
+
+ /** Returns the size of the architecture. */
+ public int getSize() {
+ return mSize;
+ }
+
+ /** Returns the XML name of the bit size. */
+ @NonNull
+ public String getXmlName() {
+ return Integer.toString(mSize);
+ }
+ /**
+ * Returns the enum value matching the given XML name.
+ * @return A valid {@link HostOs} constant or null if not a valid XML name.
+ */
+ @Nullable
+ public static BitSize fromXmlName(@Nullable String xmlName) {
+ if (xmlName != null) {
+ for (BitSize v : values()) {
+ if (v.getXmlName().equalsIgnoreCase(xmlName)) {
+ return v;
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ChecksumType.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ChecksumType.java
new file mode 100755
index 0000000..3d74f5b
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ChecksumType.java
@@ -0,0 +1,47 @@
+/*
+ * 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.android.sdklib.internal.repository.archives;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/** The checksum type. */
+public enum ChecksumType {
+ /** A SHA1 checksum, represented as a 40-hex string. */
+ SHA1("SHA-1"); //$NON-NLS-1$
+
+ private final String mAlgorithmName;
+
+ /**
+ * Constructs a {@link ChecksumType} with the algorithm name
+ * suitable for {@link MessageDigest#getInstance(String)}.
+ * <p/>
+ * These names are officially documented at
+ * http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest
+ */
+ private ChecksumType(String algorithmName) {
+ mAlgorithmName = algorithmName;
+ }
+
+ /**
+ * Returns a new {@link MessageDigest} instance for this checksum type.
+ * @throws NoSuchAlgorithmException if this algorithm is not available.
+ */
+ public MessageDigest getMessageDigest() throws NoSuchAlgorithmException {
+ return MessageDigest.getInstance(mAlgorithmName);
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/HostOs.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/HostOs.java
new file mode 100755
index 0000000..48cd6a7
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/HostOs.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archives;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Locale;
+
+/**
+ * The OS that this archive can be downloaded on. <br/>
+ * The represents a "host" where the SDK tools and the SDK Manager can run,
+ * not the Android device targets.
+ * <p/>
+ * The actual OS requirements for the SDK are listed at
+ * <a href="http://d.android.com/sdk">http://d.android.com/sdk</a>
+ */
+public enum HostOs {
+ /** Any of the Unix-like host OSes. */
+ LINUX("Linux"),
+ /** Any variation of MacOS X. */
+ MACOSX("MacOS X"),
+ /** Any variation of Windows. */
+ WINDOWS("Windows");
+
+ private final String mUiName;
+
+ private HostOs(@NonNull String uiName) {
+ mUiName = uiName;
+ }
+
+ /**
+ * Returns the UI name of the OS.
+ */
+ @NonNull
+ public String getUiName() {
+ return mUiName;
+ }
+
+ /**
+ * Returns the XML name of the OS.
+ * @returns Null, windows, macosx or linux.
+ */
+ @NonNull
+ public String getXmlName() {
+ return toString().toLowerCase(Locale.US);
+ }
+
+ /**
+ * Returns the enum value matching the given XML name.
+ * @return A valid {@link HostOs} constnat or null if not a valid XML name.
+ */
+ @Nullable
+ public static HostOs fromXmlName(@Nullable String xmlName) {
+ if (xmlName != null) {
+ for (HostOs v : values()) {
+ if (v.getXmlName().equalsIgnoreCase(xmlName)) {
+ return v;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyArch.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyArch.java
new file mode 100755
index 0000000..437fd78
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyArch.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archives;
+
+
+
+/**
+ * The legacy Architecture that this archive can be downloaded on.
+ * <p/>
+ * This attribute was used for the <archive> element in repo schema 1-9.
+ * add-on schema 1-6 and sys-img schema 1-2.
+ * Starting with repo schema 10, add-on schema 7 and sys-img schema 3, this is replaced
+ * by the <host-bit> and <jvm-bit> elements and {@link ArchFilter}.
+ *
+ * @see HostOs
+ */
+public enum LegacyArch {
+ ANY("Any"),
+ PPC("PowerPC"),
+ X86("x86"),
+ X86_64("x86_64");
+
+ private final String mUiName;
+
+ private LegacyArch(String uiName) {
+ mUiName = uiName;
+ }
+
+ /** Returns the UI name of the architecture. */
+ public String getUiName() {
+ return mUiName;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyOs.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyOs.java
new file mode 100755
index 0000000..de7ec06
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyOs.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archives;
+
+
+
+/**
+ * The legacy OS that this archive can be downloaded on.
+ * <p/>
+ * This attribute was used for the <archive> element in repo schema 1-9.
+ * add-on schema 1-6 and sys-img schema 1-2.
+ * Starting with repo schema 10, add-on schema 7 and sys-img schema 3, this is replaced
+ * by the <host-os> element and {@link ArchFilter}.
+ *
+ * @see HostOs
+ */
+public enum LegacyOs {
+ ANY("Any"),
+ LINUX("Linux"),
+ MACOSX("MacOS X"),
+ WINDOWS("Windows");
+
+ private final String mUiName;
+
+ private LegacyOs(String uiName) {
+ mUiName = uiName;
+ }
+
+ /** Returns the UI name of the OS. */
+ public String getUiName() {
+ return mUiName;
+ }
+}
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 89c0181..de7fdcf 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
@@ -25,13 +25,16 @@
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
-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.AddonManifestIniProps;
+import com.android.sdklib.repository.MajorRevision;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkAddonConstants;
import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repository.local.LocalAddonPkgInfo;
import com.android.utils.Pair;
import org.w3c.dom.Node;
@@ -55,6 +58,7 @@
private final String mNameId;
private final String mDisplayName;
private final AndroidVersion mVersion;
+ private final IPkgDesc mPkgDesc;
/**
* The helper handling the layoutlib version.
@@ -157,7 +161,7 @@
// For a missing id, we simply use a sanitized version of the display name
if (nameId.length() == 0) {
- nameId = sanitizeDisplayToNameId(name.length() > 0 ? name : nameDisp);
+ nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(name.length() > 0 ? name : nameDisp);
}
assert nameId.length() > 0;
@@ -184,7 +188,7 @@
// For a missing id, we simply use a sanitized version of the display vendor
if (vendorId.length() == 0) {
boolean hasVendor = vendor.length() > 0;
- vendorId = sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
+ vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
}
assert vendorId.length() > 0;
@@ -203,6 +207,14 @@
PackageParserUtils.findChildElement(packageNode, SdkAddonConstants.NODE_LIBS));
mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
+
+ mPkgDesc = PkgDesc.Builder
+ .newAddon(mVersion,
+ (MajorRevision) getRevision(),
+ new IdDisplay(mVendorId, mVendorDisplay),
+ new IdDisplay(mNameId, mDisplayName))
+ .setDescriptions(this)
+ .create();
}
/**
@@ -230,8 +242,6 @@
null, //license
target.getDescription(), //description
null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
target.getLocation() //archiveOsPath
);
@@ -252,7 +262,7 @@
// For a missing id, we simply use a sanitized version of the display name
if (nameId.length() == 0) {
- nameId = sanitizeDisplayToNameId(name.length() > 0 ? name : nameDisp);
+ nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(name.length() > 0 ? name : nameDisp);
}
assert nameId.length() > 0;
@@ -276,7 +286,7 @@
// For a missing id, we simply use a sanitized version of the display vendor
if (vendorId.length() == 0) {
boolean hasVendor = vendor.length() > 0;
- vendorId = sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
+ vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
}
assert vendorId.length() > 0;
@@ -299,6 +309,20 @@
mLibs[i] = new Lib(optLibs[i].getName(), optLibs[i].getDescription());
}
}
+
+ mPkgDesc = PkgDesc.Builder
+ .newAddon(mVersion,
+ (MajorRevision) getRevision(),
+ new IdDisplay(mVendorId, mVendorDisplay),
+ new IdDisplay(mNameId, mDisplayName))
+ .setDescriptions(this)
+ .create();
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -314,22 +338,44 @@
Properties sourceProps,
Map<String, String> addonProps,
String error) {
- String name = getProperty(sourceProps,
- PkgProps.ADDON_NAME_DISPLAY,
- getProperty(sourceProps,
- PkgProps.ADDON_NAME,
- addonProps.get(LocalAddonPkgInfo.ADDON_NAME)));
- String vendor = getProperty(sourceProps,
- PkgProps.ADDON_VENDOR_DISPLAY,
- getProperty(sourceProps,
- PkgProps.ADDON_VENDOR,
- addonProps.get(LocalAddonPkgInfo.ADDON_VENDOR)));
- String api = addonProps.get(LocalAddonPkgInfo.ADDON_API);
- String revision = addonProps.get(LocalAddonPkgInfo.ADDON_REVISION);
+
+
+ String nameId = getProperty(sourceProps, PkgProps.ADDON_NAME_ID, null);
+ String nameDisp = getProperty(sourceProps, PkgProps.ADDON_NAME_DISPLAY, null);
+ if (nameDisp == null) {
+ nameDisp = getProperty(sourceProps, PkgProps.ADDON_NAME_DISPLAY, null);
+ }
+ if (nameDisp == null) {
+ nameDisp = addonProps.get(AddonManifestIniProps.ADDON_NAME);
+ }
+ if (nameDisp == null) {
+ nameDisp = "Unknown";
+ }
+ if (nameId == null) {
+ nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(nameDisp);
+ }
+
+ String vendorId = getProperty(sourceProps, PkgProps.ADDON_VENDOR_ID, null);
+ String vendorDisp = getProperty(sourceProps, PkgProps.ADDON_VENDOR_DISPLAY, null);
+ if (vendorDisp == null) {
+ vendorDisp = getProperty(sourceProps, PkgProps.ADDON_VENDOR_DISPLAY, null);
+ }
+ if (vendorDisp == null) {
+ vendorDisp = addonProps.get(AddonManifestIniProps.ADDON_VENDOR);
+ }
+ if (vendorDisp == null) {
+ vendorDisp = "Unknown";
+ }
+ if (vendorId == null) {
+ vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(vendorDisp);
+ }
+
+ String api = addonProps.get(AddonManifestIniProps.ADDON_API);
+ String revision = addonProps.get(AddonManifestIniProps.ADDON_REVISION);
String shortDesc = String.format("%1$s by %2$s, Android API %3$s, revision %4$s [*]",
- name,
- vendor,
+ nameDisp,
+ vendorDisp,
api,
revision);
@@ -343,14 +389,26 @@
try {
apiLevel = Integer.parseInt(api);
- } catch(NumberFormatException e) {
- // ignore
- }
+ } catch(NumberFormatException ignore) {}
+
+ int intRevision = MajorRevision.MISSING_MAJOR_REV;
+ try {
+ intRevision = Integer.parseInt(revision);
+ } catch (NumberFormatException ignore) {}
+
+ IPkgDesc desc = PkgDesc.Builder
+ .newAddon(new AndroidVersion(apiLevel, null),
+ new MajorRevision(intRevision),
+ new IdDisplay(vendorId, vendorDisp),
+ new IdDisplay(nameId, nameDisp))
+ .setDescriptionShort(shortDesc)
+ .create();
return new BrokenPackage(null/*props*/, shortDesc, longDesc,
IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
apiLevel,
- archiveOsPath);
+ archiveOsPath,
+ desc);
}
@Override
@@ -483,6 +541,11 @@
*/
@Override
public String getListDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
+
return String.format("%1$s%2$s",
getDisplayName(),
isObsolete() ? " (Obsolete)" : "");
@@ -493,6 +556,14 @@
*/
@Override
public String getShortDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, revision %2$s%3$s",
+ ld,
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
return String.format("%1$s, Android API %2$s, revision %3$s%4$s",
getDisplayName(),
mVersion.getApiString(),
@@ -582,28 +653,6 @@
return name;
}
- /**
- * Computes a sanitized name-id based on an addon name-display.
- * This is used to provide compatibility with older addons that lacks the new fields.
- *
- * @param displayName A name-display field or a old-style name field.
- * @return A non-null sanitized name-id that fits in the {@code [a-zA-Z0-9_-]+} pattern.
- */
- private String sanitizeDisplayToNameId(String displayName) {
- String name = displayName.toLowerCase(Locale.US);
- name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
-
- // Trim leading and trailing underscores
- if (name.length() > 1) {
- name = name.replaceAll("^_+", ""); //$NON-NLS-1$ //$NON-NLS-2$
- }
- if (name.length() > 1) {
- name = name.replaceAll("_+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
- }
- return name;
- }
-
@Override
public boolean sameItemAs(Package pkg) {
if (pkg instanceof AddonPackage) {
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BrokenPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BrokenPackage.java
index e2c11a0..87e5f35 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BrokenPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BrokenPackage.java
@@ -16,12 +16,13 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
import java.io.File;
import java.util.Properties;
@@ -47,6 +48,7 @@
private final String mShortDescription;
private final String mLongDescription;
+ private final IPkgDesc mPkgDesc;
/**
* Creates a new "broken" package that represents a package that we failed to load,
@@ -55,26 +57,32 @@
* <p/>
* By design, this creates a package with one and only one archive.
*/
- BrokenPackage(Properties props,
- String shortDescription,
- String longDescription,
+ BrokenPackage(@Nullable Properties props,
+ @NonNull String shortDescription,
+ @NonNull String longDescription,
int minApiLevel,
int exactApiLevel,
- String archiveOsPath) {
+ @Nullable String archiveOsPath,
+ @NonNull IPkgDesc pkgDesc) {
super( null, //source
props, //properties
0, //revision will be taken from props
null, //license
longDescription, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
archiveOsPath //archiveOsPath
);
mShortDescription = shortDescription;
mLongDescription = longDescription;
mMinApiLevel = minApiLevel;
mExactApiLevel = exactApiLevel;
+ mPkgDesc = pkgDesc;
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -124,6 +132,11 @@
*/
@Override
public String getListDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
+
return mShortDescription;
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BuildToolPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BuildToolPackage.java
index 23fe5ac..081a775 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BuildToolPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BuildToolPackage.java
@@ -17,16 +17,17 @@
package com.android.sdklib.internal.repository.packages;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
-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.FullRevision.PreviewComparison;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import org.w3c.dom.Node;
@@ -44,6 +45,8 @@
/** The base value returned by {@link BuildToolPackage#installId()}. */
private static final String INSTALL_ID_BASE = SdkConstants.FD_BUILD_TOOLS + '-';
+ private final IPkgDesc mPkgDesc;
+
/**
* Creates a new build-tool package from the attributes and elements of the given XML node.
* This constructor should throw an exception if the package cannot be created.
@@ -60,6 +63,11 @@
String nsUri,
Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
+
+ mPkgDesc = PkgDesc.Builder
+ .newBuildTool(getRevision())
+ .setDescriptions(this)
+ .create();
}
/**
@@ -139,17 +147,20 @@
}
if (error == null && rev != null) {
- return new BuildToolPackage(
+ BuildToolPackage pkg = new BuildToolPackage(
null, //source
props,
0, //revision (extracted from props)
null, //license
null, //description
null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
buildToolDir.getAbsolutePath());
+ if (pkg.hasCompatibleArchive()) {
+ return pkg;
+ } else {
+ error = "Package is not compatible with current OS";
+ }
}
@@ -166,10 +177,16 @@
String longDesc = sb.toString();
+ IPkgDesc desc = PkgDesc.Builder
+ .newBuildTool(rev != null ? rev : new FullRevision(FullRevision.MISSING_MAJOR_REV))
+ .setDescriptionShort(shortDesc)
+ .create();
+
return new BrokenPackage(props, shortDesc, longDesc,
IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
IExactApiLevelDependency.API_LEVEL_INVALID,
- buildToolDir.getAbsolutePath());
+ buildToolDir.getAbsolutePath(),
+ desc);
}
@VisibleForTesting(visibility=Visibility.PRIVATE)
@@ -180,8 +197,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
super(source,
props,
@@ -189,9 +204,18 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
+
+ mPkgDesc = PkgDesc.Builder
+ .newBuildTool(getRevision())
+ .setDescriptions(this)
+ .create();
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -213,6 +237,11 @@
*/
@Override
public String getListDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
+
return String.format("Android SDK Build-tools%1$s",
isObsolete() ? " (Obsolete)" : "");
}
@@ -222,6 +251,14 @@
*/
@Override
public String getShortDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, revision %2$s%3$s",
+ ld,
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
return String.format("Android SDK Build-tools, revision %1$s%2$s",
getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/DocPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/DocPackage.java
index 927d361..4bdfa00 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/DocPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/DocPackage.java
@@ -21,10 +21,11 @@
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
-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.MajorRevision;
import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import org.w3c.dom.Node;
@@ -42,6 +43,7 @@
public class DocPackage extends MajorRevisionPackage implements IAndroidVersionProvider {
private final AndroidVersion mVersion;
+ private final IPkgDesc mPkgDesc;
/**
* Creates a new doc package from the attributes and elements of the given XML node.
@@ -67,6 +69,11 @@
codeName = null;
}
mVersion = new AndroidVersion(apiLevel, codeName);
+
+ mPkgDesc = PkgDesc.Builder
+ .newDoc(mVersion, (MajorRevision) getRevision())
+ .setDescriptions(this)
+ .create();
}
/**
@@ -84,11 +91,9 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
return new DocPackage(source, props, apiLevel, codename, revision, license, description,
- descUrl, archiveOs, archiveArch, archiveOsPath);
+ descUrl, archiveOsPath);
}
private DocPackage(SdkSource source,
@@ -99,8 +104,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
super(source,
props,
@@ -108,10 +111,19 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
mVersion = new AndroidVersion(props, apiLevel, codename);
+
+ mPkgDesc = PkgDesc.Builder
+ .newDoc(mVersion, (MajorRevision) getRevision())
+ .setDescriptions(this)
+ .create();
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -152,6 +164,10 @@
*/
@Override
public String getListDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
if (mVersion.isPreview()) {
return String.format("Documentation for Android '%1$s' Preview SDK%2$s",
mVersion.getCodename(),
@@ -168,6 +184,14 @@
*/
@Override
public String getShortDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, revision %2$s%3$s",
+ ld,
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
if (mVersion.isPreview()) {
return String.format("Documentation for Android '%1$s' Preview SDK, revision %2$s%3$s",
mVersion.getCodename(),
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 71bef30..7df8c08 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
@@ -17,6 +17,7 @@
package com.android.sdklib.internal.repository.packages;
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;
@@ -25,12 +26,15 @@
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.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.sdklib.repository.descriptors.IPkgDescExtra;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDescExtra;
+import com.android.sdklib.repository.local.LocalExtraPkgInfo;
import com.android.utils.NullLogger;
import org.w3c.dom.Node;
@@ -57,14 +61,11 @@
private final String mDisplayName;
/**
- * The vendor id name. It is a simple alphanumeric string [a-zA-Z0-9_-].
+ * The vendor id + name.
+ * The id is a simple alphanumeric string [a-zA-Z0-9_-].
+ * The display name is used in the UI to represent the vendor. It can be anything.
*/
- private final String mVendorId;
-
- /**
- * The vendor display name. Used in the UI to represent the vendor. It can be anything.
- */
- private final String mVendorDisplay;
+ private final IdDisplay mVendor;
/**
* The sub-folder name. It must be a non-empty single-segment path.
@@ -89,6 +90,8 @@
*/
private final String[] mProjectFiles;
+ private final IPkgDescExtra mPkgDesc;
+
/**
* Creates a new tool package from the attributes and elements of the given XML node.
* This constructor should throw an exception if the package cannot be created.
@@ -136,12 +139,11 @@
// The vendor-display name can be empty, in which case we use the vendor-id.
vname = vid;
}
- mVendorDisplay = vname.trim();
- mVendorId = vid.trim();
+ mVendor = new IdDisplay(vid.trim(), vname.trim());
if (name.length() == 0) {
// If name is missing, use the <path> attribute as done in an addon-3 schema.
- name = getPrettyName();
+ name = LocalExtraPkgInfo.getPrettyName(mVendor, mPath);
}
mDisplayName = name.trim();
@@ -152,6 +154,15 @@
PackageParserUtils.findChildElement(packageNode, RepoConstants.NODE_PROJECT_FILES));
mOldPaths = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_OLD_PATHS);
+
+ mPkgDesc = (IPkgDescExtra) PkgDesc.Builder
+ .newExtra(mVendor,
+ mPath,
+ mDisplayName,
+ getOldPaths(),
+ getRevision())
+ .setDescriptions(this)
+ .create();
}
private String[] parseProjectFiles(Node projectFilesNode) {
@@ -195,11 +206,9 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
ExtraPackage ep = new ExtraPackage(source, props, vendor, path, revision, license,
- description, descUrl, archiveOs, archiveArch, archiveOsPath);
+ description, descUrl, archiveOsPath);
return ep;
}
@@ -217,8 +226,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
super(source,
props,
@@ -226,8 +233,6 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
mMinToolsMixin = new MinToolsMixin(
@@ -237,8 +242,6 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
// The path argument comes before whatever could be in the properties
@@ -262,12 +265,11 @@
// The vendor-display name can be empty, in which case we use the vendor-id.
vname = vid;
}
- mVendorDisplay = vname.trim();
- mVendorId = vid.trim();
+ mVendor = new IdDisplay(vid.trim(), vname.trim());
if (name == null || name.length() == 0) {
// If name is missing, use the <path> attribute as done in an addon-3 schema.
- name = getPrettyName();
+ name = LocalExtraPkgInfo.getPrettyName(mVendor, mPath);
}
mDisplayName = name.trim();
@@ -286,7 +288,23 @@
}
}
}
+
mProjectFiles = filePaths.toArray(new String[filePaths.size()]);
+
+ mPkgDesc = (IPkgDescExtra) PkgDesc.Builder
+ .newExtra(mVendor,
+ mPath,
+ mDisplayName,
+ getOldPaths(),
+ getRevision())
+ .setDescriptions(this)
+ .create();
+ }
+
+ @Override
+ @NonNull
+ public IPkgDescExtra getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -300,8 +318,8 @@
props.setProperty(PkgProps.EXTRA_PATH, mPath);
props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, mDisplayName);
- props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, mVendorDisplay);
- props.setProperty(PkgProps.EXTRA_VENDOR_ID, mVendorId);
+ props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, mVendor.getDisplay());
+ props.setProperty(PkgProps.EXTRA_VENDOR_ID, mVendor.getId());
if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
props.setProperty(PkgProps.EXTRA_MIN_API_LEVEL, Integer.toString(getMinApiLevel()));
@@ -371,10 +389,7 @@
* @return A list of old paths. Can be empty but not null.
*/
public String[] getOldPaths() {
- if (mOldPaths == null || mOldPaths.length() == 0) {
- return new String[0];
- }
- return mOldPaths.split(";"); //$NON-NLS-1$
+ return PkgDescExtra.convertOldPaths(mOldPaths);
}
/**
@@ -401,11 +416,11 @@
* Returns the vendor id.
*/
public String getVendorId() {
- return mVendorId;
+ return mVendor.getId();
}
public String getVendorDisplay() {
- return mVendorDisplay;
+ return mVendor.getDisplay();
}
public String getDisplayName() {
@@ -435,57 +450,6 @@
}
/**
- * Used to produce a suitable name-display based on the current {@link #mPath}
- * and {@link #mVendorDisplay} in addon-3 schemas.
- */
- private String getPrettyName() {
- String name = mPath;
-
- // In the past, we used to save the extras in a folder vendor-path,
- // and that "vendor" would end up in the path when we reload the extra from
- // disk. Detect this and compensate.
- if (mVendorDisplay != null && mVendorDisplay.length() > 0) {
- if (name.startsWith(mVendorDisplay + "-")) { //$NON-NLS-1$
- name = name.substring(mVendorDisplay.length() + 1);
- }
- }
-
- // Uniformize all spaces in the name
- if (name != null) {
- name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- }
- if (name == null || name.length() == 0) {
- name = "Unknown Extra";
- }
-
- if (mVendorDisplay != null && mVendorDisplay.length() > 0) {
- name = mVendorDisplay + " " + name; //$NON-NLS-1$
- name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- // Look at all lower case characters in range [1..n-1] and replace them by an upper
- // case if they are preceded by a space. Also upper cases the first character of the
- // string.
- boolean changed = false;
- char[] chars = name.toCharArray();
- for (int n = chars.length - 1, i = 0; i < n; i++) {
- if (Character.isLowerCase(chars[i]) && (i == 0 || chars[i - 1] == ' ')) {
- chars[i] = Character.toUpperCase(chars[i]);
- changed = true;
- }
- }
- if (changed) {
- name = new String(chars);
- }
-
- // Special case: reformat a few typical acronyms.
- name = name.replaceAll(" Usb ", " USB "); //$NON-NLS-1$
- name = name.replaceAll(" Api ", " API "); //$NON-NLS-1$
-
- return name;
- }
-
- /**
* Returns a string identifier to install this package from the command line.
* For extras, we use "extra-vendor-path".
* <p/>
@@ -505,6 +469,11 @@
*/
@Override
public String getListDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
+
String s = String.format("%1$s%2$s",
getDisplayName(),
isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
@@ -517,6 +486,14 @@
*/
@Override
public String getShortDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, revision %2$s%3$s",
+ ld,
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
String s = String.format("%1$s, revision %2$s%3$s",
getDisplayName(),
getRevision().toShortString(),
@@ -631,64 +608,7 @@
// Extra packages are similar if they have the same path and vendor
if (pkg instanceof ExtraPackage) {
ExtraPackage ep = (ExtraPackage) pkg;
-
- String[] epOldPaths = ep.getOldPaths();
- int lenEpOldPaths = epOldPaths.length;
- for (int indexEp = -1; indexEp < lenEpOldPaths; indexEp++) {
- if (sameVendorAndPath(
- mVendorId, mPath,
- ep.mVendorId, indexEp < 0 ? ep.mPath : epOldPaths[indexEp])) {
- return true;
- }
- }
-
- String[] thisOldPaths = getOldPaths();
- int lenThisOldPaths = thisOldPaths.length;
- for (int indexThis = -1; indexThis < lenThisOldPaths; indexThis++) {
- if (sameVendorAndPath(
- mVendorId, indexThis < 0 ? mPath : thisOldPaths[indexThis],
- ep.mVendorId, ep.mPath)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static boolean sameVendorAndPath(
- String thisVendor, String thisPath,
- String otherVendor, String otherPath) {
- // To be backward compatible, we need to support the old vendor-path form
- // in either the current or the remote package.
- //
- // The vendor test below needs to account for an old installed package
- // (e.g. with an install path of vendor-name) that has then been updated
- // in-place and thus when reloaded contains the vendor name in both the
- // path and the vendor attributes.
- if (otherPath != null && thisPath != null && thisVendor != null) {
- if (otherPath.equals(thisVendor + '-' + thisPath) &&
- (otherVendor == null ||
- otherVendor.length() == 0 ||
- otherVendor.equals(thisVendor))) {
- return true;
- }
- }
- if (thisPath != null && otherPath != null && otherVendor != null) {
- if (thisPath.equals(otherVendor + '-' + otherPath) &&
- (thisVendor == null ||
- thisVendor.length() == 0 ||
- thisVendor.equals(otherVendor))) {
- return true;
- }
- }
-
-
- if (thisPath != null && thisPath.equals(otherPath)) {
- if ((thisVendor == null && otherVendor == null) ||
- (thisVendor != null && thisVendor.equals(otherVendor))) {
- return true;
- }
+ return PkgDescExtra.compatibleVendorAndPath(mPkgDesc, ep.mPkgDesc);
}
return false;
@@ -706,7 +626,7 @@
int pos = s.indexOf("|r:"); //$NON-NLS-1$
assert pos > 0;
s = s.substring(0, pos) +
- "|ve:" + getVendorId() + //$NON-NLS-1$
+ "|ve:" + getVendorId() + //$NON-NLS-1$
"|pa:" + getPath() + //$NON-NLS-1$
s.substring(pos);
return s;
@@ -737,7 +657,7 @@
result = prime * result + mMinApiLevel;
result = prime * result + ((mPath == null) ? 0 : mPath.hashCode());
result = prime * result + Arrays.hashCode(mProjectFiles);
- result = prime * result + ((mVendorDisplay == null) ? 0 : mVendorDisplay.hashCode());
+ result = prime * result + ((mVendor == null) ? 0 : mVendor.hashCode());
return result;
}
@@ -766,11 +686,11 @@
if (!Arrays.equals(mProjectFiles, other.mProjectFiles)) {
return false;
}
- if (mVendorDisplay == null) {
- if (other.mVendorDisplay != null) {
+ if (mVendor == null) {
+ if (other.mVendor != null) {
return false;
}
- } else if (!mVendorDisplay.equals(other.mVendorDisplay)) {
+ } else if (!mVendor.equals(other.mVendor)) {
return false;
}
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 305d9fa..8cc9a96 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
@@ -16,13 +16,11 @@
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.FullRevision.PreviewComparison;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.repository.FullRevision.PreviewComparison;
import org.w3c.dom.Node;
@@ -74,13 +72,10 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
- super(source, props, revision, license, description, descUrl,
- archiveOs, archiveArch, archiveOsPath);
+ super(source, props, revision, license, description, descUrl, archiveOsPath);
- FullRevision rev = PackageParserUtils.getPropertyFullRevision(props);
+ FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
if (rev == null) {
rev = new FullRevision(revision);
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/License.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/License.java
new file mode 100755
index 0000000..1edf1c6
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/License.java
@@ -0,0 +1,110 @@
+/*
+ * 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.android.sdklib.internal.repository.packages;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * License text, with an optional license XML reference.
+ */
+public class License {
+ private final String mLicense;
+ private final String mLicenseRef;
+
+ public License(@NonNull String license) {
+ mLicense = license;
+ mLicenseRef = null;
+ }
+
+ public License(@NonNull String license, @Nullable String licenseRef) {
+ mLicense = license;
+ mLicenseRef = licenseRef;
+ }
+
+ /** Returns the license text. Never null. */
+ @NonNull
+ public String getLicense() {
+ return mLicense;
+ }
+
+ /**
+ * Returns the license XML reference.
+ * Could be null, e.g. in tests or synthetic packages
+ * recreated from local source.properties.
+ */
+ @Nullable
+ public String getLicenseRef() {
+ return mLicenseRef;
+ }
+
+ /**
+ * Returns a string representation of the license, useful for debugging.
+ * This is not designed to be shown to the user.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<License ref:")
+ .append(mLicenseRef)
+ .append(", text:")
+ .append(mLicense)
+ .append(">");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((mLicense == null) ? 0 : mLicense.hashCode());
+ result = prime * result
+ + ((mLicenseRef == null) ? 0 : mLicenseRef.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof License)) {
+ return false;
+ }
+ License other = (License) obj;
+ if (mLicense == null) {
+ if (other.mLicense != null) {
+ return false;
+ }
+ } else if (!mLicense.equals(other.mLicense)) {
+ return false;
+ }
+ if (mLicenseRef == null) {
+ if (other.mLicenseRef != null) {
+ return false;
+ }
+ } else if (!mLicenseRef.equals(other.mLicenseRef)) {
+ return false;
+ }
+ return true;
+ }
+}
+
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 d3c56c8..982c3c5 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
@@ -16,8 +16,6 @@
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.MajorRevision;
@@ -73,11 +71,8 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
- super(source, props, revision, license, description, descUrl,
- archiveOs, archiveArch, archiveOsPath);
+ super(source, props, revision, license, description, descUrl, archiveOsPath);
String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
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
index 497741f..56fbf03 100755
--- 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
@@ -16,8 +16,6 @@
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;
@@ -66,8 +64,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
String revStr = Package.getProperty(props, PkgProps.MIN_TOOLS_REV, null);
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 a9069e9..1fcbf71 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
@@ -16,8 +16,6 @@
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;
@@ -65,11 +63,8 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
- super(source, props, revision, license, description, descUrl,
- archiveOs, archiveArch, archiveOsPath);
+ super(source, props, revision, license, description, descUrl, archiveOsPath);
mMinToolsMixin = new MinToolsMixin(
source,
@@ -78,8 +73,6 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
}
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
index 33274e7..9e122a6 100755
--- 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
@@ -16,8 +16,6 @@
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;
@@ -72,11 +70,8 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
- super(source, props, revision, license, description, descUrl,
- archiveOs, archiveArch, archiveOsPath);
+ super(source, props, revision, license, description, descUrl, archiveOsPath);
String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
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 1ecf267..6b4326e 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
@@ -27,8 +27,6 @@
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;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkAddonSource;
import com.android.sdklib.internal.repository.sources.SdkRepoSource;
import com.android.sdklib.internal.repository.sources.SdkSource;
@@ -37,6 +35,7 @@
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkAddonConstants;
import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.w3c.dom.Node;
@@ -63,9 +62,12 @@
private final String mObsolete;
private final License mLicense;
+ private final String mListDisplay;
private final String mDescription;
private final String mDescUrl;
+ @Deprecated
private final String mReleaseNote;
+ @Deprecated
private final String mReleaseUrl;
private final Archive[] mArchives;
private final SdkSource mSource;
@@ -76,93 +78,6 @@
SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ||
SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX;
- /** License text, with an optional license XML reference. */
- public static class License {
- private final String mLicense;
- private final String mLicenseRef;
-
- public License(@NonNull String license) {
- mLicense = license;
- mLicenseRef = null;
- }
-
- public License(@NonNull String license, @Nullable String licenseRef) {
- mLicense = license;
- mLicenseRef = licenseRef;
- }
-
- /** Returns the license text. Never null. */
- @NonNull
- public String getLicense() {
- return mLicense;
- }
-
- /**
- * Returns the license XML reference.
- * Could be null, e.g. in tests or synthetic packages
- * recreated from local source.properties.
- */
- @Nullable
- public String getLicenseRef() {
- return mLicenseRef;
- }
-
- /**
- * Returns a string representation of the license, useful for debugging.
- * This is not designed to be shown to the user.
- */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("<License ref:")
- .append(mLicenseRef)
- .append(", text:")
- .append(mLicense)
- .append(">");
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result
- + ((mLicense == null) ? 0 : mLicense.hashCode());
- result = prime * result
- + ((mLicenseRef == null) ? 0 : mLicenseRef.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof License)) {
- return false;
- }
- License other = (License) obj;
- if (mLicense == null) {
- if (other.mLicense != null) {
- return false;
- }
- } else if (!mLicense.equals(other.mLicense)) {
- return false;
- }
- if (mLicenseRef == null) {
- if (other.mLicenseRef != null) {
- return false;
- }
- } else if (!mLicenseRef.equals(other.mLicenseRef)) {
- return false;
- }
- return true;
- }
- }
-
/**
* Enum for the result of {@link Package#canBeUpdatedBy(Package)}. This used so that we can
* differentiate between a package that is totally incompatible, and one that is the same item
@@ -192,6 +107,8 @@
*/
Package(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
mSource = source;
+ mListDisplay =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_LIST_DISPLAY);
mDescription =
PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESCRIPTION);
mDescUrl =
@@ -224,8 +141,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
if (description == null) {
@@ -237,6 +152,7 @@
mLicense = new License(getProperty(props, PkgProps.PKG_LICENSE, license),
getProperty(props, PkgProps.PKG_LICENSE_REF, null));
+ mListDisplay = getProperty(props, PkgProps.PKG_LIST_DISPLAY, ""); //$NON-NLS-1$
mDescription = getProperty(props, PkgProps.PKG_DESC, description);
mDescUrl = getProperty(props, PkgProps.PKG_DESC_URL, descUrl);
mReleaseNote = getProperty(props, PkgProps.PKG_RELEASE_NOTE, ""); //$NON-NLS-1$
@@ -262,10 +178,18 @@
// Note: if archiveOsPath is non-null, this makes a local archive (e.g. a locally
// installed package.) If it's null, this makes a remote archive.
- mArchives = initializeArchives(props, archiveOs, archiveArch, archiveOsPath);
+ mArchives = initializeArchives(props, archiveOsPath);
}
/**
+ * Returns the {@link IPkgDesc} describing this package's meta data.
+ *
+ * @return A non-null {@link IPkgDesc}.
+ */
+ @NonNull
+ public abstract IPkgDesc getPkgDesc();
+
+ /**
* Called by the constructor to get the initial {@link #mArchives} array.
* <p/>
* This is invoked by the local-package constructor and allows mock testing
@@ -278,14 +202,10 @@
@VisibleForTesting(visibility=Visibility.PRIVATE)
protected Archive[] initializeArchives(
Properties props,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
return new Archive[] {
new Archive(this,
props,
- archiveOs,
- archiveArch,
archiveOsPath) };
}
@@ -332,7 +252,7 @@
* Save the properties of the current packages in the given {@link Properties} object.
* These properties will later be give the constructor that takes a {@link Properties} object.
*/
- public void saveProperties(Properties props) {
+ public void saveProperties(@NonNull Properties props) {
if (mLicense != null) {
String license = mLicense.getLicense();
if (license != null && license.length() > 0) {
@@ -343,6 +263,9 @@
props.setProperty(PkgProps.PKG_LICENSE_REF, licenseRef);
}
}
+ if (mListDisplay != null && mListDisplay.length() > 0) {
+ props.setProperty(PkgProps.PKG_LIST_DISPLAY, mListDisplay);
+ }
if (mDescription != null && mDescription.length() > 0) {
props.setProperty(PkgProps.PKG_DESC, mDescription);
}
@@ -369,7 +292,8 @@
* definition if there's one. Returns null if there's no uses-license element or no
* license of this name defined.
*/
- private License parseLicense(Node packageNode, Map<String, String> licenses) {
+ @Nullable
+ private License parseLicense(@NonNull Node packageNode, @NonNull Map<String, String> licenses) {
Node usesLicense =
PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_USES_LICENSE);
if (usesLicense != null) {
@@ -386,7 +310,8 @@
* Parses an XML node to process the <archives> element.
* Always return a non-null array. The array may be empty.
*/
- private Archive[] parseArchives(Node archivesNode) {
+ @NonNull
+ private Archive[] parseArchives(@NonNull Node archivesNode) {
ArrayList<Archive> archives = new ArrayList<Archive>();
if (archivesNode != null) {
@@ -409,13 +334,11 @@
/**
* Parses one <archive> element from an <archives> container.
*/
- private Archive parseArchive(Node archiveNode) {
+ @NonNull
+ private Archive parseArchive(@NonNull Node archiveNode) {
Archive a = new Archive(
this,
- (Os) PackageParserUtils.getEnumAttribute(
- archiveNode, SdkRepoConstants.ATTR_OS, Os.values(), null),
- (Arch) PackageParserUtils.getEnumAttribute(
- archiveNode, SdkRepoConstants.ATTR_ARCH, Arch.values(), Arch.ANY),
+ PackageParserUtils.parseArchFilter(archiveNode),
PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_URL),
PackageParserUtils.getXmlLong (archiveNode, SdkRepoConstants.NODE_SIZE, 0),
PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_CHECKSUM)
@@ -427,6 +350,7 @@
/**
* Returns the source that created (and owns) this package. Can be null.
*/
+ @Nullable
public SdkSource getParentSource() {
return mSource;
}
@@ -443,28 +367,50 @@
* 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.
*/
+ @NonNull
public abstract FullRevision getRevision();
/**
* Returns the optional description for all packages (platform, add-on, tool, doc) or
* for a lib. It is null if the element has not been specified in the repository XML.
*/
+ @Nullable
public License getLicense() {
return mLicense;
}
/**
* Returns the optional description for all packages (platform, add-on, tool, doc) or
- * for a lib. Can be empty but not null.
+ * for a lib. This is the raw description available from the XML meta data and is typically
+ * only used internally.
+ * <p/>
+ * For actual display in the UI, use the methods from {@link IDescription} instead.
+ * <p/>
+ * Can be empty but not null.
*/
- public String getDescription() {
+ @NonNull
+ protected String getDescription() {
return mDescription;
}
/**
+ * Returns the optional list-display for all packages as defined in the XML meta data
+ * and is typically only used internally.
+ * <p/>
+ * For actual display in the UI, use {@link IListDescription} instead.
+ * <p/>
+ * Can be empty but not null.
+ */
+ @NonNull
+ public String getListDisplay() {
+ return mListDisplay;
+ }
+
+ /**
* Returns the optional description URL for all packages (platform, add-on, tool, doc).
* Can be empty but not null.
*/
+ @NonNull
public String getDescUrl() {
return mDescUrl;
}
@@ -473,6 +419,7 @@
* Returns the optional release note for all packages (platform, add-on, tool, doc) or
* for a lib. Can be empty but not null.
*/
+ @NonNull
public String getReleaseNote() {
return mReleaseNote;
}
@@ -481,6 +428,7 @@
* Returns the optional release note URL for all packages (platform, add-on, tool, doc).
* Can be empty but not null.
*/
+ @NonNull
public String getReleaseNoteUrl() {
return mReleaseUrl;
}
@@ -489,6 +437,7 @@
* Returns the archives defined in this package.
* Can be an empty array but not null.
*/
+ @NonNull
public Archive[] getArchives() {
return mArchives;
}
@@ -545,6 +494,7 @@
* If you need a strong unique identifier, you should use {@link #comparisonKey()}
* and the {@link Comparable} interface.
*/
+ @NonNull
public abstract String installId();
/**
@@ -554,6 +504,7 @@
* This is mostly helpful for debugging.
* For UI display, use the {@link IDescription} interface.
*/
+ @NonNull
@Override
public String toString() {
String s = getShortDescription();
@@ -565,13 +516,21 @@
/**
* Returns a description of this package that is suitable for a list display.
- * Should not be empty. Must never be null.
+ * Should not be empty. Can never be null.
* <p/>
- * Note that this is the "base" name for the package
- * with no specific revision nor API mentioned.
+ * Derived classes should use {@link #getListDisplay()} if it's not empty.
+ * <p/>
+ * When it is empty, the default behavior is to recompute a string that depends
+ * on the package type.
+ * <p/>
+ * In both cases, the string should indicate whether the package is marked as obsolete.
+ * <p/>
+ * Note that this is the "base" name for the package with no specific revision nor API
+ * mentioned as this is likely used in a table that will already provide these details.
* In contrast, {@link #getShortDescription()} should be used if you want more details
- * such as the package revision number or the API, if applicable.
+ * such as the package revision number or the API, if applicable, all in the same string.
*/
+ @NonNull
@Override
public abstract String getListDescription();
@@ -579,6 +538,7 @@
* Returns a short description for an {@link IDescription}.
* Can be empty but not null.
*/
+ @NonNull
@Override
public abstract String getShortDescription();
@@ -586,6 +546,7 @@
* Returns a long description for an {@link IDescription}.
* Can be empty but not null.
*/
+ @NonNull
@Override
public String getLongDescription() {
StringBuilder sb = new StringBuilder();
@@ -647,6 +608,7 @@
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
+ @NonNull
public abstract File getInstallFolder(String osSdkRoot, SdkManager sdkManager);
/**
@@ -746,6 +708,7 @@
*
* @see #sameItemAs(Package)
*/
+ @NonNull
public abstract UpdateInfo canBeUpdatedBy(Package replacementPackage);
/**
@@ -805,6 +768,7 @@
* For example an extra vendor name & path can be inserted before the revision
* number, since it has more sorting weight.
*/
+ @NonNull
protected String comparisonKey() {
StringBuilder sb = new StringBuilder();
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 993a348..b19bcfb 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
@@ -18,6 +18,11 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.archives.ArchFilter;
+import com.android.sdklib.internal.repository.archives.BitSize;
+import com.android.sdklib.internal.repository.archives.HostOs;
+import com.android.sdklib.internal.repository.archives.LegacyArch;
+import com.android.sdklib.internal.repository.archives.LegacyOs;
import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.NoPreviewRevision;
import com.android.sdklib.repository.MajorRevision;
@@ -29,11 +34,60 @@
import java.util.Properties;
/**
- * Misc utilities to help extracting elements and attributes out of an XML document.
+ * Misc utilities to help extracting elements and attributes out of a repository XML document.
*/
public class PackageParserUtils {
/**
+ * Parse the {@link ArchFilter} of an <archive> element..
+ * <p/>
+ * Starting with repo schema 10, add-on schema 7 and sys-img schema 3, this is done using
+ * specific optional elements contained within the <archive> element.
+ * <p/>
+ * If none of the new element are defined, for backward compatibility we try to find
+ * the previous style XML attributes "os" and "arch" in the <archive> element.
+ *
+ * @param archiveNode
+ * @return A new {@link ArchFilter}
+ */
+ @NonNull
+ public static ArchFilter parseArchFilter(@NonNull Node archiveNode) {
+ String hos = PackageParserUtils.getOptionalXmlString(archiveNode, SdkRepoConstants.NODE_HOST_OS);
+ String hb = PackageParserUtils.getOptionalXmlString(archiveNode, SdkRepoConstants.NODE_HOST_BITS);
+ String jb = PackageParserUtils.getOptionalXmlString(archiveNode, SdkRepoConstants.NODE_JVM_BITS);
+ String mjv = PackageParserUtils.getOptionalXmlString(archiveNode, SdkRepoConstants.NODE_MIN_JVM_VERSION);
+
+ if (hos != null || hb != null || jb != null || mjv != null) {
+ NoPreviewRevision rev = null;
+ try {
+ rev = NoPreviewRevision.parseRevision(mjv);
+ } catch (NumberFormatException ignore) {}
+
+ return new ArchFilter(
+ HostOs.fromXmlName(hos),
+ BitSize.fromXmlName(hb),
+ BitSize.fromXmlName(jb),
+ rev);
+ }
+
+ Properties props = new Properties();
+
+ LegacyOs o = (LegacyOs) PackageParserUtils.getEnumAttribute(
+ archiveNode, SdkRepoConstants.LEGACY_ATTR_OS, LegacyOs.values(), null);
+ if (o != null) {
+ props.setProperty(ArchFilter.LEGACY_PROP_OS, o.toString());
+ }
+
+ LegacyArch a = (LegacyArch) PackageParserUtils.getEnumAttribute(
+ archiveNode, SdkRepoConstants.LEGACY_ATTR_ARCH, LegacyArch.values(), null);
+ if (a != null) {
+ props.setProperty(ArchFilter.LEGACY_PROP_ARCH, a.toString());
+ }
+
+ return new ArchFilter(props);
+ }
+
+ /**
* Parses a full revision element such as <revision> or <min-tools-rev>.
* This supports both the single-integer format as well as the full revision
* format with major/minor/micro/preview sub-elements.
@@ -117,17 +171,20 @@
}
/**
- * Returns the first child element with the given XML local name.
+ * Returns the first child element with the given XML local name and the same NS URI.
* If xmlLocalName is null, returns the very first child element.
*/
public static Node findChildElement(Node node, String xmlLocalName) {
if (node != null) {
String nsUri = node.getNamespaceURI();
for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {
- return child;
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ String nsUriChild = child.getNamespaceURI();
+ if ((nsUri == null && nsUriChild == null) ||
+ (nsUri != null && nsUri.equals(nsUriChild))) {
+ if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {
+ return child;
+ }
}
}
}
@@ -149,9 +206,26 @@
* is missing or empty, so you can't tell the difference.
*/
public static String getXmlString(Node node, String xmlLocalName) {
- Node child = findChildElement(node, xmlLocalName);
+ return getXmlString(node, xmlLocalName, ""); //$NON-NLS-1$
+ }
- return child == null ? "" : child.getTextContent(); //$NON-NLS-1$
+ /**
+ * Retrieves the value of that XML element as a string.
+ * Returns the defaultValue if the element is missing or empty.
+ * <p/>
+ * Note: use {@link #getOptionalXmlString(Node, String)} if you need to know when the
+ * element is missing versus empty.
+ *
+ * @param node The XML <em>parent</em> node to parse.
+ * @param xmlLocalName The XML local name to find in the parent node.
+ * @param defaultValue A default value to return if the element is missing.
+ * @return The text content of the element
+ * or the defaultValue if the element is missing or empty.
+ */
+ public static String getXmlString(Node node, String xmlLocalName, String defaultValue) {
+ Node child = findChildElement(node, xmlLocalName);
+ String content = child == null ? null : child.getTextContent();
+ return content == null || content.isEmpty() ? defaultValue : content;
}
/**
@@ -169,7 +243,6 @@
*/
public static String getOptionalXmlString(Node node, String xmlLocalName) {
Node child = findChildElement(node, xmlLocalName);
-
return child == null ? null : child.getTextContent(); //$NON-NLS-1$
}
@@ -279,10 +352,13 @@
*
* @param props The properties to parse.
* @return A {@link FullRevision} or null if there is no such property or it couldn't be parsed.
+ * @param propKey The name of the property. Must not be null.
*/
@Nullable
- public static FullRevision getPropertyFullRevision(@Nullable Properties props) {
- String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+ public static FullRevision getPropertyFull(
+ @Nullable Properties props,
+ @NonNull String propKey) {
+ String revStr = getProperty(props, propKey, null);
FullRevision rev = null;
if (revStr != null) {
@@ -300,10 +376,13 @@
*
* @param props The properties to parse.
* @return A {@link MajorRevision} or null if there is no such property or it couldn't be parsed.
+ * @param propKey The name of the property. Must not be null.
*/
@Nullable
- public static MajorRevision getPropertyMajorRevision(@Nullable Properties props) {
- String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+ public static MajorRevision getPropertyMajor(
+ @Nullable Properties props,
+ @NonNull String propKey) {
+ String revStr = getProperty(props, propKey, null);
MajorRevision rev = null;
if (revStr != null) {
@@ -322,10 +401,13 @@
* @param props The properties to parse.
* @return A {@link NoPreviewRevision} or
* null if there is no such property or it couldn't be parsed.
+ * @param propKey The name of the property. Must not be null.
*/
@Nullable
- public static NoPreviewRevision getPropertyNoPreviewRevision(@Nullable Properties props) {
- String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+ public static NoPreviewRevision getPropertyNoPreview(
+ @Nullable Properties props,
+ @NonNull String propKey) {
+ String revStr = getProperty(props, propKey, null);
NoPreviewRevision rev = null;
if (revStr != null) {
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 31f36be..2ddc8e1 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
@@ -26,11 +26,12 @@
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
-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.MajorRevision;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import com.android.utils.Pair;
import org.w3c.dom.Node;
@@ -57,6 +58,8 @@
/** The helper handling the layoutlib version. */
private final LayoutlibVersionMixin mLayoutlibVersion;
+ private final IPkgDesc mPkgDesc;
+
/**
* Creates a new platform package from the attributes and elements of the given XML node.
* This constructor should throw an exception if the package cannot be created.
@@ -90,6 +93,13 @@
SdkRepoConstants.NODE_ABI_INCLUDED);
mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
+
+ mPkgDesc = PkgDesc.Builder
+ .newPlatform(mVersion,
+ (MajorRevision) getRevision(),
+ getMinToolsRevision())
+ .setDescriptions(this)
+ .create();
}
/**
@@ -119,8 +129,6 @@
null, //license
target.getDescription(), //description
null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
target.getLocation() //archiveOsPath
);
@@ -128,6 +136,19 @@
mVersionName = target.getVersionName();
mLayoutlibVersion = new LayoutlibVersionMixin(props);
mIncludedAbi = props == null ? null : props.getProperty(PkgProps.PLATFORM_INCLUDED_ABI);
+
+ mPkgDesc = PkgDesc.Builder
+ .newPlatform(mVersion,
+ (MajorRevision) getRevision(),
+ getMinToolsRevision())
+ .setDescriptions(this)
+ .create();
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -207,8 +228,12 @@
*/
@Override
public String getListDescription() {
- String s;
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
+ String s;
if (mVersion.isPreview()) {
s = String.format("SDK Platform Android %1$s Preview%2$s",
getVersionName(),
@@ -227,8 +252,15 @@
*/
@Override
public String getShortDescription() {
- String s;
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, revision %2$s%3$s",
+ ld,
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+ String s;
if (mVersion.isPreview()) {
s = String.format("SDK Platform Android %1$s Preview, revision %2$s%3$s",
getVersionName(),
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
index 0930f86..44d40db 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
@@ -17,6 +17,7 @@
package com.android.sdklib.internal.repository.packages;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.SdkManager;
@@ -24,10 +25,10 @@
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
-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.PreviewComparison;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import org.w3c.dom.Node;
@@ -47,6 +48,8 @@
/** The value returned by {@link PlatformToolPackage#installId()}. */
public static final String INSTALL_ID_PREVIEW = "platform-tools-preview"; //$NON-NLS-1$
+ private final IPkgDesc mPkgDesc;
+
/**
* Creates a new platform-tool package from the attributes and elements of the given XML node.
* This constructor should throw an exception if the package cannot be created.
@@ -60,6 +63,11 @@
public PlatformToolPackage(SdkSource source, Node packageNode,
String nsUri, Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
+
+ mPkgDesc = PkgDesc.Builder
+ .newPlatformTool(getRevision())
+ .setDescriptions(this)
+ .create();
}
/**
@@ -76,12 +84,10 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
PlatformToolPackage ptp = new PlatformToolPackage(source, props, revision, license,
- description, descUrl, archiveOs, archiveArch, archiveOsPath);
+ description, descUrl, archiveOsPath);
File platformToolsFolder = new File(archiveOsPath);
String error = null;
@@ -133,7 +139,10 @@
BrokenPackage ba = new BrokenPackage(props, shortDesc, longDesc,
IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
IExactApiLevelDependency.API_LEVEL_INVALID,
- archiveOsPath);
+ archiveOsPath,
+ PkgDesc.Builder.newPlatformTool(ptp.getRevision())
+ .setDescriptionShort(shortDesc)
+ .create());
return ba;
}
@@ -149,8 +158,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
super(source,
props,
@@ -158,9 +165,18 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
+
+ mPkgDesc = PkgDesc.Builder
+ .newPlatformTool(getRevision())
+ .setDescriptions(this)
+ .create();
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -186,6 +202,11 @@
*/
@Override
public String getListDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
+
return String.format("Android SDK Platform-tools%1$s",
isObsolete() ? " (Obsolete)" : "");
}
@@ -195,6 +216,14 @@
*/
@Override
public String getShortDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, revision %2$s%3$s",
+ ld,
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
return String.format("Android SDK Platform-tools, revision %1$s%2$s",
getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SamplePackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SamplePackage.java
index 06eabb9..8869d3b 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SamplePackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SamplePackage.java
@@ -25,12 +25,13 @@
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
-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.io.IFileOp;
+import com.android.sdklib.repository.MajorRevision;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.w3c.dom.Node;
@@ -60,6 +61,8 @@
*/
private final int mMinApiLevel;
+ private final IPkgDesc mPkgDesc;
+
/**
* Creates a new sample package from the attributes and elements of the given XML node.
* This constructor should throw an exception if the package cannot be created.
@@ -88,6 +91,13 @@
mMinApiLevel = PackageParserUtils.getXmlInt(packageNode,
SdkRepoConstants.NODE_MIN_API_LEVEL,
MIN_API_LEVEL_NOT_SPECIFIED);
+
+ mPkgDesc = PkgDesc.Builder
+ .newSample(mVersion,
+ (MajorRevision) getRevision(),
+ getMinToolsRevision())
+ .setDescriptions(this)
+ .create();
}
/**
@@ -114,8 +124,6 @@
null, //license
null, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
target.getPath(IAndroidTarget.SAMPLES) //archiveOsPath
);
@@ -123,6 +131,13 @@
mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,
MIN_API_LEVEL_NOT_SPECIFIED);
+
+ mPkgDesc = PkgDesc.Builder
+ .newSample(mVersion,
+ (MajorRevision) getRevision(),
+ getMinToolsRevision())
+ .setDescriptions(this)
+ .create();
}
/**
@@ -149,8 +164,6 @@
null, //license
null, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
archiveOsPath //archiveOsPath
);
@@ -158,6 +171,19 @@
mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,
MIN_API_LEVEL_NOT_SPECIFIED);
+
+ mPkgDesc = PkgDesc.Builder
+ .newSample(mVersion,
+ (MajorRevision) getRevision(),
+ getMinToolsRevision())
+ .setDescriptions(this)
+ .create();
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -208,6 +234,11 @@
*/
@Override
public String getListDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
+
String s = String.format("Samples for SDK API %1$s%2$s%3$s",
mVersion.getApiString(),
mVersion.isPreview() ? " Preview" : "",
@@ -220,6 +251,14 @@
*/
@Override
public String getShortDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, revision %2$s%3$s",
+ ld,
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
String s = String.format("Samples for SDK API %1$s%2$s, revision %3$s%4$s",
mVersion.getApiString(),
mVersion.isPreview() ? " Preview" : "",
@@ -498,7 +537,7 @@
}
try {
- md.update(name.getBytes("UTF-8")); //$NON-NLS-1$
+ md.update(name.getBytes(SdkConstants.UTF_8));
} catch (UnsupportedEncodingException e) {
// There is no valid reason for UTF-8 to be unsupported. Ignore.
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SourcePackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SourcePackage.java
index 7d65634..71b4e77 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SourcePackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SourcePackage.java
@@ -26,11 +26,12 @@
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
-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.io.IFileOp;
+import com.android.sdklib.repository.MajorRevision;
import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.w3c.dom.Node;
@@ -51,6 +52,8 @@
/** The package version, for platform, add-on and doc packages. */
private final AndroidVersion mVersion;
+ private final IPkgDesc mPkgDesc;
+
/**
* Creates a new source package from the attributes and elements of the given XML node.
* This constructor should throw an exception if the package cannot be created.
@@ -76,6 +79,12 @@
codeName = null;
}
mVersion = new AndroidVersion(apiLevel, codeName);
+
+ mPkgDesc = PkgDesc.Builder
+ .newSource(mVersion,
+ (MajorRevision) getRevision())
+ .setDescriptions(this)
+ .create();
}
@VisibleForTesting(visibility=Visibility.PRIVATE)
@@ -100,11 +109,15 @@
null, //license
null, //description
null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
localOsPath //archiveOsPath
);
mVersion = platformVersion;
+
+ mPkgDesc = PkgDesc.Builder
+ .newSource(mVersion,
+ (MajorRevision) getRevision())
+ .setDescriptions(this)
+ .create();
}
/**
@@ -168,10 +181,23 @@
String longDesc = sb.toString();
+ IPkgDesc desc = PkgDesc.Builder
+ .newSource(version != null ? version : new AndroidVersion(0, null),
+ new MajorRevision(MajorRevision.MISSING_MAJOR_REV))
+ .setDescriptionShort(shortDesc)
+ .create();
+
return new BrokenPackage(props, shortDesc, longDesc,
IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
version==null ? IExactApiLevelDependency.API_LEVEL_INVALID : version.getApiLevel(),
- srcDir.getAbsolutePath());
+ srcDir.getAbsolutePath(),
+ desc);
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -210,6 +236,11 @@
*/
@Override
public String getListDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
+
if (mVersion.isPreview()) {
return String.format("Sources for Android '%1$s' Preview SDK%2$s",
mVersion.getCodename(),
@@ -226,6 +257,14 @@
*/
@Override
public String getShortDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, revision %2$s%3$s",
+ ld,
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
if (mVersion.isPreview()) {
return String.format("Sources for Android '%1$s' Preview SDK, revision %2$s%3$s",
mVersion.getCodename(),
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SystemImagePackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
index 69335a5..d7d0048 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
@@ -18,18 +18,26 @@
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.SdkManager;
import com.android.sdklib.SystemImage;
+import com.android.sdklib.devices.Abi;
import com.android.sdklib.internal.repository.IDescription;
-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.MajorRevision;
import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkAddonConstants;
import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.repository.SdkSysImgConstants;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repository.local.LocalSysImgPkgInfo;
import org.w3c.dom.Node;
@@ -37,6 +45,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
+import java.util.regex.Pattern;
/**
* Represents a system-image XML node in an SDK repository.
@@ -50,6 +59,11 @@
/** The ABI of the system-image. Must not be null nor empty. */
private final String mAbi;
+ private final IPkgDesc mPkgDesc;
+
+ private final IdDisplay mTag;
+ private final IdDisplay mAddonVendor;
+
/**
* Creates a new system-image package from the attributes and elements of the given XML node.
* This constructor should throw an exception if the package cannot be created.
@@ -76,6 +90,63 @@
mVersion = new AndroidVersion(apiLevel, codeName);
mAbi = PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI);
+
+ // tag id
+ String tagId = PackageParserUtils.getXmlString(packageNode,
+ SdkSysImgConstants.ATTR_TAG_ID,
+ SystemImage.DEFAULT_TAG.getId());
+ String tagDisp = PackageParserUtils.getOptionalXmlString(packageNode,
+ SdkSysImgConstants.ATTR_TAG_DISPLAY);
+ if (tagDisp == null || tagDisp.isEmpty()) {
+ tagDisp = LocalSysImgPkgInfo.tagIdToDisplay(tagId);
+ }
+ assert tagId != null;
+ assert tagDisp != null;
+ mTag = new IdDisplay(tagId, tagDisp);
+
+
+ Node addonNode =
+ PackageParserUtils.findChildElement(packageNode, SdkSysImgConstants.NODE_ADD_ON);
+
+ IPkgDesc desc = null;
+ IdDisplay vendor = null;
+
+ if (addonNode == null) {
+ // A platform system-image
+ desc = PkgDesc.Builder
+ .newSysImg(mVersion,
+ mTag,
+ mAbi,
+ (MajorRevision) getRevision())
+ .setDescriptions(this)
+ .create();
+ } else {
+ // An add-on system-image
+ String vendorId = PackageParserUtils.getXmlString(
+ addonNode,
+ SdkAddonConstants.NODE_VENDOR_ID);
+ String vendorDisp = PackageParserUtils.getXmlString(
+ addonNode,
+ SdkAddonConstants.NODE_VENDOR_DISPLAY,
+ vendorId);
+
+ assert vendorId.length() > 0;
+ assert vendorDisp.length() > 0;
+
+ vendor = new IdDisplay(vendorId, vendorDisp);
+
+ desc = PkgDesc.Builder
+ .newAddonSysImg(mVersion,
+ mTag,
+ vendor,
+ mAbi,
+ (MajorRevision) getRevision())
+ .setDescriptions(this)
+ .create();
+ }
+
+ mPkgDesc = desc;
+ mAddonVendor = vendor;
}
@VisibleForTesting(visibility=Visibility.PRIVATE)
@@ -102,8 +173,6 @@
null, //license
null, //description
null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
localOsPath //archiveOsPath
);
mVersion = platformVersion;
@@ -112,13 +181,50 @@
}
assert abi != null : "To use this SystemImagePackage constructor you must pass an ABI as a parameter or as a PROP_ABI property";
mAbi = abi;
+
+ mTag = LocalSysImgPkgInfo.extractTagFromProps(props);
+
+ String vendorId = getProperty(props, PkgProps.ADDON_VENDOR_ID, null);
+ String vendorDisp = getProperty(props, PkgProps.ADDON_VENDOR_DISPLAY, vendorId);
+
+ IPkgDesc desc = null;
+ IdDisplay vendor = null;
+
+ if (vendorId == null) {
+ // A platform system-image
+ desc = PkgDesc.Builder
+ .newSysImg(mVersion,
+ mTag,
+ mAbi,
+ (MajorRevision) getRevision())
+ .setDescriptions(this)
+ .create();
+ } else {
+ // An add-on system-image
+ assert vendorId.length() > 0;
+ assert vendorDisp.length() > 0;
+
+ vendor = new IdDisplay(vendorId, vendorDisp);
+
+ desc = PkgDesc.Builder
+ .newAddonSysImg(mVersion,
+ mTag,
+ vendor,
+ mAbi,
+ (MajorRevision) getRevision())
+ .setDescriptions(this)
+ .create();
+ }
+
+ mPkgDesc = desc;
+ mAddonVendor = vendor;
}
/**
* Creates a {@link BrokenPackage} representing a system image that failed to load
* with the regular {@link SdkManager} workflow.
*
- * @param abiDir The SDK/system-images/android-N/abi folder
+ * @param abiDir The SDK/system-images/android-N/tag/abi folder
* @param props The properties located in {@code abiDir} or null if not found.
* @return A new {@link BrokenPackage} that represents this installed package.
*/
@@ -126,8 +232,9 @@
AndroidVersion version = null;
String abiType = abiDir.getName();
String error = null;
+ IdDisplay tag = null;
- // Try to load the android version & ABI from the sources.props.
+ // Try to load the android version, tag & ABI from the sources.props.
// If we don't find them, it would explain why this package is broken.
if (props == null) {
error = String.format("Missing file %1$s", SdkConstants.FN_SOURCE_PROP);
@@ -135,6 +242,7 @@
try {
version = new AndroidVersion(props);
+ tag = LocalSysImgPkgInfo.extractTagFromProps(props);
String abi = props.getProperty(PkgProps.SYS_IMG_ABI);
if (abi != null) {
abiType = abi;
@@ -150,23 +258,57 @@
}
}
- if (version == null) {
- try {
- // Try to parse the first number out of the platform folder name.
- String platform = abiDir.getParentFile().getName();
- platform = platform.replaceAll("[^0-9]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- int pos = platform.indexOf(' ');
- if (pos >= 0) {
- platform = platform.substring(0, pos);
+ try {
+ // Try to parse the first number out of the platform folder name.
+ // Also try to parse the tag if not known yet.
+ // Folder structure should be:
+ // Tools < 22.6 / API < 20: sdk/system-images/android-N/abi/
+ // Tools >=22.6 / API >=20: sdk/system-images/android-N/tag/abi/
+ String[] segments = abiDir.getAbsolutePath().split(Pattern.quote(File.separator));
+ int len = segments.length;
+ for (int i = len - 2; version == null && i >= 0; i--) {
+ if (SdkConstants.FD_SYSTEM_IMAGES.equals(segments[i])) {
+ if (version == null) {
+ String platform = segments[i+1];
+ try {
+ platform = platform.replaceAll("[^0-9]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
+ int pos = platform.indexOf(' ');
+ if (pos >= 0) {
+ platform = platform.substring(0, pos);
+ }
+ int apiLevel = Integer.parseInt(platform);
+ version = new AndroidVersion(apiLevel, null /*codename*/);
+ } catch (Exception ignore) {}
+ }
+ if (tag == null && i+2 < len) {
+ // If we failed to find a tag id in the properties, check whether
+ // we can guess one from the system image folder path. It should
+ // match the limited tag id character set and not be one of the
+ // known ABIs.
+ String abiOrTag = segments[i+2].trim();
+ if (abiOrTag.matches("[A-Za-z0-9_-]+")) {
+ if (Abi.getEnum(abiOrTag) == null) {
+ tag = new IdDisplay(abiOrTag,
+ LocalSysImgPkgInfo.tagIdToDisplay(abiOrTag));
+ }
+ }
+ }
}
- int apiLevel = Integer.parseInt(platform);
- version = new AndroidVersion(apiLevel, null /*codename*/);
- } catch (Exception ignore) {
}
- }
+ } catch (Exception ignore) {}
- StringBuilder sb = new StringBuilder(
- String.format("Broken %1$s System Image", getAbiDisplayNameInternal(abiType)));
+ String vendorId = getProperty(props, PkgProps.ADDON_VENDOR_ID, null);
+ String vendorDisp = getProperty(props, PkgProps.ADDON_VENDOR_DISPLAY, vendorId);
+
+ StringBuilder sb = new StringBuilder("Broken ");
+ sb.append(getAbiDisplayNameInternal(abiType)).append(' ');
+ if (tag != null && !tag.getId().equals(SystemImage.DEFAULT_TAG.getId())) {
+ sb.append(tag).append(' ');
+ }
+ sb.append("System Image");
+ if (vendorDisp != null) {
+ sb.append(", by ").append(vendorDisp);
+ }
if (version != null) {
sb.append(String.format(", API %1$s", version.getApiString()));
}
@@ -179,10 +321,31 @@
String longDesc = sb.toString();
+ if (tag == null) {
+ // No tag? Use the default.
+ tag = SystemImage.DEFAULT_TAG;
+ }
+ assert tag != null;
+
+ IPkgDesc desc = PkgDesc.Builder
+ .newSysImg(version != null ? version : new AndroidVersion(0, null),
+ tag,
+ abiType,
+ new MajorRevision(MajorRevision.MISSING_MAJOR_REV))
+ .setDescriptionShort(shortDesc)
+ .create();
+
return new BrokenPackage(props, shortDesc, longDesc,
IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
version==null ? IExactApiLevelDependency.API_LEVEL_INVALID : version.getApiLevel(),
- abiDir.getAbsolutePath());
+ abiDir.getAbsolutePath(),
+ desc);
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
/**
@@ -194,7 +357,20 @@
super.saveProperties(props);
mVersion.saveProperties(props);
- props.setProperty(PkgProps.SYS_IMG_ABI, mAbi);
+ props.setProperty(PkgProps.SYS_IMG_ABI, mAbi);
+ props.setProperty(PkgProps.SYS_IMG_TAG_ID, mTag.getId());
+ props.setProperty(PkgProps.SYS_IMG_TAG_DISPLAY, mTag.getDisplay());
+
+ if (mAddonVendor != null) {
+ props.setProperty(PkgProps.ADDON_VENDOR_ID, mAddonVendor.getId());
+ props.setProperty(PkgProps.ADDON_VENDOR_DISPLAY, mAddonVendor.getDisplay());
+ }
+ }
+
+ /** Returns the tag of the system-image. */
+ @NonNull
+ public IdDisplay getTag() {
+ return mTag;
}
/** Returns the ABI of the system-image. Cannot be null nor empty. */
@@ -208,10 +384,12 @@
}
private static String getAbiDisplayNameInternal(String abi) {
- return abi.replace("armeabi", "ARM EABI") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("x86", "Intel x86 Atom") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("mips", "MIPS") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ return abi.replace("armeabi", "ARM EABI") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("arm64", "ARM 64") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("x86", "Intel x86 Atom") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("x86_64", "Intel x86_64 Atom") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("mips", "MIPS") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
@@ -219,12 +397,33 @@
* <p/>
* A system-image has the same {@link AndroidVersion} as the platform it depends on.
*/
- @Override @NonNull
+ @NonNull
+ @Override
public AndroidVersion getAndroidVersion() {
return mVersion;
}
/**
+ * Returns true if the system-image belongs to a standard Android platform.
+ * In this case {@link #getAddonVendor()} returns null.
+ * <p/.
+ * Returns false if the system-image belongs to an add-on.
+ * In this case {@link #getAndroidVersion()} returns a non-null {@link IdDisplay}.
+ */
+ public boolean isPlatform() {
+ return mAddonVendor == null;
+ }
+
+ /**
+ * Returns the add-on vendor if this is an add-on system image.
+ * Returns null if this is a platform system-image.
+ */
+ @Nullable
+ public IdDisplay getAddonVendor() {
+ return mAddonVendor;
+ }
+
+ /**
* Returns a string identifier to install this package from the command line.
* For system images, we use "sysimg-N" where N is the API or the preview codename.
* <p/>
@@ -232,7 +431,22 @@
*/
@Override
public String installId() {
- return "sysimg-" + mVersion.getApiString(); //$NON-NLS-1$
+ StringBuilder sb = new StringBuilder("sys-img-"); //$NON-NLS-1$
+ sb.append(getAbi()).append('-');
+ if (!isPlatform()) {
+ sb.append("addon-");
+ }
+ sb.append(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId());
+ sb.append('-');
+ if (!isPlatform()) {
+ sb.append(getAddonVendor().getId()).append('-');
+ }
+ sb.append(getAndroidVersion().getApiString());
+
+ String s = sb.toString();
+ s = s.toLowerCase(Locale.US).replaceAll("[^a-z0-9_.-]+", "_").replaceAll("_+", "_");
+ return s;
+
}
/**
@@ -242,7 +456,14 @@
*/
@Override
public String getListDescription() {
- return String.format("%1$s System Image%2$s",
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
+ }
+
+ boolean isDefaultTag = SystemImage.DEFAULT_TAG.equals(mTag);
+ return String.format("%1$s%2$s System Image%3$s",
+ isDefaultTag ? "" : (mTag.getDisplay() + " "),
getAbiDisplayName(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -252,8 +473,21 @@
*/
@Override
public String getShortDescription() {
- return String.format("%1$s System Image, Android API %2$s, revision %3$s%4$s",
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, %2$s API %3$s, revision %4$s%5$s",
+ ld,
+ mAddonVendor == null ? "Android" : mAddonVendor.getDisplay(),
+ mVersion.getApiString(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ boolean isDefaultTag = SystemImage.DEFAULT_TAG.equals(mTag);
+ return String.format("%1$s%2$s System Image, %3$s API %4$s, revision %5$s%6$s",
+ isDefaultTag ? "" : (mTag.getDisplay() + " "),
getAbiDisplayName(),
+ mAddonVendor == null ? "Android" : mAddonVendor.getDisplay(),
mVersion.getApiString(),
getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
@@ -287,7 +521,7 @@
* Computes a potential installation folder if an archive of this package were
* to be installed right away in the given SDK root.
* <p/>
- * A system-image package is typically installed in SDK/systems/platform/abi.
+ * A system-image package is typically installed in SDK/systems/platform/tag/abi.
* The name needs to be sanitized to be acceptable as a directory name.
*
* @param osSdkRoot The OS path of the SDK root folder.
@@ -297,13 +531,22 @@
@Override
public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
File folder = new File(osSdkRoot, SdkConstants.FD_SYSTEM_IMAGES);
- folder = new File(folder, SystemImage.ANDROID_PREFIX + mVersion.getApiString());
+ folder = new File(folder, AndroidTargetHash.getPlatformHashString(mVersion));
- // Computes a folder directory using the sanitized abi string.
+ // Computes a folder directory using the sanitized tag & abi strings.
+ String tag = mTag.getId();
+ tag = tag.toLowerCase(Locale.US);
+ tag = tag.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+ tag = tag.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+ tag = tag.replaceAll("-+", "-"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ folder = new File(folder, tag);
+
String abi = mAbi;
abi = abi.toLowerCase(Locale.US);
abi = abi.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
abi = abi.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+ abi = abi.replaceAll("-+", "-"); //$NON-NLS-1$ //$NON-NLS-2$
folder = new File(folder, abi);
return folder;
@@ -314,9 +557,12 @@
if (pkg instanceof SystemImagePackage) {
SystemImagePackage newPkg = (SystemImagePackage)pkg;
- // check they are the same abi and version.
- return getAbi().equals(newPkg.getAbi()) &&
- getAndroidVersion().equals(newPkg.getAndroidVersion());
+ // check they are the same tag, abi and version.
+ return getTag().equals(newPkg.getTag()) &&
+ getAbi().equals(newPkg.getAbi()) &&
+ getAndroidVersion().equals(newPkg.getAndroidVersion()) &&
+ (mAddonVendor == newPkg.mAddonVendor ||
+ (mAddonVendor != null && mAddonVendor.equals(newPkg.mAddonVendor)));
}
return false;
@@ -326,7 +572,9 @@
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
- result = prime * result + ((mAbi == null) ? 0 : mAbi.hashCode());
+ result = prime * result + ((mAddonVendor == null) ? 0 : mAddonVendor.hashCode());
+ result = prime * result + ((mTag == null) ? 0 : mTag.hashCode());
+ result = prime * result + ((mAbi == null) ? 0 : mAbi.hashCode());
result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
return result;
}
@@ -343,6 +591,20 @@
return false;
}
SystemImagePackage other = (SystemImagePackage) obj;
+ if (mAddonVendor == null) {
+ if (other.mAddonVendor != null) {
+ return false;
+ }
+ } else if (!mAddonVendor.equals(other.mAddonVendor)) {
+ return false;
+ }
+ if (mTag == null) {
+ if (other.mTag != null) {
+ return false;
+ }
+ } else if (!mTag.equals(other.mTag)) {
+ return false;
+ }
if (mAbi == null) {
if (other.mAbi != null) {
return false;
@@ -361,7 +623,7 @@
}
/**
- * For sys img packages, we want to add abi to the sorting key
+ * For sys img packages, we want to add tag/abi to the sorting key
* <em>before<em/> the revision number.
* <p/>
* {@inheritDoc}
@@ -369,10 +631,11 @@
@Override
protected String comparisonKey() {
String s = super.comparisonKey();
- int pos = s.indexOf("|r:"); //$NON-NLS-1$
+ int pos = s.indexOf("|r:"); //$NON-NLS-1$
assert pos > 0;
s = s.substring(0, pos) +
- "|abi:" + getAbiDisplayName() + //$NON-NLS-1$
+ "|tag:" + getTag().getId() + //$NON-NLS-1$
+ "|abi:" + getAbiDisplayName() + //$NON-NLS-1$
s.substring(pos);
return s;
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ToolPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ToolPackage.java
index 2f0094d..e866b8a 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ToolPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ToolPackage.java
@@ -17,6 +17,7 @@
package com.android.sdklib.internal.repository.packages;
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;
@@ -24,16 +25,16 @@
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
-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.FullRevision.PreviewComparison;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.repository.FullRevision.PreviewComparison;
-import com.android.sdklib.util.GrabProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.Wait;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.utils.GrabProcessOutput;
+import com.android.utils.GrabProcessOutput.IProcessOutput;
+import com.android.utils.GrabProcessOutput.Wait;
import org.w3c.dom.Node;
@@ -57,6 +58,8 @@
*/
private final FullRevision mMinPlatformToolsRevision;
+ private final IPkgDesc mPkgDesc;
+
/**
* Creates a new tool package from the attributes and elements of the given XML node.
* This constructor should throw an exception if the package cannot be created.
@@ -87,6 +90,12 @@
SdkRepoConstants.NODE_PLATFORM_TOOL));
}
}
+
+ mPkgDesc = PkgDesc.Builder
+ .newTool(getRevision(),
+ mMinPlatformToolsRevision)
+ .setDescriptions(this)
+ .create();
}
/**
@@ -103,11 +112,9 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
return new ToolPackage(source, props, revision, license, description,
- descUrl, archiveOs, archiveArch, archiveOsPath);
+ descUrl, archiveOsPath);
}
@VisibleForTesting(visibility=Visibility.PRIVATE)
@@ -118,8 +125,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
super(source,
props,
@@ -127,8 +132,6 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
// Setup min-platform-tool
@@ -142,6 +145,18 @@
}
mMinPlatformToolsRevision = rev;
+
+ mPkgDesc = PkgDesc.Builder
+ .newTool(getRevision(),
+ mMinPlatformToolsRevision)
+ .setDescriptions(this)
+ .create();
+ }
+
+ @Override
+ @NonNull
+ public IPkgDesc getPkgDesc() {
+ return mPkgDesc;
}
@Override
@@ -171,7 +186,9 @@
*/
@Override
public String getListDescription() {
- return String.format("Android SDK Tools%1$s",
+ String ld = getListDisplay();
+ return String.format("%1$s%2$s",
+ ld.isEmpty() ? "Android SDK Tools" : ld,
isObsolete() ? " (Obsolete)" : "");
}
@@ -180,6 +197,14 @@
*/
@Override
public String getShortDescription() {
+ String ld = getListDisplay();
+ if (!ld.isEmpty()) {
+ return String.format("%1$s, revision %2$s%3$s",
+ ld,
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
return String.format("Android SDK Tools, revision %1$s%2$s",
getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkRepoSource.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
index 09913ed..a79e755 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
@@ -17,8 +17,7 @@
package com.android.sdklib.internal.repository.sources;
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.archives.ArchFilter;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.packages.PackageParserUtils;
import com.android.sdklib.repository.RepoConstants;
@@ -79,17 +78,18 @@
@Override
protected String[] getDefaultXmlFileUrls() {
if (sDefaults == null) {
- sDefaults = new String[SdkRepoConstants.NS_LATEST_VERSION
- - SdkRepoConstants.NS_SERVER_MIN_VERSION
- + 2];
+ String[] values = new String[SdkRepoConstants.NS_LATEST_VERSION
+ - SdkRepoConstants.NS_SERVER_MIN_VERSION
+ + 2];
int k = 0;
for (int i = SdkRepoConstants.NS_LATEST_VERSION;
i >= SdkRepoConstants.NS_SERVER_MIN_VERSION;
i--) {
- sDefaults[k++] = String.format(SdkRepoConstants.URL_FILENAME_PATTERN, i);
+ values[k++] = String.format(SdkRepoConstants.URL_FILENAME_PATTERN, i);
}
- sDefaults[k++] = SdkRepoConstants.URL_DEFAULT_FILENAME;
- assert k == sDefaults.length;
+ values[k++] = SdkRepoConstants.URL_DEFAULT_FILENAME;
+ assert k == values.length;
+ sDefaults = values;
}
return sDefaults;
@@ -350,16 +350,8 @@
prefix,
RepoConstants.NODE_ARCHIVE)) != null) {
try {
- Os os = (Os) PackageParserUtils.getEnumAttribute(archive,
- RepoConstants.ATTR_OS,
- Os.values(),
- null /*default*/);
- Arch arch = (Arch) PackageParserUtils.getEnumAttribute(archive,
- RepoConstants.ATTR_ARCH,
- Arch.values(),
- Arch.ANY);
- if (os == null || !os.isCompatible() ||
- arch == null || !arch.isCompatible()) {
+ ArchFilter af = PackageParserUtils.parseArchFilter(archive);
+ if (af == null || !af.isCompatibleWith(ArchFilter.getCurrent())) {
continue;
}
@@ -401,16 +393,23 @@
isElementValid = true;
} catch (Exception ignore1) {
- // pass
+ // For debugging it is useful to re-throw the exception.
+ // For end-users, not so much. It would be nice to make it
+ // happen automatically during unit tests.
+ if (System.getenv("TESTING") != null ||
+ System.getProperty("THROW_DEEP_EXCEPTION_DURING_TESTING") != null) {
+ throw new RuntimeException(ignore1);
+ }
}
} // while <archive>
} catch (Exception ignore2) {
// For debugging it is useful to re-throw the exception.
// For end-users, not so much. It would be nice to make it
// happen automatically during unit tests.
- if (System.getenv("TESTING") != null) {
- throw new RuntimeException(ignore2);
- }
+ if (System.getenv("TESTING") != null ||
+ System.getProperty("THROW_DEEP_EXCEPTION_DURING_TESTING") != null) {
+ throw new RuntimeException(ignore2);
+ }
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSource.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSource.java
index 3bc3379..fff61b2 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSource.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSource.java
@@ -19,6 +19,8 @@
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.io.NonClosingInputStream;
+import com.android.io.NonClosingInputStream.CloseBehavior;
import com.android.sdklib.internal.repository.CanceledByUserException;
import com.android.sdklib.internal.repository.DownloadCache;
import com.android.sdklib.internal.repository.IDescription;
@@ -34,8 +36,6 @@
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.io.NonClosingInputStream;
-import com.android.sdklib.io.NonClosingInputStream.CloseBehavior;
import com.android.sdklib.repository.RepoConstants;
import com.android.sdklib.repository.SdkAddonConstants;
import com.android.sdklib.repository.SdkRepoConstants;
@@ -894,7 +894,7 @@
Package p = null;
try {
- // We can load addon and extra packages from all sources, either
+ // We can load add-on and extra packages from all sources, either
// internal or user sources.
if (SdkAddonConstants.NODE_ADD_ON.equals(name)) {
p = new AddonPackage(this, child, nsUri, licenses);
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSources.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSources.java
index c89df5e..83f020b 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSources.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSources.java
@@ -148,6 +148,16 @@
}
/**
+ * Returns true if there are sources for the given category.
+ */
+ public boolean hasSources(SdkSourceCategory category) {
+ synchronized (mSources) {
+ ArrayList<SdkSource> list = mSources.get(category);
+ return list != null && !list.isEmpty();
+ }
+ }
+
+ /**
* Returns an array of the sources across all categories. This is never null.
*/
public SdkSource[] getAllSources() {
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PkgItem.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PkgItem.java
index c5eb07e..af18f41 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PkgItem.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PkgItem.java
@@ -16,6 +16,7 @@
package com.android.sdklib.internal.repository.updater;
+import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
import com.android.sdklib.internal.repository.packages.Package;
@@ -94,8 +95,12 @@
return mMainPkg.getRevision();
}
+ /**
+ * @deprecated Use {@link #getMainPackage()} with the {@link IDescription} interface instead.
+ */
+ @Deprecated
public String getDescription() {
- return mMainPkg.getDescription();
+ return mMainPkg.getLongDescription();
}
public Package getMainPackage() {
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterLogic.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterLogic.java
index 006e45f..f42df32 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterLogic.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterLogic.java
@@ -341,7 +341,8 @@
if (pp.getIncludedAbi() == null) {
for (Package p2 : remotePkgs) {
if (!(p2 instanceof SystemImagePackage) ||
- (p2.isObsolete() && !includeAll)) {
+ ((SystemImagePackage)p2).isPlatform() ||
+ (p2.isObsolete() && !includeAll)) {
continue;
}
SystemImagePackage sip = (SystemImagePackage) p2;
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterNoWindow.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterNoWindow.java
index b4605ab..59cda79 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterNoWindow.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterNoWindow.java
@@ -514,7 +514,13 @@
* @throws IOException in case the buffer isn't long enough.
*/
private String readLine(byte[] buffer) throws IOException {
- int count = System.in.read(buffer);
+
+ int count;
+ if (mSdkLog instanceof IReaderLogger) {
+ count = ((IReaderLogger) mSdkLog).readLine(buffer);
+ } else {
+ count = System.in.read(buffer);
+ }
// is the input longer than the buffer?
if (count == buffer.length && buffer[count-1] != 10) {
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SettingsController.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SettingsController.java
index dc45443..3231758 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SettingsController.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SettingsController.java
@@ -17,15 +17,17 @@
package com.android.sdklib.internal.repository.updater;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.io.IFileOp;
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.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
@@ -42,25 +44,37 @@
private static final String SETTINGS_FILENAME = "androidtool.cfg"; //$NON-NLS-1$
+ private final IFileOp mFileOp;
private final ILogger mSdkLog;
private final Settings mSettings;
public interface OnChangedListener {
- public void onSettingsChanged(SettingsController controller, Settings oldSettings);
+ public void onSettingsChanged(@NonNull SettingsController controller,
+ @NonNull Settings oldSettings);
}
private final List<OnChangedListener> mChangedListeners = new ArrayList<OnChangedListener>(1);
/** The currently associated {@link ISettingsPage}. Can be null. */
private ISettingsPage mSettingsPage;
+
/**
* Constructs a new default {@link SettingsController}.
*
* @param sdkLog A non-null logger to use.
*/
public SettingsController(@NonNull ILogger sdkLog) {
- mSdkLog = sdkLog;
- mSettings = new Settings();
+ this(new FileOp(), sdkLog);
+ }
+
+ /**
+ * Constructs a new default {@link SettingsController}.
+ *
+ * @param fileOp A non-null {@link FileOp} to perform file operations (to load/save settings.)
+ * @param sdkLog A non-null logger to use.
+ */
+ public SettingsController(@NonNull IFileOp fileOp, @NonNull ILogger sdkLog) {
+ this(fileOp, sdkLog, new Settings());
}
/**
@@ -68,25 +82,31 @@
* This is mostly used in unit-tests to override settings that are being used.
* Normal usage should NOT need to call this constructor.
*
+ * @param fileOp A non-null {@link FileOp} to perform file operations (to load/save settings)
* @param sdkLog A non-null logger to use.
* @param settings A non-null {@link Settings} to use as-is. It is not duplicated.
*/
- protected SettingsController(@NonNull ILogger sdkLog, @NonNull Settings settings) {
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected SettingsController(@NonNull IFileOp fileOp,
+ @NonNull ILogger sdkLog,
+ @NonNull Settings settings) {
+ mFileOp = fileOp;
mSdkLog = sdkLog;
mSettings = settings;
}
+ @NonNull
public Settings getSettings() {
return mSettings;
}
- public void registerOnChangedListener(OnChangedListener listener) {
+ public void registerOnChangedListener(@Nullable OnChangedListener listener) {
if (listener != null && !mChangedListeners.contains(listener)) {
mChangedListeners.add(listener);
}
}
- public void unregisterOnChangedListener(OnChangedListener listener) {
+ public void unregisterOnChangedListener(@Nullable OnChangedListener listener) {
if (listener != null) {
mChangedListeners.remove(listener);
}
@@ -104,7 +124,7 @@
}
/** Duplicates a set of settings. */
- public Settings(Settings settings) {
+ public Settings(@NonNull Settings settings) {
this();
for (Entry<Object, Object> entry : settings.mProperties.entrySet()) {
mProperties.put(entry.getKey(), entry.getValue());
@@ -116,7 +136,7 @@
* {@link Properties} instance. The properties instance is not duplicated,
* it's merely used as-is and changes will be reflected directly.
*/
- protected Settings(Properties properties) {
+ protected Settings(@NonNull Properties properties) {
mProperties = properties;
}
@@ -213,7 +233,7 @@
/**
* Internal helper to set a boolean setting.
*/
- void setSetting(String key, boolean value) {
+ void setSetting(@NonNull String key, boolean value) {
mSettings.mProperties.setProperty(key, Boolean.toString(value));
}
@@ -229,7 +249,7 @@
*
* @param settingsPage An {@link ISettingsPage} to associate with the controller.
*/
- public void setSettingsPage(ISettingsPage settingsPage) {
+ public void setSettingsPage(@Nullable ISettingsPage settingsPage) {
mSettingsPage = settingsPage;
if (settingsPage != null) {
@@ -248,22 +268,21 @@
* Load settings from the settings file.
*/
public void loadSettings() {
- FileInputStream fis = null;
+
String path = null;
try {
String folder = AndroidLocation.getFolder();
File f = new File(folder, SETTINGS_FILENAME);
path = f.getPath();
- if (f.exists()) {
- fis = new FileInputStream(f);
- mSettings.mProperties.load(fis);
+ Properties props = mFileOp.loadProperties(f);
+ mSettings.mProperties.clear();
+ mSettings.mProperties.putAll(props);
- // Properly reformat some settings to enforce their default value when missing.
- setShowUpdateOnly(mSettings.getShowUpdateOnly());
- setSetting(ISettingsPage.KEY_ASK_ADB_RESTART, mSettings.getAskBeforeAdbRestart());
- setSetting(ISettingsPage.KEY_USE_DOWNLOAD_CACHE, mSettings.getUseDownloadCache());
- }
+ // Properly reformat some settings to enforce their default value when missing.
+ setShowUpdateOnly(mSettings.getShowUpdateOnly());
+ setSetting(ISettingsPage.KEY_ASK_ADB_RESTART, mSettings.getAskBeforeAdbRestart());
+ setSetting(ISettingsPage.KEY_USE_DOWNLOAD_CACHE, mSettings.getUseDownloadCache());
} catch (Exception e) {
if (mSdkLog != null) {
@@ -271,13 +290,6 @@
"Failed to load settings from .android folder. Path is '%1$s'.",
path);
}
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
}
}
@@ -286,16 +298,12 @@
*/
public void saveSettings() {
- FileOutputStream fos = null;
String path = null;
try {
String folder = AndroidLocation.getFolder();
File f = new File(folder, SETTINGS_FILENAME);
path = f.getPath();
-
- fos = new FileOutputStream(f);
-
- mSettings.mProperties.store(fos, "## Settings for Android Tool"); //$NON-NLS-1$
+ mFileOp.saveProperties(f, mSettings.mProperties, "## Settings for Android Tool"); //$NON-NLS-1$
} catch (Exception e) {
if (mSdkLog != null) {
@@ -316,13 +324,6 @@
mSdkLog.error(e, "Failed to save settings file '%1$s': %2$s", path, reason);
}
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- }
- }
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/UpdaterData.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/UpdaterData.java
index b0d83fa..d2e3189 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/UpdaterData.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/UpdaterData.java
@@ -34,7 +34,7 @@
import com.android.sdklib.internal.repository.archives.ArchiveInstaller;
import com.android.sdklib.internal.repository.packages.AddonPackage;
import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.Package.License;
+import com.android.sdklib.internal.repository.packages.License;
import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
import com.android.sdklib.internal.repository.packages.ToolPackage;
import com.android.sdklib.internal.repository.sources.SdkRepoSource;
@@ -249,7 +249,7 @@
setSdkManager(SdkManager.createManager(mOsSdkRoot, mSdkLog));
try {
mAvdManager = null;
- mAvdManager = AvdManager.getInstance(mSdkManager, mSdkLog);
+ mAvdManager = AvdManager.getInstance(mSdkManager.getLocalSdk(), mSdkLog);
} catch (AndroidLocationException e) {
mSdkLog.error(e, "Unable to read AVDs: " + e.getMessage()); //$NON-NLS-1$
@@ -990,15 +990,12 @@
nextEntry: for (Map.Entry<String, License> entry : lidToAccept.entrySet()) {
String lid = entry.getKey();
License lic = entry.getValue();
- mSdkLog.info(
- "-------------------------------\n" +
- "License id: %1$s\n" +
- "Used by: \n - %3$s\n" +
- "-------------------------------\n" +
- "%2$s\n" +
- "\n",
- lid, lic.getLicense(),
- Joiner.on("\n - ").skipNulls().join(lidPkgNames.get(lid)));
+ mSdkLog.info("-------------------------------\n");
+ mSdkLog.info("License id: %1$s\n", lid);
+ mSdkLog.info("Used by: \n - %1$s\n",
+ Joiner.on("\n - ").skipNulls().join(lidPkgNames.get(lid)));
+ mSdkLog.info("-------------------------------\n\n");
+ mSdkLog.info("%1$s\n", lic.getLicense());
int retries = numRetries;
tryAgain: while(true) {
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 a15e6fd..fe29e8a 100755
--- a/sdklib/src/main/java/com/android/sdklib/io/FileOp.java
+++ b/sdklib/src/main/java/com/android/sdklib/io/FileOp.java
@@ -18,7 +18,7 @@
import com.android.SdkConstants;
import com.android.annotations.NonNull;
-import com.google.common.io.Closeables;
+import com.google.common.io.Closer;
import java.io.File;
import java.io.FileInputStream;
@@ -31,6 +31,7 @@
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Properties;
+import java.util.regex.Pattern;
/**
@@ -40,6 +41,8 @@
*/
public class FileOp implements IFileOp {
+ public static final File[] EMPTY_FILE_ARRAY = new File[0];
+
/**
* Reflection method for File.setExecutable(boolean, boolean). Only present in Java 6.
*/
@@ -337,7 +340,7 @@
public File[] listFiles(@NonNull File file) {
File[] r = file.listFiles();
if (r == null) {
- return new File[0];
+ return EMPTY_FILE_ARRAY;
} else {
return r;
}
@@ -365,40 +368,121 @@
@Override
@NonNull
- @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly
public Properties loadProperties(@NonNull File file) {
Properties props = new Properties();
- FileInputStream fis = null;
+ Closer closer = Closer.create();
try {
- fis = new FileInputStream(file);
+ FileInputStream fis = closer.register(new FileInputStream(file));
props.load(fis);
} catch (IOException ignore) {
} finally {
- Closeables.closeQuietly(fis);
+ try {
+ closer.close();
+ } catch (IOException e) {
+ }
}
return props;
}
@Override
- @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly
- public boolean saveProperties(@NonNull File file, @NonNull Properties props,
- @NonNull String comments) {
- OutputStream fos = null;
+ public void saveProperties(
+ @NonNull File file,
+ @NonNull Properties props,
+ @NonNull String comments) throws IOException {
+ Closer closer = Closer.create();
try {
- fos = newFileOutputStream(file);
-
+ OutputStream fos = closer.register(newFileOutputStream(file));
props.store(fos, comments);
- return true;
- } catch (IOException ignore) {
+ } catch (Throwable e) {
+ throw closer.rethrow(e);
} finally {
- Closeables.closeQuietly(fos);
+ closer.close();
}
-
- return false;
}
@Override
public long lastModified(@NonNull File file) {
return file.lastModified();
}
+
+ /**
+ * Computes a relative path from "toBeRelative" relative to "baseDir".
+ *
+ * Rule:
+ * - let relative2 = makeRelative(path1, path2)
+ * - then pathJoin(path1 + relative2) == path2 after canonicalization.
+ *
+ * Principle:
+ * - let base = /c1/c2.../cN/a1/a2../aN
+ * - let toBeRelative = /c1/c2.../cN/b1/b2../bN
+ * - result is removes the common paths, goes back from aN to cN then to bN:
+ * - result = ../..../../1/b2../bN
+ *
+ * @param baseDir The base directory to be relative to.
+ * @param toBeRelative The file or directory to make relative to the base.
+ * @return A path that makes toBeRelative relative to baseDir.
+ * @throws IOException If drive letters don't match on Windows or path canonicalization fails.
+ */
+ @NonNull
+ public static String makeRelative(@NonNull File baseDir, @NonNull File toBeRelative)
+ throws IOException {
+ return makeRelativeImpl(
+ baseDir.getCanonicalPath(),
+ toBeRelative.getCanonicalPath(),
+ SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS,
+ File.separator);
+ }
+
+ /**
+ * Implementation detail of makeRelative to make it testable
+ * Independently of the platform.
+ */
+ @NonNull
+ static String makeRelativeImpl(@NonNull String path1,
+ @NonNull String path2,
+ boolean isWindows,
+ @NonNull String dirSeparator)
+ throws IOException {
+ if (isWindows) {
+ // Check whether both path are on the same drive letter, if any.
+ String p1 = path1;
+ String p2 = path2;
+ char drive1 = (p1.length() >= 2 && p1.charAt(1) == ':') ? p1.charAt(0) : 0;
+ char drive2 = (p2.length() >= 2 && p2.charAt(1) == ':') ? p2.charAt(0) : 0;
+ if (drive1 != drive2) {
+ // Either a mix of UNC vs drive or not the same drives.
+ throw new IOException("makeRelative: incompatible drive letters");
+ }
+ }
+
+ String[] segments1 = path1.split(Pattern.quote(dirSeparator));
+ String[] segments2 = path2.split(Pattern.quote(dirSeparator));
+
+ int len1 = segments1.length;
+ int len2 = segments2.length;
+ int len = Math.min(len1, len2);
+ int start = 0;
+ for (; start < len; start++) {
+ // On Windows should compare in case-insensitive.
+ // Mac & Linux file systems can be both type, although their default
+ // is generally to have a case-sensitive file system.
+ if (( isWindows && !segments1[start].equalsIgnoreCase(segments2[start])) ||
+ (!isWindows && !segments1[start].equals(segments2[start]))) {
+ break;
+ }
+ }
+
+ StringBuilder result = new StringBuilder();
+ for (int i = start; i < len1; i++) {
+ result.append("..").append(dirSeparator);
+ }
+ while (start < len2) {
+ result.append(segments2[start]);
+ if (++start < len2) {
+ result.append(dirSeparator);
+ }
+ }
+
+ return result.toString();
+ }
}
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 0aa784c..c0eeb25 100755
--- a/sdklib/src/main/java/com/android/sdklib/io/IFileOp.java
+++ b/sdklib/src/main/java/com/android/sdklib/io/IFileOp.java
@@ -143,12 +143,12 @@
* @param file A non-null file to write to.
* @param props The properties to write.
* @param comments A non-null description of the properly list, written in the file.
- * @return True if the properties could be saved, false otherwise.
+ * @throws IOException if the write operation failed.
*/
- public boolean saveProperties(
+ public void saveProperties(
@NonNull File file,
@NonNull Properties props,
- @NonNull String comments);
+ @NonNull String comments) throws IOException;
/**
* Returns the lastModified attribute of the file.
diff --git a/sdklib/src/main/java/com/android/sdklib/io/NonClosingInputStream.java b/sdklib/src/main/java/com/android/sdklib/io/NonClosingInputStream.java
deleted file mode 100755
index f939e48..0000000
--- a/sdklib/src/main/java/com/android/sdklib/io/NonClosingInputStream.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.io;
-
-import com.android.annotations.NonNull;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-
-/**
- * Wraps an {@link InputStream} to change its closing behavior:
- * this makes it possible to ignore close operations or have them perform a
- * {@link InputStream#reset()} instead (if supported by the underlying stream)
- * or plain ignored.
- */
-public class NonClosingInputStream extends FilterInputStream {
-
- private final InputStream mInputStream;
- private CloseBehavior mCloseBehavior = CloseBehavior.CLOSE;
-
- public enum CloseBehavior {
- /**
- * The behavior of {@link NonClosingInputStream#close()} is to close the
- * underlying input stream. This is the default.
- */
- CLOSE,
- /**
- * The behavior of {@link NonClosingInputStream#close()} is to ignore the
- * close request and do nothing.
- */
- IGNORE,
- /**
- * The behavior of {@link NonClosingInputStream#close()} is to call
- * {@link InputStream#reset()} on the underlying stream. This will
- * only succeed if the underlying stream supports it, e.g. it must
- * have {@link InputStream#markSupported()} return true <em>and</em>
- * the caller should have called {@link InputStream#mark(int)} at some
- * point before.
- */
- RESET
- }
-
- /**
- * Wraps an existing stream into this filtering stream.
- * @param in A non-null input stream.
- */
- public NonClosingInputStream(@NonNull InputStream in) {
- super(in);
- mInputStream = in;
- }
-
- /**
- * Returns the current {@link CloseBehavior}.
- * @return the current {@link CloseBehavior}. Never null.
- */
- @NonNull
- public CloseBehavior getCloseBehavior() {
- return mCloseBehavior;
- }
-
- /**
- * Changes the current {@link CloseBehavior}.
- *
- * @param closeBehavior A new non-null {@link CloseBehavior}.
- * @return Self for chaining.
- */
- public NonClosingInputStream setCloseBehavior(@NonNull CloseBehavior closeBehavior) {
- mCloseBehavior = closeBehavior;
- return this;
- }
-
- /**
- * Performs the requested {@code close()} operation, depending on the current
- * {@link CloseBehavior}.
- */
- @Override
- public void close() throws IOException {
- switch (mCloseBehavior) {
- case IGNORE:
- break;
- case RESET:
- mInputStream.reset();
- break;
- case CLOSE:
- mInputStream.close();
- break;
- }
- }
-}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalAddonPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalAddonPkgInfo.java
deleted file mode 100755
index 6181501..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalAddonPkgInfo.java
+++ /dev/null
@@ -1,445 +0,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.
- */
-
-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
deleted file mode 100755
index e5b2d48..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalAndroidVersionPkgInfo.java
+++ /dev/null
@@ -1,48 +0,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.
- */
-
-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
deleted file mode 100755
index 7e11470..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalBuildToolPkgInfo.java
+++ /dev/null
@@ -1,67 +0,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.
- */
-
-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
deleted file mode 100755
index 8c3af52..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalDocPkgInfo.java
+++ /dev/null
@@ -1,70 +0,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.
- */
-
-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
deleted file mode 100755
index 90fbbfb..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalExtraPkgInfo.java
+++ /dev/null
@@ -1,99 +0,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.
- */
-
-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
deleted file mode 100755
index b2f22ad..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalFullRevisionPkgInfo.java
+++ /dev/null
@@ -1,49 +0,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.
- */
-
-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
deleted file mode 100755
index c7ed8f1..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalMajorRevisionPkgInfo.java
+++ /dev/null
@@ -1,48 +0,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.
- */
-
-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
deleted file mode 100755
index 4f7ec28..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalPkgInfo.java
+++ /dev/null
@@ -1,218 +0,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.
- */
-
-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
deleted file mode 100755
index 1178e40..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalPlatformPkgInfo.java
+++ /dev/null
@@ -1,443 +0,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.
- */
-
-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
deleted file mode 100755
index 80ef933..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalPlatformToolPkgInfo.java
+++ /dev/null
@@ -1,68 +0,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.
- */
-
-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
deleted file mode 100755
index 7eb3b6d..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalSamplePkgInfo.java
+++ /dev/null
@@ -1,78 +0,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.
- */
-
-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
deleted file mode 100755
index 82ab484..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalSdk.java
+++ /dev/null
@@ -1,1129 +0,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.
- */
-
-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
deleted file mode 100755
index fec0877..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalSourcePkgInfo.java
+++ /dev/null
@@ -1,78 +0,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.
- */
-
-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
deleted file mode 100755
index 01c16b7..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalSysImgPkgInfo.java
+++ /dev/null
@@ -1,86 +0,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.
- */
-
-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
deleted file mode 100755
index 832b278..0000000
--- a/sdklib/src/main/java/com/android/sdklib/local/LocalToolPkgInfo.java
+++ /dev/null
@@ -1,68 +0,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.
- */
-
-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/AddonManifestIniProps.java b/sdklib/src/main/java/com/android/sdklib/repository/AddonManifestIniProps.java
new file mode 100755
index 0000000..de82b2e
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/AddonManifestIniProps.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2014 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.sdklib.repository;
+
+/**
+ * This class describes the properties that can appear in an add-on's manifest.ini file.
+ * <p/>
+ * These constants are public and part of the SDK Manager public API.
+ * Once published we can't change them arbitrarily since various parts
+ * of our build process depend on them.
+ */
+public class AddonManifestIniProps {
+
+ /**
+ * The <em>display</em> name of the add-on. Always present. <br/>
+ * In source.properties, this matches {@link PkgProps#ADDON_NAME_DISPLAY}.
+ */
+ public static final String ADDON_NAME = "name"; //$NON-NLS-1$
+
+ /**
+ * The optional "name id" of the add-on. <br/>
+ * In source.properties, this matches {@link PkgProps#ADDON_NAME_ID}.
+ * <p/>
+ * Historically the manifest used to have only a 'name' property for both internal unique id
+ * and display, in which case the internal id was synthesized using the display name and
+ * matching a {@code [a-zA-Z0-9_-]+} pattern (see {@code Addonpackage#sanitizeDisplayToNameId}
+ * for details.)
+ */
+ public static final String ADDON_NAME_ID = "name-id"; //$NON-NLS-1$
+
+ /**
+ * The <em>display</em> vendor of the add-on. Always present. <br/>
+ * In source.properties, this matches {@link PkgProps#ADDON_VENDOR_DISPLAY}.
+ */
+ public static final String ADDON_VENDOR = "vendor"; //$NON-NLS-1$
+
+ /**
+ * The optional vendor id of the add-on. <br/>
+ * In source.properties, this matches {@link PkgProps#ADDON_VENDOR_ID}.
+ * <p/>
+ * Historically the manifest used to have only a 'vendor' property for both internal unique id
+ * and display, in which case the internal id was synthesized using the display name and
+ * matching a {@code [a-zA-Z0-9_-]+} pattern (see {@code Addonpackage#sanitizeDisplayToNameId}
+ * for details.)
+ */
+ public static final String ADDON_VENDOR_ID = "vendor-id"; //$NON-NLS-1$
+
+ /**
+ * The free description string of the add-on. <br/>
+ * Not saved in source.properties.
+ */
+ public static final String ADDON_DESCRIPTION = "description"; //$NON-NLS-1$
+
+ /**
+ * The revision of the add-on. <br/>
+ * In source.properties, this matches {@link PkgProps#PKG_REVISION}.
+ */
+ public static final String ADDON_REVISION = "revision"; //$NON-NLS-1$
+
+ /**
+ * An older/obsolete attribute for the revision of the add-on. <br/>
+ * The name was changed as it is ambiguous (platform version vs platform revision.)
+ */
+ public static final String ADDON_REVISION_OLD = "version"; //$NON-NLS-1$
+
+ /**
+ * The API level of the add-on, always an integer. <br/>
+ * <em>Note: add-ons do not currently support API codenames. </em> <br/>
+ * In source.properties, this matches {@link PkgProps#VERSION_API_LEVEL}.
+ */
+ public static final String ADDON_API = "api"; //$NON-NLS-1$
+
+ /**
+ * The list of libraries of the add-on. <br/>
+ * This is a string in the format "java.package1;java.package2;...java.packageN".
+ * For each library's java package name, the manifest.ini contains a key with
+ * value "library.jar;Jar Description String". Example:
+ * <pre>
+ * libraries=com.example.foo;com.example.bar
+ * com.example.foo=foo.jar;Foo Library
+ * com.example.bar=bar.jar;Bar Library
+ * </pre>
+ * Not saved in source.properties.
+ */
+ public static final String ADDON_LIBRARIES = "libraries"; //$NON-NLS-1$
+
+ /**
+ * An optional default skin string of the add-on. <br/>
+ * Not saved in source.properties.
+ */
+ public static final String ADDON_DEFAULT_SKIN = "skin"; //$NON-NLS-1$
+
+ /**
+ * An optional USB vendor string for the add-on. <br/>
+ * Not saved in source.properties.
+ */
+ public static final String ADDON_USB_VENDOR = "usb-vendor"; //$NON-NLS-1$
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/FullRevision.java b/sdklib/src/main/java/com/android/sdklib/repository/FullRevision.java
index c7bc84b..4365aa4 100755
--- a/sdklib/src/main/java/com/android/sdklib/repository/FullRevision.java
+++ b/sdklib/src/main/java/com/android/sdklib/repository/FullRevision.java
@@ -37,6 +37,8 @@
public static final int IMPLICIT_MICRO_REV = 0;
public static final int NOT_A_PREVIEW = 0;
+ public static final FullRevision NOT_SPECIFIED = new FullRevision(MISSING_MAJOR_REV);
+
private static final Pattern FULL_REVISION_PATTERN =
// 1=major 2=minor 3=micro 4=preview
Pattern.compile("\\s*([0-9]+)(?:\\.([0-9]+)(?:\\.([0-9]+))?)?\\s*(?:rc([0-9]+))?\\s*");
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java b/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java
index 6696fef..3126984 100755
--- a/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java
+++ b/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java
@@ -38,6 +38,7 @@
public static final String PKG_RELEASE_URL = "Pkg.RelNoteUrl"; //$NON-NLS-1$
public static final String PKG_SOURCE_URL = "Pkg.SourceUrl"; //$NON-NLS-1$
public static final String PKG_OBSOLETE = "Pkg.Obsolete"; //$NON-NLS-1$
+ public static final String PKG_LIST_DISPLAY = "Pkg.ListDisplay"; //$NON-NLS-1$
// AndroidVersion
@@ -98,4 +99,6 @@
// SystemImagePackage
public static final String SYS_IMG_ABI = "SystemImage.Abi"; //$NON-NLS-1$
+ public static final String SYS_IMG_TAG_ID = "SystemImage.TagId"; //$NON-NLS-1$
+ public static final String SYS_IMG_TAG_DISPLAY = "SystemImage.TagDisplay"; //$NON-NLS-1$
}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/RepoConstants.java b/sdklib/src/main/java/com/android/sdklib/repository/RepoConstants.java
index 3fdfc91..f69c5e1 100755
--- a/sdklib/src/main/java/com/android/sdklib/repository/RepoConstants.java
+++ b/sdklib/src/main/java/com/android/sdklib/repository/RepoConstants.java
@@ -79,12 +79,13 @@
public static final String NODE_NAME_DISPLAY = "name-display"; //$NON-NLS-1$
/** The unique name id string, for add-on packages or for libraries. */
public static final String NODE_NAME_ID = "name-id"; //$NON-NLS-1$
-
+ /** The optional string used to display a package in a list view. */
+ public static final String NODE_LIST_DISPLAY = "list-display"; //$NON-NLS-1$
/** A layoutlib package. */
- public static final String NODE_LAYOUT_LIB = "layoutlib"; //$NON-NLS-1$
+ public static final String NODE_LAYOUT_LIB = "layoutlib"; //$NON-NLS-1$
/** The API integer for a layoutlib element. */
- public static final String NODE_API = "api"; //$NON-NLS-1$
+ public static final String NODE_API = "api"; //$NON-NLS-1$
/** The libs container, optional for an add-on. */
public static final String NODE_LIBS = "libs"; //$NON-NLS-1$
@@ -109,18 +110,54 @@
/** A download archive URL, either absolute or relative to the repository xml. */
public static final String NODE_URL = "url"; //$NON-NLS-1$
+ /**
+ * Optional element to indicate an archive is only suitable for the specified OS. <br/>
+ * Values: windows | macosx | linux.
+ * @since repo-10, addon-7 and sys-img-3.
+ * @replaces {@link #LEGACY_ATTR_OS}
+ */
+ public static final String NODE_HOST_OS = "host-os"; //$NON-NLS-1$
+ /**
+ * Optional element to indicate an archive is only suitable for the specified host bit size.<br/>
+ * Values: 32 | 64.
+ * @since repo-10, addon-7 and sys-img-3.
+ */
+ public static final String NODE_HOST_BITS = "host-bits"; //$NON-NLS-1$
+ /**
+ * Optional element to indicate an archive is only suitable for the specified JVM bit size.<br/>
+ * Values: 32 | 64.
+ * @since repo-10, addon-7 and sys-img-3.
+ * @replaces {@link #LEGACY_ATTR_ARCH}
+ */
+ public static final String NODE_JVM_BITS = "jvm-bits"; //$NON-NLS-1$
+ /**
+ * Optional element to indicate an archive is only suitable for a JVM equal or greater than
+ * the specified value. <br/>
+ * Value format: [1-9](\.[0-9]{1,2}){0,2}, e.g. "1.6", "1.7.0", "1.10" or "2"
+ * @since repo-10, addon-7 and sys-img-3.
+ */
+ public static final String NODE_MIN_JVM_VERSION = "min-jvm-version"; //$NON-NLS-1$
+
+
/** An archive checksum type, mandatory. */
public static final String ATTR_TYPE = "type"; //$NON-NLS-1$
- /** An archive OS attribute, mandatory. */
- public static final String ATTR_OS = "os"; //$NON-NLS-1$
- /** An optional archive Architecture attribute. */
- public static final String ATTR_ARCH = "arch"; //$NON-NLS-1$
+ /**
+ * An archive OS attribute, mandatory. <br/>
+ * Use {@link #NODE_HOST_OS} instead in repo-10, addon-7 and sys-img-3.
+ */
+ public static final String LEGACY_ATTR_OS = "os"; //$NON-NLS-1$
+ /**
+ * An optional archive Architecture attribute. <br/>
+ * Use {@link #NODE_JVM_BITS} instead in repo-10, addon-7 and sys-img-3.
+ */
+ public static final String LEGACY_ATTR_ARCH = "arch"; //$NON-NLS-1$
/** A license definition ID. */
public static final String ATTR_ID = "id"; //$NON-NLS-1$
/** A license reference. */
public static final String ATTR_REF = "ref"; //$NON-NLS-1$
+
/** Type of a sha1 checksum. */
public static final String SHA1_TYPE = "sha1"; //$NON-NLS-1$
@@ -148,7 +185,7 @@
* @see SdkAddonConstants#getXsdStream(int)
*/
protected static InputStream getXsdStream(String rootElement, int version) {
- String filename = String.format("%1$s-%2$d.xsd", rootElement, version); //$NON-NLS-1$
+ String filename = String.format("%1$s-%2$02d.xsd", rootElement, version); //$NON-NLS-1$
InputStream stream = null;
try {
@@ -161,7 +198,7 @@
// Try the alternate schemas that are not published yet.
// This allows us to internally test with new schemas before the
// public repository uses it.
- filename = String.format("-%1$s-%2$d.xsd", rootElement, version); //$NON-NLS-1$
+ filename = String.format("-%1$s-%2$02d.xsd", rootElement, version); //$NON-NLS-1$
try {
stream = RepoConstants.class.getResourceAsStream(filename);
} catch (Exception e) {
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 f818820..f23e446 100755
--- a/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonConstants.java
+++ b/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonConstants.java
@@ -30,7 +30,7 @@
* 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;
+ public static final int NS_LATEST_VERSION = 7;
/**
* The default name looked for by {@link SdkSource} when trying to load an
@@ -46,7 +46,7 @@
* The pattern of our sdk-addon XML namespace.
* Matcher's group(1) is the schema version (integer).
*/
- public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
+ public static final String NS_PATTERN = NS_BASE + "([0-9]+)"; //$NON-NLS-1$
/** 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/SdkRepoConstants.java b/sdklib/src/main/java/com/android/sdklib/repository/SdkRepoConstants.java
index 600ca9f..32ea605 100755
--- a/sdklib/src/main/java/com/android/sdklib/repository/SdkRepoConstants.java
+++ b/sdklib/src/main/java/com/android/sdklib/repository/SdkRepoConstants.java
@@ -33,7 +33,7 @@
* The latest version of the sdk-repository XML Schema.
* Valid version numbers are between 1 and this number, included.
*/
- public static final int NS_LATEST_VERSION = 8;
+ public static final int NS_LATEST_VERSION = 10;
/**
* The min version of the sdk-repository XML Schema we'll try to load.
@@ -75,7 +75,7 @@
* The pattern of our sdk-repository XML namespace.
* Matcher's group(1) is the schema version (integer).
*/
- public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
+ public static final String NS_PATTERN = NS_BASE + "([0-9]+)"; //$NON-NLS-1$
/** The XML namespace of the latest sdk-repository XML. */
public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/SdkSysImgConstants.java b/sdklib/src/main/java/com/android/sdklib/repository/SdkSysImgConstants.java
index ba3017f..f2b5857 100755
--- a/sdklib/src/main/java/com/android/sdklib/repository/SdkSysImgConstants.java
+++ b/sdklib/src/main/java/com/android/sdklib/repository/SdkSysImgConstants.java
@@ -40,13 +40,13 @@
* The pattern of our sdk-sys-img XML namespace.
* Matcher's group(1) is the schema version (integer).
*/
- public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
+ public static final String NS_PATTERN = NS_BASE + "([0-9]+)"; //$NON-NLS-1$
/**
* The latest version of the sdk-sys-img XML Schema.
* Valid version numbers are between 1 and this number, included.
*/
- public static final int NS_LATEST_VERSION = 1;
+ public static final int NS_LATEST_VERSION = 3;
/** The XML namespace of the latest sdk-sys-img XML. */
public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
@@ -54,6 +54,14 @@
/** The root sdk-sys-img element */
public static final String NODE_SDK_SYS_IMG = "sdk-sys-img"; //$NON-NLS-1$
+ /** A system-image tag id. */
+ public static final String ATTR_TAG_ID = "tag-id"; //$NON-NLS-1$
+ /** The user-visible display part of a system-image tag id. Optional. */
+ public static final String ATTR_TAG_DISPLAY = "tag-display"; //$NON-NLS-1$
+
+ /** An add-on sub-element, indicating this is an add-on system image. */
+ public static final String NODE_ADD_ON = SdkAddonConstants.NODE_ADD_ON;
+
/**
* List of possible nodes in a repository XML. Used to populate options automatically
* in the no-GUI mode.
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgCapabilities.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgCapabilities.java
new file mode 100755
index 0000000..8b2b450
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgCapabilities.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.sdklib.repository.descriptors;
+
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+
+/**
+ * {@link IPkgCapabilities} describe which attributes are available for each kind of
+ * SDK Manager package type.
+ * <p/>
+ * To query packages capabilities, rely on {@code PkgType.hasXxx()} or {@code PkgDesc.hasXxx()}.
+ *
+ * @see PkgType
+ * @see PkgDesc
+ */
+public interface IPkgCapabilities {
+
+ /**
+ * Indicates whether this package type has a {@link FullRevision}.
+ * @return True if this package type has a {@link FullRevision}.
+ */
+ public boolean hasFullRevision();
+
+ /**
+ * Indicates whether this package type has a {@link MajorRevision}.
+ * @return True if this package type has a {@link MajorRevision}.
+ */
+ public boolean hasMajorRevision();
+
+ /**
+ * Indicates whether this package type has a {@link AndroidVersion}.
+ * @return True if this package type has a {@link AndroidVersion}.
+ */
+ public boolean hasAndroidVersion();
+
+ /**
+ * Indicates whether this package type has a path.
+ * @return True if this package type has a path.
+ */
+ public boolean hasPath();
+
+ /**
+ * Indicates whether this package type has a tag.
+ * @return True if this package type has a tag id-display tuple.
+ */
+ public boolean hasTag();
+
+ /**
+ * Indicates whether this package type has a vendor id.
+ * @return True if this package type has a vendor id.
+ */
+ public boolean hasVendor();
+
+ /**
+ * Indicates whether this package type has a {@code min-tools-rev} attribute.
+ * @return True if this package type has a {@code min-tools-rev} attribute.
+ */
+ public boolean hasMinToolsRev();
+
+ /**
+ * Indicates whether this package type has a {@code min-platform-tools-rev} attribute.
+ * @return True if this package type has a {@code min-platform-tools-rev} attribute.
+ */
+ public boolean hasMinPlatformToolsRev();
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDesc.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDesc.java
new file mode 100755
index 0000000..36b8718
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDesc.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.descriptors;
+
+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.License;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+
+import java.io.File;
+
+/**
+ * {@link IPkgDesc} keeps information on individual SDK packages
+ * (both local or remote packages definitions.)
+ * <br/>
+ * Packages have different attributes depending on their type.
+ * <p/>
+ * To create a new {@link IPkgDesc}, use one of the package-specific constructors
+ * provided by {@code PkgDesc.Builder.newXxx()}.
+ * <p/>
+ * To query packages capabilities, rely on {@link #getType()} and the {@code IPkgDesc.hasXxx()}
+ * methods provided by {@link IPkgDesc}.
+ */
+public interface IPkgDesc extends Comparable<IPkgDesc>, IPkgCapabilities, IListDescription {
+
+ /**
+ * Returns the type of the package.
+ * @return Returns one of the {@link PkgType} constants.
+ */
+ @NonNull
+ public abstract PkgType getType();
+
+ /**
+ * Returns the list-display meta data of this package.
+ * @return The list-display data, if available, or null.
+ */
+ @Nullable
+ public String getListDisplay();
+
+ @Nullable
+ public String getDescriptionShort();
+
+ @Nullable
+ public String getDescriptionUrl();
+
+ @Nullable
+ public License getLicense();
+
+ public boolean isObsolete();
+
+ /**
+ * Returns the package's {@link FullRevision} or null.
+ * @return A non-null value if {@link #hasFullRevision()} is true; otherwise a null value.
+ */
+ @Nullable
+ public FullRevision getFullRevision();
+
+ /**
+ * Returns the package's {@link MajorRevision} or null.
+ * @return A non-null value if {@link #hasMajorRevision()} is true; otherwise a null value.
+ */
+ @Nullable
+ public MajorRevision getMajorRevision();
+
+ /**
+ * Returns the package's {@link AndroidVersion} or null.
+ * @return A non-null value if {@link #hasAndroidVersion()} is true; otherwise a null value.
+ */
+ @Nullable
+ public AndroidVersion getAndroidVersion();
+
+ /**
+ * Returns the package's path string or null.
+ * <p/>
+ * For {@link PkgType#PKG_SYS_IMAGE}, the path is the system-image ABI. <br/>
+ * For {@link PkgType#PKG_PLATFORM}, the path is the platform hash string. <br/>
+ * For {@link PkgType#PKG_ADDON}, the path is the platform hash string. <br/>
+ * For {@link PkgType#PKG_EXTRA}, the path is the extra-path string. <br/>
+ *
+ * @return A non-null value if {@link #hasPath()} is true; otherwise a null value.
+ */
+ @Nullable
+ public String getPath();
+
+ /**
+ * Returns the package's tag id-display tuple or null.
+ *
+ * @return A non-null tag if {@link #hasTag()} is true; otherwise a null value.
+ */
+ @Nullable
+ public IdDisplay getTag();
+
+ /**
+ * Returns the package's vendor-id string or null.
+ * @return A non-null value if {@link #hasVendor()} is true; otherwise a null value.
+ */
+ @Nullable
+ public IdDisplay getVendor();
+
+ /**
+ * Returns the package's {@code min-tools-rev} or null.
+ * @return A non-null value if {@link #hasMinToolsRev()} is true; otherwise a null value.
+ */
+ @Nullable
+ public FullRevision getMinToolsRev();
+
+ /**
+ * Returns the package's {@code min-platform-tools-rev} or null.
+ * @return A non-null value if {@link #hasMinPlatformToolsRev()} is true; otherwise null.
+ */
+ @Nullable
+ public FullRevision getMinPlatformToolsRev();
+
+ /**
+ * Indicates whether <em>this</em> package descriptor is an update for the given
+ * existing descriptor.
+ *
+ * @param existingDesc A non-null existing descriptor.
+ * @return True if this package is an update for the given one.
+ */
+ public boolean isUpdateFor(@NonNull IPkgDesc existingDesc);
+
+ /**
+ * Returns a stable string id that can be used to reference this package.
+ * @return A stable string id that can be used to reference this package.
+ */
+ @NonNull
+ public String getInstallId();
+
+ /**
+ * Returns the canonical location where such a package would be installed.
+ * @param sdkLocation The root of the SDK.
+ * @return the canonical location where such a package would be installed.
+ */
+ @NonNull
+ public File getCanonicalInstallFolder(@NonNull File sdkLocation);
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescAddon.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescAddon.java
new file mode 100755
index 0000000..eff1232
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescAddon.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.descriptors;
+
+import com.android.annotations.NonNull;
+
+/**
+ * {@link IPkgDescAddon} keeps information on individual add-on SDK packages
+ * (both local or remote packages definitions.) The base {@link IPkgDesc} tries
+ * to present a unified interface to package attributes and this interface
+ * adds methods specific to extras.
+ * <p/>
+ * To create a new {@link IPkgDescAddon},
+ * use {@link PkgDesc.Builder#newAddon(com.android.sdklib.AndroidVersion, com.android.sdklib.repository.MajorRevision, IdDisplay, IdDisplay)}.
+ * <p/>
+ * To query generic packages capabilities, rely on {@link #getType()} and the
+ * {@code IPkgDesc.hasXxx()} methods provided by {@link IPkgDesc}.
+ */
+public interface IPkgDescAddon extends IPkgDesc {
+
+ /**
+ * Returns the id/display name of the add-on.
+ * @return A non-null id/display name for the add-on
+ */
+ @NonNull public IdDisplay getName();
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescExtra.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescExtra.java
new file mode 100755
index 0000000..db7aad8
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescExtra.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.sdklib.repository.descriptors;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.repository.NoPreviewRevision;
+
+/**
+ * {@link IPkgDescExtra} keeps information on individual extra SDK packages
+ * (both local or remote packages definitions.) The base {@link IPkgDesc} tries
+ * to present a unified interface to package attributes and this interface
+ * adds methods specific to extras.
+ * <p/>
+ * To create a new {@link IPkgDescExtra},
+ * use {@link PkgDesc.Builder#newExtra(IdDisplay, String, String, String[], NoPreviewRevision)}.
+ * <p/>
+ * The extra's revision is a {@link NoPreviewRevision}; the attribute is however
+ * accessed via {@link IPkgDesc#getFullRevision()} instead of introducing a new
+ * custom method.
+ * <p/>
+ * To query generic packages capabilities, rely on {@link #getType()} and the
+ * {@code IPkgDesc.hasXxx()} methods provided by {@link IPkgDesc}.
+ */
+public interface IPkgDescExtra extends IPkgDesc {
+ /**
+ * Returns an optional list of older paths for this extra package.
+ * @return A non-null, possibly empty, for old paths previously used for the same extra.
+ */
+ @NonNull public String[] getOldPaths();
+
+ /**
+ * Returns the display name of the Extra.
+ * @return A non-null name for the Extra, used for display purposes.
+ */
+ @NonNull public String getNameDisplay();
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IdDisplay.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IdDisplay.java
new file mode 100755
index 0000000..0278ba5
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IdDisplay.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.descriptors;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Immutable structure that represents a tuple (id-string + display-string.)
+ */
+public final class IdDisplay implements Comparable<IdDisplay> {
+
+ private final String mId;
+ private final String mDisplay;
+
+ /**
+ * Creates a new immutable tuple (id-string + display-string.)
+ *
+ * @param id The non-null id string.
+ * @param display The non-null display string.
+ */
+ public IdDisplay(@NonNull String id, @NonNull String display) {
+ mId = id;
+ mDisplay = display;
+ }
+
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ @NonNull
+ public String getDisplay() {
+ return mDisplay;
+ }
+
+ /**
+ * {@link IdDisplay} instances are the same if they have the same id.
+ * The display value is not used for comparison or ordering.
+ */
+ @Override
+ public int compareTo(IdDisplay tag) {
+ return mId.compareTo(tag.mId);
+ }
+
+ /**
+ * Hash code of {@link IdDisplay} instances only rely on the id hash code.
+ */
+ @Override
+ public int hashCode() {
+ return mId.hashCode();
+ }
+
+ /**
+ * Equality of {@link IdDisplay} instances only rely on the id equality.
+ * The display value is not used for comparison or ordering.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof IdDisplay) && mId.equals(((IdDisplay)obj).mId);
+ }
+
+ /**
+ * Returns a string representation for *debug* purposes only, not for UI display.
+ */
+ @Override
+ public String toString() {
+ return String.format("%1$s [%2$s]", mId, mDisplay);
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDesc.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDesc.java
new file mode 100755
index 0000000..2e8470c
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDesc.java
@@ -0,0 +1,1155 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.descriptors;
+
+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.SystemImage;
+import com.android.sdklib.internal.repository.packages.License;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.FullRevision.PreviewComparison;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
+
+import java.io.File;
+import java.util.Locale;
+
+/**
+ * {@link PkgDesc} keeps information on individual SDK packages
+ * (both local or remote packages definitions.)
+ * <br/>
+ * Packages have different attributes depending on their type.
+ * <p/>
+ * To create a new {@link PkgDesc}, use one of the package-specific constructors
+ * provided here.
+ * <p/>
+ * To query packages capabilities, rely on {@link #getType()} and the {@code PkgDesc.hasXxx()}
+ * methods provided in the base {@link PkgDesc}.
+ */
+public class PkgDesc implements IPkgDesc {
+ private final PkgType mType;
+ private final FullRevision mFullRevision;
+ private final MajorRevision mMajorRevision;
+ private final AndroidVersion mAndroidVersion;
+ private final String mPath;
+ private final IdDisplay mTag;
+ private final IdDisplay mVendor;
+ private final FullRevision mMinToolsRev;
+ private final FullRevision mMinPlatformToolsRev;
+ private final IIsUpdateFor mCustomIsUpdateFor;
+ private final IGetPath mCustomPath;
+
+ private final License mLicense;
+ private final String mListDisplay;
+ private final String mDescriptionShort;
+ private final String mDescriptionUrl;
+ private final boolean mIsObsolete;
+
+ protected PkgDesc(@NonNull PkgType type,
+ @Nullable License license,
+ @Nullable String listDisplay,
+ @Nullable String descriptionShort,
+ @Nullable String descriptionUrl,
+ boolean isObsolete,
+ @Nullable FullRevision fullRevision,
+ @Nullable MajorRevision majorRevision,
+ @Nullable AndroidVersion androidVersion,
+ @Nullable String path,
+ @Nullable IdDisplay tag,
+ @Nullable IdDisplay vendor,
+ @Nullable FullRevision minToolsRev,
+ @Nullable FullRevision minPlatformToolsRev,
+ @Nullable IIsUpdateFor customIsUpdateFor,
+ @Nullable IGetPath customPath) {
+ mType = type;
+ mIsObsolete = isObsolete;
+ mLicense = license;
+ mListDisplay = listDisplay;
+ mDescriptionShort = descriptionShort;
+ mDescriptionUrl = descriptionUrl;
+ mFullRevision = fullRevision;
+ mMajorRevision = majorRevision;
+ mAndroidVersion = androidVersion;
+ mPath = path;
+ mTag = tag;
+ mVendor = vendor;
+ mMinToolsRev = minToolsRev;
+ mMinPlatformToolsRev = minPlatformToolsRev;
+ mCustomIsUpdateFor = customIsUpdateFor;
+ mCustomPath = customPath;
+ }
+
+ @NonNull
+ @Override
+ public PkgType getType() {
+ return mType;
+ }
+
+ @Override
+ @Nullable
+ public String getListDisplay() {
+ return mListDisplay;
+ }
+
+ @Override
+ @Nullable
+ public String getDescriptionShort() {
+ return mDescriptionShort;
+ }
+
+ @Override
+ @Nullable
+ public String getDescriptionUrl() {
+ return mDescriptionUrl;
+ }
+
+ @Override
+ @Nullable
+ public License getLicense() {
+ return mLicense;
+ }
+
+ @Override
+ public boolean isObsolete() {
+ return mIsObsolete;
+ }
+
+ @Override
+ public final boolean hasFullRevision() {
+ return getType().hasFullRevision();
+ }
+
+ @Override
+ public final boolean hasMajorRevision() {
+ return getType().hasMajorRevision();
+ }
+
+ @Override
+ public final boolean hasAndroidVersion() {
+ return getType().hasAndroidVersion();
+ }
+
+ @Override
+ public final boolean hasPath() {
+ return getType().hasPath();
+ }
+
+ @Override
+ public final boolean hasTag() {
+ return getType().hasTag();
+ }
+
+ @Override
+ public boolean hasVendor() {
+ return getType().hasVendor();
+ }
+
+ @Override
+ public final boolean hasMinToolsRev() {
+ return getType().hasMinToolsRev();
+ }
+
+ @Override
+ public final boolean hasMinPlatformToolsRev() {
+ return getType().hasMinPlatformToolsRev();
+ }
+
+ @Nullable
+ @Override
+ public FullRevision getFullRevision() {
+ return mFullRevision;
+ }
+
+ @Nullable
+ @Override
+ public MajorRevision getMajorRevision() {
+ return mMajorRevision;
+ }
+
+ @Nullable
+ @Override
+ public AndroidVersion getAndroidVersion() {
+ return mAndroidVersion;
+ }
+
+ @Nullable
+ @Override
+ public String getPath() {
+ if (mCustomPath != null) {
+ return mCustomPath.getPath(this);
+ } else {
+ return mPath;
+ }
+ }
+
+ @Nullable
+ @Override
+ public IdDisplay getTag() {
+ return mTag;
+ }
+
+ @Nullable
+ @Override
+ public IdDisplay getVendor() {
+ return mVendor;
+ }
+
+ @Nullable
+ @Override
+ public FullRevision getMinToolsRev() {
+ return mMinToolsRev;
+ }
+
+ @Nullable
+ @Override
+ public FullRevision getMinPlatformToolsRev() {
+ return mMinPlatformToolsRev;
+ }
+
+ @Override
+ public String getInstallId() {
+ StringBuilder sb = new StringBuilder();
+
+ /* iid patterns:
+ tools, platform-tools => FOLDER / FOLDER-preview
+ build-tools => FOLDER-REV
+ doc, sample, source => ENUM-API
+ extra => ENUM-VENDOR.id-PATH
+ platform => android-API
+ add-on => addon-NAME.id-VENDOR.id-API
+ platform sys-img => sys-img-ABI-TAG|android-API
+ add-on sys-img => sys-img-ABI-addon-NAME.id-VENDOR.id-API
+ */
+
+ switch (mType) {
+ case PKG_TOOLS:
+ case PKG_PLATFORM_TOOLS:
+ sb.append(mType.getFolderName());
+ if (getFullRevision().isPreview()) {
+ sb.append("-preview");
+ }
+ break;
+
+ case PKG_BUILD_TOOLS:
+ sb.append(mType.getFolderName());
+ sb.append('-').append(getFullRevision().toString());
+ break;
+
+ case PKG_DOC:
+ case PKG_SAMPLE:
+ case PKG_SOURCE:
+ sb.append(mType.toString().toLowerCase(Locale.US).replace("pkg_", ""));
+ sb.append('-').append(getAndroidVersion().getApiString());
+ break;
+
+ case PKG_EXTRA:
+ sb.append("extra-")
+ .append(getVendor().getId())
+ .append('-')
+ .append(getPath());
+ break;
+
+ case PKG_PLATFORM:
+ sb.append(AndroidTargetHash.PLATFORM_HASH_PREFIX).append(getAndroidVersion().getApiString());
+ break;
+
+ case PKG_ADDON:
+ sb.append("addon-")
+ .append(((IPkgDescAddon) this).getName().getId())
+ .append('-')
+ .append(getVendor().getId())
+ .append('-')
+ .append(getAndroidVersion().getApiString());
+ break;
+
+ case PKG_SYS_IMAGE:
+ sb.append("sys-img-")
+ .append(getPath()) // path==ABI for sys-img
+ .append('-')
+ .append(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId())
+ .append('-')
+ .append(getAndroidVersion().getApiString());
+ break;
+
+ case PKG_ADDON_SYS_IMAGE:
+ sb.append("sys-img-")
+ .append(getPath()) // path==ABI for sys-img
+ .append("-addon-")
+ .append(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId())
+ .append('-')
+ .append(getVendor().getId())
+ .append('-')
+ .append(getAndroidVersion().getApiString());
+ break;
+
+ default:
+ throw new IllegalArgumentException("IID not defined for type " + mType.toString());
+ }
+
+ return sanitize(sb.toString());
+ }
+
+ @Override
+ public File getCanonicalInstallFolder(@NonNull File sdkLocation) {
+ File f = FileOp.append(sdkLocation, mType.getFolderName());
+
+ /* folder patterns:
+ tools, platform-tools, doc => FOLDER
+ build-tools, add-on => FOLDER/IID
+ platform, sample, source => FOLDER/android-API
+ platform sys-img => FOLDER/android-API/TAG/ABI
+ add-on sys-img => FOLDER/addon-NAME.id-VENDOR.id-API/ABI
+ extra => FOLDER/VENDOR.id/PATH
+ */
+
+ switch (mType) {
+ case PKG_TOOLS:
+ case PKG_PLATFORM_TOOLS:
+ case PKG_DOC:
+ // no-op, top-folder is all what is needed here
+ break;
+
+ case PKG_BUILD_TOOLS:
+ case PKG_ADDON:
+ f = FileOp.append(f, getInstallId());
+ break;
+
+ case PKG_PLATFORM:
+ case PKG_SAMPLE:
+ case PKG_SOURCE:
+ f = FileOp.append(f, AndroidTargetHash.PLATFORM_HASH_PREFIX + sanitize(getAndroidVersion().getApiString()));
+ break;
+
+ case PKG_SYS_IMAGE:
+ f = FileOp.append(f,
+ AndroidTargetHash.PLATFORM_HASH_PREFIX + sanitize(getAndroidVersion().getApiString()),
+ sanitize(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId()),
+ sanitize(getPath())); // path==abi
+ break;
+
+ case PKG_ADDON_SYS_IMAGE:
+ String name = "addon-"
+ + (SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId())
+ + '-'
+ + getVendor().getId()
+ + '-'
+ + getAndroidVersion().getApiString();
+ f = FileOp.append(f,
+ sanitize(name),
+ sanitize(getPath())); // path==abi
+ break;
+
+ case PKG_EXTRA:
+ f = FileOp.append(f,
+ sanitize(getVendor().getId()),
+ sanitize(getPath()));
+ break;
+
+ default:
+ throw new IllegalArgumentException("CanonicalFolder not defined for type " + mType.toString());
+ }
+
+ return f;
+ }
+
+ //---- Updating ----
+
+ /**
+ * Computes the most general case of {@link #isUpdateFor(IPkgDesc)}.
+ * Individual package types use this and complement with their own specific cases
+ * as needed.
+ *
+ * @param existingDesc A non-null package descriptor to compare with.
+ * @return True if this package descriptor would generally update the given one.
+ */
+ @Override
+ public boolean isUpdateFor(@NonNull IPkgDesc existingDesc) {
+ if (mCustomIsUpdateFor != null) {
+ return mCustomIsUpdateFor.isUpdateFor(this, existingDesc);
+ } else {
+ return isGenericUpdateFor(existingDesc);
+ }
+ }
+
+ /**
+ * Computes the most general case of {@link #isUpdateFor(IPkgDesc)}.
+ * Individual package types use this and complement with their own specific cases
+ * as needed.
+ *
+ * @param existingDesc A non-null package descriptor to compare with.
+ * @return True if this package descriptor would generally update the given one.
+ */
+ private boolean isGenericUpdateFor(@NonNull IPkgDesc existingDesc) {
+
+ if (existingDesc == null || !getType().equals(existingDesc.getType())) {
+ return false;
+ }
+
+ // Packages that have an Android version can generally only be updated
+ // for the same Android version (otherwise it's a new artifact.)
+ if (hasAndroidVersion() && !getAndroidVersion().equals(existingDesc.getAndroidVersion())) {
+ return false;
+ }
+
+ // Packages that have a vendor id need the same vendor id on both sides
+ if (hasVendor() && !getVendor().equals(existingDesc.getVendor())) {
+ return false;
+ }
+
+ // Packages that have a tag id need the same tag id on both sides
+ if (hasTag() && !getTag().getId().equals(existingDesc.getTag().getId())) {
+ return false;
+ }
+
+ // Packages that have a path can generally only be updated if both use the same path
+ if (hasPath()) {
+ if (this instanceof IPkgDescExtra) {
+ // Extra package handle paths differently, they need to use the old_path
+ // to allow upgrade compatibility.
+ if (!PkgDescExtra.compatibleVendorAndPath((IPkgDescExtra) this,
+ (IPkgDescExtra) existingDesc)) {
+ return false;
+ }
+ } else if (!getPath().equals(existingDesc.getPath())) {
+ return false;
+ }
+ }
+
+ // Packages that have a major version are generally updates if it increases.
+ if (hasMajorRevision() &&
+ getMajorRevision().compareTo(existingDesc.getMajorRevision()) > 0) {
+ return true;
+ }
+
+ // Packages that have a full revision are generally updates if it increases
+ // but keeps the same kind of preview (e.g. previews are only updates by previews.)
+ if (hasFullRevision() &&
+ getFullRevision().isPreview() == existingDesc.getFullRevision().isPreview()) {
+ // If both packages match in their preview type (both previews or both not previews)
+ // then is the RC/preview number an update?
+ return getFullRevision().compareTo(existingDesc.getFullRevision(),
+ PreviewComparison.COMPARE_NUMBER) > 0;
+ }
+
+ return false;
+ }
+
+
+ //---- Ordering ----
+
+ /**
+ * Compares this descriptor to another one.
+ * All fields must match for equality.
+ * <p/>
+ * This is must not be used an indication that a package is a suitable update for another one.
+ * The comparison order is however suitable for sorting packages for display purposes.
+ */
+ @Override
+ public int compareTo(@NonNull IPkgDesc o) {
+ int t1 = getType().getIntValue();
+ int t2 = o.getType().getIntValue();
+ if (t1 != t2) {
+ return t1 - t2;
+ }
+
+ if (hasAndroidVersion() && o.hasAndroidVersion()) {
+ t1 = getAndroidVersion().compareTo(o.getAndroidVersion());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ if (hasVendor() && o.hasVendor()) {
+ t1 = getVendor().compareTo(o.getVendor());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ if (hasTag() && o.hasTag()) {
+ t1 = getTag().compareTo(o.getTag());
+ 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;
+ }
+ }
+
+ if (hasMinToolsRev() && o.hasMinToolsRev()) {
+ t1 = getMinToolsRev().compareTo(o.getMinToolsRev());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ if (hasMinPlatformToolsRev() && o.hasMinPlatformToolsRev()) {
+ t1 = getMinPlatformToolsRev().compareTo(o.getMinPlatformToolsRev());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ return 0;
+ }
+
+ // --- display description ----
+
+ @NonNull
+ @Override
+ public String getListDescription() {
+ if (mListDisplay != null && !mListDisplay.isEmpty()) {
+ return mListDisplay;
+ }
+
+ return patternReplaceImpl(getType().getListDisplayPattern());
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected String patternReplaceImpl(String result) {
+ // Flags for list description pattern string, used in PkgType:
+ // $MAJ $FULL $API $PATH $TAG $VEND $NAME (for extras)
+
+ result = result.replace("$MAJ", hasMajorRevision() ? getMajorRevision().toShortString() : "");
+ result = result.replace("$FULL", hasFullRevision() ? getFullRevision() .toShortString() : "");
+ result = result.replace("$API", hasAndroidVersion() ? getAndroidVersion().getApiString() : "");
+ result = result.replace("$PATH", hasPath() ? getPath() : "");
+ result = result.replace("$TAG", hasTag() && !getTag().equals(SystemImage.DEFAULT_TAG) ?
+ getTag().getDisplay() : "");
+ result = result.replace("$VEND", hasVendor() ? getVendor().getDisplay() : "");
+ String name = "";
+ if (this instanceof IPkgDescExtra) {
+ name = ((IPkgDescExtra) this).getNameDisplay();
+ } else if (this instanceof IPkgDescAddon) {
+ name = ((IPkgDescAddon) this).getName().getDisplay();
+ }
+ result = result.replace("$NAME", name);
+
+ // Evaluate replacements.
+ // {|choice1|choice2|...|choiceN|} gets replaced by the first non-empty choice.
+ for (int start = result.indexOf("{|");
+ start >= 0;
+ start = result.indexOf("{|")) {
+ int end = result.indexOf('}', start);
+ int last = start + 1;
+ for (int pipe = result.indexOf('|', last+1);
+ pipe > start;
+ last = pipe, pipe = result.indexOf('|', last+1)) {
+ if (pipe - last > 1) {
+ result = result.substring(0, start) +
+ result.substring(last+1, pipe) +
+ result.substring(end+1);
+ break;
+ }
+ }
+ }
+
+ // Evaluate conditions.
+ // {?value>1:text to use} -- uses the text if value is greater than 1.
+ // Simplification: this is our only test right now so hard-code it instead of
+ // using a generic expression evaluation.
+ for (int start = result.indexOf("{?");
+ start >= 0;
+ start = result.indexOf("{?")) {
+ int end = result.indexOf('}', start);
+ int op = result.indexOf(">1:");
+ if (op > start) {
+ String value = "";
+ try {
+ FullRevision i = FullRevision.parseRevision(result.substring(start+2, op));
+ if (i.compareTo(new FullRevision(1)) > 0) {
+ value = result.substring(op+3, end);
+ }
+ } catch (NumberFormatException e) {
+ value = "ERROR " + e.getMessage() + " in " + result.substring(start, end+1);
+ }
+ result = result.substring(0, start) +
+ value +
+ result.substring(end+1);
+ }
+ }
+
+
+ return result;
+ }
+
+ /** String representation for debugging purposes. */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<PkgDesc"); //NON-NLS-1$
+
+ builder.append(" Type="); //NON-NLS-1$
+ builder.append(getType().toString()
+ .toLowerCase(Locale.US)
+ .replace("pkg_", "")); //NON-NLS-1$ //NON-NLS-2$
+
+ if (hasAndroidVersion()) {
+ builder.append(" Android=").append(getAndroidVersion()); //NON-NLS-1$
+ }
+
+ if (hasVendor()) {
+ builder.append(" Vendor=").append(getVendor().toString()); //NON-NLS-1$
+ }
+
+ if (hasTag()) {
+ builder.append(" Tag=").append(getTag()); //NON-NLS-1$
+ }
+
+ if (hasPath()) {
+ builder.append(" Path=").append(getPath()); //NON-NLS-1$
+ }
+
+ if (hasFullRevision()) {
+ builder.append(" FullRev=").append(getFullRevision()); //NON-NLS-1$
+ }
+
+ if (hasMajorRevision()) {
+ builder.append(" MajorRev=").append(getMajorRevision()); //NON-NLS-1$
+ }
+
+ if (hasMinToolsRev()) {
+ builder.append(" MinToolsRev=").append(getMinToolsRev()); //NON-NLS-1$
+ }
+
+ if (hasMinPlatformToolsRev()) {
+ builder.append(" MinPlatToolsRev=").append(getMinPlatformToolsRev()); //NON-NLS-1$
+ }
+
+ if (mListDisplay != null) {
+ builder.append(" ListDisp=").append(mListDisplay); //NON-NLS-1$
+ }
+
+ if (mDescriptionShort != null) {
+ builder.append(" DescShort=").append(mDescriptionShort); //NON-NLS-1$
+ }
+
+ if (mDescriptionUrl != null) {
+ builder.append(" DescUrl=").append(mDescriptionUrl); //NON-NLS-1$
+ }
+
+ if (mLicense != null) {
+ builder.append(" License['").append(mLicense.getLicenseRef()) //NON-NLS-1$
+ .append("]=") //NON-NLS-1$
+ .append(mLicense.getLicense().length()).append(" chars"); //NON-NLS-1$
+ }
+
+ if (isObsolete()) {
+ builder.append(" Obsolete=yes"); //NON-NLS-1$
+ }
+
+ builder.append('>');
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (hasAndroidVersion() ? getAndroidVersion().hashCode() : 0);
+ result = prime * result + (hasVendor() ? getVendor() .hashCode() : 0);
+ result = prime * result + (hasTag() ? getTag() .hashCode() : 0);
+ result = prime * result + (hasPath() ? getPath() .hashCode() : 0);
+ result = prime * result + (hasFullRevision() ? getFullRevision() .hashCode() : 0);
+ result = prime * result + (hasMajorRevision() ? getMajorRevision() .hashCode() : 0);
+ result = prime * result + (hasMinToolsRev() ? getMinToolsRev() .hashCode() : 0);
+ result = prime * result + (hasMinPlatformToolsRev() ?
+ getMinPlatformToolsRev().hashCode() : 0);
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof IPkgDesc)) return false;
+ IPkgDesc rhs = (IPkgDesc) obj;
+
+ if (hasAndroidVersion() != rhs.hasAndroidVersion()) {
+ return false;
+ }
+ if (hasAndroidVersion() && !getAndroidVersion().equals(rhs.getAndroidVersion())) {
+ return false;
+ }
+
+ if (hasTag() != rhs.hasTag()) {
+ return false;
+ }
+ if (hasTag() && !getTag().equals(rhs.getTag())) {
+ return false;
+ }
+
+ if (hasPath() != rhs.hasPath()) {
+ return false;
+ }
+ if (hasPath() && !getPath().equals(rhs.getPath())) {
+ return false;
+ }
+
+ if (hasFullRevision() != rhs.hasFullRevision()) {
+ return false;
+ }
+ if (hasFullRevision() && !getFullRevision().equals(rhs.getFullRevision())) {
+ return false;
+ }
+
+ if (hasMajorRevision() != rhs.hasMajorRevision()) {
+ return false;
+ }
+ if (hasMajorRevision() && !getMajorRevision().equals(rhs.getMajorRevision())) {
+ return false;
+ }
+
+ if (hasMinToolsRev() != rhs.hasMinToolsRev()) {
+ return false;
+ }
+ if (hasMinToolsRev() && !getMinToolsRev().equals(rhs.getMinToolsRev())) {
+ return false;
+ }
+
+ if (hasMinPlatformToolsRev() != rhs.hasMinPlatformToolsRev()) {
+ return false;
+ }
+ if (hasMinPlatformToolsRev() &&
+ !getMinPlatformToolsRev().equals(rhs.getMinPlatformToolsRev())) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ // ---- Constructors -----
+
+ public interface IIsUpdateFor {
+ public boolean isUpdateFor(@NonNull PkgDesc thisPkgDesc, @NonNull IPkgDesc existingDesc);
+ }
+
+ public interface IGetPath {
+ public String getPath(@NonNull PkgDesc thisPkgDesc);
+ }
+
+ public static class Builder {
+ private final PkgType mType;
+ private FullRevision mFullRevision;
+ private MajorRevision mMajorRevision;
+ private AndroidVersion mAndroidVersion;
+ private String mPath;
+ private IdDisplay mTag;
+ private IdDisplay mVendor;
+ private FullRevision mMinToolsRev;
+ private FullRevision mMinPlatformToolsRev;
+ private IIsUpdateFor mCustomIsUpdateFor;
+ private IGetPath mCustomPath;
+ private String[] mOldPaths;
+ private String mNameDisplay;
+ private IdDisplay mNameIdDisplay;
+
+ private License mLicense;
+ private String mListDisplay;
+ private String mDescriptionShort;
+ private String mDescriptionUrl;
+ private boolean mIsObsolete;
+
+
+ private Builder(PkgType type) {
+ mType = type;
+ }
+
+ /**
+ * Creates a new tool package descriptor.
+ *
+ * @param revision The revision of the tool package.
+ * @param minPlatformToolsRev The {@code min-platform-tools-rev}.
+ * Use {@link FullRevision#NOT_SPECIFIED} to indicate there is no requirement.
+ * @return A {@link PkgDesc} describing this tool package.
+ */
+ @NonNull
+ public static Builder newTool(@NonNull FullRevision revision,
+ @NonNull FullRevision minPlatformToolsRev) {
+ Builder p = new Builder(PkgType.PKG_TOOLS);
+ p.mFullRevision = revision;
+ p.mMinPlatformToolsRev = minPlatformToolsRev;
+ return p;
+ }
+
+ /**
+ * Creates a new platform-tool package descriptor.
+ *
+ * @param revision The revision of the platform-tool package.
+ * @return A {@link PkgDesc} describing this platform-tool package.
+ */
+ @NonNull
+ public static Builder newPlatformTool(@NonNull FullRevision revision) {
+ Builder p = new Builder(PkgType.PKG_PLATFORM_TOOLS);
+ p.mFullRevision = revision;
+ return p;
+ }
+
+ /**
+ * Creates a new build-tool package descriptor.
+ *
+ * @param revision The revision of the build-tool package.
+ * @return A {@link PkgDesc} describing this build-tool package.
+ */
+ @NonNull
+ public static Builder newBuildTool(@NonNull FullRevision revision) {
+ Builder p = new Builder(PkgType.PKG_BUILD_TOOLS);
+ p.mFullRevision = revision;
+ p.mCustomIsUpdateFor = new IIsUpdateFor() {
+ @Override
+ public boolean isUpdateFor(PkgDesc thisPkgDesc, IPkgDesc existingDesc) {
+ // Generic test checks that the preview type is the same (both previews or not).
+ // Build tool is different in that the full revision must be an exact match
+ // and not an increase.
+ return thisPkgDesc.isGenericUpdateFor(existingDesc) &&
+ thisPkgDesc.getFullRevision().compareTo(
+ existingDesc.getFullRevision(),
+ PreviewComparison.COMPARE_TYPE) == 0;
+ }
+ };
+ return p;
+ }
+
+ /**
+ * Creates a new doc package descriptor.
+ *
+ * @param revision The revision of the doc package.
+ * @return A {@link PkgDesc} describing this doc package.
+ */
+ @NonNull
+ public static Builder newDoc(@NonNull AndroidVersion version,
+ @NonNull MajorRevision revision) {
+ Builder p = new Builder(PkgType.PKG_DOC);
+ p.mAndroidVersion = version;
+ p.mMajorRevision = revision;
+ p.mCustomIsUpdateFor = new IIsUpdateFor() {
+ @Override
+ public boolean isUpdateFor(PkgDesc thisPkgDesc, IPkgDesc existingDesc) {
+ if (existingDesc == null ||
+ !thisPkgDesc.getType().equals(existingDesc.getType())) {
+ return false;
+ }
+
+ // This package is unique in the SDK. It's an update if the API is newer
+ // or the revision is newer for the same API.
+ int diff = thisPkgDesc.getAndroidVersion().compareTo(
+ existingDesc.getAndroidVersion());
+ return diff > 0 ||
+ (diff == 0 && thisPkgDesc.getMajorRevision().compareTo(
+ existingDesc.getMajorRevision()) > 0);
+ }
+ };
+ return p;
+ }
+
+ /**
+ * Creates a new extra package descriptor.
+ *
+ * @param vendor The vendor id string of the extra package.
+ * @param path The path id string of the extra package.
+ * @param displayName The display name. If missing, caller should build one using the path.
+ * @param oldPaths An optional list of older paths for this extra package.
+ * @param revision The revision of the extra package.
+ * @return A {@link PkgDesc} describing this extra package.
+ */
+ @NonNull
+ public static Builder newExtra(@NonNull IdDisplay vendor,
+ @NonNull String path,
+ @NonNull String displayName,
+ @Nullable String[] oldPaths,
+ @NonNull NoPreviewRevision revision) {
+ Builder p = new Builder(PkgType.PKG_EXTRA);
+ p.mFullRevision = revision;
+ p.mVendor = vendor;
+ p.mPath = path;
+ p.mNameDisplay = displayName;
+ p.mOldPaths = oldPaths;
+ return p;
+ }
+
+ /**
+ * Creates a new platform package descriptor.
+ *
+ * @param version The android version of the platform package.
+ * @param revision The revision of the extra package.
+ * @param minToolsRev An optional {@code min-tools-rev}.
+ * Use {@link FullRevision#NOT_SPECIFIED} to indicate
+ * there is no requirement.
+ * @return A {@link PkgDesc} describing this platform package.
+ */
+ @NonNull
+ public static Builder newPlatform(@NonNull AndroidVersion version,
+ @NonNull MajorRevision revision,
+ @NonNull FullRevision minToolsRev) {
+ Builder p = new Builder(PkgType.PKG_PLATFORM);
+ p.mAndroidVersion = version;
+ p.mMajorRevision = revision;
+ p.mMinToolsRev = minToolsRev;
+ p.mCustomPath = new IGetPath() {
+ @Override
+ public String getPath(PkgDesc thisPkgDesc) {
+ /** The "path" of a Platform is its Target Hash. */
+ return AndroidTargetHash.getPlatformHashString(thisPkgDesc.getAndroidVersion());
+ }
+ };
+ return p;
+ }
+
+ /**
+ * Create a new add-on package descriptor.
+ * <p/>
+ * The vendor id and the name id provided are used to compute the add-on's
+ * target hash.
+ *
+ * @param version The android version of the add-on package.
+ * @param revision The revision of the add-on package.
+ * @param addonVendor The vendor id/display of the add-on package.
+ * @param addonName The name id/display of the add-on package.
+ * @return A {@link PkgDesc} describing this add-on package.
+ */
+ @NonNull
+ public static Builder newAddon(@NonNull AndroidVersion version,
+ @NonNull MajorRevision revision,
+ @NonNull IdDisplay addonVendor,
+ @NonNull IdDisplay addonName) {
+ Builder p = new Builder(PkgType.PKG_ADDON);
+ p.mAndroidVersion = version;
+ p.mMajorRevision = revision;
+ p.mVendor = addonVendor;
+ p.mNameIdDisplay = addonName;
+ return p;
+ }
+
+ /**
+ * Create a new platform system-image package descriptor.
+ * <p/>
+ * For system-images, {@link PkgDesc#getPath()} returns the ABI.
+ *
+ * @param version The android version of the system-image package.
+ * @param tag The tag of the system-image package.
+ * @param abi The ABI of the system-image package.
+ * @param revision The revision of the system-image package.
+ * @return A {@link PkgDesc} describing this system-image package.
+ */
+ @NonNull
+ public static Builder newSysImg(@NonNull AndroidVersion version,
+ @NonNull IdDisplay tag,
+ @NonNull String abi,
+ @NonNull MajorRevision revision) {
+ Builder p = new Builder(PkgType.PKG_SYS_IMAGE);
+ p.mAndroidVersion = version;
+ p.mMajorRevision = revision;
+ p.mTag = tag;
+ p.mPath = abi;
+ p.mVendor = null;
+ return p;
+ }
+
+ /**
+ * Create a new add-on system-image package descriptor.
+ * <p/>
+ * For system-images, {@link PkgDesc#getPath()} returns the ABI.
+ *
+ * @param version The android version of the system-image package.
+ * @param addonVendor The vendor id/display of an associated add-on.
+ * @param addonName The tag of the system-image package is the add-on name.
+ * @param abi The ABI of the system-image package.
+ * @param revision The revision of the system-image package.
+ * @return A {@link PkgDesc} describing this system-image package.
+ */
+ @NonNull
+ public static Builder newAddonSysImg(@NonNull AndroidVersion version,
+ @NonNull IdDisplay addonVendor,
+ @NonNull IdDisplay addonName,
+ @NonNull String abi,
+ @NonNull MajorRevision revision) {
+ Builder p = new Builder(PkgType.PKG_ADDON_SYS_IMAGE);
+ p.mAndroidVersion = version;
+ p.mMajorRevision = revision;
+ p.mTag = addonName;
+ p.mPath = abi;
+ p.mVendor = addonVendor;
+ return p;
+ }
+
+ /**
+ * Create a new source package descriptor.
+ *
+ * @param version The android version of the source package.
+ * @param revision The revision of the source package.
+ * @return A {@link PkgDesc} describing this source package.
+ */
+ @NonNull
+ public static Builder newSource(@NonNull AndroidVersion version,
+ @NonNull MajorRevision revision) {
+ Builder p = new Builder(PkgType.PKG_SOURCE);
+ p.mAndroidVersion = version;
+ p.mMajorRevision = revision;
+ return p;
+ }
+
+ /**
+ * Create a new sample package descriptor.
+ *
+ * @param version The android version of the sample package.
+ * @param revision The revision of the sample package.
+ * @param minToolsRev An optional {@code min-tools-rev}.
+ * Use {@link FullRevision#NOT_SPECIFIED} to indicate
+ * there is no requirement.
+ * @return A {@link PkgDesc} describing this sample package.
+ */
+ @NonNull
+ public static Builder newSample(@NonNull AndroidVersion version,
+ @NonNull MajorRevision revision,
+ @NonNull FullRevision minToolsRev) {
+ Builder p = new Builder(PkgType.PKG_SAMPLE);
+ p.mAndroidVersion = version;
+ p.mMajorRevision = revision;
+ p.mMinToolsRev = minToolsRev;
+ return p;
+ }
+
+ public Builder setDescriptions(@NonNull Package pkg) {
+ mDescriptionShort = pkg.getShortDescription();
+ mDescriptionUrl = pkg.getDescUrl();
+ mListDisplay = pkg.getListDisplay();
+ mIsObsolete = pkg.isObsolete();
+ mLicense = pkg.getLicense();
+ return this;
+ }
+
+ public Builder setLicense(@Nullable License license) {
+ mLicense = license;
+ return this;
+ }
+
+ public Builder setListDisplay(@Nullable String text) {
+ mListDisplay = text;
+ return this;
+ }
+
+ public Builder setDescriptionShort(@Nullable String text) {
+ mDescriptionShort = text;
+ return this;
+ }
+
+ public Builder setDescriptionUrl(@Nullable String text) {
+ mDescriptionUrl = text;
+ return this;
+ }
+
+ public Builder setIsObsolete(boolean isObsolete) {
+ mIsObsolete = isObsolete;
+ return this;
+ }
+
+ public IPkgDesc create() {
+ if (mType == PkgType.PKG_ADDON) {
+ return new PkgDescAddon(
+ mType,
+ mLicense,
+ mListDisplay,
+ mDescriptionShort,
+ mDescriptionUrl,
+ mIsObsolete,
+ mMajorRevision,
+ mAndroidVersion,
+ mVendor,
+ mNameIdDisplay);
+ }
+
+ if (mType == PkgType.PKG_EXTRA) {
+ return new PkgDescExtra(
+ mType,
+ mLicense,
+ mListDisplay,
+ mDescriptionShort,
+ mDescriptionUrl,
+ mIsObsolete,
+ mFullRevision,
+ mMajorRevision,
+ mAndroidVersion,
+ mPath,
+ mTag,
+ mVendor,
+ mMinToolsRev,
+ mMinPlatformToolsRev,
+ mNameDisplay,
+ mOldPaths);
+ }
+
+ return new PkgDesc(
+ mType,
+ mLicense,
+ mListDisplay,
+ mDescriptionShort,
+ mDescriptionUrl,
+ mIsObsolete,
+ mFullRevision,
+ mMajorRevision,
+ mAndroidVersion,
+ mPath,
+ mTag,
+ mVendor,
+ mMinToolsRev,
+ mMinPlatformToolsRev,
+ mCustomIsUpdateFor,
+ mCustomPath);
+ }
+ }
+
+ // ---- Helpers -----
+
+ @NonNull
+ private static String sanitize(@NonNull String str) {
+ if (str.startsWith(AndroidTargetHash.PLATFORM_HASH_PREFIX)) {
+ // This block is necessary when installing android-L. Currently installation fails because this method converts it to
+ // "android-l" (lowercase "L".)
+ String platform = str.substring(AndroidTargetHash.PLATFORM_HASH_PREFIX.length());
+ if (!platform.isEmpty() && !Character.isDigit(platform.charAt(0))) {
+ return str;
+ }
+ }
+ str = str.toLowerCase(Locale.US).replaceAll("[^a-z0-9_.-]+", "_").replaceAll("_+", "_");
+ return str;
+ }
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescAddon.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescAddon.java
new file mode 100755
index 0000000..875f7ff
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescAddon.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.sdklib.repository.descriptors;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.repository.packages.License;
+import com.android.sdklib.repository.MajorRevision;
+
+/**
+ * Implementation detail of {@link PkgDesc} for add-ons.
+ * Do not use this class directly.
+ * To create an instance use {@link PkgDesc.Builder#newAddon} instead.
+ */
+final class PkgDescAddon extends PkgDesc implements IPkgDescAddon {
+
+ private final IdDisplay mAddonName;
+
+ /**
+ * Add-on descriptor.
+ * The following attributes are mandatory:
+ */
+ PkgDescAddon(@NonNull PkgType type,
+ @Nullable License license,
+ @Nullable String listDisplay,
+ @Nullable String descriptionShort,
+ @Nullable String descriptionUrl,
+ boolean isObsolete,
+ @NonNull MajorRevision majorRevision,
+ @NonNull AndroidVersion androidVersion,
+ @NonNull IdDisplay addonVendor,
+ @NonNull IdDisplay addonName) {
+ super(type,
+ license,
+ listDisplay,
+ descriptionShort,
+ descriptionUrl,
+ isObsolete,
+ null, //fullRevision
+ majorRevision,
+ androidVersion,
+ AndroidTargetHash.getAddonHashString(addonVendor.getDisplay(),
+ addonName.getDisplay(),
+ androidVersion),
+ null, //tag
+ addonVendor,
+ null, //minToolsRev
+ null, //minPlatformToolsRev
+ null, //customIsUpdateFor
+ null); //customPath
+
+ mAddonName = addonName;
+ }
+
+ @NonNull
+ @Override
+ public IdDisplay getName() {
+ return mAddonName;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescExtra.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescExtra.java
new file mode 100755
index 0000000..f7e24e3
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescExtra.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.descriptors;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.repository.packages.License;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+
+/**
+ * Implementation detail of {@link IPkgDescExtra} for extra packages.
+ */
+public final class PkgDescExtra extends PkgDesc implements IPkgDescExtra {
+
+ private final String[] mOldPaths;
+ private final String mNameDisplay;
+
+ PkgDescExtra(@NonNull PkgType type,
+ @Nullable License license,
+ @Nullable String listDisplay,
+ @Nullable String descriptionShort,
+ @Nullable String descriptionUrl,
+ boolean isObsolete,
+ @Nullable FullRevision fullRevision,
+ @Nullable MajorRevision majorRevision,
+ @Nullable AndroidVersion androidVersion,
+ @Nullable String path,
+ @Nullable IdDisplay tag,
+ @Nullable IdDisplay vendor,
+ @Nullable FullRevision minToolsRev,
+ @Nullable FullRevision minPlatformToolsRev,
+ @NonNull String nameDisplay,
+ @Nullable final String[] oldPaths) {
+ super(type,
+ license,
+ listDisplay,
+ descriptionShort,
+ descriptionUrl,
+ isObsolete,
+ fullRevision,
+ majorRevision,
+ androidVersion,
+ path,
+ tag,
+ vendor,
+ minToolsRev,
+ minPlatformToolsRev,
+ null, //customIsUpdateFor
+ null); //customPath
+ mNameDisplay = nameDisplay;
+ mOldPaths = oldPaths != null ? oldPaths : new String[0];
+ }
+
+ @NonNull
+ @Override
+ public String[] getOldPaths() {
+ return mOldPaths;
+ }
+
+ @NonNull
+ @Override
+ public String getNameDisplay() {
+ return mNameDisplay;
+ }
+
+ // ---- Helpers ----
+
+ /**
+ * Helper method that converts the old_paths property string into the
+ * an old paths array.
+ *
+ * @param oldPathsProperty A possibly-null old_path property string.
+ * @return A list of old paths split by their separator. Can be empty but not null.
+ */
+ @NonNull
+ public static String[] convertOldPaths(@Nullable String oldPathsProperty) {
+ if (oldPathsProperty == null || oldPathsProperty.length() == 0) {
+ return new String[0];
+ }
+ return oldPathsProperty.split(";"); //$NON-NLS-1$
+ }
+
+ /**
+ * Helper to computhe whether the extra path of both {@link IPkgDescExtra}s
+ * are compatible with each other, which means they are either equal or are
+ * matched between existing path and the potential old paths list.
+ * <p/>
+ * This also covers backward compatibility -- in earlier schemas the vendor id was
+ * merged into the path string when reloading installed extras.
+ *
+ * @param lhs A non-null {@link IPkgDescExtra}.
+ * @param rhs Another non-null {@link IPkgDescExtra}.
+ * @return true if the paths are compatible.
+ */
+ public static boolean compatibleVendorAndPath(
+ @NonNull IPkgDescExtra lhs,
+ @NonNull IPkgDescExtra rhs) {
+ String[] epOldPaths = rhs.getOldPaths();
+ int lenEpOldPaths = epOldPaths.length;
+ for (int indexEp = -1; indexEp < lenEpOldPaths; indexEp++) {
+ if (sameVendorAndPath(
+ lhs.getVendor().getId(), lhs.getPath(),
+ rhs.getVendor().getId(), indexEp < 0 ? rhs.getPath() : epOldPaths[indexEp])) {
+ return true;
+ }
+ }
+
+ String[] thisOldPaths = lhs.getOldPaths();
+ int lenThisOldPaths = thisOldPaths.length;
+ for (int indexThis = -1; indexThis < lenThisOldPaths; indexThis++) {
+ if (sameVendorAndPath(
+ lhs.getVendor().getId(), indexThis < 0 ? lhs.getPath() : thisOldPaths[indexThis],
+ rhs.getVendor().getId(), rhs.getPath())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean sameVendorAndPath(
+ @Nullable String thisVendor, @Nullable String thisPath,
+ @Nullable String otherVendor, @Nullable String otherPath) {
+ // To be backward compatible, we need to support the old vendor-path form
+ // in either the current or the remote package.
+ //
+ // The vendor test below needs to account for an old installed package
+ // (e.g. with an install path of vendor-name) that has then been updated
+ // in-place and thus when reloaded contains the vendor name in both the
+ // path and the vendor attributes.
+ if (otherPath != null && thisPath != null && thisVendor != null) {
+ if (otherPath.equals(thisVendor + '-' + thisPath) &&
+ (otherVendor == null ||
+ otherVendor.length() == 0 ||
+ otherVendor.equals(thisVendor))) {
+ return true;
+ }
+ }
+ if (thisPath != null && otherPath != null && otherVendor != null) {
+ if (thisPath.equals(otherVendor + '-' + otherPath) &&
+ (thisVendor == null ||
+ thisVendor.length() == 0 ||
+ thisVendor.equals(otherVendor))) {
+ return true;
+ }
+ }
+
+
+ if (thisPath != null && thisPath.equals(otherPath)) {
+ if ((thisVendor == null && otherVendor == null) ||
+ (thisVendor != null && thisVendor.equals(otherVendor))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgType.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgType.java
new file mode 100755
index 0000000..251f627
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgType.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.descriptors;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.repository.LocalSdkParser;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.local.LocalSdk;
+
+import java.util.EnumSet;
+
+/**
+ * Package types handled by the {@link LocalSdk}.
+ * <p/>
+ * Integer bit values are provided via {@link #getIntValue()} for backward
+ * compatibility with the older {@link LocalSdkParser} class.
+ * The integer bit values also indicate the natural ordering of the packages.
+ */
+public enum PkgType implements IPkgCapabilities {
+
+ // Boolean attributes below, in that order:
+ // maj-r, full-r, api, path, tag, vend, min-t-r, min-pt-r
+ //
+ // Corresponding flags for list description pattern string:
+ // $MAJ $FULL $API $PATH $TAG $VEND $NAME (for extras & add-ons)
+ //
+ //
+
+ /** Filter the SDK/tools folder.
+ * Has {@link FullRevision}. */
+ PKG_TOOLS(0x0001, SdkConstants.FD_TOOLS,
+ "Android SDK Tools $FULL",
+ false, true /*full-r*/, false, false, false, false, false, true /*min-pt-r*/),
+
+ /** Filter the SDK/platform-tools folder.
+ * Has {@link FullRevision}. */
+ PKG_PLATFORM_TOOLS(0x0002, SdkConstants.FD_PLATFORM_TOOLS,
+ "Android SDK Platform-Tools $FULL",
+ false, true /*full-r*/, false, false, false, false, false, false),
+
+ /** Filter the SDK/build-tools folder.
+ * Has {@link FullRevision}. */
+ PKG_BUILD_TOOLS(0x0004, SdkConstants.FD_BUILD_TOOLS,
+ "Android SDK Build-Tools $FULL",
+ false, true /*full-r*/, false, false, false, false, false, false),
+
+ /** Filter the SDK/docs folder.
+ * Has {@link MajorRevision}. */
+ PKG_DOC(0x0010, SdkConstants.FD_DOCS,
+ "Documentation for Android SDK $API{?$MAJ>1:, rev $MAJ}",
+ true /*maj-r*/, false, true /*api*/, false, false, false, false, false),
+
+ /** Filter the SDK/platforms.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}.
+ * Path returns the platform's target hash. */
+ PKG_PLATFORM(0x0100, SdkConstants.FD_PLATFORMS,
+ "Android SDK Platform $API{?$MAJ>1:, rev $MAJ}",
+ true /*maj-r*/, false, true /*api*/, true /*path*/, false, false, true /*min-t-r*/, false),
+
+ /** Filter the SDK/system-images/android.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}. Has tag.
+ * Path returns the system image ABI. */
+ PKG_SYS_IMAGE(0x0200, SdkConstants.FD_SYSTEM_IMAGES,
+ "$PATH System Image, Android $API{?$MAJ>1:, rev $MAJ}",
+ true /*maj-r*/, false, true /*api*/, true /*path*/, true /*tag*/, false /*vend*/, false, false),
+
+ /** Filter the SDK/addons.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}.
+ * Path returns the add-on's target hash. */
+ PKG_ADDON(0x0400, SdkConstants.FD_ADDONS,
+ "{|$NAME|$VEND $PATH|}, Android $API{?$MAJ>1:, rev $MAJ}",
+ true /*maj-r*/, false, true /*api*/, true /*path*/, false, true /*vend*/, false, false),
+
+ /** Filter the SDK/system-images/addons.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}. Has tag.
+ * Path returns the system image ABI. */
+ PKG_ADDON_SYS_IMAGE(0x0800, SdkConstants.FD_SYSTEM_IMAGES,
+ "{|$NAME|$VEND $PATH|} System Image, Android $API{?$MAJ>1:, rev $MAJ}",
+ true /*maj-r*/, false, true /*api*/, true /*path*/, true /*tag*/, true /*vend*/, false, false),
+
+ /** Filter the SDK/samples folder.
+ * Note: this will not detect samples located in the SDK/extras packages.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}. */
+ PKG_SAMPLE(0x1000, SdkConstants.FD_SAMPLES,
+ "Samples for Android $API{?$MAJ>1:, rev $MAJ}",
+ true /*maj-r*/, false, true /*api*/, false, false, false, true /*min-t-r*/, false),
+
+ /** Filter the SDK/sources folder.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}. */
+ PKG_SOURCE(0x2000, SdkConstants.FD_ANDROID_SOURCES,
+ "Sources for Android $API{?$MAJ>1:, rev $MAJ}",
+ true /*maj-r*/, false, true /*api*/, false, false, false, false, false),
+
+ /** Filter the SDK/extras folder.
+ * Has {@code Path}. Has {@link MajorRevision}.
+ * Path returns the combined vendor id + extra path.
+ * Cast the descriptor to {@link IPkgDescExtra} to get extra's specific attributes. */
+ PKG_EXTRA(0x4000, SdkConstants.FD_EXTRAS,
+ "{|$NAME|$VEND $PATH|}{?$FULL>1:, rev $FULL}",
+ false, true /*full-r*/, false, true /*path*/, false, true /*vend*/, false, false);
+
+ /** A collection of all the known PkgTypes. */
+ public static final EnumSet<PkgType> PKG_ALL = EnumSet.allOf(PkgType.class);
+
+ /** Integer value matching all available pkg types, for the old LocalSdkParer. */
+ public static final int PKG_ALL_INT = 0xFFFF;
+
+ private int mIntValue;
+ private String mFolderName;
+
+ private final boolean mHasMajorRevision;
+ private final boolean mHasFullRevision;
+ private final boolean mHasAndroidVersion;
+ private final boolean mHasPath;
+ private final boolean mHasTag;
+ private final boolean mHasVendor;
+ private final boolean mHasMinToolsRev;
+ private final boolean mHasMinPlatformToolsRev;
+ private final String mListDisplayPattern;
+
+ PkgType(int intValue,
+ @NonNull String folderName,
+ @NonNull String listDisplayPattern,
+ boolean hasMajorRevision,
+ boolean hasFullRevision,
+ boolean hasAndroidVersion,
+ boolean hasPath,
+ boolean hasTag,
+ boolean hasVendor,
+ boolean hasMinToolsRev,
+ boolean hasMinPlatformToolsRev) {
+ mIntValue = intValue;
+ mFolderName = folderName;
+ mListDisplayPattern = listDisplayPattern;
+ mHasMajorRevision = hasMajorRevision;
+ mHasFullRevision = hasFullRevision;
+ mHasAndroidVersion = hasAndroidVersion;
+ mHasPath = hasPath;
+ mHasTag = hasTag;
+ mHasVendor = hasVendor;
+ mHasMinToolsRev = hasMinToolsRev;
+ mHasMinPlatformToolsRev = hasMinPlatformToolsRev;
+ }
+
+ /** Returns the integer value matching the type, compatible with the old LocalSdkParer. */
+ public int getIntValue() {
+ return mIntValue;
+ }
+
+ /** Returns the name of SDK top-folder where this type of package is stored. */
+ @NonNull
+ public String getFolderName() {
+ return mFolderName;
+ }
+
+ @Override
+ public boolean hasMajorRevision() {
+ return mHasMajorRevision;
+ }
+
+ @Override
+ public boolean hasFullRevision() {
+ return mHasFullRevision;
+ }
+
+ @Override
+ public boolean hasAndroidVersion() {
+ return mHasAndroidVersion;
+ }
+
+ @Override
+ public boolean hasPath() {
+ return mHasPath;
+ }
+
+ @Override
+ public boolean hasTag() {
+ return mHasTag;
+ }
+
+ @Override
+ public boolean hasVendor() {
+ return mHasVendor;
+ }
+
+ @Override
+ public boolean hasMinToolsRev() {
+ return mHasMinToolsRev;
+ }
+
+ @Override
+ public boolean hasMinPlatformToolsRev() {
+ return mHasMinPlatformToolsRev;
+ }
+
+ /*
+ * Returns a pattern string used by {@link PkgDesc#getListDescription()} to
+ * compute a default list-display representation string for this package.
+ */
+ public String getListDisplayPattern() {
+ return mListDisplayPattern;
+ }
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonPkgInfo.java
new file mode 100755
index 0000000..eef734a
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonPkgInfo.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+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.FileOp;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.repository.AddonManifestIniProps;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IPkgDescAddon;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.utils.Pair;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.TreeMultimap;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+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 {
+
+ 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$
+
+ private final @NonNull IPkgDescAddon mAddonDesc;
+
+ public LocalAddonPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull MajorRevision revision,
+ @NonNull IdDisplay vendor,
+ @NonNull IdDisplay name) {
+ super(localSdk, localDir, sourceProps, version, revision, FullRevision.NOT_SPECIFIED);
+ mAddonDesc = (IPkgDescAddon) PkgDesc.Builder.newAddon(version, revision, vendor, name)
+ .create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mAddonDesc;
+ }
+
+ /** The "path" of an add-on is its Target Hash. */
+ @Override
+ @NonNull
+ public String getTargetHash() {
+ return getDesc().getPath();
+ }
+
+ //-----
+
+ /**
+ * Computes a sanitized name-id based on an addon name-display.
+ * This is used to provide compatibility with older add-ons that lacks the new fields.
+ *
+ * @param displayName A name-display field or a old-style name field.
+ * @return A non-null sanitized name-id that fits in the {@code [a-zA-Z0-9_-]+} pattern.
+ */
+ public static String sanitizeDisplayToNameId(@NonNull String displayName) {
+ String name = displayName.toLowerCase(Locale.US);
+ name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+ name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ // Trim leading and trailing underscores
+ if (name.length() > 1) {
+ name = name.replaceAll("^_+", ""); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (name.length() > 1) {
+ name = name.replaceAll("_+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return name;
+ }
+
+ //-----
+
+ /**
+ * 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(AddonManifestIniProps.ADDON_API);
+ String name = propertyMap.get(AddonManifestIniProps.ADDON_NAME);
+ String vendor = propertyMap.get(AddonManifestIniProps.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(PkgType.PKG_PLATFORM,
+ getDesc().getAndroidVersion());
+ if (plat instanceof LocalPlatformPkgInfo) {
+ baseTarget = (PlatformTarget) ((LocalPlatformPkgInfo) plat).getAndroidTarget();
+ }
+ assert baseTarget != null;
+
+ // get the optional description
+ String description = propertyMap.get(AddonManifestIniProps.ADDON_DESCRIPTION);
+
+ // get the add-on revision
+ int revisionValue = 1;
+ String revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION);
+ if (revision == null) {
+ revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION_OLD);
+ }
+ if (revision != null) {
+ revisionValue = Integer.parseInt(revision);
+ }
+
+ // get the optional libraries
+ String librariesValue = propertyMap.get(AddonManifestIniProps.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(fileOp);
+
+ // 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.
+ File targetSkinFolder = target.getFile(IAndroidTarget.SKINS);
+ List<File> skins = parseSkinFolder(targetSkinFolder);
+
+ // get the default skin
+ File defaultSkin = null;
+ String defaultSkinName = propertyMap.get(AddonManifestIniProps.ADDON_DEFAULT_SKIN);
+ if (defaultSkinName != null) {
+ defaultSkin = new File(targetSkinFolder, defaultSkinName);
+ } else {
+ // No default skin name specified, use the first one from the addon
+ // or the default from the platform.
+ if (skins.size() == 1) {
+ defaultSkin = skins.get(0);
+ } else {
+ defaultSkin = baseTarget.getDefaultSkin();
+ }
+ }
+
+ // get the USB ID (if available)
+ int usbVendorId = convertId(propertyMap.get(AddonManifestIniProps.ADDON_USB_VENDOR));
+ if (usbVendorId != IAndroidTarget.NO_USB_ID) {
+ target.setUsbVendorId(usbVendorId);
+ }
+
+ target.setSkins(skins.toArray(new File[skins.size()]), 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(AddonManifestIniProps.ADDON_NAME);
+ if (name == null) {
+ error = addonManifestWarning(AddonManifestIniProps.ADDON_NAME);
+ break;
+ }
+
+ String vendor = propertyMap.get(AddonManifestIniProps.ADDON_VENDOR);
+ if (vendor == null) {
+ error = addonManifestWarning(AddonManifestIniProps.ADDON_VENDOR);
+ break;
+ }
+
+ String api = propertyMap.get(AddonManifestIniProps.ADDON_API);
+ if (api == null) {
+ error = addonManifestWarning(AddonManifestIniProps.ADDON_API);
+ break;
+ }
+
+ // Look for a platform that has a matching api level or codename.
+ IAndroidTarget baseTarget = null;
+ LocalPkgInfo plat = getLocalSdk().getPkgInfo(PkgType.PKG_PLATFORM,
+ getDesc().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(AddonManifestIniProps.ADDON_REVISION);
+ if (revision == null) {
+ revision = propertyMap.get(AddonManifestIniProps.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.",
+ AddonManifestIniProps.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 in the new sdk/system-images folders then we 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.
+ * If any given API appears twice or more, the first occurrence wins.
+ * <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 fileOp File operation wrapper.
+ * @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(IFileOp fileOp) {
+ Set<ISystemImage> found = new TreeSet<ISystemImage>();
+ SetMultimap<IdDisplay, String> tagToAbiFound = TreeMultimap.create();
+
+
+ // Look in the SDK/system-image/platform-n/tag/abi folders.
+ // 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.
+
+ LocalPkgInfo[] sysImgInfos = getLocalSdk().getPkgsInfos(PkgType.PKG_ADDON_SYS_IMAGE);
+ for (LocalPkgInfo pkg : sysImgInfos) {
+ IPkgDesc d = pkg.getDesc();
+ if (pkg instanceof LocalAddonSysImgPkgInfo &&
+ d.hasVendor() &&
+ mAddonDesc.getVendor().equals(d.getVendor()) &&
+ mAddonDesc.getName().equals(d.getTag())) {
+ final IdDisplay tag = mAddonDesc.getName();
+ final String abi = d.getPath();
+ if (abi != null && !tagToAbiFound.containsEntry(tag, abi)) {
+ List<File> parsedSkins = parseSkinFolder(
+ new File(pkg.getLocalDir(), SdkConstants.FD_SKINS));
+ File[] skins = FileOp.EMPTY_FILE_ARRAY;
+ if (!parsedSkins.isEmpty()) {
+ skins = parsedSkins.toArray(new File[parsedSkins.size()]);
+ }
+
+ found.add(new SystemImage(
+ pkg.getLocalDir(),
+ LocationType.IN_SYSTEM_IMAGE,
+ tag,
+ mAddonDesc.getVendor(),
+ abi,
+ skins));
+ tagToAbiFound.put(tag, abi);
+ }
+ }
+ }
+
+ // Look for sub-directories
+ boolean useLegacy = true;
+ boolean hasImgFiles = false;
+ final IdDisplay defaultTag = SystemImage.DEFAULT_TAG;
+
+ File imagesDir = new File(getLocalDir(), SdkConstants.OS_IMAGES_FOLDER);
+ File[] files = fileOp.listFiles(imagesDir);
+ for (File file : files) {
+ if (fileOp.isDirectory(file)) {
+ useLegacy = false;
+ String abi = file.getName();
+ if (!tagToAbiFound.containsEntry(defaultTag, abi)) {
+ found.add(new SystemImage(
+ file,
+ LocationType.IN_IMAGES_SUBFOLDER,
+ SystemImage.DEFAULT_TAG,
+ file.getName(),
+ FileOp.EMPTY_FILE_ARRAY));
+ tagToAbiFound.put(defaultTag, abi);
+ }
+ } else if (!hasImgFiles && fileOp.isFile(file)) {
+ if (file.getName().endsWith(".img")) { //$NON-NLS-1$
+ hasImgFiles = true;
+ }
+ }
+ }
+
+ if (useLegacy &&
+ hasImgFiles &&
+ fileOp.isDirectory(imagesDir) &&
+ !tagToAbiFound.containsEntry(defaultTag, 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(
+ imagesDir,
+ LocationType.IN_LEGACY_FOLDER,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI,
+ FileOp.EMPTY_FILE_ARRAY));
+ }
+
+ return found.toArray(new ISystemImage[found.size()]);
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonSysImgPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonSysImgPkgInfo.java
new file mode 100755
index 0000000..60c9cf5
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonSysImgPkgInfo.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Local add-on system-image package, for a given addon's {@link AndroidVersion} and given ABI.
+ * The system-image tag is the add-on name.
+ * The package itself has a major revision.
+ * There should be only one for a given android platform version & ABI.
+ */
+public class LocalAddonSysImgPkgInfo extends LocalPkgInfo {
+
+
+ private final @NonNull IPkgDesc mDesc;
+
+ public LocalAddonSysImgPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @Nullable IdDisplay addonVendor,
+ @Nullable IdDisplay addonName,
+ @NonNull String abi,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newAddonSysImg(version, addonVendor, addonName, abi, revision)
+ .create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalBuildToolPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalBuildToolPkgInfo.java
new file mode 100755
index 0000000..0bfe0ce
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalBuildToolPkgInfo.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.sdklib.repository.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 com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalBuildToolPkgInfo extends LocalPkgInfo {
+
+
+ private final @Nullable BuildToolInfo mBuildToolInfo;
+ private final @NonNull IPkgDesc mDesc;
+
+ public LocalBuildToolPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull FullRevision revision,
+ @Nullable BuildToolInfo btInfo) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newBuildTool(revision).create();
+ mBuildToolInfo = btInfo;
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ @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/repository/local/LocalDirInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDirInfo.java
new file mode 100755
index 0000000..c968158
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDirInfo.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.io.IFileOp;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.zip.Adler32;
+
+/**
+ * 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. To have a consistent behavior between OSes, we compute a quick
+ * checksum using all the files & directories modified timestamps.
+ * The content of files is not included though, except for the checksum on
+ * the source.property file since this one is the most important for the SDK.
+ * <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.
+ */
+class LocalDirInfo {
+ @NonNull
+ private final IFileOp mFileOp;
+ @NonNull
+ private final File mDir;
+ private final long mDirModifiedTS;
+ private final long mDirChecksum;
+ private final long mPropsModifiedTS;
+ private final long mPropsChecksum;
+
+ /**
+ * Creates a new immutable {@link LocalDirInfo}.
+ *
+ * @param fileOp The {@link FileOp} to use for all file-based interactions.
+ * @param dir The platform/addon directory of the target. It should be a directory.
+ */
+ public LocalDirInfo(@NonNull IFileOp fileOp, @NonNull File dir) {
+ mFileOp = fileOp;
+ 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;
+ mDirChecksum = getDirChecksum(mDir);
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ // Has the deep directory checksum changed?
+ if (mDirChecksum != getDirChecksum(mDir)) {
+ 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;
+ }
+
+ /**
+ * Computes a checksum using the last-modified attributes of all
+ * the files and <em>first-level</em>directories in this root directory.
+ * <p/>
+ * Heuristic: the SDK Manager updates package by replacing whole directories
+ * so we don't need to do a recursive deep-first checksum of all files. Only
+ * the top-level of the package directory should be sufficient to detect
+ * SDK updates.
+ */
+ private long getDirChecksum(@NonNull File dir) {
+ long checksum = mFileOp.lastModified(dir);
+
+ // Get the file & directory list sorted by case-insensitive name
+ // to make the checksum more consistent.
+ File[] files = mFileOp.listFiles(dir);
+ Arrays.sort(files, new Comparator<File>() {
+ @Override
+ public int compare(File o1, File o2) {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ });
+ for (File file : files) {
+ checksum = 31 * checksum | mFileOp.lastModified(file);
+ }
+ return checksum;
+ }
+
+ /** 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 LocalDirInfo} 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 LocalDirInfo} 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/repository/local/LocalDocPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDocPkgInfo.java
new file mode 100755
index 0000000..30e603d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDocPkgInfo.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.repository.packages.DocPackage;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalDocPkgInfo extends LocalPkgInfo {
+
+ private final @NonNull IPkgDesc mDesc;
+
+ public LocalDocPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newDoc(version, revision).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ @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
+ 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/local/LocalExtraPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalExtraPkgInfo.java
new file mode 100755
index 0000000..9ef94bf
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalExtraPkgInfo.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.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.packages.ExtraPackage;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.repository.NoPreviewRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IPkgDescExtra;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalExtraPkgInfo extends LocalPkgInfo {
+
+ private final @NonNull IPkgDescExtra mDesc;
+
+ public LocalExtraPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull IdDisplay vendor,
+ @NonNull String path,
+ @Nullable String displayName,
+ @NonNull String[] oldPaths,
+ @NonNull NoPreviewRevision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = (IPkgDescExtra) PkgDesc.Builder.newExtra(
+ vendor,
+ path,
+ displayName,
+ oldPaths,
+ revision).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ @NonNull
+ public String[] getOldPaths() {
+ return mDesc.getOldPaths();
+ }
+
+ @Nullable
+ @Override
+ public Package getPackage() {
+ Package pkg = super.getPackage();
+ if (pkg == null) {
+ try {
+ pkg = ExtraPackage.create(
+ null, //source
+ getSourceProperties(), //properties
+ mDesc.getVendor().getId(), //vendor
+ mDesc.getPath(), //path
+ 0, //revision
+ null, //license
+ null, //description
+ null, //descUrl
+ getLocalDir().getPath() //archiveOsPath
+ );
+ setPackage(pkg);
+ } catch (Exception e) {
+ appendLoadError("Failed to parse package: %1$s", e.toString());
+ }
+ }
+ return pkg;
+ }
+
+ // --- helpers ---
+
+ /**
+ * Used to produce a suitable name-display based on the extra's path
+ * and vendor display string in addon-3 schemas.
+ *
+ * @param vendor The vendor id of the extra.
+ * @param extraPath The non-null path of the extra.
+ * @return A non-null display name based on the extra's path id.
+ */
+ public static String getPrettyName(@Nullable IdDisplay vendor, @NonNull String extraPath) {
+ String name = extraPath;
+
+ // In the past, we used to save the extras in a folder vendor-path,
+ // and that "vendor" would end up in the path when we reload the extra from
+ // disk. Detect this and compensate.
+ String disp = vendor == null ? null : vendor.getDisplay();
+ if (disp != null && disp.length() > 0) {
+ if (name.startsWith(disp + "-")) { //$NON-NLS-1$
+ name = name.substring(disp.length() + 1);
+ }
+ }
+
+ // Uniformize all spaces in the name
+ if (name != null) {
+ name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (name == null || name.length() == 0) {
+ name = "Unknown Extra";
+ }
+
+ if (disp != null && disp.length() > 0) {
+ name = disp + " " + name; //$NON-NLS-1$
+ name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ // Look at all lower case characters in range [1..n-1] and replace them by an upper
+ // case if they are preceded by a space. Also upper cases the first character of the
+ // string.
+ boolean changed = false;
+ char[] chars = name.toCharArray();
+ for (int n = chars.length - 1, i = 0; i < n; i++) {
+ if (Character.isLowerCase(chars[i]) && (i == 0 || chars[i - 1] == ' ')) {
+ chars[i] = Character.toUpperCase(chars[i]);
+ changed = true;
+ }
+ }
+ if (changed) {
+ name = new String(chars);
+ }
+
+ // Special case: reformat a few typical acronyms.
+ name = name.replaceAll(" Usb ", " USB "); //$NON-NLS-1$
+ name = name.replaceAll(" Api ", " API "); //$NON-NLS-1$
+
+ return name;
+ }
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPkgInfo.java
new file mode 100755
index 0000000..739a58e
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPkgInfo.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.IListDescription;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.remote.RemotePkgInfo;
+import com.android.sdklib.repository.remote.RemoteSdk;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Information about a locally installed package.
+ * <p/>
+ * Local package information is retrieved via the {@link LocalSdk} object.
+ * Clients should not need to create instances of {@link LocalPkgInfo} directly.
+ * Instead please use the {@link LocalSdk} methods to parse and retrieve packages.
+ * <p/>
+ * These objects can also contain optional information about updates available
+ * from remote servers. These are computed and set by the {@link RemoteSdk} object.
+ */
+public abstract class LocalPkgInfo
+ implements IDescription, IListDescription, Comparable<LocalPkgInfo> {
+
+ private final LocalSdk mLocalSdk;
+ private final File mLocalDir;
+ private final Properties mSourceProperties;
+
+ private Package mPackage;
+ private String mLoadError;
+ private RemotePkgInfo mUpdate;
+
+ 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;
+ }
+
+ /**
+ * Indicates whether this local package has an update available.
+ * This is only defined if {@link Update} has been used to decorate the packages.
+ *
+ * @return True if {@link #getUpdate()} would return a non-null {@link RemotePkgInfo}.
+ */
+ public boolean hasUpdate() {
+ return mUpdate != null;
+ }
+
+ /**
+ * Returns a {@link RemotePkgInfo} that can update this package, if available.
+ * This is only defined if {@link Update} has been used to decorate the packages.
+ *
+ * @return A {@link RemotePkgInfo} or null.
+ */
+ @Nullable
+ public RemotePkgInfo getUpdate() {
+ return mUpdate;
+ }
+
+ /**
+ * Used by {@link Update} to indicate if there's an update available for this package.
+ */
+ void setUpdate(@Nullable RemotePkgInfo update) {
+ mUpdate = update;
+ }
+
+ // ----
+
+ /** Returns the {@link IPkgDesc} describing this package. */
+ @NonNull
+ public abstract IPkgDesc getDesc();
+
+
+ //---- Ordering ----
+
+ /**
+ * Comparison is solely done based on the {@link IPkgDesc}.
+ * <p/>
+ * Other local attributes (local directory, source properties, updates available)
+ * are <em>not used</em> in the comparison. Consequently {@link #compareTo(LocalPkgInfo)}
+ * does not match {@link #equals(Object)} and the {@link #hashCode()} properties.
+ */
+ @Override
+ public int compareTo(@NonNull LocalPkgInfo o) {
+ return getDesc().compareTo(o.getDesc());
+ }
+
+ /** String representation for debugging purposes. */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append('<').append(this.getClass().getSimpleName()).append(' ');
+ builder.append(getDesc().toString());
+ if (mUpdate != null) {
+ builder.append(" Updated by: "); //$NON-NLS-1$
+ builder.append(mUpdate.toString());
+ }
+ builder.append('>');
+ return builder.toString();
+ }
+
+ /**
+ * Computes a hash code specific to this instance based on the underlying
+ * {@link IPkgDesc} but also specific local properties such a local directory,
+ * update available and actual source properties.
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((getDesc() == null) ? 0 : getDesc().hashCode());
+ result = prime * result + ((mLocalDir == null) ? 0 : mLocalDir.hashCode());
+ result = prime * result + ((mSourceProperties == null) ? 0 : mSourceProperties.hashCode());
+ result = prime * result + ((mUpdate == null) ? 0 : mUpdate.hashCode());
+ return result;
+ }
+
+ /**
+ * Computes object equality to this instance based on the underlying
+ * {@link IPkgDesc} but also specific local properties such a local directory,
+ * update available and actual source properties. This is different from
+ * the behavior of {@link #compareTo(LocalPkgInfo)} which only uses the
+ * {@link IPkgDesc} for ordering.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof LocalPkgInfo)) {
+ return false;
+ }
+ LocalPkgInfo other = (LocalPkgInfo) obj;
+
+ if (!getDesc().equals(other.getDesc())) {
+ return false;
+ }
+ if (mLocalDir == null) {
+ if (other.mLocalDir != null) {
+ return false;
+ }
+ } else if (!mLocalDir.equals(other.mLocalDir)) {
+ return false;
+ }
+ if (mSourceProperties == null) {
+ if (other.mSourceProperties != null) {
+ return false;
+ }
+ } else if (!mSourceProperties.equals(other.mSourceProperties)) {
+ return false;
+ }
+ if (mUpdate == null) {
+ if (other.mUpdate != null) {
+ return false;
+ }
+ } else if (!mUpdate.equals(other.mUpdate)) {
+ return false;
+ }
+ return true;
+ }
+
+
+ //---- 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() {
+ return getDesc().getListDescription();
+ }
+
+ @Override
+ public String getShortDescription() {
+ // TODO revisit to differentiate from list-description depending
+ // on how we'll use it in the sdkman UI.
+ return getListDescription();
+ }
+
+ @Override
+ public String getLongDescription() {
+ StringBuilder sb = new StringBuilder();
+ IPkgDesc desc = getDesc();
+
+ sb.append(desc.getListDescription()).append('\n');
+
+ if (desc.hasVendor()) {
+ assert desc.getVendor() != null;
+ sb.append("By ").append(desc.getVendor().getDisplay()).append('\n');
+ }
+
+ if (desc.hasMinPlatformToolsRev()) {
+ assert desc.getMinPlatformToolsRev() != null;
+ sb.append("Requires Platform-Tools revision ").append(desc.getMinPlatformToolsRev().toShortString()).append('\n');
+ }
+
+ if (desc.hasMinToolsRev()) {
+ assert desc.getMinToolsRev() != null;
+ sb.append("Requires Tools revision ").append(desc.getMinToolsRev().toShortString()).append('\n');
+ }
+
+ sb.append("Location: ").append(mLocalDir.getAbsolutePath());
+
+ return sb.toString();
+ }
+
+
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformPkgInfo.java
new file mode 100755
index 0000000..1e9e3db
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformPkgInfo.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+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.FileOp;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.TreeMultimap;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+@SuppressWarnings("ConstantConditions")
+public class LocalPlatformPkgInfo extends LocalPkgInfo {
+
+ 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$
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ /** 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,
+ @NonNull FullRevision minToolsRev) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newPlatform(version, revision, minToolsRev).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ /** The "path" of a Platform is its Target Hash. */
+ @NonNull
+ public String getTargetHash() {
+ return getDesc().getPath();
+ }
+
+ @Nullable
+ public IAndroidTarget getAndroidTarget() {
+ if (!mLoaded) {
+ mTarget = createAndroidTarget();
+ mLoaded = true;
+ }
+ return mTarget;
+ }
+
+ public boolean isLoaded() {
+ return mLoaded;
+ }
+
+ @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 from the platform. Make a copy to not modify the original collection.
+ List<File> skins = new ArrayList<File>(parseSkinFolder(pt.getFile(IAndroidTarget.SKINS)));
+
+ // add the system-image specific skins, if any.
+ for (ISystemImage systemImage : systemImages) {
+ skins.addAll(Arrays.asList(systemImage.getSkins()));
+ }
+
+ pt.setSkins(skins.toArray(new File[skins.size()]));
+
+ // add path to the non-legacy samples package if it exists
+ LocalPkgInfo samples = sdk.getPkgInfo(PkgType.PKG_SAMPLE, getDesc().getAndroidVersion());
+ if (samples != null) {
+ pt.setSamplesPath(samples.getLocalDir().getAbsolutePath());
+ }
+
+ // add path to the non-legacy sources package if it exists
+ LocalPkgInfo sources = sdk.getPkgInfo(PkgType.PKG_SOURCE, getDesc().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>();
+ SetMultimap<IdDisplay, String> tagToAbiFound = TreeMultimap.create();
+
+
+ // Look in the SDK/system-image/platform-n/tag/abi folders.
+ // 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.
+
+ LocalPkgInfo[] sysImgInfos = getLocalSdk().getPkgsInfos(PkgType.PKG_SYS_IMAGE);
+ for (LocalPkgInfo pkg : sysImgInfos) {
+ IPkgDesc d = pkg.getDesc();
+ if (pkg instanceof LocalSysImgPkgInfo &&
+ !d.hasVendor() &&
+ apiVersion.equals(d.getAndroidVersion())) {
+ IdDisplay tag = d.getTag();
+ String abi = d.getPath();
+ if (tag != null && abi != null && !tagToAbiFound.containsEntry(tag, abi)) {
+ List<File> parsedSkins = parseSkinFolder(
+ new File(pkg.getLocalDir(), SdkConstants.FD_SKINS));
+ File[] skins = FileOp.EMPTY_FILE_ARRAY;
+ if (!parsedSkins.isEmpty()) {
+ skins = parsedSkins.toArray(new File[parsedSkins.size()]);
+ }
+
+ found.add(new SystemImage(
+ pkg.getLocalDir(),
+ LocationType.IN_SYSTEM_IMAGE,
+ tag,
+ abi,
+ skins));
+ tagToAbiFound.put(tag, abi);
+ }
+ }
+ }
+
+ // 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;
+ final IdDisplay defaultTag = SystemImage.DEFAULT_TAG;
+
+ // Look for sub-directories
+ for (File file : files) {
+ if (fileOp.isDirectory(file)) {
+ useLegacy = false;
+ String abi = file.getName();
+ if (!tagToAbiFound.containsEntry(defaultTag, abi)) {
+ found.add(new SystemImage(
+ file,
+ LocationType.IN_IMAGES_SUBFOLDER,
+ defaultTag,
+ abi,
+ FileOp.EMPTY_FILE_ARRAY));
+ tagToAbiFound.put(defaultTag, abi);
+ }
+ } else if (!hasImgFiles && fileOp.isFile(file)) {
+ if (file.getName().endsWith(".img")) { //$NON-NLS-1$
+ hasImgFiles = true;
+ }
+ }
+ }
+
+ if (useLegacy &&
+ hasImgFiles &&
+ fileOp.isDirectory(imgDir) &&
+ !tagToAbiFound.containsEntry(defaultTag, 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_LEGACY_FOLDER,
+ defaultTag,
+ SdkConstants.ABI_ARMEABI,
+ FileOp.EMPTY_FILE_ARRAY));
+ }
+
+ return found.toArray(new ISystemImage[found.size()]);
+ }
+
+ /**
+ * Parses the skin folder and builds the skin list.
+ * @param skinRootFolder The path to the skin root folder.
+ */
+ @NonNull
+ protected List<File> parseSkinFolder(@NonNull File skinRootFolder) {
+ IFileOp fileOp = getLocalSdk().getFileOp();
+
+ if (fileOp.isDirectory(skinRootFolder)) {
+ ArrayList<File> skinList = new ArrayList<File>();
+
+ 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);
+ }
+ }
+ }
+
+ return skinList;
+ }
+
+ return Collections.emptyList();
+ }
+
+
+ /** 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/repository/local/LocalPlatformToolPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformToolPkgInfo.java
new file mode 100755
index 0000000..6b04fbf
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformToolPkgInfo.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.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalPlatformToolPkgInfo extends LocalPkgInfo {
+
+ private final @NonNull IPkgDesc mDesc;
+
+ public LocalPlatformToolPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull FullRevision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newPlatformTool(revision).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ @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
+ 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/local/LocalSamplePkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSamplePkgInfo.java
new file mode 100755
index 0000000..ee2852b
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSamplePkgInfo.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.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.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+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 LocalPkgInfo {
+
+ private final @NonNull IPkgDesc mDesc;
+
+ public LocalSamplePkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull MajorRevision revision,
+ @NonNull FullRevision minToolsRev) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newSample(version, revision, minToolsRev).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ @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/repository/local/LocalSdk.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSdk.java
new file mode 100755
index 0000000..4997374
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSdk.java
@@ -0,0 +1,1186 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.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.annotations.concurrency.GuardedBy;
+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.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDescExtra;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repository.remote.RemoteSdk;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * 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(PkgType.PKG_TOOLS)} => {@link LocalPkgInfo}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Platform-Tools</td>
+ * <td>Unique instance</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_PLATFORM_TOOLS)} => {@link LocalPkgInfo}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Docs</td>
+ * <td>Unique instance</td>
+ * <td>{@code getPkgInfo(PkgType.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(PkgType.PKG_BUILD_TOOLS, FullRevision)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.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(PkgType.PKG_EXTRAS, String)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.PKG_EXTRAS)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Sources</td>
+ * <td>{@link AndroidVersion}</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_SOURCES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.PKG_SOURCES)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Samples</td>
+ * <td>{@link AndroidVersion}</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_SAMPLES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.PKG_SAMPLES)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Platforms</td>
+ * <td>{@link AndroidVersion}</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_PLATFORMS, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgInfo(PkgType.PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.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(PkgType.PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.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(PkgType.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/>
+ * Threading: All accessor methods are synchronized on the same internal lock so
+ * it's safe to call them from any thread, even concurrently. <br/>
+ * A method like {@code getPkgsInfos} returns a copy of its data array, which objects are
+ * not altered after creation, so its value is not influenced by the internal state after
+ * it returns.
+ * <p/>
+ *
+ * Implementation Background:
+ * <ul>
+ * <li> The sdk manager has a set of "Package" classes that cover both local
+ * and remote SDK operations.
+ * <li> Goal was to split it in 2 cleanly separated parts: {@link LocalSdk} parses sdk on disk,
+ * and {@link RemoteSdk} wraps 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 uses 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 {
+
+ /** 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. */
+ @GuardedBy(value="mLocalPackages")
+ private final Multimap<PkgType, LocalPkgInfo> mLocalPackages = TreeMultimap.create();
+ /** Directories already parsed into {@link #mLocalPackages}. */
+ @GuardedBy(value="mLocalPackages")
+ private final Multimap<PkgType, LocalDirInfo> mVisitedDirs = HashMultimap.create();
+ /** A legacy build-tool for older platform-tools < 17. */
+ private BuildToolInfo mLegacyBuildTools;
+ /** Cache of targets from local sdk. See {@link #getTargets()}. */
+ @GuardedBy(value="mLocalPackages")
+ private final List<IAndroidTarget> mCachedTargets = new ArrayList<IAndroidTarget>();
+ private boolean mReloadTargets = true;
+
+ /**
+ * 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.
+ */
+ @NonNull
+ 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(PkgType.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;
+ }
+
+ /**
+ * Location of the SDK. Maybe null. Can be changed.
+ * The getLocation() API replaces this function.
+ * @return The location of the SDK. Null if not initialized yet.
+ */
+ @Deprecated
+ @Nullable
+ public String getPath() {
+ return mSdkRoot != null ? mSdkRoot.getPath() : null;
+ }
+
+ /**
+ * Clear the tracked visited folders & the cached {@link LocalPkgInfo} for the
+ * given filter types.
+ *
+ * @param filters A set of PkgType constants or {@link PkgType#PKG_ALL} to clear everything.
+ */
+ public void clearLocalPkg(@NonNull EnumSet<PkgType> filters) {
+ mLegacyBuildTools = null;
+
+ synchronized (mLocalPackages) {
+ for (PkgType filter : filters) {
+ mVisitedDirs.removeAll(filter);
+ mLocalPackages.removeAll(filter);
+ }
+ }
+
+ // Clear the targets if the platforms or addons are being cleared
+ if (filters.contains(PkgType.PKG_PLATFORM) || filters.contains(PkgType.PKG_ADDON)) {
+ mReloadTargets = true;
+ }
+ }
+
+ /**
+ * 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 A set of PkgType constants or {@link PkgType#PKG_ALL} to clear everything.
+ */
+ public boolean hasChanged(@NonNull EnumSet<PkgType> filters) {
+ for (PkgType filter : filters) {
+ Collection<LocalDirInfo> dirInfos;
+ synchronized (mLocalPackages) {
+ dirInfos = mVisitedDirs.get(filter);
+ }
+ for(LocalDirInfo dirInfo : dirInfos) {
+ if (dirInfo.hasChanged()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ //--------- Generic querying ---------
+
+
+ /**
+ * Retrieves information on a package identified by an {@link IPkgDesc}.
+ *
+ * @param descriptor {@link IPkgDesc} describing a package.
+ * @return The first package found with the same descriptor or null.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull IPkgDesc descriptor) {
+
+ for (LocalPkgInfo pkg : getPkgsInfos(EnumSet.of(descriptor.getType()))) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.equals(descriptor)) {
+ return pkg;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by an {@link AndroidVersion}.
+ *
+ * Note: don't use this for {@link PkgType#PKG_SYS_IMAGE} since there can be more than
+ * one ABI and this method only returns a single package per filter type.
+ *
+ * @param filter {@link PkgType#PKG_PLATFORM}, {@link PkgType#PKG_SAMPLE}
+ * or {@link PkgType#PKG_SOURCE}.
+ * @param version The {@link AndroidVersion} specific for this package type.
+ * @return An existing package information or null if not found.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull AndroidVersion version) {
+ assert filter == PkgType.PKG_PLATFORM ||
+ filter == PkgType.PKG_SAMPLE ||
+ filter == PkgType.PKG_SOURCE;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.hasAndroidVersion() && d.getAndroidVersion().equals(version)) {
+ return pkg;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by its {@link FullRevision}.
+ * <p/>
+ * Note that {@link PkgType#PKG_TOOLS} and {@link PkgType#PKG_PLATFORM_TOOLS}
+ * are unique in a local SDK so you'll want to use {@link #getPkgInfo(PkgType)}
+ * to retrieve them instead.
+ *
+ * @param filter {@link PkgType#PKG_BUILD_TOOLS}.
+ * @param revision The {@link FullRevision} uniquely identifying this package.
+ * @return An existing package information or null if not found.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull FullRevision revision) {
+
+ assert filter == PkgType.PKG_BUILD_TOOLS;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.hasFullRevision() && d.getFullRevision().equals(revision)) {
+ return pkg;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by its {@link String} path.
+ * <p/>
+ * For add-ons and platforms, the path is the target hash string
+ * (see {@link AndroidTargetHash} for helpers methods to generate this string.)
+ *
+ * @param filter {@link PkgType#PKG_ADDON}, {@link PkgType#PKG_PLATFORM}.
+ * @param path The vendor/path uniquely identifying this package.
+ * @return An existing package information or null if not found.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull String path) {
+
+ assert filter == PkgType.PKG_ADDON ||
+ filter == PkgType.PKG_PLATFORM;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.hasPath() && path.equals(d.getPath())) {
+ return pkg;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by both vendor and path strings.
+ * <p/>
+ * For add-ons the path is target hash string
+ * (see {@link AndroidTargetHash} for helpers methods to generate this string.)
+ *
+ * @param filter {@link PkgType#PKG_EXTRA}, {@link PkgType#PKG_ADDON}.
+ * @param vendor The vendor id of the extra package.
+ * @param path The path uniquely identifying this package for its vendor.
+ * @return An existing package information or null if not found.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter,
+ @NonNull String vendor,
+ @NonNull String path) {
+
+ assert filter == PkgType.PKG_EXTRA ||
+ filter == PkgType.PKG_ADDON;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.hasVendor() && vendor.equals(d.getVendor().getId())) {
+ if (d.hasPath() && path.equals(d.getPath())) {
+ return pkg;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves information on an extra package identified by its {@link String} vendor/path.
+ *
+ * @param vendor The vendor id of the extra package.
+ * @param path The path uniquely identifying this package for its vendor.
+ * @return An existing extra package information or null if not found.
+ */
+ @Nullable
+ public LocalExtraPkgInfo getExtra(@NonNull String vendor, @NonNull String path) {
+ return (LocalExtraPkgInfo) getPkgInfo(PkgType.PKG_EXTRA, vendor, path);
+ }
+
+ /**
+ * For unique local packages.
+ * Returns the cached LocalPkgInfo for the requested type.
+ * Loads it from disk if not cached.
+ *
+ * @param filter {@link PkgType#PKG_TOOLS} or {@link PkgType#PKG_PLATFORM_TOOLS}
+ * or {@link PkgType#PKG_DOC}.
+ * @return null if the package is not installed.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter) {
+
+ assert filter == PkgType.PKG_TOOLS ||
+ filter == PkgType.PKG_PLATFORM_TOOLS ||
+ filter == PkgType.PKG_DOC;
+
+ if (filter != PkgType.PKG_TOOLS &&
+ filter != PkgType.PKG_PLATFORM_TOOLS &&
+ filter != PkgType.PKG_DOC) {
+ return null;
+ }
+
+ LocalPkgInfo info = null;
+ synchronized (mLocalPackages) {
+ Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
+ assert existing.size() <= 1;
+ if (existing.size() > 0) {
+ return existing.iterator().next();
+ }
+
+ File uniqueDir = new File(mSdkRoot, filter.getFolderName());
+
+ if (!mVisitedDirs.containsEntry(filter, uniqueDir)) {
+ switch(filter) {
+ case PKG_TOOLS:
+ info = scanTools(uniqueDir);
+ break;
+ case PKG_PLATFORM_TOOLS:
+ info = scanPlatformTools(uniqueDir);
+ break;
+ case PKG_DOC:
+ info = scanDoc(uniqueDir);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Whether we have found a valid pkg or not, this directory has been visited.
+ mVisitedDirs.put(filter, new LocalDirInfo(mFileOp, 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.
+ * The resulting array is sorted according to the PkgInfo's sort order.
+ * <p/>
+ * Note: you can use this with {@link PkgType#PKG_TOOLS}, {@link PkgType#PKG_PLATFORM_TOOLS} and
+ * {@link PkgType#PKG_DOC} but since there can only be one package of these types, it is
+ * more efficient to use {@link #getPkgInfo(PkgType)} to query them.
+ *
+ * @param filter One of {@link PkgType} constants.
+ * @return A list (possibly empty) of matching installed packages. Never returns null.
+ */
+ @NonNull
+ public LocalPkgInfo[] getPkgsInfos(@NonNull PkgType filter) {
+ return getPkgsInfos(EnumSet.of(filter));
+ }
+
+ /**
+ * Retrieve all the info about the requested package types.
+ * This is used for the package types that have one or more instances, each with different
+ * versions.
+ * The resulting array is sorted according to the PkgInfo's sort order.
+ * <p/>
+ * To force the LocalSdk parser to load <b>everything</b>, simply call this method
+ * with the {@link PkgType#PKG_ALL} argument to load all the known package types.
+ * <p/>
+ * Note: you can use this with {@link PkgType#PKG_TOOLS}, {@link PkgType#PKG_PLATFORM_TOOLS} and
+ * {@link PkgType#PKG_DOC} but since there can only be one package of these types, it is
+ * more efficient to use {@link #getPkgInfo(PkgType)} to query them.
+ *
+ * @param filters One or more of {@link PkgType#PKG_ADDON}, {@link PkgType#PKG_PLATFORM},
+ * {@link PkgType#PKG_BUILD_TOOLS}, {@link PkgType#PKG_EXTRA},
+ * {@link PkgType#PKG_SOURCE}, {@link PkgType#PKG_SYS_IMAGE}
+ * @return A list (possibly empty) of matching installed packages. Never returns null.
+ */
+ @NonNull
+ public LocalPkgInfo[] getPkgsInfos(@NonNull EnumSet<PkgType> filters) {
+ List<LocalPkgInfo> list = Lists.newArrayList();
+
+ for (PkgType filter : filters) {
+ if (filter == PkgType.PKG_TOOLS ||
+ filter == PkgType.PKG_PLATFORM_TOOLS ||
+ filter == PkgType.PKG_DOC) {
+ LocalPkgInfo info = getPkgInfo(filter);
+ if (info != null) {
+ list.add(info);
+ }
+ } else {
+ synchronized (mLocalPackages) {
+ Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
+ assert existing != null; // Multimap returns an empty set if not found
+
+ if (!existing.isEmpty()) {
+ list.addAll(existing);
+ continue;
+ }
+
+ File subDir = new File(mSdkRoot, filter.getFolderName());
+
+ if (!mVisitedDirs.containsEntry(filter, subDir)) {
+ switch(filter) {
+ case PKG_BUILD_TOOLS:
+ scanBuildTools(subDir, existing);
+ break;
+
+ case PKG_PLATFORM:
+ scanPlatforms(subDir, existing);
+ break;
+
+ case PKG_SYS_IMAGE:
+ scanSysImages(subDir, existing, false);
+ break;
+
+ case PKG_ADDON_SYS_IMAGE:
+ scanSysImages(subDir, existing, true);
+ break;
+
+ case PKG_ADDON:
+ scanAddons(subDir, existing);
+ break;
+
+ case PKG_SAMPLE:
+ scanSamples(subDir, existing);
+ break;
+
+ case PKG_SOURCE:
+ scanSources(subDir, existing);
+ break;
+
+ case PKG_EXTRA:
+ scanExtras(subDir, existing);
+ break;
+
+ case PKG_TOOLS:
+ case PKG_PLATFORM_TOOLS:
+ case PKG_DOC:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported pkg type " + filter.toString());
+ }
+ mVisitedDirs.put(filter, new LocalDirInfo(mFileOp, subDir));
+ list.addAll(existing);
+ }
+ }
+ }
+ }
+
+ Collections.sort(list);
+ 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(PkgType.PKG_BUILD_TOOLS)}.
+ */
+ @Nullable
+ public BuildToolInfo getBuildTool(@Nullable FullRevision revision) {
+ LocalPkgInfo pkg = getPkgInfo(PkgType.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(PkgType.PKG_BUILD_TOOLS);
+
+ if (pkgs.length == 0) {
+ LocalPkgInfo ptPkg = getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
+ if (ptPkg instanceof LocalPlatformToolPkgInfo &&
+ ptPkg.getDesc().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;
+ }
+
+ @NonNull
+ private BuildToolInfo createLegacyBuildTools(@NonNull 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.getDesc().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,
+ new File(platformTools, SdkConstants.FN_ZIPALIGN));
+ }
+
+ /**
+ * 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 #clearLocalPkg} is called to clear platforms
+ * and/or add-ons.
+ * <p/>
+ * The array can be empty but not null.
+ */
+ @NonNull
+ public IAndroidTarget[] getTargets() {
+ synchronized (mLocalPackages) {
+ if (mReloadTargets) {
+ LocalPkgInfo[] pkgsInfos = getPkgsInfos(EnumSet.of(PkgType.PKG_PLATFORM,
+ PkgType.PKG_ADDON));
+ int n = pkgsInfos.length;
+ mCachedTargets.clear();
+ 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) {
+ mCachedTargets.add(target);
+ }
+ }
+ }
+ }
+ return mCachedTargets.toArray(new IAndroidTarget[mCachedTargets.size()]);
+ }
+ }
+
+ /**
+ * 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) {
+ IAndroidTarget[] targets = getTargets();
+ for (IAndroidTarget target : targets) {
+ 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.getPropertyFull(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ return null;
+ }
+
+ FullRevision minPlatToolsRev =
+ PackageParserUtils.getPropertyFull(props, PkgProps.MIN_PLATFORM_TOOLS_REV);
+ if (minPlatToolsRev == null) {
+ minPlatToolsRev = FullRevision.NOT_SPECIFIED;
+ }
+
+ LocalToolPkgInfo info = new LocalToolPkgInfo(this, toolFolder, props, rev, minPlatToolsRev);
+
+ // 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.getPropertyFull(props, PkgProps.PKG_REVISION);
+ 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.getPropertyMajor(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ return null;
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+ LocalDocPkgInfo info = new LocalDocPkgInfo(this, docFolder, props, vers, 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;
+
+ } catch (AndroidVersionException e) {
+ return null; // skip invalid or missing android version.
+ }
+ }
+
+ /**
+ * Helper used by scanXyz methods below to check whether a directory should be visited.
+ * It can be skipped if it's not a directory or if it's already marked as visited in
+ * mVisitedDirs for the given package type -- in which case the directory is added to
+ * the visited map.
+ *
+ * @param pkgType The package type being scanned.
+ * @param directory The file or directory to check.
+ * @return False if directory can/should be skipped.
+ * True if directory should be visited, in which case it's registered in mVisitedDirs.
+ */
+ private boolean shouldVisitDir(@NonNull PkgType pkgType, @NonNull File directory) {
+ if (!mFileOp.isDirectory(directory)) {
+ return false;
+ }
+ synchronized (mLocalPackages) {
+ if (mVisitedDirs.containsEntry(pkgType, directory)) {
+ return false;
+ }
+ mVisitedDirs.put(pkgType, new LocalDirInfo(mFileOp, directory));
+ }
+ return true;
+ }
+
+ 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 (!shouldVisitDir(PkgType.PKG_BUILD_TOOLS, buildToolDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(buildToolDir, SdkConstants.FN_SOURCE_PROP));
+ FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
+ 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 (!shouldVisitDir(PkgType.PKG_PLATFORM, platformDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ FullRevision minToolsRev =
+ PackageParserUtils.getPropertyFull(props, PkgProps.MIN_TOOLS_REV);
+ if (minToolsRev == null) {
+ minToolsRev = FullRevision.NOT_SPECIFIED;
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+
+ LocalPlatformPkgInfo pkgInfo =
+ new LocalPlatformPkgInfo(this, platformDir, props, vers, rev, minToolsRev);
+ 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 (!shouldVisitDir(PkgType.PKG_ADDON, addonDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(addonDir, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+
+ // Starting with addon-4.xsd, we have vendor-id and name-id available
+ // in the add-on source properties so we'll use that directly.
+
+ String nameId = props.getProperty(PkgProps.ADDON_NAME_ID);
+ String nameDisp = props.getProperty(PkgProps.ADDON_NAME_DISPLAY);
+ String vendorId = props.getProperty(PkgProps.ADDON_VENDOR_ID);
+ String vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR_DISPLAY);
+
+ if (nameId == null) {
+ // Support earlier add-ons that only had a name display attribute
+ nameDisp = props.getProperty(PkgProps.ADDON_NAME, "Unknown");
+ nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(nameDisp);
+ }
+
+ if (nameId != null && nameDisp == null) {
+ nameDisp = LocalExtraPkgInfo.getPrettyName(null, nameId);
+ }
+
+ if (vendorId != null && vendorDisp == null) {
+ vendorDisp = LocalExtraPkgInfo.getPrettyName(null, nameId);
+ }
+
+ if (vendorId == null) {
+ // Support earlier add-ons that only had a vendor display attribute
+ vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR, "Unknown");
+ vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(vendorDisp);
+ }
+
+ LocalAddonPkgInfo pkgInfo = new LocalAddonPkgInfo(
+ this, addonDir, props, vers, rev,
+ new IdDisplay(vendorId, vendorDisp),
+ new IdDisplay(nameId, nameDisp));
+ outCollection.add(pkgInfo);
+
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanSysImages(
+ File collectionDir,
+ Collection<LocalPkgInfo> outCollection,
+ boolean scanAddons) {
+ List<File> propFiles = Lists.newArrayList();
+
+ // Create a list of folders that contains a source.properties file matching these pattenrs:
+ // sys-img/target/tag/abi
+ // sys-img/target/abis
+ // sys-img/add-on-target/abi
+ for (File platformDir : mFileOp.listFiles(collectionDir)) {
+ if (!shouldVisitDir(PkgType.PKG_SYS_IMAGE, platformDir)) {
+ continue;
+ }
+
+ for (File dir1 : mFileOp.listFiles(platformDir)) {
+ // dir1 might be either a tag or an abi folder.
+ if (!shouldVisitDir(PkgType.PKG_SYS_IMAGE, dir1)) {
+ continue;
+ }
+
+ File prop1 = new File(dir1, SdkConstants.FN_SOURCE_PROP);
+ if (mFileOp.isFile(prop1)) {
+ // dir1 was a legacy abi folder.
+ if (!propFiles.contains(prop1)) {
+ propFiles.add(prop1);
+ }
+ } else {
+ File[] dir1Files = mFileOp.listFiles(dir1);
+ for (File dir2 : dir1Files) {
+ // dir2 should be an abi folder in a tag folder.
+ if (!shouldVisitDir(PkgType.PKG_SYS_IMAGE, dir2)) {
+ continue;
+ }
+
+ File prop2 = new File(dir2, SdkConstants.FN_SOURCE_PROP);
+ if (mFileOp.isFile(prop2)) {
+ if (!propFiles.contains(prop2)) {
+ propFiles.add(prop2);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (File propFile : propFiles) {
+ Properties props = parseProperties(propFile);
+ MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+
+ IdDisplay tag = LocalSysImgPkgInfo.extractTagFromProps(props);
+ String vendorId = props.getProperty(PkgProps.ADDON_VENDOR_ID, null);
+ File abiDir = propFile.getParentFile();
+
+ if (vendorId == null && !scanAddons) {
+ LocalSysImgPkgInfo pkgInfo =
+ new LocalSysImgPkgInfo(this, abiDir, props, vers, tag, abiDir.getName(), rev);
+ outCollection.add(pkgInfo);
+
+ } else if (vendorId != null && scanAddons) {
+ String vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR_DISPLAY, vendorId);
+ IdDisplay vendor = new IdDisplay(vendorId, vendorDisp);
+
+ LocalAddonSysImgPkgInfo pkgInfo =
+ new LocalAddonSysImgPkgInfo(
+ this, abiDir, props, vers, vendor, tag, 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 (!shouldVisitDir(PkgType.PKG_SAMPLE, platformDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ FullRevision minToolsRev =
+ PackageParserUtils.getPropertyFull(props, PkgProps.MIN_TOOLS_REV);
+ if (minToolsRev == null) {
+ minToolsRev = FullRevision.NOT_SPECIFIED;
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+
+ LocalSamplePkgInfo pkgInfo =
+ new LocalSamplePkgInfo(this, platformDir, props, vers, rev, minToolsRev);
+ 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 (!shouldVisitDir(PkgType.PKG_SOURCE, platformDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
+ 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 (!shouldVisitDir(PkgType.PKG_EXTRA, vendorDir)) {
+ continue;
+ }
+
+ for (File extraDir : mFileOp.listFiles(vendorDir)) {
+ if (!shouldVisitDir(PkgType.PKG_EXTRA, extraDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(extraDir, SdkConstants.FN_SOURCE_PROP));
+ NoPreviewRevision rev =
+ PackageParserUtils.getPropertyNoPreview(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ String oldPaths =
+ PackageParserUtils.getProperty(props, PkgProps.EXTRA_OLD_PATHS, null);
+
+ String vendorId = vendorDir.getName();
+ String vendorDisp = props.getProperty(PkgProps.EXTRA_VENDOR_DISPLAY);
+ if (vendorDisp == null || vendorDisp.isEmpty()) {
+ vendorDisp = vendorId;
+ }
+
+ String displayName = props.getProperty(PkgProps.EXTRA_NAME_DISPLAY, null);
+
+ LocalExtraPkgInfo pkgInfo = new LocalExtraPkgInfo(
+ this,
+ extraDir,
+ props,
+ new IdDisplay(vendorId, vendorDisp),
+ extraDir.getName(),
+ displayName,
+ PkgDescExtra.convertOldPaths(oldPaths),
+ 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;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSourcePkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSourcePkgInfo.java
new file mode 100755
index 0000000..9b13409
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSourcePkgInfo.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.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 com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+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 LocalPkgInfo {
+
+ private final @NonNull IPkgDesc mDesc;
+
+ public LocalSourcePkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newSource(version, revision).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ @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/repository/local/LocalSysImgPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSysImgPkgInfo.java
new file mode 100755
index 0000000..fc52a2f
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSysImgPkgInfo.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.SystemImage;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Locale;
+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 LocalPkgInfo {
+
+
+ private final @NonNull IPkgDesc mDesc;
+
+ public LocalSysImgPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @Nullable IdDisplay tag,
+ @NonNull String abi,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newSysImg(version, tag, abi, revision).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ /**
+ * Extracts the tag id & display from the properties.
+ * If missing, uses the "default" tag id.
+ */
+ @NonNull
+ public static IdDisplay extractTagFromProps(Properties props) {
+ if (props != null) {
+ String tagId = props.getProperty(PkgProps.SYS_IMG_TAG_ID,
+ SystemImage.DEFAULT_TAG.getId());
+ String tagDisp = props.getProperty(PkgProps.SYS_IMG_TAG_DISPLAY, ""); //$NON-NLS-1$
+ if (tagDisp == null || tagDisp.isEmpty()) {
+ tagDisp = tagIdToDisplay(tagId);
+ }
+ assert tagId != null;
+ assert tagDisp != null;
+ return new IdDisplay(tagId, tagDisp);
+ }
+ return SystemImage.DEFAULT_TAG;
+ }
+
+ /**
+ * Computes a display-friendly tag string based on the tag id.
+ * This is typically used when there's no tag-display attribute.
+ *
+ * @param tagId A non-null tag id to sanitize for display.
+ * @return The tag id with all non-alphanum symbols replaced by spaces and trimmed.
+ */
+ @NonNull
+ public static String tagIdToDisplay(@NonNull String tagId) {
+ String name;
+ name = tagId.replaceAll("[^A-Za-z0-9]+", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ name = name.replaceAll(" +", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ name = name.trim();
+
+ if (name.length() > 0) {
+ char c = name.charAt(0);
+ if (!Character.isUpperCase(c)) {
+ StringBuilder sb = new StringBuilder(name);
+ sb.replace(0, 1, String.valueOf(c).toUpperCase(Locale.US));
+ name = sb.toString();
+ }
+ }
+ return name;
+ }
+
+ // 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/repository/local/LocalToolPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalToolPkgInfo.java
new file mode 100755
index 0000000..c375eb7
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalToolPkgInfo.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.ToolPackage;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalToolPkgInfo extends LocalPkgInfo {
+
+ private final @NonNull IPkgDesc mDesc;
+
+ public LocalToolPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull FullRevision revision,
+ @NonNull FullRevision minPlatformToolsRev) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newTool(revision, minPlatformToolsRev).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ @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
+ 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/local/Update.java b/sdklib/src/main/java/com/android/sdklib/repository/local/Update.java
new file mode 100755
index 0000000..00f1c3d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/Update.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.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repository.remote.RemotePkgInfo;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+
+
+/**
+ * Helper methods to compute updates available for local packages.
+ */
+public abstract class Update {
+
+ public static UpdateResult computeUpdates(@NonNull LocalPkgInfo[] localPkgs,
+ @NonNull Multimap<PkgType, RemotePkgInfo> remotePkgs) {
+
+ UpdateResult result = new UpdateResult();
+ Set<RemotePkgInfo> updates = Sets.newTreeSet();
+
+ // Find updates to locally installed packages
+ for (LocalPkgInfo local : localPkgs) {
+ RemotePkgInfo update = findUpdate(local, remotePkgs, result);
+ if (update != null) {
+ updates.add(update);
+ }
+ }
+
+ // Find new packages not yet installed
+ nextRemote: for (RemotePkgInfo remote : remotePkgs.values()) {
+ if (updates.contains(remote)) {
+ // if package is already a known update, it's not new.
+ continue nextRemote;
+ }
+ IPkgDesc remoteDesc = remote.getDesc();
+ for (LocalPkgInfo local : localPkgs) {
+ IPkgDesc localDesc = local.getDesc();
+ if (remoteDesc.compareTo(localDesc) == 0 || remoteDesc.isUpdateFor(localDesc)) {
+ // if package is same as an installed or is an update for an installed
+ // one, then it's not new.
+ continue nextRemote;
+ }
+ }
+
+ result.addNewPkgs(remote);
+ }
+
+ return result;
+ }
+
+ private static RemotePkgInfo findUpdate(@NonNull LocalPkgInfo local,
+ @NonNull Multimap<PkgType, RemotePkgInfo> remotePkgs,
+ @NonNull UpdateResult result) {
+ RemotePkgInfo currUpdatePkg = null;
+ IPkgDesc currUpdateDesc = null;
+ IPkgDesc localDesc = local.getDesc();
+
+ for (RemotePkgInfo remote: remotePkgs.get(localDesc.getType())) {
+ IPkgDesc remoteDesc = remote.getDesc();
+ if ((currUpdateDesc == null && remoteDesc.isUpdateFor(localDesc)) ||
+ (currUpdateDesc != null && remoteDesc.isUpdateFor(currUpdateDesc))) {
+ currUpdatePkg = remote;
+ currUpdateDesc = remoteDesc;
+ }
+ }
+
+ local.setUpdate(currUpdatePkg);
+ if (currUpdatePkg != null) {
+ result.addUpdatedPkgs(local);
+ }
+
+ return currUpdatePkg;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/UpdateResult.java b/sdklib/src/main/java/com/android/sdklib/repository/local/UpdateResult.java
new file mode 100755
index 0000000..9253e45
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/UpdateResult.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.repository.remote.RemotePkgInfo;
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+
+/**
+ * Results from {@link Update#computeUpdates(LocalPkgInfo[], com.google.common.collect.Multimap)}.
+ */
+public final class UpdateResult {
+ private final Set<LocalPkgInfo> mUpdatedPkgs = Sets.newTreeSet();
+ private final Set<RemotePkgInfo> mNewPkgs = Sets.newTreeSet();
+ private final long mTimestampMs;
+
+ public UpdateResult() {
+ mTimestampMs = System.currentTimeMillis();
+ }
+
+ /**
+ * Returns the timestamp (in {@link System#currentTimeMillis()} time) when this object was created.
+ */
+ public long getTimestampMs() {
+ return mTimestampMs;
+ }
+
+ /**
+ * Returns the set of packages that have local updates available.
+ * Use {@link com.android.sdklib.repository.local.LocalPkgInfo#getUpdate()} to retrieve the computed updated candidate.
+ *
+ * @return A non-null, possibly empty list of update candidates.
+ */
+ @NonNull
+ public Set<LocalPkgInfo> getUpdatedPkgs() {
+ return mUpdatedPkgs;
+ }
+
+ /**
+ * Returns the set of new remote packages that are not locally present
+ * and that the user could install.
+ *
+ * @return A non-null, possibly empty list of new install candidates.
+ */
+ @NonNull
+ public Set<RemotePkgInfo> getNewPkgs() {
+ return mNewPkgs;
+ }
+
+ /**
+ * Add a package to the set of packages with available updates.
+ *
+ * @param pkgInfo The {@link LocalPkgInfo} which has an available update.
+ */
+ void addUpdatedPkgs(@NonNull LocalPkgInfo pkgInfo) {
+ mUpdatedPkgs.add(pkgInfo);
+ }
+
+ /**
+ * Add a package to the set of new remote packages that are not locally present
+ * and that the user could install.
+ *
+ * @param pkgInfo The {@link RemotePkgInfo} which has an available update.
+ */
+ void addNewPkgs(@NonNull RemotePkgInfo pkgInfo) {
+ mNewPkgs.add(pkgInfo);
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/remote/RemotePkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/remote/RemotePkgInfo.java
new file mode 100755
index 0000000..b3cd752
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/remote/RemotePkgInfo.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.sdklib.repository.remote;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.IListDescription;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+
+
+/**
+ * This class provides information on a remote package available for download
+ * via a remote SDK repository server.
+ */
+public class RemotePkgInfo
+ implements IDescription, IListDescription, Comparable<RemotePkgInfo> {
+
+ /** Information on the package provided by the remote server. */
+ @NonNull
+ private final IPkgDesc mPkgDesc;
+
+ /** Source identifier of the package. */
+ @NonNull
+ private final IDescription mSourceUri;
+
+ public RemotePkgInfo(@NonNull IPkgDesc pkgDesc, @NonNull IDescription sourceUri) {
+ mPkgDesc = pkgDesc;
+ mSourceUri = sourceUri;
+ }
+
+ /** Information on the package provided by the remote server. */
+ @NonNull
+ public IPkgDesc getDesc() {
+ return mPkgDesc;
+ }
+
+ /**
+ * Returns the source identifier of the remote package.
+ * This is an opaque object that can return its own description.
+ */
+ @NonNull
+ public IDescription getSourceUri() {
+ return mSourceUri;
+ }
+
+ //---- Ordering ----
+
+ /**
+ * Compares 2 packages by comparing their {@link IPkgDesc}.
+ * The source is not used in the comparison.
+ */
+ @Override
+ public int compareTo(@NonNull RemotePkgInfo o) {
+ return mPkgDesc.compareTo(o.mPkgDesc);
+ }
+
+ /**
+ * The remote package hash code is based on the underlying {@link IPkgDesc}.
+ * The source is not used in the hash code.
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mPkgDesc == null) ? 0 : mPkgDesc.hashCode());
+ return result;
+ }
+
+ /**
+ * Compares 2 packages by comparing their {@link IPkgDesc}.
+ * The source is not used in the comparison.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof RemotePkgInfo) && this.compareTo((RemotePkgInfo) obj) == 0;
+ }
+
+ /** String representation for debugging purposes. */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<RemotePkgInfo Source:").append(mSourceUri.getShortDescription());
+ builder.append(' ').append(mPkgDesc.toString()).append('>');
+ return builder.toString();
+ }
+
+ @Override
+ public String getListDescription() {
+ return getDesc().getListDescription();
+ }
+
+ @Override
+ public String getShortDescription() {
+ // TODO revisit to differentiate from list-description depending
+ // on how we'll use it in the sdkman UI.
+ return getListDescription();
+ }
+
+ @Override
+ public String getLongDescription() {
+ StringBuilder sb = new StringBuilder();
+ IPkgDesc desc = getDesc();
+
+ sb.append(desc.getListDescription()).append('\n');
+
+ if (desc.hasVendor()) {
+ assert desc.getVendor() != null;
+ sb.append("By ").append(desc.getVendor().getDisplay()).append('\n');
+ }
+
+ if (desc.hasMinPlatformToolsRev()) {
+ assert desc.getMinPlatformToolsRev() != null;
+ sb.append("Requires Platform-Tools revision ").append(desc.getMinPlatformToolsRev().toShortString()).append('\n');
+ }
+
+ if (desc.hasMinToolsRev()) {
+ assert desc.getMinToolsRev() != null;
+ sb.append("Requires Tools revision ").append(desc.getMinToolsRev().toShortString()).append('\n');
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/remote/RemoteSdk.java b/sdklib/src/main/java/com/android/sdklib/repository/remote/RemoteSdk.java
new file mode 100755
index 0000000..02df59e
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/remote/RemoteSdk.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.remote;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.internal.repository.AddonsListFetcher;
+import com.android.sdklib.internal.repository.DownloadCache;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.NullTaskMonitor;
+import com.android.sdklib.internal.repository.AddonsListFetcher.Site;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.sources.SdkAddonSource;
+import com.android.sdklib.internal.repository.sources.SdkRepoSource;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
+import com.android.sdklib.internal.repository.sources.SdkSources;
+import com.android.sdklib.internal.repository.sources.SdkSysImgSource;
+import com.android.sdklib.internal.repository.updater.SettingsController;
+import com.android.sdklib.internal.repository.updater.SettingsController.OnChangedListener;
+import com.android.sdklib.repository.SdkAddonsListConstants;
+import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.utils.ILogger;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+
+/**
+ * This class keeps information on the remote SDK repository.
+ */
+public class RemoteSdk {
+
+ /** Default expiration delay is 24 hours. */
+ public final static long DEFAULT_EXPIRATION_PERIOD_MS = 24 * 3600 * 1000;
+
+ private final SettingsController mSettingsController;
+ private final SdkSources mSdkSources = new SdkSources();
+ private long mSdkSourceTS;
+ private DownloadCache mDownloadCache;
+
+ public RemoteSdk(SettingsController settingsController) {
+ mSettingsController = settingsController;
+ settingsController.registerOnChangedListener(new OnChangedListener() {
+ @Override
+ public void onSettingsChanged(@NonNull SettingsController controller,
+ @NonNull SettingsController.Settings oldSettings) {
+ // Reset the download cache if it doesn't match the right strategy.
+ // The cache instance gets lazily recreated later in getDownloadCache().
+ mDownloadCache = null;
+ }
+ });
+ }
+
+ /**
+ * Fetches the remote list of packages.
+ * <p/>
+ * This respects the settings from the {@link SettingsController} which
+ * dictates whether the {@link DownloadCache} is used and whether HTTP
+ * is enforced over HTTPS.
+ * <p/>
+ * The call may block on network access. Callers will likely want to invoke this
+ * from a thread and make sure the logger is thread-safe with regard to UI updates.
+ *
+ * @param sources The sources to download from.
+ * @param logger A logger to report status & progress.
+ * @return A non-null map of {@link PkgType} to {@link RemotePkgInfo}
+ * describing the remote packages available for install/download.
+ */
+ @NonNull
+ public Multimap<PkgType, RemotePkgInfo> fetch(@NonNull SdkSources sources,
+ @NonNull ILogger logger) {
+ Multimap<PkgType, RemotePkgInfo> remotes = HashMultimap.create();
+
+ boolean forceHttp = mSettingsController.getSettings().getForceHttp();
+
+ // Implementation detail: right now this reuses the SdkSource(s) classes
+ // from the sdk-repository v2. The problem with that is that the sources are
+ // mutable and hold the fetch logic and hold the packages array.
+ // Instead I'd prefer to have the sources be immutable descriptors and move
+ // the fetch logic here. Eventually my goal is to get rid of them
+ // and include the logic directly here instead but for right now lets
+ // just start with what we have to avoid implementing it all at once.
+ // It does mean however that this code needs to convert the old Package
+ // type into the new RemotePkgInfo type.
+
+ for (SdkSource source : sources.getAllSources()) {
+ source.load(getDownloadCache(),
+ new NullTaskMonitor(logger),
+ forceHttp);
+ Package[] pkgs = source.getPackages();
+ if (pkgs == null || pkgs.length == 0) {
+ continue;
+ }
+
+ // Adapt the legacy Package instances into the new RemotePkgInfo
+ for (Package p : pkgs) {
+ IPkgDesc d = p.getPkgDesc();
+ RemotePkgInfo r = new RemotePkgInfo(d, source);
+ remotes.put(d.getType(), r);
+ }
+ }
+
+ return remotes;
+ }
+
+ /**
+ * Returns the {@link SdkSources} object listing all sources to load from.
+ * This includes the main repository.xml, the main addon.xml as well as all the
+ * add-ons or sys-img xmls listed in the addons-list.xml.
+ * <p/>
+ * The method caches the last access and only refresh it if data is either not
+ * present or the expiration time has be passed.
+ *
+ * @param expirationDelayMs The expiration delay in milliseconds.
+ * Use {@link #DEFAULT_EXPIRATION_PERIOD_MS} by default.
+ * @param logger A non-null object to log messages. TODO change to an ITaskMonitor
+ * to be able to update the caller's progress bar UI, if any.
+ * @return A non-null {@link SdkSources}
+ */
+ @NonNull
+ public SdkSources fetchSources(long expirationDelayMs, @NonNull ILogger logger) {
+ long now = System.currentTimeMillis();
+ boolean expired = (now - mSdkSourceTS) > expirationDelayMs;
+
+ // Load the conventional sources.
+ // For testing, the env var can be set to replace the default root download URL.
+ // It must end with a / and its the location where the updater will look for
+ // the repository.xml, addons_list.xml and such files.
+
+ if (expired || !mSdkSources.hasSources(SdkSourceCategory.ANDROID_REPO)) {
+ String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
+ if (baseUrl == null || baseUrl.length() <= 0 || !baseUrl.endsWith("/")) { //$NON-NLS-1$
+ baseUrl = SdkRepoConstants.URL_GOOGLE_SDK_SITE;
+ }
+
+ mSdkSources.removeAll(SdkSourceCategory.ANDROID_REPO);
+
+ mSdkSources.add(SdkSourceCategory.ANDROID_REPO,
+ new SdkRepoSource(baseUrl,
+ SdkSourceCategory.ANDROID_REPO.getUiName()));
+ }
+
+ // Load user sources (this will also notify change listeners but this operation is
+ // done early enough that there shouldn't be any anyway.)
+ if (expired || !mSdkSources.hasSources(SdkSourceCategory.USER_ADDONS)) {
+ mSdkSources.loadUserAddons(logger);
+ }
+
+ if (expired || !mSdkSources.hasSources(SdkSourceCategory.ADDONS_3RD_PARTY)) {
+ ITaskMonitor tempMonitor = new NullTaskMonitor(logger);
+
+ String url = SdkAddonsListConstants.URL_ADDON_LIST;
+
+ // We override SdkRepoConstants.URL_GOOGLE_SDK_SITE if this is defined
+ String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
+ if (baseUrl != null) {
+ if (baseUrl.length() > 0 && baseUrl.endsWith("/")) { //$NON-NLS-1$
+ if (url.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {
+ url = baseUrl + url.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());
+ }
+ } else {
+ tempMonitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl); //$NON-NLS-1$
+ }
+ }
+
+ if (mSettingsController.getSettings().getForceHttp()) {
+ url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ // Hook to bypass loading 3rd party addons lists.
+ boolean fetch3rdParties = System.getenv("SDK_SKIP_3RD_PARTIES") == null;
+
+ AddonsListFetcher fetcher = new AddonsListFetcher();
+ Site[] sites = fetcher.fetch(url, getDownloadCache(), tempMonitor);
+
+ if (sites != null) {
+ mSdkSources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);
+
+ if (fetch3rdParties) {
+ for (Site s : sites) {
+ switch (s.getType()) {
+ case ADDON_SITE:
+ mSdkSources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
+ new SdkAddonSource(s.getUrl(), s.getUiName()));
+ break;
+ case SYS_IMG_SITE:
+ mSdkSources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
+ new SdkSysImgSource(s.getUrl(), s.getUiName()));
+ break;
+ }
+ }
+ }
+ mSdkSources.notifyChangeListeners();
+ }
+ }
+
+ mSdkSourceTS = now;
+
+ return mSdkSources;
+ }
+
+ /**
+ * Returns the {@link DownloadCache}
+ * Extracted so that we can override this in unit tests.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected DownloadCache getDownloadCache() {
+ if (mDownloadCache == null) {
+ mDownloadCache = new DownloadCache(
+ mSettingsController.getSettings().getUseDownloadCache() ?
+ DownloadCache.Strategy.FRESH_CACHE :
+ DownloadCache.Strategy.DIRECT);
+ }
+ return mDownloadCache;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-1.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-01.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-1.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-01.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-2.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-02.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-2.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-02.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-3.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-03.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-3.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-03.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-4.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-04.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-4.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-04.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-5.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-05.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-5.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-05.xsd
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-06.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-6.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-06.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-07.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-07.xsd
new file mode 100755
index 0000000..3c2c13a
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-07.xsd
@@ -0,0 +1,499 @@
+<?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/7"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/addon/7"
+ 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.
+
+ - v7 is used by the SDK Manager in Tools r22.6.4:
+ - It introduces a <list-display> string for all package types.
+ - it changes <archive os=... arch=...> to sub-elements: <host-os>, <host-bits>,
+ <jvm-bits> and <min-jvm-version>.
+ -->
+
+ <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" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- 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 optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The internal vendor id of the extra. Must be unique amongst vendors. -->
+ <xsd:element name="vendor-id" type="sdk:idType" />
+ <!-- The displayed vendor name of the extra. -->
+ <xsd:element name="vendor-display" type="xsd:normalizedString" />
+
+ <!-- The install path sub-folder name. It must not be empty. -->
+ <xsd:element name="path" type="sdk:segmentType" />
+
+ <!-- A semi-colon separated list of "obsolete" path names which are equivalent
+ to the current 'path' name. When a package is seen using an old-paths' name,
+ the package manager will try to upgrade it to the new path. -->
+ <xsd:element name="old-paths" type="sdk:segmentListType" minOccurs="0" />
+
+ <!-- The revision, in major.minor.micro format, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionNoPreviewType" minOccurs="0" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+ <!-- The minimal API level required by this package.
+ Optional. If present, must be an int > 0. -->
+ <xsd:element name="min-api-level" type="xsd:positiveInteger" minOccurs="0" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+
+ <!-- A list of project files contributed by this package. Optional. -->
+ <xsd:element name="project-files" type="sdk:projectFilesType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- A full revision, with a major.minor.micro and an optional preview number.
+ The major number is mandatory, the other elements are optional.
+ -->
+
+ <xsd:complexType name="revisionType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and an
+ optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The major revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="major" type="xsd:positiveInteger" />
+ <!-- The minor revision, an int >= 0, incremented each time a new
+ minor package is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="minor" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The micro revision, an int >= 0, incremented each time a new
+ buf fix is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="micro" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The preview/release candidate revision, an int > 0,
+ incremented each time a new preview is generated.
+ Not present for final releases. -->
+ <xsd:element name="preview" type="xsd:positiveInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- A full revision, with a major.minor.micro but that does not support the
+ optional preview number.
+ The major number is mandatory, the other elements are optional.
+ -->
+
+ <xsd:complexType name="revisionNoPreviewType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and no support for
+ the optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The major revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="major" type="xsd:positiveInteger" />
+ <!-- The minor revision, an int >= 0, incremented each time a new
+ minor package is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="minor" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The micro revision, an int >= 0, incremented each time a new
+ buf fix is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="micro" type="xsd:nonNegativeInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a path segment used by the extra element. -->
+
+ <xsd:simpleType name="segmentType">
+ <xsd:annotation>
+ <xsd:documentation>
+ One path segment for the install path of an extra element.
+ It must be a single-segment path. It must not be empty.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <xsd:element name="host-os" type="sdk:osType" minOccurs="0" />
+ <xsd:element name="host-bits" type="sdk:bitSizeType" minOccurs="0" />
+ <xsd:element name="jvm-bits" type="sdk:bitSizeType" minOccurs="0" />
+ <xsd:element name="min-jvm-version" type="sdk:jvmVersionType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of archive filters -->
+
+ <xsd:simpleType name="bitSizeType">
+ <xsd:annotation>
+ <xsd:documentation>A CPU bit size filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="32" />
+ <xsd:enumeration value="64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="osType">
+ <xsd:annotation>
+ <xsd:documentation>A host OS filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="jvmVersionType">
+ <xsd:annotation>
+ <xsd:documentation>A JVM version number, e.g. "1" or "1.6" or "1.14.15".</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([1-9](\.[1-9]{1,2}){0,2})"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- 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/main/java/com/android/sdklib/repository/sdk-repository-1.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-01.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-1.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-01.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-2.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-02.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-2.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-02.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-3.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-03.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-3.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-03.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-4.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-04.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-4.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-04.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-5.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-05.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-5.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-05.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-6.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-06.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-6.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-06.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-7.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-07.xsd
similarity index 100%
rename from sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-7.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-07.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-08.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-08.xsd
new file mode 100755
index 0000000..0c4ca63
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-08.xsd
@@ -0,0 +1,652 @@
+<?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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/repository/8"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/8"
+ 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 SDK repository is a web site that contains a "repository.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r3 and r4.
+
+ - v2 is used by the SDK Updater in Tools r5:
+ - It introduces a new <sample> repository type. Previously samples
+ were included in the <platform> packages. Instead this package is used
+ and and the samples are installed in $SDK/samples.
+ - All repository types have a new <obsolete> node. It works as a marker
+ to indicate the package is obsolete and should not be selected by default.
+ The UI also hides these out by default.
+
+ - v3 is used by the SDK Updater in Tools r8:
+ - It introduces a new <platform-tool> repository type. Previously platform-specific
+ tools were included in the <platform> packages. Instead this package is used
+ and platform-specific tools are installed in $SDK/platform-tools
+ - There's a new element <min-platform-tools-rev> in <tool>. The tool package now
+ requires that at least some minimal version of <platform-tool> be installed.
+ - It removes the <addon> repository type, which is now in its own XML Schema.
+
+ - v4 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.
+ - <platform> element now has a mandatory <layoutlib> that indicates the API
+ and revision of that layout library for this particular platform.
+
+ - v5 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.
+ - <platform> has a new optional <abi-included> that describes the ABI of the
+ system image included in the platform, if any.
+ - New <system-image> package type, to store system images outside of <platform>s.
+ - New <source> package type.
+
+ - v6 is used by the SDK Manager in Tools r18:
+ - <extra> packages are removed. They are served only by the addon XML.
+ - <platform>, <system-image>, <source>, <tool>, <platform-tool>, <doc>
+ and <sample> get a new optional field <beta-rc> which can be used to indicate
+ the package is a Beta Release Candidate and not a final release.
+
+ - v7 is used by the SDK Manager in Tools r20:
+ - For <tool> and <platform-tool> packages, the <revision> element becomes a
+ a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+ - The <beta-rc> element is no longer supported, it is replaced by
+ <revision> -> <preview> and is only for <tool> and <platform-tool> packages.
+ - <min-tools-rev> and <min-platform-tools-rev> also become a full revision element.
+
+ - v8 is used by the SDK Manager in Tools r22.0:
+ - It introduces the new <build-tool> repository type, which contains build-specific
+ tools that were before placed in either <tool> or <platform-tool> (e.g. ant,
+ aapt, aidl, etc.)
+ - <tool> has a new "min-build-tool" attribute.
+ -->
+
+ <xsd:element name="sdk-repository" 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="platform" type="sdk:platformType" />
+ <xsd:element name="system-image" type="sdk:systemImageType" />
+ <xsd:element name="source" type="sdk:sourceType" />
+ <xsd:element name="tool" type="sdk:toolType" />
+ <xsd:element name="platform-tool" type="sdk:platformToolType" />
+ <xsd:element name="build-tool" type="sdk:buildToolType" />
+ <xsd:element name="doc" type="sdk:docType" />
+ <xsd:element name="sample" type="sdk:sampleType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+ <!-- The definition of an SDK platform package. -->
+
+ <xsd:complexType name="platformType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android platform version. It is string such as "1.0". -->
+ <xsd:element name="version" type="xsd:normalizedString" />
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- Information on the layoutlib packaged in this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType" />
+
+ <!-- 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" />
+ <!-- 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 ABI of the system image *included* in this platform, if any.
+ When the field is present, it means the platform already embeds one
+ system image. A platform can also have any number of external
+ <system-image> associated with it. -->
+ <xsd:element name="included-abi" type="sdk:abiType" 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a layout library used by a platform. -->
+
+ <xsd:complexType name="layoutlibType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Version information for a layoutlib included in a platform.
+ </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 a system image used by a platform. -->
+
+ <xsd:complexType name="systemImageType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ System Image for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level + codename identifies the platform to which this system image belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The ABI of the system emulated by this image. -->
+ <xsd:element name="abi" type="sdk:abiType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a source package. -->
+
+ <xsd:complexType name="sourceType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Sources for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level + codename identifies the platform to which this source belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK tool package. -->
+
+ <xsd:complexType name="toolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- 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" />
+
+ <!-- The minimal revision of platform-tools required by this package.
+ Mandatory. Must be a revision element. -->
+ <xsd:element name="min-platform-tools-rev" type="sdk:revisionType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK platform-tool package. -->
+
+ <xsd:complexType name="platformToolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+ <!-- The definition of an SDK build-tool package. -->
+
+ <xsd:complexType name="buildToolType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK build-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK doc package. -->
+
+ <xsd:complexType name="docType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK doc package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK sample package. -->
+
+ <xsd:complexType name="sampleType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK sample package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- 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" />
+ <!-- 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" />
+
+
+ <!-- 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" />
+ </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.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- A 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 collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of a file checksum -->
+
+ <xsd:simpleType name="sha1Number">
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="checksumType">
+ <xsd:annotation>
+ <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="sdk:sha1Number">
+ <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-09.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-09.xsd
new file mode 100755
index 0000000..1b9d68d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-09.xsd
@@ -0,0 +1,677 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/repository/9"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/9"
+ 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 SDK repository is a web site that contains a "repository.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r3 and r4.
+
+ - v2 is used by the SDK Updater in Tools r5:
+ - It introduces a new <sample> repository type. Previously samples
+ were included in the <platform> packages. Instead this package is used
+ and and the samples are installed in $SDK/samples.
+ - All repository types have a new <obsolete> node. It works as a marker
+ to indicate the package is obsolete and should not be selected by default.
+ The UI also hides these out by default.
+
+ - v3 is used by the SDK Updater in Tools r8:
+ - It introduces a new <platform-tool> repository type. Previously platform-specific
+ tools were included in the <platform> packages. Instead this package is used
+ and platform-specific tools are installed in $SDK/platform-tools
+ - There's a new element <min-platform-tools-rev> in <tool>. The tool package now
+ requires that at least some minimal version of <platform-tool> be installed.
+ - It removes the <addon> repository type, which is now in its own XML Schema.
+
+ - v4 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.
+ - <platform> element now has a mandatory <layoutlib> that indicates the API
+ and revision of that layout library for this particular platform.
+
+ - v5 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.
+ - <platform> has a new optional <abi-included> that describes the ABI of the
+ system image included in the platform, if any.
+ - New <system-image> package type, to store system images outside of <platform>s.
+ - New <source> package type.
+
+ - v6 is used by the SDK Manager in Tools r18:
+ - <extra> packages are removed. They are served only by the addon XML.
+ - <platform>, <system-image>, <source>, <tool>, <platform-tool>, <doc>
+ and <sample> get a new optional field <beta-rc> which can be used to indicate
+ the package is a Beta Release Candidate and not a final release.
+
+ - v7 is used by the SDK Manager in Tools r20:
+ - For <tool> and <platform-tool> packages, the <revision> element becomes a
+ a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+ - The <beta-rc> element is no longer supported, it is replaced by
+ <revision> -> <preview> and is only for <tool> and <platform-tool> packages.
+ - <min-tools-rev> and <min-platform-tools-rev> also become a full revision element.
+
+ - v8 is used by the SDK Manager in Tools r22.0:
+ - It introduces the new <build-tool> repository type, which contains build-specific
+ tools that were before placed in either <tool> or <platform-tool> (e.g. ant,
+ aapt, aidl, etc.)
+ - <tool> has a new "min-build-tool" attribute.
+
+ - v9 is used by the SDK Manager in Tools r22.6:
+ - It introduces a sub-tag for the <system-image> repository type.
+ -->
+
+ <xsd:element name="sdk-repository" 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="platform" type="sdk:platformType" />
+ <xsd:element name="system-image" type="sdk:systemImageType" />
+ <xsd:element name="source" type="sdk:sourceType" />
+ <xsd:element name="tool" type="sdk:toolType" />
+ <xsd:element name="platform-tool" type="sdk:platformToolType" />
+ <xsd:element name="build-tool" type="sdk:buildToolType" />
+ <xsd:element name="doc" type="sdk:docType" />
+ <xsd:element name="sample" type="sdk:sampleType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK platform package. -->
+
+ <xsd:complexType name="platformType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android platform version. It is string such as "1.0". -->
+ <xsd:element name="version" type="xsd:normalizedString" />
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- Information on the layoutlib packaged in this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType" />
+
+ <!-- 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" />
+ <!-- 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 ABI of the system image *included* in this platform, if any.
+ When the field is present, it means the platform already embeds one
+ system image. A platform can also have any number of external
+ <system-image> associated with it. -->
+ <xsd:element name="included-abi" type="sdk:abiType" 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a layout library used by a platform. -->
+
+ <xsd:complexType name="layoutlibType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Version information for a layoutlib included in a platform.
+ </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 a system image used by a platform. -->
+
+ <xsd:complexType name="systemImageType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ System Image for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- Implementation detail:
+ any change here must be synchronized within a new sdk-sys-img-N.xsd
+ -->
+
+ <!-- api-level + codename identifies the platform to which this system image belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The tag of the system emulated by this image. -->
+ <xsd:element name="tag-id" type="sdk:idType" />
+ <!-- The displayed tag of the system emulated by this image. Optional. -->
+ <xsd:element name="tag-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The ABI of the system emulated by this image. -->
+ <xsd:element name="abi" type="sdk:abiType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A tag string for a system image can only be a 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 the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a source package. -->
+
+ <xsd:complexType name="sourceType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Sources for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level + codename identifies the platform to which this source belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK tool package. -->
+
+ <xsd:complexType name="toolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- 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" />
+
+ <!-- The minimal revision of platform-tools required by this package.
+ Mandatory. Must be a revision element. -->
+ <xsd:element name="min-platform-tools-rev" type="sdk:revisionType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK platform-tool package. -->
+
+ <xsd:complexType name="platformToolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+ <!-- The definition of an SDK build-tool package. -->
+
+ <xsd:complexType name="buildToolType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK build-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK doc package. -->
+
+ <xsd:complexType name="docType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK doc package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK sample package. -->
+
+ <xsd:complexType name="sampleType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK sample package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- 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" />
+ <!-- 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" />
+
+
+ <!-- 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" />
+ </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.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- A 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 collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of a file checksum -->
+
+ <xsd:simpleType name="sha1Number">
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="checksumType">
+ <xsd:annotation>
+ <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="sdk:sha1Number">
+ <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-10.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-10.xsd
new file mode 100755
index 0000000..3fc1d92
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-10.xsd
@@ -0,0 +1,653 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/repository/10"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/10"
+ 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 SDK repository is a web site that contains a "repository.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r3 and r4.
+
+ - v2 is used by the SDK Updater in Tools r5:
+ - It introduces a new <sample> repository type. Previously samples
+ were included in the <platform> packages. Instead this package is used
+ and and the samples are installed in $SDK/samples.
+ - All repository types have a new <obsolete> node. It works as a marker
+ to indicate the package is obsolete and should not be selected by default.
+ The UI also hides these out by default.
+
+ - v3 is used by the SDK Updater in Tools r8:
+ - It introduces a new <platform-tool> repository type. Previously platform-specific
+ tools were included in the <platform> packages. Instead this package is used
+ and platform-specific tools are installed in $SDK/platform-tools
+ - There's a new element <min-platform-tools-rev> in <tool>. The tool package now
+ requires that at least some minimal version of <platform-tool> be installed.
+ - It removes the <addon> repository type, which is now in its own XML Schema.
+
+ - v4 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.
+ - <platform> element now has a mandatory <layoutlib> that indicates the API
+ and revision of that layout library for this particular platform.
+
+ - v5 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.
+ - <platform> has a new optional <abi-included> that describes the ABI of the
+ system image included in the platform, if any.
+ - New <system-image> package type, to store system images outside of <platform>s.
+ - New <source> package type.
+
+ - v6 is used by the SDK Manager in Tools r18:
+ - <extra> packages are removed. They are served only by the addon XML.
+ - <platform>, <system-image>, <source>, <tool>, <platform-tool>, <doc>
+ and <sample> get a new optional field <beta-rc> which can be used to indicate
+ the package is a Beta Release Candidate and not a final release.
+
+ - v7 is used by the SDK Manager in Tools r20:
+ - For <tool> and <platform-tool> packages, the <revision> element becomes a
+ a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+ - The <beta-rc> element is no longer supported, it is replaced by
+ <revision> -> <preview> and is only for <tool> and <platform-tool> packages.
+ - <min-tools-rev> and <min-platform-tools-rev> also become a full revision element.
+
+ - v8 is used by the SDK Manager in Tools r22.0:
+ - It introduces the new <build-tool> repository type, which contains build-specific
+ tools that were before placed in either <tool> or <platform-tool> (e.g. ant,
+ aapt, aidl, etc.)
+ - <tool> has a new "min-build-tool" attribute.
+
+ - v9 is used by the SDK Manager in Tools r22.6:
+ - It introduces a sub-tag for the <system-image> repository type.
+
+ - v10 is used by the SDK Manager in Tools r22.6.4:
+ - It introduces a <list-display> string for all package types.
+ - It removes the <system-image> type (system images are handled by sdk-sys-img-3.xsd)
+ - it changes <archive os=... arch=...> to sub-elements: <host-os>, <host-bits>,
+ <jvm-bits> and <min-jvm-version>.
+ -->
+
+ <xsd:element name="sdk-repository" 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="platform" type="sdk:platformType" />
+ <xsd:element name="source" type="sdk:sourceType" />
+ <xsd:element name="tool" type="sdk:toolType" />
+ <xsd:element name="platform-tool" type="sdk:platformToolType" />
+ <xsd:element name="build-tool" type="sdk:buildToolType" />
+ <xsd:element name="doc" type="sdk:docType" />
+ <xsd:element name="sample" type="sdk:sampleType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK platform package. -->
+
+ <xsd:complexType name="platformType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android platform version. It is string such as "1.0". -->
+ <xsd:element name="version" type="xsd:normalizedString" />
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- Information on the layoutlib packaged in this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType" />
+
+ <!-- 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" />
+ <!-- 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 ABI of the system image *included* in this platform, if any.
+ When the field is present, it means the platform already embeds one
+ system image. A platform can also have any number of external
+ <system-image> associated with it. -->
+ <xsd:element name="included-abi" type="sdk:abiType" 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a layout library used by a platform. -->
+
+ <xsd:complexType name="layoutlibType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Version information for a layoutlib included in a platform.
+ </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 the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a source package. -->
+
+ <xsd:complexType name="sourceType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Sources for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level + codename identifies the platform to which this source belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK tool package. -->
+
+ <xsd:complexType name="toolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- 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" />
+
+ <!-- The minimal revision of platform-tools required by this package.
+ Mandatory. Must be a revision element. -->
+ <xsd:element name="min-platform-tools-rev" type="sdk:revisionType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK platform-tool package. -->
+
+ <xsd:complexType name="platformToolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+ <!-- The definition of an SDK build-tool package. -->
+
+ <xsd:complexType name="buildToolType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK build-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- optional elements -->
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK doc package. -->
+
+ <xsd:complexType name="docType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK doc package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK sample package. -->
+
+ <xsd:complexType name="sampleType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK sample package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- 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" />
+ <!-- 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" />
+
+
+ <!-- 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" />
+ </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.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <xsd:element name="host-os" type="sdk:osType" minOccurs="0" />
+ <xsd:element name="host-bits" type="sdk:bitSizeType" minOccurs="0" />
+ <xsd:element name="jvm-bits" type="sdk:bitSizeType" minOccurs="0" />
+ <xsd:element name="min-jvm-version" type="sdk:jvmVersionType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </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 collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of archive filters -->
+
+ <xsd:simpleType name="bitSizeType">
+ <xsd:annotation>
+ <xsd:documentation>A CPU bit size filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="32" />
+ <xsd:enumeration value="64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="osType">
+ <xsd:annotation>
+ <xsd:documentation>A host OS filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="jvmVersionType">
+ <xsd:annotation>
+ <xsd:documentation>A JVM version number, e.g. "1" or "1.6" or "1.14.15".</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([1-9](\.[1-9]{1,2}){0,2})"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- 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/main/java/com/android/sdklib/repository/sdk-repository-8.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-8.xsd
deleted file mode 100755
index ade39d6..0000000
--- a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-8.xsd
+++ /dev/null
@@ -1,652 +0,0 @@
-<?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.
--->
-<xsd:schema
- targetNamespace="http://schemas.android.com/sdk/android/repository/8"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:sdk="http://schemas.android.com/sdk/android/repository/8"
- 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 SDK repository is a web site that contains a "repository.xml"
- file that conforms to this XML Schema.
-
- History:
- - v1 is used by the SDK Updater in Tools r3 and r4.
-
- - v2 is used by the SDK Updater in Tools r5:
- - It introduces a new <sample> repository type. Previously samples
- were included in the <platform> packages. Instead this package is used
- and and the samples are installed in $SDK/samples.
- - All repository types have a new <obsolete> node. It works as a marker
- to indicate the package is obsolete and should not be selected by default.
- The UI also hides these out by default.
-
- - v3 is used by the SDK Updater in Tools r8:
- - It introduces a new <platform-tool> repository type. Previously platform-specific
- tools were included in the <platform> packages. Instead this package is used
- and platform-specific tools are installed in $SDK/platform-tools
- - There's a new element <min-platform-tools-rev> in <tool>. The tool package now
- requires that at least some minimal version of <platform-tool> be installed.
- - It removes the <addon> repository type, which is now in its own XML Schema.
-
- - v4 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.
- - <platform> element now has a mandatory <layoutlib> that indicates the API
- and revision of that layout library for this particular platform.
-
- - v5 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.
- - <platform> has a new optional <abi-included> that describes the ABI of the
- system image included in the platform, if any.
- - New <system-image> package type, to store system images outside of <platform>s.
- - New <source> package type.
-
- - v6 is used by the SDK Manager in Tools r18:
- - <extra> packages are removed. They are served only by the addon XML.
- - <platform>, <system-image>, <source>, <tool>, <platform-tool>, <doc>
- and <sample> get a new optional field <beta-rc> which can be used to indicate
- the package is a Beta Release Candidate and not a final release.
-
- - v7 is used by the SDK Manager in Tools r20:
- - For <tool> and <platform-tool> packages, the <revision> element becomes a
- a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
- - The <beta-rc> element is no longer supported, it is replaced by
- <revision> -> <preview> and is only for <tool> and <platform-tool> packages.
- - <min-tools-rev> and <min-platform-tools-rev> also become a full revision element.
-
- - v8 is used by the SDK Manager in Tools r22:
- - It introduces the new <build-tool> repository type, which contains build-specific
- tools that were before placed in either <tool> or <platform-tool> (e.g. ant,
- aapt, aidl, etc.)
- - <tool> has a new "min-build-tool" attribute.
- -->
-
- <xsd:element name="sdk-repository" 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="platform" type="sdk:platformType" />
- <xsd:element name="system-image" type="sdk:systemImageType" />
- <xsd:element name="source" type="sdk:sourceType" />
- <xsd:element name="tool" type="sdk:toolType" />
- <xsd:element name="platform-tool" type="sdk:platformToolType" />
- <xsd:element name="build-tool" type="sdk:buildToolType" />
- <xsd:element name="doc" type="sdk:docType" />
- <xsd:element name="sample" type="sdk:sampleType" />
- <xsd:element name="license" type="sdk:licenseType" />
- </xsd:choice>
- </xsd:complexType>
-
- <!-- The definition of an SDK platform package. -->
-
- <xsd:complexType name="platformType">
- <xsd:annotation>
- <xsd:documentation>An SDK platform package.</xsd:documentation>
- </xsd:annotation>
- <xsd:all>
- <!-- The Android platform version. It is string such as "1.0". -->
- <xsd:element name="version" type="xsd:normalizedString" />
- <!-- The Android API Level for the platform. An int > 0. -->
- <xsd:element name="api-level" type="xsd:positiveInteger" />
- <!-- The optional codename for this platform, if it's a preview. -->
- <xsd:element name="codename" type="xsd:string" minOccurs="0" />
- <!-- The revision, an int > 0, incremented each time a new
- package is generated. -->
- <xsd:element name="revision" type="xsd:positiveInteger" />
-
- <!-- Information on the layoutlib packaged in this platform. -->
- <xsd:element name="layoutlib" type="sdk:layoutlibType" />
-
- <!-- 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" />
- <!-- 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 ABI of the system image *included* in this platform, if any.
- When the field is present, it means the platform already embeds one
- system image. A platform can also have any number of external
- <system-image> associated with it. -->
- <xsd:element name="included-abi" type="sdk:abiType" 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" />
- </xsd:all>
- </xsd:complexType>
-
-
- <!-- The definition of a layout library used by a platform. -->
-
- <xsd:complexType name="layoutlibType" >
- <xsd:annotation>
- <xsd:documentation>
- Version information for a layoutlib included in a platform.
- </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 a system image used by a platform. -->
-
- <xsd:complexType name="systemImageType" >
- <xsd:annotation>
- <xsd:documentation>
- System Image for a platform.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:all>
- <!-- api-level + codename identifies the platform to which this system image belongs. -->
-
- <!-- The Android API Level for the platform. An int > 0. -->
- <xsd:element name="api-level" type="xsd:positiveInteger" />
- <!-- The optional codename for this platform, if it's a preview. -->
- <xsd:element name="codename" type="xsd:string" minOccurs="0" />
-
- <!-- The revision, an int > 0, incremented each time a new
- package is generated. -->
- <xsd:element name="revision" type="xsd:positiveInteger" />
-
- <!-- The ABI of the system emulated by this image. -->
- <xsd:element name="abi" type="sdk:abiType" />
-
- <!-- 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" />
- </xsd:all>
- </xsd:complexType>
-
-
- <!-- The definition of the ABI supported by a platform's system image. -->
-
- <xsd:simpleType name="abiType">
- <xsd:annotation>
- <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="armeabi" />
- <xsd:enumeration value="armeabi-v7a" />
- <xsd:enumeration value="x86" />
- <xsd:enumeration value="mips" />
- </xsd:restriction>
- </xsd:simpleType>
-
-
- <!-- The definition of a source package. -->
-
- <xsd:complexType name="sourceType" >
- <xsd:annotation>
- <xsd:documentation>
- Sources for a platform.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:all>
- <!-- api-level + codename identifies the platform to which this source belongs. -->
-
- <!-- The Android API Level for the platform. An int > 0. -->
- <xsd:element name="api-level" type="xsd:positiveInteger" />
- <!-- The optional codename for this platform, if it's a preview. -->
- <xsd:element name="codename" type="xsd:string" minOccurs="0" />
-
- <!-- The revision, an int > 0, incremented each time a new
- package is generated. -->
- <xsd:element name="revision" type="xsd:positiveInteger" />
-
- <!-- 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" />
- </xsd:all>
- </xsd:complexType>
-
-
- <!-- The definition of an SDK tool package. -->
-
- <xsd:complexType name="toolType" >
- <xsd:annotation>
- <xsd:documentation>An SDK tool package.</xsd:documentation>
- </xsd:annotation>
- <xsd:all>
- <!-- The full revision (major.minor.micro.preview), incremented each
- time a new package is generated. -->
- <xsd:element name="revision" type="sdk:revisionType" />
-
- <!-- 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" />
-
- <!-- The minimal revision of platform-tools required by this package.
- Mandatory. Must be a revision element. -->
- <xsd:element name="min-platform-tools-rev" type="sdk:revisionType" />
-
- <!-- 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" />
- </xsd:all>
- </xsd:complexType>
-
-
- <!-- The definition of an SDK platform-tool package. -->
-
- <xsd:complexType name="platformToolType" >
- <xsd:annotation>
- <xsd:documentation>An SDK platform-tool package.</xsd:documentation>
- </xsd:annotation>
- <xsd:all>
- <!-- The full revision (major.minor.micro.preview), incremented each
- time a new package is generated. -->
- <xsd:element name="revision" type="sdk:revisionType" />
-
- <!-- 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" />
- </xsd:all>
- </xsd:complexType>
-
- <!-- The definition of an SDK build-tool package. -->
-
- <xsd:complexType name="buildToolType">
- <xsd:annotation>
- <xsd:documentation>An SDK build-tool package.</xsd:documentation>
- </xsd:annotation>
- <xsd:all>
- <!-- The full revision (major.minor.micro.preview), incremented each
- time a new package is generated. -->
- <xsd:element name="revision" type="sdk:revisionType" />
-
- <!-- 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" />
- </xsd:all>
- </xsd:complexType>
-
-
- <!-- The definition of an SDK doc package. -->
-
- <xsd:complexType name="docType" >
- <xsd:annotation>
- <xsd:documentation>An SDK doc package.</xsd:documentation>
- </xsd:annotation>
- <xsd:all>
- <!-- The Android API Level for the documentation. An int > 0. -->
- <xsd:element name="api-level" type="xsd:positiveInteger" />
- <!-- The optional codename for this doc, if it's a preview. -->
- <xsd:element name="codename" type="xsd:string" minOccurs="0" />
-
- <!-- The revision, an int > 0, incremented each time a new
- package is generated. -->
- <xsd:element name="revision" type="xsd:positiveInteger" />
- <!-- 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" />
- </xsd:all>
- </xsd:complexType>
-
-
- <!-- The definition of an SDK sample package. -->
-
- <xsd:complexType name="sampleType" >
- <xsd:annotation>
- <xsd:documentation>An SDK sample package.</xsd:documentation>
- </xsd:annotation>
- <xsd:all>
- <!-- The Android API Level for the documentation. An int > 0. -->
- <xsd:element name="api-level" type="xsd:positiveInteger" />
- <!-- The optional codename for this doc, if it's a preview. -->
- <xsd:element name="codename" type="xsd:string" minOccurs="0" />
-
- <!-- The revision, an int > 0, incremented each time a new
- package is generated. -->
- <xsd:element name="revision" type="xsd:positiveInteger" />
- <!-- 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" />
- <!-- 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" />
-
-
- <!-- 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" />
- </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.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:pattern value="[a-zA-Z0-9_]+"/>
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:simpleType name="segmentListType">
- <xsd:annotation>
- <xsd:documentation>
- A semi-colon separated list of a segmentTypes.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:pattern value="[a-zA-Z0-9_;]+"/>
- </xsd:restriction>
- </xsd:simpleType>
-
-
- <!-- The definition of a license to be referenced by the uses-license element. -->
-
- <xsd:complexType name="licenseType">
- <xsd:annotation>
- <xsd:documentation>
- A license definition. Such a license must be used later as a reference
- using a uses-license element in one of the package elements.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleContent>
- <xsd:extension base="xsd:string">
- <xsd:attribute name="id" type="xsd:ID" />
- <xsd:attribute name="type" type="xsd:token" fixed="text" />
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-
-
- <!-- Type describing the license used by a package.
- The license MUST be defined using a license node and referenced
- using the ref attribute of the license element inside a package.
- -->
-
- <xsd:complexType name="usesLicenseType">
- <xsd:annotation>
- <xsd:documentation>
- Describes the license used by a package. The license MUST be defined
- using a license node and referenced using the ref attribute of the
- license element inside a package.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:attribute name="ref" type="xsd:IDREF" />
- </xsd:complexType>
-
-
- <!-- A collection of files that can be downloaded for a given architecture.
- The <archives> node is mandatory in the repository elements and the
- collection must have at least one <archive> declared.
- Each archive is a zip file that will be unzipped in a location that depends
- on its package type.
- -->
-
- <xsd:complexType name="archivesType">
- <xsd:annotation>
- <xsd:documentation>
- A collection of files that can be downloaded for a given architecture.
- The <archives> node is mandatory in the repository packages and the
- collection must have at least one <archive> declared.
- Each archive is a zip file that will be unzipped in a location that depends
- on its package type.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence minOccurs="1" maxOccurs="unbounded">
- <!-- One archive file -->
- <xsd:element name="archive">
- <xsd:complexType>
- <!-- Properties of the archive file -->
- <xsd:all>
- <!-- The size in bytes of the archive to download. -->
- <xsd:element name="size" type="xsd:positiveInteger" />
- <!-- The checksum of the archive file. -->
- <xsd:element name="checksum" type="sdk:checksumType" />
- <!-- The URL is an absolute URL if it starts with http://, https://
- or ftp://. Otherwise it is relative to the parent directory that
- contains this repository.xml -->
- <xsd:element name="url" type="xsd:token" />
- </xsd:all>
-
- <!-- Attributes that identify the OS and architecture -->
- <xsd:attribute name="os" use="required">
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="any" />
- <xsd:enumeration value="linux" />
- <xsd:enumeration value="macosx" />
- <xsd:enumeration value="windows" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:attribute>
- <xsd:attribute name="arch" use="optional">
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="any" />
- <xsd:enumeration value="ppc" />
- <xsd:enumeration value="x86" />
- <xsd:enumeration value="x86_64" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:attribute>
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
-
- <!-- A 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 collection of file paths available in an <extra> package
- that can be installed in an Android project.
- If present, the <project-files> collection must contain at least one path.
- Each path is relative to the root directory of the package.
- -->
-
- <xsd:complexType name="projectFilesType">
- <xsd:annotation>
- <xsd:documentation>
- A collection of file paths available in an <extra> package
- that can be installed in an Android project.
- If present, the <project-files> collection must contain at least one path.
- Each path is relative to the root directory of the package.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence minOccurs="1" maxOccurs="unbounded">
- <!-- One JAR Path, relative to the root folder of the package. -->
- <xsd:element name="path" type="xsd:string" />
- </xsd:sequence>
- </xsd:complexType>
-
-
- <!-- The definition of a file checksum -->
-
- <xsd:simpleType name="sha1Number">
- <xsd:annotation>
- <xsd:documentation>A SHA1 checksum.</xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:string">
- <xsd:pattern value="([0-9a-fA-F]){40}"/>
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:complexType name="checksumType">
- <xsd:annotation>
- <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
- </xsd:annotation>
- <xsd:simpleContent>
- <xsd:extension base="sdk:sha1Number">
- <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-
-</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-01.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-01.xsd
new file mode 100755
index 0000000..005b431
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-01.xsd
@@ -0,0 +1,229 @@
+<?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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/sys-img/1"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/sys-img/1"
+ 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 r20.0. It is split out of the
+ main SDK Repository XML Schema and can only contain <system-image> packages.
+ -->
+
+ <xsd:element name="sdk-sys-img" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable system images.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="system-image" type="sdk:systemImageType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+
+ <!-- The definition of a system image used by a platform. -->
+
+ <xsd:complexType name="systemImageType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ System Image for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level+codename identifies the platform to which this system image belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The ABI of the system emulated by this image. -->
+ <xsd:element name="abi" type="sdk:abiType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- 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/main/java/com/android/sdklib/repository/sdk-sys-img-02.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-02.xsd
new file mode 100755
index 0000000..a1c16a3
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-02.xsd
@@ -0,0 +1,249 @@
+<?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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/sys-img/2"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/sys-img/2"
+ 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 r20.0. It is split out of the
+ main SDK Repository XML Schema and can only contain <system-image> packages.
+
+ - v2 is used by the SDK Manager in Tools r22.6.
+ It introduces a sub-tag for the <system-image> repository type.
+ -->
+
+ <xsd:element name="sdk-sys-img" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable system images.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="system-image" type="sdk:systemImageType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+
+ <!-- The definition of a system image used by a platform. -->
+
+ <xsd:complexType name="systemImageType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ System Image for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level+codename identifies the platform to which this system image belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The tag of the system emulated by this image. -->
+ <xsd:element name="tag-id" type="sdk:idType" />
+ <!-- The displayed tag of the system emulated by this image. Optional. -->
+ <xsd:element name="tag-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The ABI of the system emulated by this image. -->
+ <xsd:element name="abi" type="sdk:abiType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ An tag string for a system image 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 the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- 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/main/java/com/android/sdklib/repository/sdk-sys-img-03.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-03.xsd
new file mode 100755
index 0000000..9b63c01
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-03.xsd
@@ -0,0 +1,309 @@
+<?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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/sys-img/3"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/sys-img/3"
+ 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 r20.0. It is split out of the
+ main SDK Repository XML Schema and can only contain <system-image> packages.
+
+ - v2 is used by the SDK Manager in Tools r22.6.
+ It introduces a sub-tag for the <system-image> repository type.
+
+ - v3 is used by the SDK Manager in Tools r22.6.4:
+ - It introduces a <list-display> string for all package types.
+ - it changes <archive os=... arch=...> to sub-elements: <host-os>, <host-bits>,
+ <jvm-bits> and <min-jvm-version>.
+ - Support for new ABIs arm64-v8a, x86_64 and mips64.
+ - Support for new <addon><vendor-id/name></addon> element.
+ -->
+
+ <xsd:element name="sdk-sys-img" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable system images.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="system-image" type="sdk:systemImageType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+
+ <!-- The definition of a system image used by a platform. -->
+
+ <xsd:complexType name="systemImageType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ System Image for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level+codename identifies the platform to which this system image belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The tag of the system emulated by this image. -->
+ <xsd:element name="tag-id" type="sdk:idType" />
+ <!-- The displayed tag of the system emulated by this image. Optional. -->
+ <xsd:element name="tag-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- Indicates a system-image is tied to an add-on and which one.
+ Mandatory for add-on system-images.
+ Must not be present for platform system-images. -->
+ <xsd:element name="add-on" type="sdk:addonType" minOccurs="0" />
+
+ <!-- The vendor id of the system-image when it is associated with an add-on.
+ Must not be present for system-image associated with a platform. -->
+ <xsd:element name="vendor-id" type="sdk:idType" minOccurs="0" />
+ <!-- The displayed vendor name of the system-image's add-on. -->
+ <xsd:element name="vendor-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The ABI of the system emulated by this image. -->
+ <xsd:element name="abi" type="sdk:abiType" />
+
+ <!-- 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" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a system image used by a platform. -->
+
+ <xsd:complexType name="addonType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Indicates a system-image is tied to an add-on and which one
+ (the combo tag-id + vendor-id uniquely identifies the add-on.)
+ Mandatory for add-on system-images.
+ Must not be present for platform system-images.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The vendor id of the add-on. -->
+ <xsd:element name="vendor-id" type="sdk:idType" />
+ <!-- The displayed vendor name of the add-on. -->
+ <xsd:element name="vendor-display" type="xsd:normalizedString" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ An tag string for a system image 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 the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="arm64-v8a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ <xsd:enumeration value="mips" />
+ <xsd:enumeration value="mips64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <xsd:element name="host-os" type="sdk:osType" minOccurs="0" />
+ <xsd:element name="host-bits" type="sdk:bitSizeType" minOccurs="0" />
+ <xsd:element name="jvm-bits" type="sdk:bitSizeType" minOccurs="0" />
+ <xsd:element name="min-jvm-version" type="sdk:jvmVersionType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of archive filters -->
+
+ <xsd:simpleType name="bitSizeType">
+ <xsd:annotation>
+ <xsd:documentation>A CPU bit size filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="32" />
+ <xsd:enumeration value="64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="osType">
+ <xsd:annotation>
+ <xsd:documentation>A host OS filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="jvmVersionType">
+ <xsd:annotation>
+ <xsd:documentation>A JVM version number, e.g. "1" or "1.6" or "1.14.15".</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([1-9](\.[1-9]{1,2}){0,2})"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- 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/main/java/com/android/sdklib/repository/sdk-sys-img-1.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-1.xsd
deleted file mode 100755
index a19aa49..0000000
--- a/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-1.xsd
+++ /dev/null
@@ -1,229 +0,0 @@
-<?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.
--->
-<xsd:schema
- targetNamespace="http://schemas.android.com/sdk/android/sys-img/1"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:sdk="http://schemas.android.com/sdk/android/sys-img/1"
- 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 r20. It is split out of the
- main SDK Repository XML Schema and can only contain <system-image> packages.
- -->
-
- <xsd:element name="sdk-sys-img" type="sdk:repositoryType" />
-
- <xsd:complexType name="repositoryType">
- <xsd:annotation>
- <xsd:documentation>
- The repository contains a collection of downloadable system images.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:choice minOccurs="0" maxOccurs="unbounded">
- <xsd:element name="system-image" type="sdk:systemImageType" />
- <xsd:element name="license" type="sdk:licenseType" />
- </xsd:choice>
- </xsd:complexType>
-
-
- <!-- The definition of a system image used by a platform. -->
-
- <xsd:complexType name="systemImageType" >
- <xsd:annotation>
- <xsd:documentation>
- System Image for a platform.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:all>
- <!-- api-level+codename identifies the platform to which this system image belongs. -->
-
- <!-- The Android API Level for the platform. An int > 0. -->
- <xsd:element name="api-level" type="xsd:positiveInteger" />
- <!-- The optional codename for this platform, if it's a preview. -->
- <xsd:element name="codename" type="xsd:string" minOccurs="0" />
-
- <!-- The revision, an int > 0, incremented each time a new
- package is generated. -->
- <xsd:element name="revision" type="xsd:positiveInteger" />
-
- <!-- The ABI of the system emulated by this image. -->
- <xsd:element name="abi" type="sdk:abiType" />
-
- <!-- 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" />
- </xsd:all>
- </xsd:complexType>
-
-
- <!-- The definition of the ABI supported by a platform's system image. -->
-
- <xsd:simpleType name="abiType">
- <xsd:annotation>
- <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="armeabi" />
- <xsd:enumeration value="armeabi-v7a" />
- <xsd:enumeration value="x86" />
- <xsd:enumeration value="mips" />
- </xsd:restriction>
- </xsd:simpleType>
-
-
- <!-- The definition of a license to be referenced by the uses-license element. -->
-
- <xsd:complexType name="licenseType">
- <xsd:annotation>
- <xsd:documentation>
- A license definition. Such a license must be used later as a reference
- using a uses-license element in one of the package elements.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleContent>
- <xsd:extension base="xsd:string">
- <xsd:attribute name="id" type="xsd:ID" />
- <xsd:attribute name="type" type="xsd:token" fixed="text" />
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-
-
- <!-- Type describing the license used by a package.
- The license MUST be defined using a license node and referenced
- using the ref attribute of the license element inside a package.
- -->
-
- <xsd:complexType name="usesLicenseType">
- <xsd:annotation>
- <xsd:documentation>
- Describes the license used by a package. The license MUST be defined
- using a license node and referenced using the ref attribute of the
- license element inside a package.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:attribute name="ref" type="xsd:IDREF" />
- </xsd:complexType>
-
-
- <!-- A collection of files that can be downloaded for a given architecture.
- The <archives> node is mandatory in the repository elements and the
- collection must have at least one <archive> declared.
- Each archive is a zip file that will be unzipped in a location that depends
- on its package type.
- -->
-
- <xsd:complexType name="archivesType">
- <xsd:annotation>
- <xsd:documentation>
- A collection of files that can be downloaded for a given architecture.
- The <archives> node is mandatory in the repository packages and the
- collection must have at least one <archive> declared.
- Each archive is a zip file that will be unzipped in a location that depends
- on its package type.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence minOccurs="1" maxOccurs="unbounded">
- <!-- One archive file -->
- <xsd:element name="archive">
- <xsd:complexType>
- <!-- Properties of the archive file -->
- <xsd:all>
- <!-- The size in bytes of the archive to download. -->
- <xsd:element name="size" type="xsd:positiveInteger" />
- <!-- The checksum of the archive file. -->
- <xsd:element name="checksum" type="sdk:checksumType" />
- <!-- The URL is an absolute URL if it starts with http://, https://
- or ftp://. Otherwise it is relative to the parent directory that
- contains this repository.xml -->
- <xsd:element name="url" type="xsd:token" />
- </xsd:all>
-
- <!-- Attributes that identify the OS and architecture -->
- <xsd:attribute name="os" use="required">
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="any" />
- <xsd:enumeration value="linux" />
- <xsd:enumeration value="macosx" />
- <xsd:enumeration value="windows" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:attribute>
- <xsd:attribute name="arch" use="optional">
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="any" />
- <xsd:enumeration value="ppc" />
- <xsd:enumeration value="x86" />
- <xsd:enumeration value="x86_64" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:attribute>
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
-
- <!-- 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/main/java/com/android/sdklib/util/CommandLineParser.java b/sdklib/src/main/java/com/android/sdklib/util/CommandLineParser.java
index 5c19052..94808e5 100644
--- a/sdklib/src/main/java/com/android/sdklib/util/CommandLineParser.java
+++ b/sdklib/src/main/java/com/android/sdklib/util/CommandLineParser.java
@@ -632,7 +632,7 @@
//----
- private static enum Accept {
+ protected static enum Accept {
CONTINUE,
ACCEPT_AND_STOP,
REJECT_AND_STOP,
@@ -642,7 +642,7 @@
* The mode of an argument specifies the type of variable it represents,
* whether an extra parameter is required after the flag and how to parse it.
*/
- public static enum Mode {
+ protected static enum Mode {
/** Argument value is a Boolean. Default value is a Boolean. */
BOOLEAN {
@Override
@@ -776,7 +776,7 @@
* Depending on the {@link Mode}, the default value can be a Boolean, an Integer, a String
* or a String array (in which case the first item is the current by default.)
*/
- static class Arg {
+ protected static class Arg {
/** Verb for that argument. Never null. */
private final String mVerb;
/** Direct Object for that argument. Never null, but can be empty string. */
@@ -965,4 +965,12 @@
protected void stderr(String format, Object...args) {
mLog.error(null, format, args);
}
+
+ /**
+ * Returns the logger object.
+ * @return the logger object.
+ */
+ protected ILogger getLog() {
+ return mLog;
+ }
}
diff --git a/sdklib/src/main/java/com/android/sdklib/util/GrabProcessOutput.java b/sdklib/src/main/java/com/android/sdklib/util/GrabProcessOutput.java
deleted file mode 100755
index 3d3734c..0000000
--- a/sdklib/src/main/java/com/android/sdklib/util/GrabProcessOutput.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.util;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-public class GrabProcessOutput {
-
- public enum Wait {
- /**
- * Doesn't wait for the exec to complete.
- * This still monitors the output but does not wait for the process to finish.
- * In this mode the process return code is unknown and always 0.
- */
- ASYNC,
- /**
- * This waits for the process to finish.
- * In this mode, {@link GrabProcessOutput#grabProcessOutput} returns the
- * error code from the process.
- * In some rare cases and depending on the OS, the process might not have
- * finished dumping data into stdout/stderr.
- * <p/>
- * Use this when you don't particularly care for the output but instead
- * care for the return code of the executed process.
- */
- WAIT_FOR_PROCESS,
- /**
- * This waits for the process to finish <em>and</em> for the stdout/stderr
- * threads to complete.
- * In this mode, {@link GrabProcessOutput#grabProcessOutput} returns the
- * error code from the process.
- * <p/>
- * Use this one when capturing all the output from the process is important.
- */
- WAIT_FOR_READERS,
- }
-
- public interface IProcessOutput {
- /**
- * Processes an stdout message line.
- * @param line The stdout message line. Null when the reader reached the end of stdout.
- */
- public void out(@Nullable String line);
- /**
- * Processes an stderr message line.
- * @param line The stderr message line. Null when the reader reached the end of stderr.
- */
- public void err(@Nullable String line);
- }
-
- /**
- * Get the stderr/stdout outputs of a process and return when the process is done.
- * Both <b>must</b> be read or the process will block on windows.
- *
- * @param process The process to get the output from.
- * @param output Optional object to capture stdout/stderr.
- * Note that on Windows capturing the output is not optional. If output is null
- * the stdout/stderr will be captured and discarded.
- * @param waitMode Whether to wait for the process and/or the readers to finish.
- * @return the process return code.
- * @throws InterruptedException if {@link Process#waitFor()} was interrupted.
- */
- public static int grabProcessOutput(
- @NonNull final Process process,
- Wait waitMode,
- @Nullable final IProcessOutput output) throws InterruptedException {
- // read the lines as they come. if null is returned, it's
- // because the process finished
- Thread threadErr = new Thread("stderr") {
- @Override
- public void run() {
- // create a buffer to read the stderr output
- InputStreamReader is = new InputStreamReader(process.getErrorStream());
- BufferedReader errReader = new BufferedReader(is);
-
- try {
- while (true) {
- String line = errReader.readLine();
- if (output != null) {
- output.err(line);
- }
- if (line == null) {
- break;
- }
- }
- } catch (IOException e) {
- // do nothing.
- }
- }
- };
-
- Thread threadOut = new Thread("stdout") {
- @Override
- public void run() {
- InputStreamReader is = new InputStreamReader(process.getInputStream());
- BufferedReader outReader = new BufferedReader(is);
-
- try {
- while (true) {
- String line = outReader.readLine();
- if (output != null) {
- output.out(line);
- }
- if (line == null) {
- break;
- }
- }
- } catch (IOException e) {
- // do nothing.
- }
- }
- };
-
- threadErr.start();
- threadOut.start();
-
- if (waitMode == Wait.ASYNC) {
- return 0;
- }
-
- // it looks like on windows process#waitFor() can return
- // before the thread have filled the arrays, so we wait for both threads and the
- // process itself.
- if (waitMode == Wait.WAIT_FOR_READERS) {
- try {
- threadErr.join();
- } catch (InterruptedException e) {
- }
- try {
- threadOut.join();
- } catch (InterruptedException e) {
- }
- }
-
- // get the return code from the process
- return process.waitFor();
- }
-}
diff --git a/sdklib/src/test/java/com/android/sdklib/AndroidLocationTestCase.java b/sdklib/src/test/java/com/android/sdklib/AndroidLocationTestCase.java
new file mode 100755
index 0000000..4b9fbf5
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/AndroidLocationTestCase.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.EnvVar;
+import com.android.sdklib.mock.MockLog;
+
+import java.io.File;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+/**
+ * Test case that overrides the {@link AndroidLocation} to point to temp one.
+ * <p/>
+ * This one doesn't create a temp fake SDK (see {@link SdkManagerTestCase}.)
+ */
+public abstract class AndroidLocationTestCase extends TestCase {
+
+ private String mOldAndroidHomeProp;
+ private File mAndroidHome;
+
+ /**
+ * Sets up a {@link MockLog}, a fake SDK in a temporary directory
+ * and an AVD Manager pointing to an initially-empty AVD directory.
+ */
+ @Override
+ public void setUp() throws Exception {
+ makeFakeAndroidHome();
+ }
+
+ /**
+ * Removes the temporary SDK and AVD directories.
+ */
+ @Override
+ public void tearDown() throws Exception {
+ tearDownAndroidHome();
+ }
+
+ private void makeFakeAndroidHome() throws IOException {
+ // First we create a temp file to "reserve" the temp directory name we want to use.
+ mAndroidHome = File.createTempFile(
+ "androidhome_" + this.getClass().getSimpleName() + '_' + this.getName(), null);
+ // Then erase the file and make the directory
+ mAndroidHome.delete();
+ mAndroidHome.mkdirs();
+
+ // Set the system property that will force AndroidLocation to use this
+ mOldAndroidHomeProp = System.getProperty(EnvVar.ANDROID_SDK_HOME.getName());
+ System.setProperty(EnvVar.ANDROID_SDK_HOME.getName(), mAndroidHome.getAbsolutePath());
+ AndroidLocation.resetFolder();
+ }
+
+ private void tearDownAndroidHome() {
+ if (mOldAndroidHomeProp == null) {
+ System.clearProperty(EnvVar.ANDROID_SDK_HOME.getName());
+ } else {
+ System.setProperty(EnvVar.ANDROID_SDK_HOME.getName(), mOldAndroidHomeProp);
+ }
+ AndroidLocation.resetFolder();
+ deleteDir(mAndroidHome);
+ }
+
+ /** Clear the .android home folder and reconstruct it empty. */
+ protected void clearAndroidHome() {
+ deleteDir(mAndroidHome);
+ mAndroidHome.mkdirs();
+ AndroidLocation.resetFolder();
+ }
+
+
+ /**
+ * Recursive delete directory. Mostly for fake SDKs.
+ *
+ * @param root directory to delete
+ */
+ private void deleteDir(File root) {
+ if (root.exists()) {
+ for (File file : root.listFiles()) {
+ if (file.isDirectory()) {
+ deleteDir(file);
+ } else {
+ file.delete();
+ }
+ }
+ root.delete();
+ }
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java b/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
index 457f7f6..93b176c 100755
--- a/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
@@ -32,18 +32,18 @@
}
public final void testGetAddonHashString() {
- assertEquals("vendor:my-addon:10",
+ assertEquals("The Vendor Inc.:My Addon:10",
AndroidTargetHash.getAddonHashString(
- "vendor",
- "my-addon",
+ "The Vendor Inc.",
+ "My Addon",
new AndroidVersion(10, null)));
}
public final void testGetTargetHashString() {
MockPlatformTarget t = new MockPlatformTarget(10, 1);
assertEquals("android-10", AndroidTargetHash.getTargetHashString(t));
- MockAddonTarget a = new MockAddonTarget("my-addon", t, 2);
- assertEquals("vendor 10:my-addon:10", AndroidTargetHash.getTargetHashString(a));
+ MockAddonTarget a = new MockAddonTarget("My Addon", t, 2);
+ assertEquals("vendor 10:My Addon:10", AndroidTargetHash.getTargetHashString(a));
}
public void testGetPlatformVersion() {
diff --git a/sdklib/src/test/java/com/android/sdklib/AndroidVersionTest.java b/sdklib/src/test/java/com/android/sdklib/AndroidVersionTest.java
index 4ea0284..714ca6b 100755
--- a/sdklib/src/test/java/com/android/sdklib/AndroidVersionTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/AndroidVersionTest.java
@@ -100,4 +100,14 @@
}
}
}
+
+ public void testGetFeatureLevel() {
+ assertEquals(1, AndroidVersion.DEFAULT.getFeatureLevel());
+
+ assertEquals(5, new AndroidVersion(5, null).getApiLevel());
+ assertEquals(5, new AndroidVersion(5, null).getFeatureLevel());
+
+ assertEquals(5, new AndroidVersion(5, "codename").getApiLevel());
+ assertEquals(6, new AndroidVersion(5, "codename").getFeatureLevel());
+ }
}
diff --git a/sdklib/src/test/java/com/android/sdklib/BuildToolInfoTest.java b/sdklib/src/test/java/com/android/sdklib/BuildToolInfoTest.java
new file mode 100755
index 0000000..4d0d255
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/BuildToolInfoTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotations.Nullable;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
+import com.android.utils.ILogger;
+
+import java.util.Properties;
+
+public class BuildToolInfoTest extends SdkManagerTestCase {
+
+ /**
+ * Wraps an *existing* build-tool info object to expose some of its internals for testing.
+ */
+ public static class BuildToolInfoWrapper extends BuildToolInfo {
+
+ private final BuildToolInfo mInfo;
+ private NoPreviewRevision mOverrideJvmVersion;
+
+ public BuildToolInfoWrapper(BuildToolInfo info) {
+ super(info.getRevision(), info.getLocation());
+ mInfo = info;
+ }
+
+ @Override
+ public String getPath(PathId pathId) {
+ return mInfo.getPath(pathId);
+ }
+
+ @Override
+ public Properties getRuntimeProps() {
+ return mInfo.getRuntimeProps();
+ }
+
+ @Override
+ public boolean isValid(ILogger log) {
+ return mInfo.isValid(log);
+ }
+
+ @Override
+ public boolean canRunOnJvm() {
+ // This runs canRunOnJvm on *this* instance so that it can
+ // access the overridden getCurrentJvmVersion below rather than
+ // the original that we are wrapping.
+ return super.canRunOnJvm();
+ }
+
+ @Override
+ protected NoPreviewRevision getCurrentJvmVersion() throws NumberFormatException {
+ if (mOverrideJvmVersion != null) {
+ return mOverrideJvmVersion;
+ }
+ return mInfo.getCurrentJvmVersion();
+ }
+
+ public void overrideJvmVersion(@Nullable NoPreviewRevision jvmVersion) {
+ mOverrideJvmVersion = jvmVersion;
+ }
+ }
+
+ public void testGetCurrentJvmVersion() {
+ SdkManager sdkman = getSdkManager();
+ BuildToolInfo bt = sdkman.getBuildTool(new FullRevision(18, 3, 4, 5));
+ assertNotNull(bt);
+
+ // Check the actual JVM running this test.
+ NoPreviewRevision curr = bt.getCurrentJvmVersion();
+ // We can reasonably expect this to at least run with JVM 1.5 or more
+ assertTrue(curr.compareTo(new FullRevision(1, 5, 0)) > 0);
+ // and we can reasonably expect to not be running with JVM 42.0.0
+ assertTrue(curr.compareTo(new FullRevision(42, 0, 0)) < 0);
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java b/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java
index 549f005..5d7d4d7 100755
--- a/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java
@@ -18,13 +18,20 @@
import com.android.SdkConstants;
+import com.android.sdklib.BuildToolInfoTest.BuildToolInfoWrapper;
import com.android.sdklib.ISystemImage.LocationType;
import com.android.sdklib.SdkManager.LayoutlibVersion;
import com.android.sdklib.internal.androidTarget.PlatformTarget;
+import com.android.sdklib.io.FileOp;
import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
+import com.android.sdklib.repository.descriptors.IdDisplay;
import com.google.common.collect.Sets;
+import java.io.File;
+import java.io.IOException;
import java.util.Arrays;
+import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
@@ -56,7 +63,7 @@
v = Sets.newTreeSet(v);
}
- assertEquals("[]", getLog().toString()); // no errors in the logger
+ assertEquals("", getLog().toString()); // no errors in the logger
assertEquals("[3.0.0, 3.0.1, 18.3.4 rc5]", Arrays.toString(v.toArray()));
assertEquals(new FullRevision(18, 3, 4, 5),
@@ -100,6 +107,48 @@
cleanPath(sdkman, i.toString()));
}
+ public void testSdkManager_BuildTools_canRunOnJvm() throws IOException {
+ SdkManager sdkman = getSdkManager();
+ BuildToolInfo bt = sdkman.getBuildTool(new FullRevision(18, 3, 4, 5));
+ assertNotNull(bt);
+
+ // By default there is no runtime.properties file and no Runtime.Jvm value.
+ // Since there is no requirement, this build-tool package can run everywhere.
+ Properties props1 = bt.getRuntimeProps();
+ assertTrue(props1.isEmpty());
+ assertTrue(bt.canRunOnJvm());
+
+ // We know our tests require at least a JVM 1.5 to run so this build-tool can run here.
+ createFileProps("runtime.properties", bt.getLocation(), "Runtime.Jvm", "1.5.0");
+ Properties props15 = bt.getRuntimeProps();
+ assertFalse(props15.isEmpty());
+ assertTrue(bt.canRunOnJvm());
+
+ createFileProps("runtime.properties", bt.getLocation(), "Runtime.Jvm", "42.0.0");
+ Properties props42 = bt.getRuntimeProps();
+ assertFalse(props42.isEmpty());
+
+ BuildToolInfoWrapper wrap = new BuildToolInfoTest.BuildToolInfoWrapper(bt);
+
+ // Let's assume a real JVM 42.0.0 doesn't exist yet
+ wrap.overrideJvmVersion(new NoPreviewRevision(1, 6, 0));
+ assertFalse(wrap.canRunOnJvm());
+
+ // Let's assume a real JVM 42.0.0 and above exists
+ wrap.overrideJvmVersion(new NoPreviewRevision(42, 0, 0));
+ assertTrue(wrap.canRunOnJvm());
+
+ wrap.overrideJvmVersion(new NoPreviewRevision(42, 0, 1));
+ assertTrue(wrap.canRunOnJvm());
+
+ wrap.overrideJvmVersion(new NoPreviewRevision(42, 1, 1));
+ assertTrue(wrap.canRunOnJvm());
+
+ wrap.overrideJvmVersion(new NoPreviewRevision(43, 1, 1));
+ assertTrue(wrap.canRunOnJvm());
+
+ }
+
public void testSdkManager_SystemImage() throws Exception {
SdkManager sdkman = getSdkManager();
assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
@@ -108,45 +157,76 @@
// By default SdkManagerTestCase creates an SDK with one platform containing
// a legacy armeabi system image.
assertEquals(
- "[SystemImage ABI=armeabi, location in platform legacy='$SDK/platforms/v0_0/images']",
+ "[SystemImage tag=default, ABI=armeabi, location in legacy folder='$SDK/platforms/v0_0/images']",
cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
- // Now add a few "platform subfolders" system images and reload the SDK.
+ // 1- add a few "platform subfolders" system images and reload the SDK.
// This disables the "legacy" mode, which means that although the armeabi
// target from above is present, it is no longer visible.
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_PLATFORM_SUBFOLDER, SdkConstants.ABI_ARMEABI_V7A));
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_PLATFORM_SUBFOLDER, SdkConstants.ABI_INTEL_ATOM));
+ makeSystemImageFolder(new SystemImage(sdkman, t,
+ LocationType.IN_IMAGES_SUBFOLDER,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI_V7A,
+ FileOp.EMPTY_FILE_ARRAY));
+ makeSystemImageFolder(new SystemImage(sdkman, t,
+ LocationType.IN_IMAGES_SUBFOLDER,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_INTEL_ATOM,
+ FileOp.EMPTY_FILE_ARRAY));
sdkman.reloadSdk(getLog());
assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
t = sdkman.getTargets()[0];
assertEquals(
- "[SystemImage ABI=armeabi-v7a, location in platform subfolder='$SDK/platforms/v0_0/images/armeabi-v7a', " +
- "SystemImage ABI=x86, location in platform subfolder='$SDK/platforms/v0_0/images/x86']",
+ "[SystemImage tag=default, ABI=armeabi-v7a, location in images subfolder='$SDK/platforms/v0_0/images/armeabi-v7a', " +
+ "SystemImage tag=default, ABI=x86, location in images subfolder='$SDK/platforms/v0_0/images/x86']",
cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
- // Now add arm + arm v7a images using the new SDK/system-images.
+ // 2- add arm + arm v7a images using the new SDK/system-images.
// The x86 one from the platform subfolder is still visible.
// The armeabi one from the legacy folder is overridden by the new one.
// The armeabi-v7a one from the platform subfolder is overridden by the new one.
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI));
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI_V7A));
+ makeSystemImageFolder(new SystemImage(sdkman, t,
+ LocationType.IN_SYSTEM_IMAGE,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI,
+ FileOp.EMPTY_FILE_ARRAY));
+ makeSystemImageFolder(new SystemImage(sdkman, t,
+ LocationType.IN_SYSTEM_IMAGE,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI_V7A,
+ FileOp.EMPTY_FILE_ARRAY));
sdkman.reloadSdk(getLog());
assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
t = sdkman.getTargets()[0];
assertEquals(
- "[SystemImage ABI=armeabi, location in system image='$SDK/system-images/android-0/armeabi', " +
- "SystemImage ABI=armeabi-v7a, location in system image='$SDK/system-images/android-0/armeabi-v7a', " +
- "SystemImage ABI=x86, location in platform subfolder='$SDK/platforms/v0_0/images/x86']",
+ "[SystemImage tag=default, ABI=armeabi, location in system image='$SDK/system-images/android-0/default/armeabi', " +
+ "SystemImage tag=default, ABI=armeabi-v7a, location in system image='$SDK/system-images/android-0/default/armeabi-v7a', " +
+ "SystemImage tag=default, ABI=x86, location in images subfolder='$SDK/platforms/v0_0/images/x86']",
+ cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
+
+ // 3- add an arm v7a image with a custom tag. It exists in parallel with the default one.
+
+ makeSystemImageFolder(new SystemImage(sdkman, t,
+ LocationType.IN_SYSTEM_IMAGE,
+ new IdDisplay("tag-1", "My Tag 1"),
+ SdkConstants.ABI_ARMEABI_V7A,
+ FileOp.EMPTY_FILE_ARRAY));
+
+ sdkman.reloadSdk(getLog());
+ assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
+ t = sdkman.getTargets()[0];
+
+ assertEquals(
+ "[SystemImage tag=default, ABI=armeabi, location in system image='$SDK/system-images/android-0/default/armeabi', " +
+ "SystemImage tag=default, ABI=armeabi-v7a, location in system image='$SDK/system-images/android-0/default/armeabi-v7a', " +
+ "SystemImage tag=default, ABI=x86, location in images subfolder='$SDK/platforms/v0_0/images/x86', " +
+ "SystemImage tag=tag-1, ABI=armeabi-v7a, location in system image='$SDK/system-images/android-0/tag-1/armeabi-v7a']",
cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
}
@@ -158,7 +238,7 @@
// By default SdkManagerTestCase creates an SDK with one platform containing
// a legacy armeabi system image.
assertEquals(
- "[SystemImage ABI=armeabi, location in platform legacy='$SDK/platforms/v0_0/images']",
+ "[SystemImage tag=default, ABI=armeabi, location in legacy folder='$SDK/platforms/v0_0/images']",
cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
// Now add a different ABI system image in the new system-images folder.
@@ -166,23 +246,29 @@
// (to contrast: having at least one sub-folder in the platform's legacy images folder
// will hide the legacy system image. Whereas this does not happen with the new type.)
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_INTEL_ATOM));
+ makeSystemImageFolder(new SystemImage(sdkman, t,
+ LocationType.IN_SYSTEM_IMAGE,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_INTEL_ATOM,
+ FileOp.EMPTY_FILE_ARRAY));
sdkman.reloadSdk(getLog());
assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
t = sdkman.getTargets()[0];
assertEquals(
- "[SystemImage ABI=armeabi, location in platform legacy='$SDK/platforms/v0_0/images', " +
- "SystemImage ABI=x86, location in system image='$SDK/system-images/android-0/x86']",
+ "[SystemImage tag=default, ABI=armeabi, location in legacy folder='$SDK/platforms/v0_0/images', " +
+ "SystemImage tag=default, ABI=x86, location in system image='$SDK/system-images/android-0/default/x86']",
cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
// Now if we have one new system-image using the same ABI type, it will override the
// legacy one. This gives us a good path for updates.
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI));
+ makeSystemImageFolder(new SystemImage(sdkman, t,
+ LocationType.IN_SYSTEM_IMAGE,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI,
+ FileOp.EMPTY_FILE_ARRAY));
sdkman.reloadSdk(getLog());
@@ -190,8 +276,8 @@
t = sdkman.getTargets()[0];
assertEquals(
- "[SystemImage ABI=armeabi, location in system image='$SDK/system-images/android-0/armeabi', " +
- "SystemImage ABI=x86, location in system image='$SDK/system-images/android-0/x86']",
+ "[SystemImage tag=default, ABI=armeabi, location in system image='$SDK/system-images/android-0/default/armeabi', " +
+ "SystemImage tag=default, ABI=x86, location in system image='$SDK/system-images/android-0/default/x86']",
cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
}
diff --git a/sdklib/src/test/java/com/android/sdklib/SdkManagerTest7.java b/sdklib/src/test/java/com/android/sdklib/SdkManagerTest7.java
index 9c41bd2..6c305ee 100755
--- a/sdklib/src/test/java/com/android/sdklib/SdkManagerTest7.java
+++ b/sdklib/src/test/java/com/android/sdklib/SdkManagerTest7.java
@@ -42,7 +42,7 @@
v = Sets.newTreeSet(v);
}
- assertEquals("[]", getLog().toString()); // no errors in the logger
+ assertEquals("", getLog().toString()); // no errors in the logger
assertEquals("[]", Arrays.toString(v.toArray()));
assertNull(sdkman.getBuildTool(new FullRevision(1)));
diff --git a/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java b/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
index 86945f3..568a017 100755
--- a/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
+++ b/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
@@ -19,33 +19,64 @@
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.resources.Density;
+import com.android.resources.Keyboard;
+import com.android.resources.KeyboardState;
+import com.android.resources.Navigation;
+import com.android.resources.NavigationState;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenRatio;
+import com.android.resources.ScreenSize;
+import com.android.resources.TouchScreen;
+import com.android.sdklib.ISystemImage.LocationType;
+import com.android.sdklib.devices.ButtonType;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Device.Builder;
+import com.android.sdklib.devices.DeviceWriter;
+import com.android.sdklib.devices.Hardware;
+import com.android.sdklib.devices.Multitouch;
+import com.android.sdklib.devices.PowerType;
+import com.android.sdklib.devices.Screen;
+import com.android.sdklib.devices.ScreenType;
+import com.android.sdklib.devices.Software;
+import com.android.sdklib.devices.State;
+import com.android.sdklib.devices.Storage;
+import com.android.sdklib.devices.Storage.Unit;
import com.android.sdklib.internal.avd.AvdManager;
+import com.android.sdklib.internal.repository.archives.ArchFilter;
+import com.android.sdklib.internal.repository.archives.HostOs;
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.sdklib.repository.local.LocalPlatformPkgInfo;
+import com.android.sdklib.repository.local.LocalSysImgPkgInfo;
import com.android.utils.ILogger;
-import junit.framework.TestCase;
-
import java.io.File;
+import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
/**
- * Test case that allocates a temporary SDK, a temporary AVD base folder
- * with an SdkManager and an AvdManager that points to them.
+ * Base Test case that allocates a temporary SDK, a temporary AVD base
+ * folder with an SdkManager and an AvdManager that points to them.
+ * <p/>
+ * Also overrides the {@link AndroidLocation} to point to temp one.
*/
-public class SdkManagerTestCase extends TestCase {
+public abstract class SdkManagerTestCase extends AndroidLocationTestCase {
+ protected static final String TARGET_DIR_NAME_0 = "v0_0";
private File mFakeSdk;
private MockLog mLog;
private SdkManager mSdkManager;
- private TmpAvdManager mAvdManager;
+ private AvdManager mAvdManager;
private int mRepoXsdLevel;
/** Returns the {@link MockLog} for this test case. */
@@ -59,7 +90,7 @@
}
/** Returns the {@link AvdManager} for this test case. */
- public TmpAvdManager getAvdManager() {
+ public AvdManager getAvdManager() {
return mAvdManager;
}
@@ -68,13 +99,42 @@
* and an AVD Manager pointing to an initially-empty AVD directory.
*/
public void setUp(int repoXsdLevel) throws Exception {
+ super.setUp();
mRepoXsdLevel = repoXsdLevel;
mLog = new MockLog();
- mFakeSdk = makeFakeSdk();
+ makeFakeSdk();
+ createSdkAvdManagers();
+ }
+
+ /**
+ * Recreate the SDK and AVD Managers from scratch even if they already existed.
+ * Useful for tests that want to reset their state without recreating the
+ * android-home or the fake SDK. The SDK will be reparsed.
+ */
+ protected void createSdkAvdManagers() throws AndroidLocationException {
mSdkManager = SdkManager.createManager(mFakeSdk.getAbsolutePath(), mLog);
assertNotNull("SdkManager location was invalid", mSdkManager);
+ // Note: it's safe to use the default AvdManager implementation since makeFakeAndroidHome
+ // above overrides the ANDROID_HOME folder to use a temp folder; consequently all
+ // the AVDs created here will be located in this temp folder and will not alter
+ // or pollute the default user's AVD folder.
+ mAvdManager = new AvdManager(mSdkManager.getLocalSdk(), mLog) {
+ @Override
+ protected boolean createSdCard(
+ String toolLocation,
+ String size,
+ String location,
+ ILogger log) {
+ if (new File(toolLocation).exists()) {
+ log.info("[EXEC] %1$s %2$s %3$s\n", toolLocation, size, location);
+ return true;
+ } else {
+ log.error(null, "Failed to create the SD card.\n");
+ return false;
+ }
- mAvdManager = new TmpAvdManager(mSdkManager, mLog);
+ };
+ };
}
/**
@@ -91,99 +151,55 @@
*/
@Override
public void tearDown() throws Exception {
- deleteDir(mFakeSdk);
- }
-
- /**
- * A empty test method to placate the JUnit test runner, which doesn't
- * like TestCase classes with no test methods.
- */
- public void testPlaceholder() {
- }
-
- /**
- * An {@link AvdManager} that uses a temporary directory
- * located <em>inside</em> the SDK directory for testing.
- * The AVD list should be initially empty.
- */
- protected static class TmpAvdManager extends AvdManager {
-
- /*
- * Implementation detail:
- * - When the super.AvdManager constructor is invoked, it will invoke
- * the buildAvdFilesList() to fill the initial AVD list, which will in
- * turn call getBaseAvdFolder().
- * - That's why mTmpAvdRoot is initialized in getAvdRoot() rather than
- * in the constructor, since we can't initialize fields before the super()
- * call.
- */
-
- /**
- * AVD Root, initialized "lazily" when the AVD root is first requested.
- */
- private File mTmpAvdRoot;
-
- public TmpAvdManager(SdkManager sdkManager, ILogger log) throws AndroidLocationException {
- super(sdkManager, log);
- }
-
- @Override
- public String getBaseAvdFolder() throws AndroidLocationException {
- if (mTmpAvdRoot == null) {
- mTmpAvdRoot = new File(getSdkManager().getLocation(), "tmp_avds");
- mTmpAvdRoot.mkdirs();
- }
- return mTmpAvdRoot.getAbsolutePath();
- }
+ tearDownSdk();
+ super.tearDown();
}
/**
* Build enough of a skeleton SDK to make the tests pass.
* <p/>
* Ideally this wouldn't touch the file system but the current
- * structure of the SdkManager and AvdManager makes this difficult.
- *
- * @return Path to the temporary SDK root
- * @throws IOException
+ * structure of the SdkManager and AvdManager makes this impossible.
*/
- private File makeFakeSdk() throws IOException {
+ private void makeFakeSdk() throws IOException {
// First we create a temp file to "reserve" the temp directory name we want to use.
- File sdkDir = File.createTempFile(
- this.getClass().getSimpleName() + '_' + this.getName(), null);
+ mFakeSdk = File.createTempFile(
+ "sdk_" + this.getClass().getSimpleName() + '_' + this.getName(), null);
// Then erase the file and make the directory
- sdkDir.delete();
- sdkDir.mkdirs();
+ mFakeSdk.delete();
+ mFakeSdk.mkdirs();
- AndroidLocation.resetFolder();
- File addonsDir = new File(sdkDir, SdkConstants.FD_ADDONS);
+ File addonsDir = new File(mFakeSdk, SdkConstants.FD_ADDONS);
addonsDir.mkdir();
- File toolsDir = new File(sdkDir, SdkConstants.OS_SDK_TOOLS_FOLDER);
+ File toolsDir = new File(mFakeSdk, SdkConstants.OS_SDK_TOOLS_FOLDER);
toolsDir.mkdir();
createSourceProps(toolsDir, PkgProps.PKG_REVISION, "1.0.1");
new File(toolsDir, SdkConstants.androidCmdName()).createNewFile();
new File(toolsDir, SdkConstants.FN_EMULATOR).createNewFile();
+ new File(toolsDir, SdkConstants.mkSdCardCmdName()).createNewFile();
- makePlatformTools(new File(sdkDir, SdkConstants.FD_PLATFORM_TOOLS));
+ makePlatformTools(new File(mFakeSdk, SdkConstants.FD_PLATFORM_TOOLS));
if (mRepoXsdLevel >= 8) {
- makeBuildTools(new File(sdkDir, SdkConstants.FD_BUILD_TOOLS));
+ makeBuildTools(mFakeSdk);
}
- File toolsLibEmuDir = new File(sdkDir, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + "emulator");
+ File toolsLibEmuDir = new File(mFakeSdk, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + "emulator");
toolsLibEmuDir.mkdirs();
new File(toolsLibEmuDir, "snapshots.img").createNewFile();
- File platformsDir = new File(sdkDir, SdkConstants.FD_PLATFORMS);
+ File platformsDir = new File(mFakeSdk, SdkConstants.FD_PLATFORMS);
// Creating a fake target here on down
File targetDir = makeFakeTargetInternal(platformsDir);
+ makeFakeLegacySysImg(targetDir, SdkConstants.ABI_ARMEABI);
- File imagesDir = new File(targetDir, "images");
- makeFakeSysImgInternal(imagesDir, SdkConstants.ABI_ARMEABI);
+ makeFakeSkin(targetDir, "HVGA");
+ makeFakeSourceInternal(mFakeSdk);
+ }
- makeFakeSkinInternal(targetDir);
- makeFakeSourceInternal(sdkDir);
- return sdkDir;
+ private void tearDownSdk() {
+ deleteDir(mFakeSdk);
}
/**
@@ -192,11 +208,57 @@
* @param systemImage A system image with a valid location.
* @throws IOException if the file fails to be created.
*/
- protected void makeSystemImageFolder(ISystemImage systemImage) throws IOException {
- File imagesDir = systemImage.getLocation();
- imagesDir.mkdirs();
+ protected void makeSystemImageFolder(ISystemImage systemImage) throws Exception {
+ File sysImgDir = systemImage.getLocation();
- makeFakeSysImgInternal(imagesDir, systemImage.getAbiType());
+ if (systemImage.getLocationType() == LocationType.IN_LEGACY_FOLDER) {
+ // legacy mode. Path should look like SDK/platforms/platform-N/userdata.img
+ makeFakeLegacySysImg(sysImgDir.getParentFile(), systemImage.getAbiType());
+
+ } else if (systemImage.getLocationType() == LocationType.IN_IMAGES_SUBFOLDER) {
+ // not-so-legacy mode.
+ // Path should look like SDK/platforms/platform-N/images/userdata.img
+ makeFakeSysImgInternal(
+ sysImgDir,
+ systemImage.getTag().getId(),
+ systemImage.getAbiType());
+
+ } else if (systemImage.getLocationType() == LocationType.IN_SYSTEM_IMAGE) {
+ // system-image folder mode.
+ // Path should like SDK/system-images/platform-N/tag/abi/userdata.img+source.properties
+ makeFakeSysImgInternal(
+ sysImgDir,
+ systemImage.getTag().getId(),
+ systemImage.getAbiType());
+ }
+ }
+
+ /**
+ * Creates the system image folder and places a fake userdata.img in it.
+ * This must be called after {@link #setUp()} so that it can use the temp fake SDK folder,
+ * and consequently you do not need to specify the SDK root.
+ *
+ * @param targetDir The targetDir segment of the sys-image folder.
+ * Use {@link #TARGET_DIR_NAME_0} to match the default single platform.
+ * @param tagId An optional tag id. Use null for legacy no-tag system images.
+ * @param abiType The abi for the system image.
+ * @return The directory of the system-image/tag/abi created.
+ * @throws IOException if the file fails to be created.
+ */
+ @NonNull
+ protected File makeSystemImageFolder(
+ @NonNull String targetDir,
+ @Nullable String tagId,
+ @NonNull String abiType) throws Exception {
+ File sysImgDir = new File(mFakeSdk, SdkConstants.FD_SYSTEM_IMAGES);
+ sysImgDir = new File(sysImgDir, targetDir);
+ if (tagId != null) {
+ sysImgDir = new File(sysImgDir, tagId);
+ }
+ sysImgDir = new File(sysImgDir, abiType);
+
+ makeFakeSysImgInternal(sysImgDir, tagId, abiType);
+ return sysImgDir;
}
//----
@@ -223,7 +285,7 @@
/** Utility used by {@link #makeFakeSdk()} to create a fake target with API 0, rev 0. */
private File makeFakeTargetInternal(File platformsDir) throws IOException {
- File targetDir = new File(platformsDir, "v0_0");
+ File targetDir = new File(platformsDir, TARGET_DIR_NAME_0);
targetDir.mkdirs();
new File(targetDir, SdkConstants.FN_FRAMEWORK_LIBRARY).createNewFile();
new File(targetDir, SdkConstants.FN_FRAMEWORK_AIDL).createNewFile();
@@ -243,20 +305,126 @@
return targetDir;
}
- /** Utility to create a fake sys image in the given folder. */
- private void makeFakeSysImgInternal(File imagesDir, String abiType) throws IOException {
+ /**
+ * Utility to create a fake *legacy* sys image in a platform folder.
+ * Legacy system images follow that path pattern:
+ * $SDK/platforms/platform-N/images/userdata.img
+ *
+ * They have no source.properties file in that directory.
+ */
+ private void makeFakeLegacySysImg(
+ @NonNull File platformDir,
+ @NonNull String abiType) throws IOException {
+ File imagesDir = new File(platformDir, "images");
imagesDir.mkdirs();
new File(imagesDir, "userdata.img").createNewFile();
+ }
- createSourceProps(imagesDir,
- PkgProps.PKG_REVISION, "0",
- PkgProps.VERSION_API_LEVEL, "0",
- PkgProps.SYS_IMG_ABI, abiType);
+ /**
+ * Utility to create a fake sys image in the system-images folder.
+ *
+ * "modern" (as in "not legacy") system-images follow that path pattern:
+ * $SDK/system-images/platform-N/abi/source.properties
+ * $SDK/system-images/platform-N/abi/userdata.img
+ * or
+ * $SDK/system-images/platform-N/tag/abi/source.properties
+ * $SDK/system-images/platform-N/tag/abi/userdata.img
+ *
+ * The tag id is optional and was only introduced in API 20 / Tools 22.6.
+ * The platform-N and the tag folder names are irrelevant as the info from
+ * source.properties matters most.
+ */
+ private void makeFakeSysImgInternal(
+ @NonNull File sysImgDir,
+ @Nullable String tagId,
+ @NonNull String abiType) throws Exception {
+ sysImgDir.mkdirs();
+ new File(sysImgDir, "userdata.img").createNewFile();
+
+ if (tagId == null) {
+ createSourceProps(sysImgDir,
+ PkgProps.PKG_REVISION, "0",
+ PkgProps.VERSION_API_LEVEL, "0",
+ PkgProps.SYS_IMG_ABI, abiType);
+ } else {
+ String tagDisplay = LocalSysImgPkgInfo.tagIdToDisplay(tagId);
+ createSourceProps(sysImgDir,
+ PkgProps.PKG_REVISION, "0",
+ PkgProps.VERSION_API_LEVEL, "0",
+ PkgProps.SYS_IMG_TAG_ID, tagId,
+ PkgProps.SYS_IMG_TAG_DISPLAY, tagDisplay,
+ PkgProps.SYS_IMG_ABI, abiType,
+ PkgProps.PKG_LIST_DISPLAY, "Sys-Img v0 for (" + tagDisplay + ", " + abiType + ")");
+
+ // create a devices.xml file
+ List<Device> devices = new ArrayList<Device>();
+ Builder b = new Device.Builder();
+ b.setName("Mock " + tagDisplay + " Device Name");
+ b.setId("MockDevice-" + tagId);
+ b.setManufacturer("Mock " + tagDisplay + " OEM");
+
+ Software sw = new Software();
+ sw.setGlVersion("4.2");
+ sw.setLiveWallpaperSupport(false);
+ sw.setMaxSdkLevel(42);
+ sw.setMinSdkLevel(1);
+ sw.setStatusBar(true);
+
+ Screen sc = new Screen();
+ sc.setDiagonalLength(7);
+ sc.setMechanism(TouchScreen.FINGER);
+ sc.setMultitouch(Multitouch.JAZZ_HANDS);
+ sc.setPixelDensity(Density.HIGH);
+ sc.setRatio(ScreenRatio.NOTLONG);
+ sc.setScreenType(ScreenType.CAPACITIVE);
+ sc.setSize(ScreenSize.LARGE);
+ sc.setXDimension(5);
+ sc.setXdpi(100);
+ sc.setYDimension(4);
+ sc.setYdpi(100);
+
+ Hardware hw = new Hardware();
+ hw.setButtonType(ButtonType.SOFT);
+ hw.setChargeType(PowerType.BATTERY);
+ hw.setCpu(abiType);
+ hw.setGpu("pixelpushing");
+ hw.setHasMic(true);
+ hw.setKeyboard(Keyboard.QWERTY);
+ hw.setNav(Navigation.NONAV);
+ hw.setRam(new Storage(512, Unit.MiB));
+ hw.setScreen(sc);
+
+ State st = new State();
+ st.setName("portrait");
+ st.setDescription("Portrait");
+ st.setDefaultState(true);
+ st.setOrientation(ScreenOrientation.PORTRAIT);
+ st.setKeyState(KeyboardState.SOFT);
+ st.setNavState(NavigationState.HIDDEN);
+ st.setHardware(hw);
+
+ b.addSoftware(sw);
+ b.addState(st);
+
+ devices.add(b.build());
+
+ File f = new File(sysImgDir, "devices.xml");
+ FileOutputStream fos = new FileOutputStream(f);
+ DeviceWriter.writeToXml(fos, devices);
+ fos.close();
+ }
}
/** Utility to make a fake skin for the given target */
- private void makeFakeSkinInternal(File targetDir) {
- FileOp.append(targetDir, "skins", "HVGA").mkdirs();
+ protected void makeFakeSkin(File targetDir, String skinName) throws IOException {
+ File skinFolder = FileOp.append(targetDir, "skins", skinName);
+ skinFolder.mkdirs();
+
+ // To be detected properly, the skin folder should have a "layout" file.
+ // Its content is however not parsed.
+ FileWriter out = new FileWriter(new File(skinFolder, "layout"));
+ out.write("parts {\n}\n");
+ out.close();
}
/** Utility to create a fake source with a few files in the given sdk folder. */
@@ -283,58 +451,73 @@
new File(platformToolsDir, SdkConstants.FN_ADB).createNewFile();
}
- private void makeBuildTools(File buildToolsTopDir) throws IOException {
- buildToolsTopDir.mkdir();
+ private void makeBuildTools(File sdkDir) throws IOException {
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);
-
- 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);
+ createFakeBuildTools(sdkDir, "ANY", revision);
}
}
- private void createFakeBuildTools(@NonNull File dir,
- @NonNull FullRevision buildToolsRevision,
- @NonNull BuildToolInfo.PathId pathId,
- @NonNull String filepath)
+ /**
+ * Adds a new fake build tools to the SDK In the given SDK/build-tools folder.
+ *
+ * @param sdkDir The SDK top folder. Must already exist.
+ * @param os The OS. One of {@link HostOs#toString()} or "ANY".
+ * @param revision The "x.y.z rc r" revision number from {@link FullRevision#toShortString()}.
+ * @throws IOException
+ */
+ protected void createFakeBuildTools(File sdkDir, String os, String revision)
+ throws IOException {
+ File buildToolsTopDir = new File(sdkDir, SdkConstants.FD_BUILD_TOOLS);
+ buildToolsTopDir.mkdir();
+ File buildToolsDir = new File(buildToolsTopDir, revision);
+ createSourceProps(buildToolsDir,
+ PkgProps.PKG_REVISION, revision,
+ ArchFilter.LEGACY_PROP_OS, os);
+
+ FullRevision fullRevision = FullRevision.parseRevision(revision);
+
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.AAPT, SdkConstants.FN_AAPT);
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.AIDL, SdkConstants.FN_AIDL);
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.DX, SdkConstants.FN_DX);
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.DX_JAR, SdkConstants.FD_LIB + File.separator +
+ SdkConstants.FN_DX_JAR);
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.LLVM_RS_CC, SdkConstants.FN_RENDERSCRIPT);
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.ANDROID_RS, SdkConstants.OS_FRAMEWORK_RS + File.separator +
+ "placeholder.txt");
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.ANDROID_RS_CLANG, SdkConstants.OS_FRAMEWORK_RS_CLANG + File.separator +
+ "placeholder.txt");
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.BCC_COMPAT, SdkConstants.FN_BCC_COMPAT);
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.LD_ARM, SdkConstants.FN_LD_ARM);
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.LD_MIPS, SdkConstants.FN_LD_MIPS);
+ createFakeBuildToolsFile(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.LD_X86, SdkConstants.FN_LD_X86);
+ }
+
+ private void createFakeBuildToolsFile(@NonNull File dir,
+ @NonNull FullRevision buildToolsRevision,
+ @NonNull BuildToolInfo.PathId pathId,
+ @NonNull String filepath)
throws IOException {
if (pathId.isPresentIn(buildToolsRevision)) {
@@ -343,11 +526,11 @@
}
- private void createSourceProps(File parentDir, String...paramValuePairs) throws IOException {
+ protected void createSourceProps(File parentDir, String...paramValuePairs) throws IOException {
createFileProps(SdkConstants.FN_SOURCE_PROP, parentDir, paramValuePairs);
}
- private void createFileProps(String fileName, File parentDir, String...paramValuePairs) throws IOException {
+ protected void createFileProps(String fileName, File parentDir, String...paramValuePairs) throws IOException {
File sourceProp = new File(parentDir, fileName);
parentDir = sourceProp.getParentFile();
if (!parentDir.isDirectory()) {
@@ -372,7 +555,7 @@
*
* @param root directory to delete
*/
- private void deleteDir(File root) {
+ protected void deleteDir(File root) {
if (root.exists()) {
for (File file : root.listFiles()) {
if (file.isDirectory()) {
diff --git a/sdklib/src/test/java/com/android/sdklib/devices/DeviceManagerTest.java b/sdklib/src/test/java/com/android/sdklib/devices/DeviceManagerTest.java
new file mode 100755
index 0000000..ad634e5
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/devices/DeviceManagerTest.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.devices;
+
+import com.android.resources.Keyboard;
+import com.android.resources.Navigation;
+import com.android.sdklib.SdkManagerTestCase;
+import com.android.sdklib.devices.Device.Builder;
+import com.android.sdklib.devices.DeviceManager.DeviceFilter;
+import com.android.sdklib.devices.DeviceManager.DeviceStatus;
+import com.android.sdklib.mock.MockLog;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DeviceManagerTest extends SdkManagerTestCase {
+
+ private DeviceManager dm;
+ private MockLog log;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ dm = createDeviceManager();
+ }
+
+ private DeviceManager createDeviceManager() {
+ log = super.getLog();
+ File sdkLocation = getSdkManager().getLocalSdk().getLocation();
+ return DeviceManager.createInstance(sdkLocation, log);
+ }
+
+ /** Returns a list of just the devices' display names, for unit test comparisons. */
+ private static String listDisplayName(Device device) {
+ if (device == null) return null;
+ return device.getDisplayName();
+ }
+
+ /** Returns a list of just the devices' display names, for unit test comparisons. */
+ private static List<String> listDisplayNames(List<Device> devices) {
+ if (devices == null) return null;
+ List<String> names = new ArrayList<String>();
+ for (Device d : devices) {
+ names.add(listDisplayName(d));
+ }
+ return names;
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public final void testGetDevices_Default() {
+ // no user devices defined in the test's custom .android home folder
+ assertEquals("[]", dm.getDevices(DeviceFilter.USER).toString());
+ assertEquals("", log.toString());
+
+ // no system-images devices defined in the SDK by default
+ assertEquals("[]", dm.getDevices(DeviceFilter.SYSTEM_IMAGES).toString());
+ assertEquals("", log.toString());
+
+ // this list comes from devices.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/devices.xml
+ assertEquals(
+ "[2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), 3.2\" QVGA (ADP2), " +
+ "3.3\" WQVGA, 3.4\" WQVGA, 3.7\" WVGA (Nexus One), 3.7\" FWVGA slider, " +
+ "4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), 4.7\" WXGA, 5.1\" WVGA, " +
+ "5.4\" FWVGA, 7\" WSVGA (Tablet), 10.1\" WXGA (Tablet)]",
+ listDisplayNames(dm.getDevices(DeviceFilter.DEFAULT)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals("2.7\" QVGA",
+ listDisplayName(dm.getDevice("2.7in QVGA", "Generic")));
+
+ // this list comes from the nexus.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
+ assertEquals(
+ "[Nexus One, Nexus S, Galaxy Nexus, Nexus 7 (2012), " +
+ "Nexus 4, Nexus 10, Nexus 7, Nexus 5]",
+ listDisplayNames(dm.getDevices(DeviceFilter.VENDOR)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals("Nexus One",
+ listDisplayName(dm.getDevice("Nexus One", "Google")));
+
+ assertEquals(
+ "[2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), 3.2\" QVGA (ADP2), " +
+ "3.3\" WQVGA, 3.4\" WQVGA, 3.7\" WVGA (Nexus One), 3.7\" FWVGA slider, " +
+ "4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), 4.7\" WXGA, 5.1\" WVGA, " +
+ "5.4\" FWVGA, 7\" WSVGA (Tablet), 10.1\" WXGA (Tablet), " +
+ "Nexus One, Nexus S, Galaxy Nexus, Nexus 7 (2012), " +
+ "Nexus 4, Nexus 10, Nexus 7, Nexus 5]",
+ listDisplayNames(dm.getDevices(DeviceManager.ALL_DEVICES)).toString());
+ assertEquals("", log.toString());
+ }
+
+ public final void testGetDevice() {
+ // get a definition from the bundled devices.xml file
+ Device d1 = dm.getDevice("7in WSVGA (Tablet)", "Generic");
+ assertEquals("7\" WSVGA (Tablet)", d1.getDisplayName());
+ assertEquals("", log.toString());
+
+ // get a definition from the bundled nexus.xml file
+ Device d2 = dm.getDevice("Nexus One", "Google");
+ assertEquals("Nexus One", d2.getDisplayName());
+ assertEquals("", log.toString());
+ }
+
+ public final void testGetDevices_UserDevice() {
+
+ Device d1 = dm.getDevice("7in WSVGA (Tablet)", "Generic");
+
+ Builder b = new Device.Builder(d1);
+ b.setId("MyCustomTablet");
+ b.setName("My Custom Tablet");
+ b.setManufacturer("OEM");
+
+ Device d2 = b.build();
+
+ dm.addUserDevice(d2);
+ dm.saveUserDevices();
+
+ assertEquals("My Custom Tablet", dm.getDevice("MyCustomTablet", "OEM").getDisplayName());
+ assertEquals("", log.toString());
+
+ // create a new device manager, forcing it reload all files
+ dm = null;
+ DeviceManager dm2 = createDeviceManager();
+
+ assertEquals("My Custom Tablet", dm2.getDevice("MyCustomTablet", "OEM").getDisplayName());
+ assertEquals("", log.toString());
+
+ // 1 user device defined in the test's custom .android home folder
+ assertEquals("[My Custom Tablet]",
+ listDisplayNames(dm2.getDevices(DeviceFilter.USER)).toString());
+ assertEquals("", log.toString());
+
+ // no system-images devices defined in the SDK by default
+ assertEquals("[]",
+ listDisplayNames(dm2.getDevices(DeviceFilter.SYSTEM_IMAGES)).toString());
+ assertEquals("", log.toString());
+
+ // this list comes from devices.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/devices.xml
+ assertEquals(
+ "[2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), 3.2\" QVGA (ADP2), " +
+ "3.3\" WQVGA, 3.4\" WQVGA, 3.7\" WVGA (Nexus One), 3.7\" FWVGA slider, " +
+ "4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), 4.7\" WXGA, 5.1\" WVGA, " +
+ "5.4\" FWVGA, 7\" WSVGA (Tablet), 10.1\" WXGA (Tablet)]",
+ listDisplayNames(dm2.getDevices(DeviceFilter.DEFAULT)).toString());
+ assertEquals("", log.toString());
+
+ // this list comes from the nexus.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
+ assertEquals(
+ "[Nexus One, Nexus S, Galaxy Nexus, Nexus 7 (2012), " +
+ "Nexus 4, Nexus 10, Nexus 7, Nexus 5]",
+ listDisplayNames(dm2.getDevices(DeviceFilter.VENDOR)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals(
+ "[My Custom Tablet, " +
+ "2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), 3.2\" QVGA (ADP2), " +
+ "3.3\" WQVGA, 3.4\" WQVGA, 3.7\" WVGA (Nexus One), 3.7\" FWVGA slider, " +
+ "4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), 4.7\" WXGA, 5.1\" WVGA, " +
+ "5.4\" FWVGA, 7\" WSVGA (Tablet), 10.1\" WXGA (Tablet), " +
+ "Nexus One, Nexus S, Galaxy Nexus, Nexus 7 (2012), " +
+ "Nexus 4, Nexus 10, Nexus 7, Nexus 5]",
+ listDisplayNames(dm2.getDevices(DeviceManager.ALL_DEVICES)).toString());
+ assertEquals("", log.toString());
+ }
+
+ public final void testGetDevices_SysImgDevice() throws Exception {
+ // this adds a devices.xml with one device
+ makeSystemImageFolder(TARGET_DIR_NAME_0, "tag-1", "x86");
+
+ // no user devices defined in the test's custom .android home folder
+ assertEquals("[]", listDisplayNames(dm.getDevices(DeviceFilter.USER)).toString());
+ assertEquals("", log.toString());
+
+ // find the system-images specific device added by makeSystemImageFolder above
+ // using both the getDevices() API and the device-specific getDevice() API.
+ assertEquals("[Mock Tag 1 Device Name]",
+ listDisplayNames(dm.getDevices(DeviceFilter.SYSTEM_IMAGES)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals("Mock Tag 1 Device Name",
+ listDisplayName(dm.getDevice("MockDevice-tag-1", "Mock Tag 1 OEM")));
+
+ // this list comes from devices.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/devices.xml
+ assertEquals(
+ "[2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), 3.2\" QVGA (ADP2), " +
+ "3.3\" WQVGA, 3.4\" WQVGA, 3.7\" WVGA (Nexus One), 3.7\" FWVGA slider, " +
+ "4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), 4.7\" WXGA, 5.1\" WVGA, " +
+ "5.4\" FWVGA, 7\" WSVGA (Tablet), 10.1\" WXGA (Tablet)]",
+ listDisplayNames(dm.getDevices(DeviceFilter.DEFAULT)).toString());
+ assertEquals("", log.toString());
+
+ // this list comes from the nexus.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
+ assertEquals(
+ "[Nexus One, Nexus S, Galaxy Nexus, Nexus 7 (2012), " +
+ "Nexus 4, Nexus 10, Nexus 7, Nexus 5]",
+ listDisplayNames(dm.getDevices(DeviceFilter.VENDOR)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals(
+ "[2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), 3.2\" QVGA (ADP2), " +
+ "3.3\" WQVGA, 3.4\" WQVGA, 3.7\" WVGA (Nexus One), 3.7\" FWVGA slider, " +
+ "4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), 4.7\" WXGA, 5.1\" WVGA, " +
+ "5.4\" FWVGA, 7\" WSVGA (Tablet), 10.1\" WXGA (Tablet), " +
+ "Nexus One, Nexus S, Galaxy Nexus, Nexus 7 (2012), " +
+ "Nexus 4, Nexus 10, Nexus 7, Nexus 5, " +
+ "Mock Tag 1 Device Name]",
+ listDisplayNames(dm.getDevices(DeviceManager.ALL_DEVICES)).toString());
+ assertEquals("", log.toString());
+ }
+
+ public final void testGetDeviceStatus() {
+ // get a definition from the bundled devices.xml file
+ assertEquals(DeviceStatus.EXISTS,
+ dm.getDeviceStatus("7in WSVGA (Tablet)", "Generic"));
+
+ // get a definition from the bundled oem file
+ assertEquals(DeviceStatus.EXISTS,
+ dm.getDeviceStatus("Nexus One", "Google"));
+
+ // try a device that does not exist
+ assertEquals(DeviceStatus.MISSING,
+ dm.getDeviceStatus("My Device", "Custom OEM"));
+ }
+
+ public final void testHasHardwarePropHashChanged_Generic() {
+ final Device d1 = dm.getDevice("7in WSVGA (Tablet)", "Generic");
+
+ assertEquals("MD5:750a657019b49e621c42ce9a20c2cc30",
+ DeviceManager.hasHardwarePropHashChanged(
+ d1,
+ "invalid"));
+
+ assertEquals(null,
+ DeviceManager.hasHardwarePropHashChanged(
+ d1,
+ "MD5:750a657019b49e621c42ce9a20c2cc30"));
+
+ // change the device hardware props, this should change the hash
+ d1.getDefaultHardware().setNav(Navigation.TRACKBALL);
+
+ assertEquals("MD5:9c4dd5018987da51f7166f139f4361a2",
+ DeviceManager.hasHardwarePropHashChanged(
+ d1,
+ "MD5:750a657019b49e621c42ce9a20c2cc30"));
+
+ // change the property back, should revert its hash to the previous one
+ d1.getDefaultHardware().setNav(Navigation.NONAV);
+
+ assertEquals(null,
+ DeviceManager.hasHardwarePropHashChanged(
+ d1,
+ "MD5:750a657019b49e621c42ce9a20c2cc30"));
+ }
+
+ public final void testHasHardwarePropHashChanged_Oem() {
+ final Device d2 = dm.getDevice("Nexus One", "Google");
+
+ assertEquals("MD5:d886364fc30320c0518f51002d0ef22d",
+ DeviceManager.hasHardwarePropHashChanged(
+ d2,
+ "invalid"));
+
+ assertEquals(null,
+ DeviceManager.hasHardwarePropHashChanged(
+ d2,
+ "MD5:d886364fc30320c0518f51002d0ef22d"));
+
+ // change the device hardware props, this should change the hash
+ d2.getDefaultHardware().setKeyboard(Keyboard.QWERTY);
+
+ assertEquals("MD5:db682e0a58e74a8614e43e6e15c05176",
+ DeviceManager.hasHardwarePropHashChanged(
+ d2,
+ "MD5:d886364fc30320c0518f51002d0ef22d"));
+
+ // change the property back, should revert its hash to the previous one
+ d2.getDefaultHardware().setKeyboard(Keyboard.NOKEY);
+
+ assertEquals(null,
+ DeviceManager.hasHardwarePropHashChanged(
+ d2,
+ "MD5:d886364fc30320c0518f51002d0ef22d"));
+ }
+}
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 5b08d2c..263a8b5 100644
--- a/sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java
@@ -41,131 +41,214 @@
public class DeviceParserTest extends TestCase {
- public void testValidDevices() throws Exception {
- InputStream devicesFile = DeviceSchemaTest.class.getResourceAsStream("devices_minimal.xml");
- List<Device> devices = DeviceParser.parse(devicesFile);
- assertEquals("Parsing devices_minimal.xml produces the wrong number of devices", 1,
- devices.size());
+ public void testValidDevicesMinimal() throws Exception {
+ InputStream stream = DeviceSchemaTest.class.getResourceAsStream("devices_minimal.xml");
+ try {
+ List<Device> devices = DeviceParser.parse(stream);
+ assertEquals("Parsing devices_minimal.xml produces the wrong number of devices",
+ 1, devices.size());
- Device device = devices.get(0);
- assertEquals("Galaxy Nexus", device.getDisplayName());
- assertEquals("Samsung", device.getManufacturer());
+ Device device = devices.get(0);
+ assertEquals("Galaxy Nexus", device.getDisplayName());
+ assertEquals("Samsung", device.getManufacturer());
- // Test Meta information
- Meta meta = device.getMeta();
- assertFalse(meta.hasIconSixtyFour());
- assertFalse(meta.hasIconSixteen());
- assertFalse(meta.hasFrame());
+ // Test Meta information
+ Meta meta = device.getMeta();
+ assertFalse(meta.hasIconSixtyFour());
+ assertFalse(meta.hasIconSixteen());
+ assertFalse(meta.hasFrame());
- // Test Hardware information
- Hardware hw = device.getDefaultHardware();
- Screen screen = hw.getScreen();
- assertEquals(screen.getSize(), ScreenSize.NORMAL);
- assertEquals(4.65, screen.getDiagonalLength());
- assertEquals(Density.XHIGH, screen.getPixelDensity());
- assertEquals(ScreenRatio.LONG, screen.getRatio());
- assertEquals(720, screen.getXDimension());
- assertEquals(1280, screen.getYDimension());
- assertEquals(316.0, screen.getXdpi());
- assertEquals(316.0, screen.getYdpi());
- assertEquals(Multitouch.JAZZ_HANDS, screen.getMultitouch());
- assertEquals(TouchScreen.FINGER, screen.getMechanism());
- assertEquals(ScreenType.CAPACITIVE, screen.getScreenType());
- Set<Network> networks = hw.getNetworking();
- assertTrue(networks.contains(Network.BLUETOOTH));
- assertTrue(networks.contains(Network.WIFI));
- assertTrue(networks.contains(Network.NFC));
- Set<Sensor> sensors = hw.getSensors();
- assertTrue(sensors.contains(Sensor.ACCELEROMETER));
- assertTrue(sensors.contains(Sensor.BAROMETER));
- assertTrue(sensors.contains(Sensor.GYROSCOPE));
- assertTrue(sensors.contains(Sensor.COMPASS));
- assertTrue(sensors.contains(Sensor.GPS));
- assertTrue(sensors.contains(Sensor.PROXIMITY_SENSOR));
- assertTrue(hw.hasMic());
- assertEquals(2, hw.getCameras().size());
- Camera c = hw.getCamera(CameraLocation.FRONT);
- assertTrue(c != null);
- assertEquals(c.getLocation(), CameraLocation.FRONT);
- assertFalse(c.hasFlash());
- assertTrue(c.hasAutofocus());
- c = hw.getCamera(CameraLocation.BACK);
- assertTrue(c != null);
- assertEquals(c.getLocation(), CameraLocation.BACK);
- assertTrue(c.hasFlash());
- assertTrue(c.hasAutofocus());
- assertEquals(Keyboard.NOKEY, hw.getKeyboard());
- assertEquals(Navigation.NONAV, hw.getNav());
- assertEquals(new Storage(1, Unit.GiB), hw.getRam());
- assertEquals(ButtonType.SOFT, hw.getButtonType());
- List<Storage> storage = hw.getInternalStorage();
- assertEquals(1, storage.size());
- assertEquals(new Storage(16, Unit.GiB), storage.get(0));
- storage = hw.getRemovableStorage();
- assertEquals(0, storage.size());
- assertEquals("OMAP 4460", hw.getCpu());
- assertEquals("PowerVR SGX540", hw.getGpu());
- Set<Abi> abis = hw.getSupportedAbis();
- assertEquals(2, abis.size());
- assertTrue(abis.contains(Abi.ARMEABI));
- assertTrue(abis.contains(Abi.ARMEABI_V7A));
- assertEquals(0, hw.getSupportedUiModes().size());
- assertEquals(PowerType.BATTERY, hw.getChargeType());
+ // Test Hardware information
+ Hardware hw = device.getDefaultHardware();
+ Screen screen = hw.getScreen();
+ assertEquals(screen.getSize(), ScreenSize.NORMAL);
+ assertEquals(4.65, screen.getDiagonalLength());
+ assertEquals(Density.XHIGH, screen.getPixelDensity());
+ assertEquals(ScreenRatio.LONG, screen.getRatio());
+ assertEquals(720, screen.getXDimension());
+ assertEquals(1280, screen.getYDimension());
+ assertEquals(316.0, screen.getXdpi());
+ assertEquals(316.0, screen.getYdpi());
+ assertEquals(Multitouch.JAZZ_HANDS, screen.getMultitouch());
+ assertEquals(TouchScreen.FINGER, screen.getMechanism());
+ assertEquals(ScreenType.CAPACITIVE, screen.getScreenType());
+ Set<Network> networks = hw.getNetworking();
+ assertTrue(networks.contains(Network.BLUETOOTH));
+ assertTrue(networks.contains(Network.WIFI));
+ assertTrue(networks.contains(Network.NFC));
+ Set<Sensor> sensors = hw.getSensors();
+ assertTrue(sensors.contains(Sensor.ACCELEROMETER));
+ assertTrue(sensors.contains(Sensor.BAROMETER));
+ assertTrue(sensors.contains(Sensor.GYROSCOPE));
+ assertTrue(sensors.contains(Sensor.COMPASS));
+ assertTrue(sensors.contains(Sensor.GPS));
+ assertTrue(sensors.contains(Sensor.PROXIMITY_SENSOR));
+ assertTrue(hw.hasMic());
+ assertEquals(2, hw.getCameras().size());
+ Camera c = hw.getCamera(CameraLocation.FRONT);
+ assertTrue(c != null);
+ assert c != null;
+ assertEquals(c.getLocation(), CameraLocation.FRONT);
+ assertFalse(c.hasFlash());
+ assertTrue(c.hasAutofocus());
+ c = hw.getCamera(CameraLocation.BACK);
+ assertTrue(c != null);
+ assert c != null;
+ assertEquals(c.getLocation(), CameraLocation.BACK);
+ assertTrue(c.hasFlash());
+ assertTrue(c.hasAutofocus());
+ assertEquals(Keyboard.NOKEY, hw.getKeyboard());
+ assertEquals(Navigation.NONAV, hw.getNav());
+ assertEquals(new Storage(1, Unit.GiB), hw.getRam());
+ assertEquals(ButtonType.SOFT, hw.getButtonType());
+ List<Storage> storage = hw.getInternalStorage();
+ assertEquals(1, storage.size());
+ assertEquals(new Storage(16, Unit.GiB), storage.get(0));
+ storage = hw.getRemovableStorage();
+ assertEquals(0, storage.size());
+ assertEquals("OMAP 4460", hw.getCpu());
+ assertEquals("PowerVR SGX540", hw.getGpu());
+ Set<Abi> abis = hw.getSupportedAbis();
+ assertEquals(2, abis.size());
+ assertTrue(abis.contains(Abi.ARMEABI));
+ assertTrue(abis.contains(Abi.ARMEABI_V7A));
+ assertEquals(0, hw.getSupportedUiModes().size());
+ assertEquals(PowerType.BATTERY, hw.getChargeType());
- // Test Software
- assertEquals(1, device.getAllSoftware().size());
- Software sw = device.getSoftware(15);
- assertEquals(15, sw.getMaxSdkLevel());
- assertEquals(15, sw.getMinSdkLevel());
- assertTrue(sw.hasLiveWallpaperSupport());
- assertEquals(12, sw.getBluetoothProfiles().size());
- assertTrue(sw.getBluetoothProfiles().contains(BluetoothProfile.A2DP));
- assertEquals("2.0", sw.getGlVersion());
- assertEquals(29, sw.getGlExtensions().size());
- assertTrue(sw.getGlExtensions().contains("GL_OES_depth24"));
+ // Test Software
+ assertEquals(1, device.getAllSoftware().size());
+ Software sw = device.getSoftware(15);
+ assertEquals(15, sw.getMaxSdkLevel());
+ assertEquals(15, sw.getMinSdkLevel());
+ assertTrue(sw.hasLiveWallpaperSupport());
+ assertEquals(12, sw.getBluetoothProfiles().size());
+ assertTrue(sw.getBluetoothProfiles().contains(BluetoothProfile.A2DP));
+ assertEquals("2.0", sw.getGlVersion());
+ assertEquals(29, sw.getGlExtensions().size());
+ assertTrue(sw.getGlExtensions().contains("GL_OES_depth24"));
- // Test States
- assertEquals(2, device.getAllStates().size());
- State s = device.getDefaultState();
- assertEquals("Portrait", s.getName());
- assertTrue(s.isDefaultState());
- assertEquals("The phone in portrait view", s.getDescription());
- assertEquals(ScreenOrientation.PORTRAIT, s.getOrientation());
- assertEquals(KeyboardState.SOFT, s.getKeyState());
- assertEquals(NavigationState.HIDDEN, s.getNavState());
- s = device.getState("Landscape");
- assertEquals("Landscape", s.getName());
- assertFalse(s.isDefaultState());
- assertEquals(ScreenOrientation.LANDSCAPE, s.getOrientation());
+ // Test States
+ assertEquals(2, device.getAllStates().size());
+ State s = device.getDefaultState();
+ assertEquals("Portrait", s.getName());
+ assertTrue(s.isDefaultState());
+ assertEquals("The phone in portrait view", s.getDescription());
+ assertEquals(ScreenOrientation.PORTRAIT, s.getOrientation());
+ assertEquals(KeyboardState.SOFT, s.getKeyState());
+ assertEquals(NavigationState.HIDDEN, s.getNavState());
+ s = device.getState("Landscape");
+ assertEquals("Landscape", s.getName());
+ assertFalse(s.isDefaultState());
+ assertEquals(ScreenOrientation.LANDSCAPE, s.getOrientation());
+
+ // Test tag-id
+ assertEquals("tag-id", device.getTagId());
+
+ // Test boot-properties
+ assertEquals("{boot.prop.property=boot.prop.value}", device.getBootProps().toString());
+ } finally {
+ stream.close();
+ }
+ }
+
+ public void testValidDevicesFull_v1() throws Exception {
+ InputStream stream = DeviceSchemaTest.class.getResourceAsStream("devices.xml");
+ try {
+ List<Device> devices = DeviceParser.parse(stream);
+ assertEquals("Parsing devices.xml produces the wrong number of devices",
+ 3, devices.size());
+
+ Device device0 = devices.get(0);
+ assertEquals(null, device0.getTagId());
+ assertEquals("{}", device0.getBootProps().toString());
+ assertEquals("OMAP 4460", device0.getDefaultHardware().getCpu());
+ assertEquals("[armeabi, armeabi-v7a]", device0.getDefaultHardware().getSupportedAbis().toString());
+
+ Device device1 = devices.get(1);
+ assertEquals(null, device1.getTagId());
+ assertEquals("{}", device1.getBootProps().toString());
+ assertEquals("OMAP 3430", device1.getDefaultHardware().getCpu());
+ assertEquals("[armeabi, armeabi-v7a]", device1.getDefaultHardware().getSupportedAbis().toString());
+
+ Device device2 = devices.get(2);
+ assertEquals("tag-1", device2.getTagId());
+ assertEquals("{ro-myservice-port=1234, " +
+ "ro.RAM.Size=1024 MiB, " +
+ "ro.build.display.id=sdk-eng 4.3 JB_MR2 774058 test-keys}",
+ device2.getBootProps().toString());
+ assertEquals("Snapdragon 800 (MSM8974)", device2.getDefaultHardware().getCpu());
+ assertEquals("[armeabi, armeabi-v7a]", device2.getDefaultHardware().getSupportedAbis().toString());
+ } finally {
+ stream.close();
+ }
+ }
+
+ public void testValidDevicesFull_v2() throws Exception {
+ InputStream stream = DeviceSchemaTest.class.getResourceAsStream("devices_v2.xml");
+ try {
+ List<Device> devices = DeviceParser.parse(stream);
+ assertEquals("Parsing devices.xml produces the wrong number of devices",
+ 3, devices.size());
+
+ Device device0 = devices.get(0);
+ assertEquals(null, device0.getTagId());
+ assertEquals("{}", device0.getBootProps().toString());
+ assertEquals("arm64", device0.getDefaultHardware().getCpu());
+ assertEquals("[arm64-v8a]", device0.getDefaultHardware().getSupportedAbis().toString());
+
+ Device device1 = devices.get(1);
+ assertEquals("tag-1", device1.getTagId());
+ assertEquals("{ro-myservice-port=1234, " +
+ "ro.RAM.Size=1024 MiB, " +
+ "ro.build.display.id=sdk-eng 4.3 JB_MR2 774058 test-keys}",
+ device1.getBootProps().toString());
+ assertEquals("Intel Atom 64", device1.getDefaultHardware().getCpu());
+ assertEquals("[x86_64]", device1.getDefaultHardware().getSupportedAbis().toString());
+
+ Device device2 = devices.get(2);
+ assertEquals("tag-2", device2.getTagId());
+ assertEquals("{ro-myservice-port=1234, " +
+ "ro.RAM.Size=1024 MiB, " +
+ "ro.build.display.id=sdk-eng 4.3 JB_MR2 774058 test-keys}",
+ device2.getBootProps().toString());
+ assertEquals("MIPS32+64", device2.getDefaultHardware().getCpu());
+ assertEquals("[mips, mips64]", device2.getDefaultHardware().getSupportedAbis().toString());
+ } finally {
+ stream.close();
+ }
}
public void testApiRange() throws Exception {
Map<String, String> replacements = new HashMap<String, String>();
replacements.put("api-level", "1-");
InputStream stream = DeviceSchemaTest.getReplacedStream(replacements);
- List<Device> devices = DeviceParser.parse(stream);
- assertEquals(1, devices.size());
- Device device = devices.get(0);
- assertTrue(device.getSoftware(1) != null);
- assertTrue(device.getSoftware(2) != null);
- assertTrue(device.getSoftware(0) == null);
- replacements.put("api-level", "-2");
- stream = DeviceSchemaTest.getReplacedStream(replacements);
- device = DeviceParser.parse(stream).get(0);
- assertTrue(device.getSoftware(2) != null);
- assertTrue(device.getSoftware(3) == null);
- replacements.put("api-level", "1-2");
- stream = DeviceSchemaTest.getReplacedStream(replacements);
- device = DeviceParser.parse(stream).get(0);
- assertTrue(device.getSoftware(0) == null);
- assertTrue(device.getSoftware(1) != null);
- assertTrue(device.getSoftware(2) != null);
- assertTrue(device.getSoftware(3) == null);
- replacements.put("api-level", "-");
- stream = DeviceSchemaTest.getReplacedStream(replacements);
- device = DeviceParser.parse(stream).get(0);
- assertTrue(device.getSoftware(0) != null);
- assertTrue(device.getSoftware(15) != null);
+ try {
+ List<Device> devices = DeviceParser.parse(stream);
+ assertEquals(1, devices.size());
+ Device device = devices.get(0);
+ assertTrue(device.getSoftware(1) != null);
+ assertTrue(device.getSoftware(2) != null);
+ assertTrue(device.getSoftware(0) == null);
+ replacements.put("api-level", "-2");
+ stream = DeviceSchemaTest.getReplacedStream(replacements);
+ device = DeviceParser.parse(stream).get(0);
+ assertTrue(device.getSoftware(2) != null);
+ assertTrue(device.getSoftware(3) == null);
+ replacements.put("api-level", "1-2");
+ stream = DeviceSchemaTest.getReplacedStream(replacements);
+ device = DeviceParser.parse(stream).get(0);
+ assertTrue(device.getSoftware(0) == null);
+ assertTrue(device.getSoftware(1) != null);
+ assertTrue(device.getSoftware(2) != null);
+ assertTrue(device.getSoftware(3) == null);
+ replacements.put("api-level", "-");
+ stream = DeviceSchemaTest.getReplacedStream(replacements);
+ device = DeviceParser.parse(stream).get(0);
+ assertTrue(device.getSoftware(0) != null);
+ assertTrue(device.getSoftware(15) != null);
+ } finally {
+ stream.close();
+ }
}
public void testBadNetworking() throws Exception {
@@ -179,20 +262,27 @@
fail();
} catch (SAXParseException e) {
assertTrue(e.getMessage().startsWith("cvc-enumeration-valid: Value 'NFD'"));
+ } finally {
+ stream.close();
}
}
public void testScreenDimension() throws Exception {
- InputStream devicesFile = DeviceSchemaTest.class.getResourceAsStream(
+ InputStream stream = DeviceSchemaTest.class.getResourceAsStream(
"devices_minimal.xml");
- List<Device> devices = DeviceParser.parse(devicesFile);
- assertEquals("Parsing devices_minimal.xml produces the wrong number of devices", 1,
- devices.size());
+ try {
+ List<Device> devices = DeviceParser.parse(stream);
+ assertEquals("Parsing devices_minimal.xml produces the wrong number of devices", 1,
+ devices.size());
- Device device = devices.get(0);
- assertEquals("Galaxy Nexus", device.getDisplayName());
+ Device device = devices.get(0);
+ assertEquals("Galaxy Nexus", device.getDisplayName());
- assertEquals(new Dimension(1280, 720), device.getScreenSize(ScreenOrientation.LANDSCAPE));
- assertEquals(new Dimension(720, 1280), device.getScreenSize(ScreenOrientation.PORTRAIT));
+ assertEquals(new Dimension(1280, 720), device.getScreenSize(ScreenOrientation.LANDSCAPE));
+ assertEquals(new Dimension(720, 1280), device.getScreenSize(ScreenOrientation.PORTRAIT));
+ } finally {
+ stream.close();
+ }
}
+
}
diff --git a/sdklib/src/test/java/com/android/sdklib/devices/DeviceWriterTest.java b/sdklib/src/test/java/com/android/sdklib/devices/DeviceWriterTest.java
index 811eed2..e0bcf66 100644
--- a/sdklib/src/test/java/com/android/sdklib/devices/DeviceWriterTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/devices/DeviceWriterTest.java
@@ -28,23 +28,45 @@
import java.util.Locale;
import java.util.Map;
-@SuppressWarnings("javadoc")
public class DeviceWriterTest extends TestCase {
- public void testWriteIsValid() throws Exception {
+ public void testWriteIsValid_Minimal() throws Exception {
InputStream devicesFile =
- DeviceSchemaTest.class.getResourceAsStream("devices.xml");
+ DeviceSchemaTest.class.getResourceAsStream("devices_minimal.xml");
List<Device> devices = DeviceParser.parse(devicesFile);
assertEquals("Parsed devices contained an un expected number of devices",
- 2, devices.size());
+ 1, devices.size());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DeviceWriter.writeToXml(baos, devices);
+ String written = baos.toString();
List<Device> writtenDevices = DeviceParser.parse(
- new ByteArrayInputStream(baos.toString().getBytes()));
+ new ByteArrayInputStream(written.getBytes()));
assertEquals("Writing and reparsing returns a different number of devices",
devices.size(), writtenDevices.size());
for (int i = 0; i < devices.size(); i++) {
- assertEquals(devices.get(i), writtenDevices.get(i));
+ assertEquals(
+ "Device " + i + " differs in XML " + written,
+ "\n" + devices.get(i), "\n" + writtenDevices.get(i));
+ }
+ }
+
+ public void testWriteIsValid_Full() throws Exception {
+ InputStream devicesFile =
+ DeviceSchemaTest.class.getResourceAsStream("devices.xml");
+ List<Device> devices = DeviceParser.parse(devicesFile);
+ assertEquals("Parsed devices contained an unexpected number of devices",
+ 3, devices.size());
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DeviceWriter.writeToXml(baos, devices);
+ String written = baos.toString();
+ List<Device> writtenDevices = DeviceParser.parse(
+ new ByteArrayInputStream(written.getBytes()));
+ assertEquals("Writing and reparsing returns a different number of devices",
+ devices.size(), writtenDevices.size());
+ for (int i = 0; i < devices.size(); i++) {
+ assertEquals(
+ "Device " + i + " differs in XML " + written,
+ "\n" + devices.get(i), "\n" + writtenDevices.get(i));
}
}
@@ -55,8 +77,8 @@
InputStream devicesFile =
DeviceSchemaTest.class.getResourceAsStream("devices.xml");
List<Device> devices = DeviceParser.parse(devicesFile);
- assertEquals("Parsed devices contained an un expected number of devices",
- 2, devices.size());
+ assertEquals("Parsed devices contained an unexpected number of devices",
+ 3, devices.size());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DeviceWriter.writeToXml(baos, devices);
String xml = baos.toString();
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/AddonsListFetcherTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/AddonsListFetcherTest.java
index f62533d..3d77f01 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/AddonsListFetcherTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/AddonsListFetcherTest.java
@@ -84,7 +84,7 @@
/**
* Validate we can load a valid addon schema version 1
*/
- public void testLoadSample_1() throws Exception {
+ public void testLoadAddonsListXml_1() throws Exception {
InputStream xmlStream =
getTestResource("/com/android/sdklib/testdata/addons_list_sample_1.xml");
@@ -130,7 +130,7 @@
/**
* Validate we can load a valid addon schema version 2
*/
- public void testLoadSample_2() throws Exception {
+ public void testLoadAddonsListXml_2() throws Exception {
InputStream xmlStream =
getTestResource("/com/android/sdklib/testdata/addons_list_sample_2.xml");
@@ -175,6 +175,14 @@
assertEquals(6, result.length);
}
+ /**
+ * Validate there isn't a next-version we haven't tested yet
+ */
+ public void testLoadAddonsListXml_3() throws Exception {
+ InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addons_list_sample_3.xml");
+ assertNull("There is a sample for addons-list-3.xsd but there is not corresponding unit test", xmlStream);
+ }
+
// IMPORTANT: Each time you add a test here for a new version, you should
// also add a test in ValidateAddonsListXmlTest.
@@ -190,7 +198,9 @@
*/
private ByteArrayInputStream getTestResource(String filename) throws IOException {
InputStream xmlStream = this.getClass().getResourceAsStream(filename);
-
+ if (xmlStream == null) {
+ return null;
+ }
try {
byte[] data = new byte[8192];
int offset = 0;
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/DownloadCacheTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/DownloadCacheTest.java
new file mode 100755
index 0000000..43b5011
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/DownloadCacheTest.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidLocationTestCase;
+import com.android.sdklib.internal.repository.DownloadCache.Strategy;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.io.MockFileOp;
+import com.android.utils.Pair;
+import com.google.common.base.Charsets;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.message.BasicStatusLine;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DownloadCacheTest extends AndroidLocationTestCase {
+
+ private MockFileOp mFileOp;
+ private MockMonitor mMonitor;
+
+ /**
+ * A private version of DownloadCache that never calls {@link UrlOpener}.
+ */
+ private static class NoDownloadCache extends DownloadCache {
+
+ private final Map<String, Pair<InputStream, HttpResponse>> mReplies =
+ new HashMap<String, Pair<InputStream,HttpResponse>>();
+
+ public NoDownloadCache(@NonNull Strategy strategy) {
+ super(strategy);
+ }
+
+ public NoDownloadCache(@NonNull IFileOp fileOp, @NonNull Strategy strategy) {
+ super(fileOp, strategy);
+ }
+
+ @Override
+ protected Pair<InputStream, HttpResponse> openUrl(
+ @NonNull String url,
+ boolean needsMarkResetSupport,
+ @NonNull ITaskMonitor monitor,
+ @Nullable Header[] headers) throws IOException, CanceledByUserException {
+
+ Pair<InputStream, HttpResponse> reply = mReplies.get(url);
+ if (reply != null) {
+ return reply;
+ }
+
+ // http-client's behavior is to return a FNF instead of 404.
+ throw new FileNotFoundException(url);
+ }
+
+ public void registerResponse(@NonNull String url, int httpCode, @Nullable String content) {
+ InputStream is = null;
+ if (content != null) {
+ is = new ByteArrayInputStream(content.getBytes(Charsets.UTF_8));
+ }
+
+ ProtocolVersion p = new ProtocolVersion("HTTP", 1, 1);
+ StatusLine statusLine = new BasicStatusLine(p, httpCode, "Code " + httpCode);
+ HttpResponse httpResponse = new BasicHttpResponse(statusLine);
+ Pair<InputStream, HttpResponse> reply = Pair.of(is, httpResponse);
+
+ mReplies.put(url, reply);
+ }
+
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mFileOp = new MockFileOp();
+ mMonitor = new MockMonitor();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testMissingResource() throws Exception {
+ // Downloads must fail when using the only-cache strategy and there's nothing in the cache.
+ // In that case, it returns null to indicate the resource is simply not found.
+ // Since the mock implementation always returns a 404 and no stream, there is no
+ // difference between the various cache strategies.
+
+ mFileOp.reset();
+ NoDownloadCache d1 = new NoDownloadCache(mFileOp, Strategy.ONLY_CACHE);
+ InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is1);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot()));
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ // HTTP-Client's behavior is to return a FNF instead of 404 so we'll try that first
+ mFileOp.reset();
+ NoDownloadCache d2 = new NoDownloadCache(mFileOp, Strategy.DIRECT);
+
+ try {
+ d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ fail("Expected: NoDownloadCache.openCachedUrl should have thrown a FileNotFoundException");
+ } catch (FileNotFoundException e) {
+ assertEquals("http://www.example.com/download1.xml", e.getMessage());
+ }
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ // Try again but this time we'll define a 404 reply to test the rest of the code path.
+ mFileOp.reset();
+ d2.registerResponse("http://www.example.com/download1.xml", 404, null);
+ InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is2);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ mFileOp.reset();
+ NoDownloadCache d3 = new NoDownloadCache(mFileOp, Strategy.SERVE_CACHE);
+ d3.registerResponse("http://www.example.com/download1.xml", 404, null);
+ InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is3);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ mFileOp.reset();
+ NoDownloadCache d4 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
+ d4.registerResponse("http://www.example.com/download1.xml", 404, null);
+ InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is4);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+ }
+
+ public void testExistingResource() throws Exception {
+ // The resource exists but only-cache doesn't hit the network so it will
+ // fail when the resource is not cached.
+ mFileOp.reset();
+ NoDownloadCache d1 = new NoDownloadCache(mFileOp, Strategy.ONLY_CACHE);
+ d1.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
+ InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is1);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot()));
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ // HTTP-Client's behavior is to return a FNF instead of 404 so we'll try that first
+ mFileOp.reset();
+ NoDownloadCache d2 = new NoDownloadCache(mFileOp, Strategy.DIRECT);
+ d2.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
+ InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNotNull(is2);
+ assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is2, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ mFileOp.reset();
+ NoDownloadCache d3 = new NoDownloadCache(mFileOp, Strategy.SERVE_CACHE);
+ d3.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
+ InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNotNull(is3);
+ assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is3, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals(
+ "[<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'Blah blah blah'>, " +
+ "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" +
+ "#<creation timestamp>\n" +
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n" +
+ "'>]",
+ sanitize(d3, Arrays.toString(mFileOp.getOutputStreams())));
+
+ mFileOp.reset();
+ NoDownloadCache d4 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
+ d4.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
+ InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNotNull(is4);
+ assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is4, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals(
+ "[<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'Blah blah blah'>, " +
+ "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" +
+ "#<creation timestamp>\n" +
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n" +
+ "'>]",
+ sanitize(d4, Arrays.toString(mFileOp.getOutputStreams())));
+ }
+
+ public void testCachedResource() throws Exception {
+ mFileOp.reset();
+ NoDownloadCache d1 = new NoDownloadCache(mFileOp, Strategy.ONLY_CACHE);
+ d1.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d1.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ 123456L,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d1.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ 123456L,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // Only-cache strategy returns the value from the cache, not the actual resource.
+ assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is1, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot()));
+ // The cache hasn't been modified, only read
+ assertEquals("[]", sanitize(d1, Arrays.toString(mFileOp.getOutputStreams())));
+
+ // Direct ignores the cache.
+ mFileOp.reset();
+ NoDownloadCache d2 = new NoDownloadCache(mFileOp, Strategy.DIRECT);
+ d2.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d2.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ 123456L,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d2.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ 123456L,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // Direct strategy ignores the cache.
+ assertEquals("This is the new content", new BufferedReader(new InputStreamReader(is2, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d2.getCacheRoot()));
+ // Direct strategy doesn't update the cache.
+ assertEquals("[]", sanitize(d2, Arrays.toString(mFileOp.getOutputStreams())));
+
+ // Serve-cache reads from the cache if available, ignoring its freshness (here the timestamp
+ // is way older than the 10-minute freshness encoded in the DownloadCache.)
+ mFileOp.reset();
+ NoDownloadCache d3 = new NoDownloadCache(mFileOp, Strategy.SERVE_CACHE);
+ d3.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d3.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ 123456L,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d3.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ 123456L,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // We get content from the cache.
+ assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is3, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d3.getCacheRoot()));
+ // Cache isn't updated since nothing fresh was read.
+ assertEquals("[]", sanitize(d3, Arrays.toString(mFileOp.getOutputStreams())));
+
+ // fresh-cache reads the cache, finds it stale (here the timestamp
+ // is way older than the 10-minute freshness encoded in the DownloadCache)
+ // and will fetch the new resource instead and update the cache.
+ mFileOp.reset();
+ NoDownloadCache d4 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
+ d4.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d4.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ 123456L,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d4.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ 123456L,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // Cache is discarded, actual resource is returned.
+ assertEquals("This is the new content", new BufferedReader(new InputStreamReader(is4, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d4.getCacheRoot()));
+ // Cache isn updated since something fresh was read.
+ assertEquals(
+ "[<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'This is the new content'>, " +
+ "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" +
+ "#<creation timestamp>\n" +
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n" +
+ "'>]",
+ sanitize(d4, Arrays.toString(mFileOp.getOutputStreams())));
+
+ // fresh-cache reads the cache, finds it still valid stale (less than 10-minute old),
+ // and uses the cached resource.
+ mFileOp.reset();
+ NoDownloadCache d5 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
+ d5.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d5.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ System.currentTimeMillis() - 1000,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOp.append(d5.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ System.currentTimeMillis() - 1000,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is5 = d5.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // Cache is used.
+ assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is5, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d5.getCacheRoot()));
+ // Cache isn't updated since nothing fresh was read.
+ assertEquals("[]", sanitize(d5, Arrays.toString(mFileOp.getOutputStreams())));
+ }
+
+ // --------
+
+ @Nullable
+ private String sanitize(@NonNull DownloadCache dc, @Nullable String msg) {
+ if (msg != null) {
+ msg = msg.replace("\r\n", "\n");
+
+ String absRoot = mFileOp.getAgnosticAbsPath(dc.getCacheRoot());
+ msg = msg.replace(absRoot, "$CACHE");
+
+ // Cached files also contain a creation timestamp which we need to find and remove.
+ msg = msg.replaceAll("\n#[A-Z][A-Za-z0-9: ]+20[0-9]{2}\n", "\n#<creation timestamp>\n");
+ }
+ return msg;
+ }
+
+}
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 8baaee4..3c6f2b6 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
@@ -22,16 +22,31 @@
import com.android.sdklib.SdkManager;
import com.android.sdklib.SdkManagerTestCase;
import com.android.sdklib.SystemImage;
+import com.android.sdklib.internal.androidTarget.PlatformTarget;
+import com.android.sdklib.internal.repository.archives.ArchFilter;
+import com.android.sdklib.internal.repository.archives.HostOs;
+import com.android.sdklib.io.FileOp;
+import java.io.File;
import java.util.Arrays;
+import java.util.regex.Pattern;
public class LocalSdkParserTest extends SdkManagerTestCase {
- public void testLocalSdkParser_SystemImages() throws Exception {
- SdkManager sdkman = getSdkManager();
- LocalSdkParser parser = new LocalSdkParser();
- MockMonitor monitor = new MockMonitor();
+ private SdkManager mSdkMan;
+ private LocalSdkParser mParser;
+ private MockMonitor mMonitor;
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mSdkMan = getSdkManager();
+ mParser = new LocalSdkParser();
+ mMonitor = new MockMonitor();
+ }
+
+ public void testLocalSdkParser_SystemImages() throws Exception {
// By default SdkManagerTestCase creates an SDK with one platform containing
// a legacy armeabi system image (this is not a separate system image package)
@@ -43,64 +58,70 @@
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
"Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(), mSdkMan, mMonitor)));
assertEquals(
"[SDK Platform Android 0.0, API 0, revision 1, " +
"Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(),
- sdkman,
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
LocalSdkParser.PARSE_PLATFORMS | LocalSdkParser.PARSE_SOURCES,
- monitor)));
+ mMonitor)));
assertEquals(
"[SDK Platform Android 0.0, API 0, revision 1]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(),
- sdkman,
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
LocalSdkParser.PARSE_PLATFORMS,
- monitor)));
+ mMonitor)));
assertEquals(
"[Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(),
- sdkman,
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
LocalSdkParser.PARSE_SOURCES,
- monitor)));
+ mMonitor)));
assertEquals(
"[Android SDK Tools, revision 1.0.1]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(),
- sdkman,
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
LocalSdkParser.PARSE_TOOLS,
- monitor)));
+ mMonitor)));
assertEquals(
"[Android SDK Platform-tools, revision 17.1.2]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(),
- sdkman,
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
LocalSdkParser.PARSE_PLATFORM_TOOLS,
- monitor)));
+ mMonitor)));
assertEquals(
"[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(),
- sdkman,
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
LocalSdkParser.PARSE_BUILD_TOOLS,
- monitor)));
+ mMonitor)));
// Now add a few "platform subfolders" system images and reload the SDK.
// This disables the "legacy" mode but it still doesn't create any system image package
- IAndroidTarget t = sdkman.getTargets()[0];
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_PLATFORM_SUBFOLDER, SdkConstants.ABI_ARMEABI_V7A));
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_PLATFORM_SUBFOLDER, SdkConstants.ABI_INTEL_ATOM));
+ IAndroidTarget t = mSdkMan.getTargets()[0];
+ makeSystemImageFolder(new SystemImage(mSdkMan, t,
+ LocationType.IN_IMAGES_SUBFOLDER,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI_V7A,
+ FileOp.EMPTY_FILE_ARRAY));
+ makeSystemImageFolder(new SystemImage(mSdkMan, t,
+ LocationType.IN_IMAGES_SUBFOLDER,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_INTEL_ATOM,
+ FileOp.EMPTY_FILE_ARRAY));
- sdkman.reloadSdk(getLog());
- t = sdkman.getTargets()[0];
+ mSdkMan.reloadSdk(getLog());
+ t = mSdkMan.getTargets()[0];
assertEquals(
"[Android SDK Tools, revision 1.0.1, " +
@@ -110,18 +131,24 @@
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
"Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(), mSdkMan, mMonitor)));
// Now add arm + arm v7a images using the new SDK/system-images.
// The local parser will find the 2 system image packages which are associated
- // with the PlatformTarger in the SdkManager.
+ // with the PlatformTarget in the SdkManager.
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI));
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI_V7A));
+ makeSystemImageFolder(new SystemImage(mSdkMan, t,
+ LocationType.IN_SYSTEM_IMAGE,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI,
+ FileOp.EMPTY_FILE_ARRAY));
+ makeSystemImageFolder(new SystemImage(mSdkMan, t,
+ LocationType.IN_SYSTEM_IMAGE,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI_V7A,
+ FileOp.EMPTY_FILE_ARRAY));
- sdkman.reloadSdk(getLog());
+ mSdkMan.reloadSdk(getLog());
assertEquals(
"[Android SDK Tools, revision 1.0.1, " +
@@ -130,17 +157,20 @@
"Android SDK Build-tools, revision 3.0.1, " +
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
- "ARM EABI v7a System Image, Android API 0, revision 0, " +
- "ARM EABI System Image, Android API 0, revision 0, " +
+ "Sys-Img v0 for (Default, armeabi-v7a), Android API 0, revision 0, " +
+ "Sys-Img v0 for (Default, armeabi), Android API 0, revision 0, " +
"Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(), mSdkMan, mMonitor)));
// Now add an x86 image using the new SDK/system-images.
// Now this time we do NOT reload the SdkManager instance. Instead the parser
// will find an unused system image and load it as a "broken package".
- makeSystemImageFolder(new SystemImage(
- sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_INTEL_ATOM));
+ makeSystemImageFolder(new SystemImage(mSdkMan, t,
+ LocationType.IN_SYSTEM_IMAGE,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_INTEL_ATOM,
+ FileOp.EMPTY_FILE_ARRAY));
assertEquals(
"[Android SDK Tools, revision 1.0.1, " +
@@ -149,11 +179,11 @@
"Android SDK Build-tools, revision 3.0.1, " +
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
- "ARM EABI v7a System Image, Android API 0, revision 0, " +
- "ARM EABI System Image, Android API 0, revision 0, " +
+ "Sys-Img v0 for (Default, armeabi-v7a), Android API 0, revision 0, " +
+ "Sys-Img v0 for (Default, armeabi), Android API 0, revision 0, " +
"Sources for Android SDK, API 0, revision 0, " +
"Broken Intel x86 Atom System Image, API 0]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(), mSdkMan, mMonitor)));
assertEquals(
"[Android SDK Tools, revision 1.0.1, " +
@@ -162,34 +192,129 @@
"Android SDK Build-tools, revision 3.0.1, " +
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
- "ARM EABI v7a System Image, Android API 0, revision 0, " +
- "ARM EABI System Image, Android API 0, revision 0, " +
+ "Sys-Img v0 for (Default, armeabi-v7a), Android API 0, revision 0, " +
+ "Sys-Img v0 for (Default, armeabi), Android API 0, revision 0, " +
"Sources for Android SDK, API 0, revision 0, " +
"Broken Intel x86 Atom System Image, API 0]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(),
- sdkman,
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
LocalSdkParser.PARSE_ALL,
- monitor)));
+ mMonitor)));
assertEquals(
"[SDK Platform Android 0.0, API 0, revision 1, " +
- "ARM EABI v7a System Image, Android API 0, revision 0, " +
- "ARM EABI System Image, Android API 0, revision 0, " +
+ "Sys-Img v0 for (Default, armeabi-v7a), Android API 0, revision 0, " +
+ "Sys-Img v0 for (Default, armeabi), Android API 0, revision 0, " +
"Sources for Android SDK, API 0, revision 0, " +
"Broken Intel x86 Atom System Image, API 0]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(),
- sdkman,
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
LocalSdkParser.PARSE_PLATFORMS | // platform also loads system-images
LocalSdkParser.PARSE_SOURCES,
- monitor)));
+ mMonitor)));
assertEquals(
"[Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(parser.parseSdk(sdkman.getLocation(),
- sdkman,
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
LocalSdkParser.PARSE_SOURCES,
- monitor)));
+ mMonitor)));
}
+ public void testLocalSdkParser_Platform_DefaultSkin() throws Exception {
+ IAndroidTarget[] targets = mSdkMan.getTargets();
+
+ assertEquals(
+ "[PlatformTarget API 0 rev 1]",
+ Arrays.toString(targets));
+
+ PlatformTarget p = (PlatformTarget) targets[0];
+
+ assertEquals("[SDK/platforms/v0_0/skins/HVGA]",
+ sanitizeInput(p.getSkins()));
+
+ assertEquals("[SystemImage tag=default, ABI=armeabi, location in legacy folder='SDK/platforms/v0_0/images']",
+ sanitizeInput(p.getSystemImages()));
+
+ // the in-platform system image has no skins
+ assertEquals("[]",
+ sanitizeInput(p.getSystemImages()[0].getSkins()));
+ }
+
+ public void testLocalSdkParser_Platform_CustomSkin() throws Exception {
+ // add a new-style system-image with a tag and an embedded custom skin
+ File siArm = makeSystemImageFolder(TARGET_DIR_NAME_0, "tag-1", "x86");
+ makeFakeSkin(siArm, "Tag1ArmSkin");
+
+ IAndroidTarget[] targets = mSdkMan.getTargets();
+
+ assertEquals(
+ "[PlatformTarget API 0 rev 1]",
+ Arrays.toString(targets));
+
+ PlatformTarget p = (PlatformTarget) targets[0];
+
+ assertEquals(
+ "[SDK/platforms/v0_0/skins/HVGA, " +
+ "SDK/system-images/v0_0/tag-1/x86/skins/Tag1ArmSkin]",
+ sanitizeInput(p.getSkins()));
+
+ assertEquals(
+ "[SystemImage tag=default, ABI=armeabi, location in legacy folder='SDK/platforms/v0_0/images', " +
+ "SystemImage tag=tag-1, ABI=x86, location in system image='SDK/system-images/v0_0/tag-1/x86']",
+ sanitizeInput(p.getSystemImages()));
+
+ // the in-platform system image has no skins, the second one has a custom skin
+ assertEquals("[]",
+ sanitizeInput(p.getSystemImages()[0].getSkins()));
+ assertEquals("[SDK/system-images/v0_0/tag-1/x86/skins/Tag1ArmSkin]",
+ sanitizeInput(p.getSystemImages()[1].getSkins()));
+ }
+
+ public void testLocalSdkParser_BuildTools_InvalidOs() throws Exception {
+ assertEquals(
+ "[Android SDK Build-tools, revision 18.3.4 rc5, " +
+ "Android SDK Build-tools, revision 3.0.1, " +
+ "Android SDK Build-tools, revision 3, " +
+ "Platform Tools, revision 17.1.2, Tools, revision 1.0.1]",
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
+ LocalSdkParser.PARSE_BUILD_TOOLS |
+ LocalSdkParser.PARSE_EXTRAS,
+ mMonitor)));
+
+ // We have many OS possible. Choose 2 that do not match the current platform.
+ ArchFilter current = ArchFilter.getCurrent();
+ HostOs others[] = new HostOs[2];
+ int i = 0;
+ for (HostOs o : HostOs.values()) {
+ if (o != current.getHostOS() && i < others.length) {
+ others[i++] = o;
+ }
+ }
+ createFakeBuildTools(new File(mSdkMan.getLocation()), others[0].toString(), "5.0.1");
+ createFakeBuildTools(new File(mSdkMan.getLocation()), others[1].toString(), "5.0.2");
+
+ assertEquals(
+ "[Android SDK Build-tools, revision 18.3.4 rc5, " +
+ "Android SDK Build-tools, revision 3.0.1, " +
+ "Android SDK Build-tools, revision 3, " +
+ "Broken Build-Tools Package, revision 5.0.1, " +
+ "Broken Build-Tools Package, revision 5.0.2, " +
+ "Platform Tools, revision 17.1.2, Tools, revision 1.0.1]",
+ Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
+ mSdkMan,
+ LocalSdkParser.PARSE_BUILD_TOOLS |
+ LocalSdkParser.PARSE_EXTRAS,
+ mMonitor)));
+ }
+
+ private String sanitizeInput(Object[] array) {
+ String input = Arrays.toString(array);
+ String sdkPath = mSdkMan.getLocation();
+ input = input.replaceAll(Pattern.quote(sdkPath), "SDK");
+ input = input.replace(File.separatorChar, '/');
+ return input;
+ }
}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/MockAddonTarget.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/MockAddonTarget.java
index 9fa98a1..0d203ba 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/MockAddonTarget.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/MockAddonTarget.java
@@ -18,6 +18,7 @@
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
@@ -25,7 +26,9 @@
import com.android.sdklib.ISystemImage.LocationType;
import com.android.sdklib.SystemImage;
import com.android.sdklib.io.FileOp;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import java.io.File;
import java.util.List;
import java.util.Map;
@@ -39,6 +42,7 @@
private final int mRevision;
private final String mName;
private ISystemImage[] mSystemImages;
+ private IOptionalLibrary[] mOptionalLibraries;
public MockAddonTarget(String name, IAndroidTarget parentTarget, int revision) {
mName = name;
@@ -57,7 +61,7 @@
}
@Override
- public String getDefaultSkin() {
+ public File getDefaultSkin() {
return null;
}
@@ -76,16 +80,19 @@
if (mSystemImages == null) {
SystemImage si = new SystemImage(
FileOp.append(getLocation(), SdkConstants.OS_IMAGES_FOLDER),
- LocationType.IN_PLATFORM_LEGACY,
- SdkConstants.ABI_ARMEABI);
+ LocationType.IN_LEGACY_FOLDER,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI,
+ FileOp.EMPTY_FILE_ARRAY);
mSystemImages = new SystemImage[] { si };
}
return mSystemImages;
}
@Override
- public ISystemImage getSystemImage(String abiType) {
- if (SdkConstants.ABI_ARMEABI.equals(abiType)) {
+ @Nullable
+ public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType) {
+ if (SystemImage.DEFAULT_TAG.equals(tag) && SdkConstants.ABI_ARMEABI.equals(abiType)) {
return getSystemImages()[0];
}
return null;
@@ -98,7 +105,11 @@
@Override
public IOptionalLibrary[] getOptionalLibraries() {
- return null;
+ return mOptionalLibraries;
+ }
+
+ public void setOptionalLibraries(IOptionalLibrary[] libraries) {
+ mOptionalLibraries = libraries;
}
@Override
@@ -112,6 +123,11 @@
}
@Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+ @Override
public BuildToolInfo getBuildToolInfo() {
return null;
}
@@ -152,8 +168,8 @@
}
@Override
- public String[] getSkins() {
- return null;
+ public File[] getSkins() {
+ return FileOp.EMPTY_FILE_ARRAY;
}
@Override
@@ -161,6 +177,7 @@
return 0;
}
+ @NonNull
@Override
public AndroidVersion getVersion() {
return mParentTarget.getVersion();
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/MockMonitor.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/MockMonitor.java
index 2fa79a4..3c011f2 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/MockMonitor.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/MockMonitor.java
@@ -47,6 +47,10 @@
return mCapturedDescriptions;
}
+ public String getAllCapturedLogs() {
+ return mCapturedLog + mCapturedVerboseLog + mCapturedDescriptions + mCapturedErrorLog;
+ }
+
@Override
public void log(String format, Object... args) {
mCapturedLog += String.format(format, args) + "\n"; //$NON-NLS-1$
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/MockPlatformTarget.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/MockPlatformTarget.java
index 4409efe..b52620a 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/MockPlatformTarget.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/MockPlatformTarget.java
@@ -18,6 +18,7 @@
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
@@ -25,7 +26,9 @@
import com.android.sdklib.ISystemImage.LocationType;
import com.android.sdklib.SystemImage;
import com.android.sdklib.io.FileOp;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import java.io.File;
import java.util.List;
import java.util.Map;
@@ -55,7 +58,7 @@
}
@Override
- public String getDefaultSkin() {
+ public File getDefaultSkin() {
return null;
}
@@ -74,16 +77,19 @@
if (mSystemImages == null) {
SystemImage si = new SystemImage(
FileOp.append(getLocation(), SdkConstants.OS_IMAGES_FOLDER),
- LocationType.IN_PLATFORM_LEGACY,
- SdkConstants.ABI_ARMEABI);
+ LocationType.IN_LEGACY_FOLDER,
+ SystemImage.DEFAULT_TAG,
+ SdkConstants.ABI_ARMEABI,
+ FileOp.EMPTY_FILE_ARRAY);
mSystemImages = new SystemImage[] { si };
}
return mSystemImages;
}
@Override
- public ISystemImage getSystemImage(String abiType) {
- if (SdkConstants.ABI_ARMEABI.equals(abiType)) {
+ @Nullable
+ public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType) {
+ if (SystemImage.DEFAULT_TAG.equals(tag) && SdkConstants.ABI_ARMEABI.equals(abiType)) {
return getSystemImages()[0];
}
return null;
@@ -110,6 +116,11 @@
}
@Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+ @Override
public BuildToolInfo getBuildToolInfo() {
return null;
}
@@ -150,8 +161,8 @@
}
@Override
- public String[] getSkins() {
- return null;
+ public File[] getSkins() {
+ return FileOp.EMPTY_FILE_ARRAY;
}
@Override
@@ -177,6 +188,7 @@
return "platform r" + Integer.toString(mApiLevel);
}
+ @NonNull
@Override
public AndroidVersion getVersion() {
return new AndroidVersion(mApiLevel, null /*codename*/);
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchFilterTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchFilterTest.java
new file mode 100755
index 0000000..eaa6802
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchFilterTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archives;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
+
+import junit.framework.TestCase;
+
+public class ArchFilterTest extends TestCase {
+
+ public void testGetCurrent() {
+ ArchFilter f1 = makeCurrent("Windows 7", "amd64", "1.7.0_51");
+ assertEquals(HostOs.WINDOWS, f1.getHostOS());
+ assertEquals(BitSize._64, f1.getHostBits());
+ assertEquals(BitSize._64, f1.getJvmBits());
+ assertEquals(new FullRevision(1, 7, 0), f1.getMinJvmVersion());
+
+ ArchFilter f2 = makeCurrent("Mac OS X", "x86_64", "1.7.0_51");
+ assertEquals(HostOs.MACOSX, f2.getHostOS());
+ assertEquals(BitSize._64, f2.getHostBits());
+ assertEquals(BitSize._64, f2.getJvmBits());
+ assertEquals(new FullRevision(1, 7, 0), f2.getMinJvmVersion());
+
+ ArchFilter f3 = makeCurrent("Linux", "x86", "1.6.42_43");
+ assertEquals(HostOs.LINUX, f3.getHostOS());
+ assertEquals(BitSize._32, f3.getHostBits());
+ assertEquals(BitSize._32, f3.getJvmBits());
+ assertEquals(new FullRevision(1, 6, 42), f3.getMinJvmVersion());
+ }
+
+ public void testIsCompatibleWith() {
+ ArchFilter f1 = makeCurrent("Windows 7", "amd64", "1.7.0_51");
+
+ assertTrue(new ArchFilter(null, null, null, null).isCompatibleWith(f1));
+
+ assertTrue (new ArchFilter(HostOs.WINDOWS, null, null, null).isCompatibleWith(f1));
+ assertFalse(new ArchFilter(HostOs.MACOSX , null, null, null).isCompatibleWith(f1));
+ assertFalse(new ArchFilter(HostOs.LINUX , null, null, null).isCompatibleWith(f1));
+
+ assertTrue (new ArchFilter(null, BitSize._64, null, null).isCompatibleWith(f1));
+ assertFalse(new ArchFilter(null, BitSize._32, null, null).isCompatibleWith(f1));
+
+ assertTrue (new ArchFilter(null, null, BitSize._64, null).isCompatibleWith(f1));
+ assertFalse(new ArchFilter(null, null, BitSize._32, null).isCompatibleWith(f1));
+
+ assertTrue (new ArchFilter(null, null, null, new NoPreviewRevision(1, 6, 42)).isCompatibleWith(f1));
+ assertTrue (new ArchFilter(null, null, null, new NoPreviewRevision(1, 7, 0)).isCompatibleWith(f1));
+ assertFalse(new ArchFilter(null, null, null, new NoPreviewRevision(1, 7, 1)).isCompatibleWith(f1));
+ assertFalse(new ArchFilter(null, null, null, new NoPreviewRevision(1, 8, 0)).isCompatibleWith(f1));
+ assertFalse(new ArchFilter(null, null, null, new NoPreviewRevision(2, 0, 0)).isCompatibleWith(f1));
+ }
+
+ // ---- helpers ---
+
+ /**
+ * {@link ArchFilter#getCurrent()} uses java system properties to find the
+ * current architecture attributes. This method temporarily overrides
+ * System properties, calls {@link ArchFilter#getCurrent()} and then reset
+ * the properties.
+ *
+ * @param osName The override value for the System "os.name" property.
+ * @param osArch The override value for the System "os.arch" property.
+ * @param javaVersion The override value for the System "java.version" property.
+ * @return A new {@link ArchFilter}
+ */
+ @NonNull
+ private ArchFilter makeCurrent(@NonNull String osName,
+ @NonNull String osArch,
+ @NonNull String javaVersion) {
+ String oldOsName = System.getProperty("os.name");
+ String oldOsArch = System.getProperty("os.arch");
+ String oldJavaVers = System.getProperty("java.version");
+ try {
+ System.setProperty("os.name", osName);
+ System.setProperty("os.arch", osArch);
+ System.setProperty("java.version", javaVersion);
+
+ return ArchFilter.getCurrent();
+ } finally {
+ System.setProperty("os.name", oldOsName);
+ System.setProperty("os.arch", oldOsArch);
+ System.setProperty("java.version", oldJavaVers);
+ }
+ }
+
+}
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 e5a8041..25e08b5 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
@@ -20,8 +20,6 @@
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.MockEmptySdkManager;
import com.android.sdklib.internal.repository.MockMonitor;
-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.MockEmptyPackage;
import com.android.sdklib.internal.repository.packages.MockExtraPackage;
@@ -143,9 +141,7 @@
assertEquals(
"[</sdk/mock/testPkg/source.properties: '### Android Tool: Source of this archive.\n" +
"#...date...\n" +
- "Archive.Os=ANY\n" +
"Pkg.Revision=0\n" +
- "Archive.Arch=ANY\n" +
"Pkg.SourceUrl=http\\://repo.example.com/url\n" +
"'>]",
stripDate(Arrays.toString(mFile.getOutputStreams())));
@@ -195,16 +191,14 @@
"[</sdk/extras/vendor1/oldPath/source.properties: " +
"'### Android Tool: Source of this archive.\n" +
"#...date...\n" +
- "Extra.VendorDisplay=vendor1\n" +
- "Pkg.Desc=desc\n" +
- "Extra.Path=oldPath\n" +
- "Archive.Arch=ANY\n" +
- "Pkg.DescUrl=url\n" +
"Extra.NameDisplay=Vendor1 OldPath\n" +
- "Archive.Os=ANY\n" +
- "Pkg.SourceUrl=http\\://repo.example.com/url\n" +
- "Pkg.Revision=2.0.0\n" +
+ "Extra.Path=oldPath\n" +
+ "Extra.VendorDisplay=vendor1\n" +
"Extra.VendorId=vendor1\n" +
+ "Pkg.Desc=desc\n" +
+ "Pkg.DescUrl=url\n" +
+ "Pkg.Revision=2.0.0\n" +
+ "Pkg.SourceUrl=http\\://repo.example.com/url\n" +
"'>]"),
stripDate(Arrays.toString(mFile.getOutputStreams())));
@@ -234,8 +228,6 @@
null, // license
null, // description
null, // descUrl
- Os.ANY, // archiveOs
- Arch.ANY, // archiveArch
"/sdk/extras/vendor1/oldPath" // archiveOsPath
);
@@ -271,17 +263,15 @@
"[</sdk/extras/vendor1/newPath/source.properties: " +
"'### Android Tool: Source of this archive.\n" +
"#...date...\n" +
- "Extra.VendorDisplay=vendor1\n" +
- "Pkg.Desc=desc\n" +
+ "Extra.NameDisplay=Vendor1 NewPath\n" +
"Extra.OldPaths=oldPath\n" +
"Extra.Path=newPath\n" +
- "Archive.Arch=ANY\n" +
- "Pkg.DescUrl=url\n" +
- "Extra.NameDisplay=Vendor1 NewPath\n" +
- "Archive.Os=ANY\n" +
- "Pkg.SourceUrl=http\\://repo.example.com/url\n" +
- "Pkg.Revision=2.0.0\n" +
+ "Extra.VendorDisplay=vendor1\n" +
"Extra.VendorId=vendor1\n" +
+ "Pkg.Desc=desc\n" +
+ "Pkg.DescUrl=url\n" +
+ "Pkg.Revision=2.0.0\n" +
+ "Pkg.SourceUrl=http\\://repo.example.com/url\n" +
"'>]"),
stripDate(Arrays.toString(mFile.getOutputStreams())));
@@ -302,15 +292,12 @@
@Override
protected Archive[] initializeArchives(
Properties props,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
// Create one remote archive for this package
return new Archive[] {
new Archive(
this,
- Os.ANY,
- Arch.ANY,
+ null, // arch
"http://some.source/some_url",
1234, // size
"abcdef") // sha1
@@ -333,15 +320,12 @@
@Override
protected Archive[] initializeArchives(
Properties props,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
// Create one remote archive for this package
return new Archive[] {
new Archive(
this,
- Os.ANY,
- Arch.ANY,
+ null, // arch
"http://some.source/some_url",
1234, // size
"abcdef") // sha1
@@ -369,15 +353,12 @@
@Override
protected Archive[] initializeArchives(
Properties props2,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
// Create one remote archive for this package
return new Archive[] {
new Archive(
this,
- Os.ANY,
- Arch.ANY,
+ null, // arch
"http://some.source/some_url",
1234, // size
"abcdef") // sha1
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveTest.java
index c0ae718..ce0e415 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveTest.java
@@ -16,10 +16,6 @@
package com.android.sdklib.internal.repository.archives;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-
import junit.framework.TestCase;
public class ArchiveTest extends TestCase {
@@ -32,8 +28,7 @@
public void testShortDescription() throws Exception {
Archive a = new Archive(
null, //pkg,
- Os.WINDOWS, //os
- Arch.ANY, //arch,
+ new ArchFilter(HostOs.WINDOWS, null, null, null), //arch,
null, //url
0, //size
null); //checksum
@@ -41,8 +36,7 @@
a = new Archive(
null, //pkg,
- Os.LINUX, //os
- Arch.ANY, //arch,
+ new ArchFilter(HostOs.LINUX, null, null, null), //arch,
null, //url
0, //size
null); //checksum
@@ -50,8 +44,7 @@
a = new Archive(
null, //pkg,
- Os.MACOSX, //os
- Arch.ANY, //arch,
+ new ArchFilter(HostOs.MACOSX, null, null, null), //arch,
null, //url
0, //size
null); //checksum
@@ -59,8 +52,7 @@
a = new Archive(
null, //pkg,
- Os.ANY, //os
- Arch.ANY, //arch,
+ null, //arch,
null, //url
0, //size
null); //checksum
@@ -70,8 +62,7 @@
public void testLongDescription() throws Exception {
Archive a = new Archive(
null, //pkg,
- Os.WINDOWS, //os
- Arch.ANY, //arch,
+ new ArchFilter(HostOs.WINDOWS, null, null, null), //arch,
null, //url
900, //size
"1234567890ABCDEF"); //checksum
@@ -81,35 +72,35 @@
"SHA1: 1234567890ABCDEF",
a.getLongDescription());
- a = new Archive(null, Os.WINDOWS, Arch.ANY, null, 1100, "1234567890ABCDEF");
+ a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, 1100, "1234567890ABCDEF");
assertEquals(
"Archive for Windows\n" +
"Size: 1 KiB\n" +
"SHA1: 1234567890ABCDEF",
a.getLongDescription());
- a = new Archive(null, Os.WINDOWS, Arch.ANY, null, 1900, "1234567890ABCDEF");
+ a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, 1900, "1234567890ABCDEF");
assertEquals(
"Archive for Windows\n" +
"Size: 2 KiB\n" +
"SHA1: 1234567890ABCDEF",
a.getLongDescription());
- a = new Archive(null, Os.WINDOWS, Arch.ANY, null, (long)2e6, "1234567890ABCDEF");
+ a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, (long)2e6, "1234567890ABCDEF");
assertEquals(
"Archive for Windows\n" +
"Size: 1.9 MiB\n" +
"SHA1: 1234567890ABCDEF",
a.getLongDescription());
- a = new Archive(null, Os.WINDOWS, Arch.ANY, null, (long)19e6, "1234567890ABCDEF");
+ a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, (long)19e6, "1234567890ABCDEF");
assertEquals(
"Archive for Windows\n" +
"Size: 18.1 MiB\n" +
"SHA1: 1234567890ABCDEF",
a.getLongDescription());
- a = new Archive(null, Os.WINDOWS, Arch.ANY, null, (long)18e9, "1234567890ABCDEF");
+ a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, (long)18e9, "1234567890ABCDEF");
assertEquals(
"Archive for Windows\n" +
"Size: 16.8 GiB\n" +
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/BitSizeTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/BitSizeTest.java
new file mode 100755
index 0000000..9f70a6a
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/BitSizeTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archives;
+
+import junit.framework.TestCase;
+
+public class BitSizeTest extends TestCase {
+
+ public void testGetSize() {
+ assertEquals(32, BitSize._32.getSize());
+ assertEquals(64, BitSize._64.getSize());
+ }
+
+ public void testGetXmlName() {
+ assertEquals("32", BitSize._32.getXmlName());
+ assertEquals("64", BitSize._64.getXmlName());
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/HostOsTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/HostOsTest.java
new file mode 100755
index 0000000..c7ba507
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/HostOsTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archives;
+
+import junit.framework.TestCase;
+
+public class HostOsTest extends TestCase {
+
+ public void testGetUiName() {
+ assertEquals("Windows", HostOs.WINDOWS.getUiName());
+ assertEquals("MacOS X", HostOs.MACOSX .getUiName());
+ assertEquals("Linux", HostOs.LINUX .getUiName());
+ }
+
+ public void testGetXmlName() {
+ assertEquals("windows", HostOs.WINDOWS.getXmlName());
+ assertEquals("macosx", HostOs.MACOSX .getXmlName());
+ assertEquals("linux", HostOs.LINUX .getXmlName());
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/AddonSystemImagePackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/AddonSystemImagePackageTest.java
new file mode 100755
index 0000000..9074769
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/AddonSystemImagePackageTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.AndroidVersion;
+import com.android.sdklib.SystemImage;
+import com.android.sdklib.AndroidVersion.AndroidVersionException;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.repository.PkgProps;
+
+import java.util.Properties;
+
+public class AddonSystemImagePackageTest extends PackageTest {
+
+ private static class SysImgPackageFakeArchive extends SystemImagePackage {
+ protected SysImgPackageFakeArchive(
+ AndroidVersion platformVersion,
+ int revision,
+ String addonVendorId,
+ String addonNameId,
+ String abi,
+ Properties props) {
+ super(platformVersion,
+ revision,
+ abi,
+ props,
+ String.format("/sdk/system-images/addon-%s-%s-%s/%s",
+ addonNameId,
+ addonVendorId,
+ platformVersion.getApiString(),
+ abi));
+ }
+
+ @Override
+ protected Archive[] initializeArchives(
+ Properties props,
+ String archiveOsPath) {
+ return super.initializeArchives(props, LOCAL_ARCHIVE_PATH);
+ }
+ }
+
+ private SystemImagePackage createSystemImagePackage(Properties props)
+ throws AndroidVersionException {
+ SystemImagePackage p = new SysImgPackageFakeArchive(
+ new AndroidVersion(props),
+ 1 /*revision*/,
+ props.getProperty(PkgProps.ADDON_VENDOR_ID),
+ props.getProperty(PkgProps.SYS_IMG_TAG_ID),
+ props.getProperty(PkgProps.SYS_IMG_ABI),
+ props);
+ return p;
+ }
+
+ @Override
+ protected Properties createExpectedProps() {
+ Properties props = super.createExpectedProps();
+
+ // SystemImagePackage properties
+ props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
+ props.setProperty(PkgProps.SYS_IMG_ABI, "armeabi-v7a");
+
+ // Addon-specific SystemImagePackage properties
+ props.setProperty(PkgProps.ADDON_VENDOR_ID, "vendor_id");
+ props.setProperty(PkgProps.ADDON_VENDOR_DISPLAY, "Vendor Name");
+ props.setProperty(PkgProps.SYS_IMG_TAG_ID, "addon_name");
+ props.setProperty(PkgProps.SYS_IMG_TAG_DISPLAY, "Add-on Name");
+
+ return props;
+ }
+
+ protected void testCreatedSystemImagePackage(SystemImagePackage p) {
+ super.testCreatedPackage(p);
+
+ // SystemImagePackage properties
+ assertEquals("API 5", p.getAndroidVersion().toString());
+ assertEquals("armeabi-v7a", p.getAbi());
+ }
+
+ // ----
+
+ @Override
+ public final void testCreate() throws Exception {
+ Properties props = createExpectedProps();
+ SystemImagePackage p = createSystemImagePackage(props);
+
+ testCreatedSystemImagePackage(p);
+ }
+
+ @Override
+ public void testSaveProperties() throws Exception {
+ Properties expected = createExpectedProps();
+ SystemImagePackage p = createSystemImagePackage(expected);
+
+ Properties actual = new Properties();
+ p.saveProperties(actual);
+
+ assertEquals(expected, actual);
+ }
+
+ public void testSameItemAs() throws Exception {
+ Properties props1 = createExpectedProps();
+ SystemImagePackage p1 = createSystemImagePackage(props1);
+ assertTrue(p1.sameItemAs(p1));
+
+ // different abi, same version
+ Properties props2 = new Properties(props1);
+ props2.setProperty(PkgProps.SYS_IMG_ABI, "x86");
+ SystemImagePackage p2 = createSystemImagePackage(props2);
+ assertFalse(p1.sameItemAs(p2));
+ assertFalse(p2.sameItemAs(p1));
+
+ // different abi, different version
+ props2.setProperty(PkgProps.VERSION_API_LEVEL, "6");
+ p2 = createSystemImagePackage(props2);
+ assertFalse(p1.sameItemAs(p2));
+ assertFalse(p2.sameItemAs(p1));
+
+ // same abi, different version
+ Properties props3 = new Properties(props1);
+ props3.setProperty(PkgProps.VERSION_API_LEVEL, "6");
+ SystemImagePackage p3 = createSystemImagePackage(props3);
+ assertFalse(p1.sameItemAs(p3));
+ assertFalse(p3.sameItemAs(p1));
+ }
+
+ public void testInstallId() throws Exception {
+ Properties props = createExpectedProps();
+ SystemImagePackage p = createSystemImagePackage(props);
+
+ assertEquals("sys-img-armeabi-v7a-addon-addon_name-vendor_id-5", p.installId());
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BrokenPackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BrokenPackageTest.java
index 92a04c4..05e0198 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BrokenPackageTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BrokenPackageTest.java
@@ -17,6 +17,8 @@
package com.android.sdklib.internal.repository.packages;
import com.android.sdklib.internal.repository.packages.BrokenPackage;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import junit.framework.TestCase;
@@ -33,8 +35,9 @@
"long description",
12, // min api level
13, // exact api level
- "os/path");
-
+ "os/path",
+ PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 4),
+ FullRevision.NOT_SPECIFIED).create());
}
public final void testGetShortDescription() {
@@ -56,4 +59,11 @@
public void testInstallId() {
assertEquals("", m.installId());
}
+
+ public final void testGetPkgDesc() {
+ assertEquals(
+ PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 4),
+ FullRevision.NOT_SPECIFIED).create(),
+ m.getPkgDesc());
+ }
}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BuildToolPackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BuildToolPackageTest.java
new file mode 100755
index 0000000..998c578
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BuildToolPackageTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.PkgProps;
+
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public class BuildToolPackageTest extends TestCase {
+
+ private static class BuildToolPackageFakeArchive extends BuildToolPackage {
+ protected BuildToolPackageFakeArchive(Properties props) {
+ super(null, // source
+ props,
+ 0, //ignored, the one from props will be used
+ null, //license
+ null, //description
+ null, //descUrl
+ "/sdk/tools");
+ }
+
+ @Override
+ protected Archive[] initializeArchives(
+ Properties props,
+ String archiveOsPath) {
+ return super.initializeArchives(props, PackageTest.LOCAL_ARCHIVE_PATH);
+ }
+ }
+
+ private Properties createExpectedProps(boolean isPreview) {
+ Properties props = PackageTest.createDefaultProps();
+
+ props.setProperty(PkgProps.PKG_REVISION,
+ new FullRevision(1, 2, 3, isPreview ? 4 : 0).toShortString());
+ props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
+
+ return props;
+ }
+
+ // ----
+
+ public void testInstallId() throws Exception {
+ Properties props1 = createExpectedProps(true);
+ BuildToolPackage p1 = new BuildToolPackageFakeArchive(props1);
+ assertEquals("build-tools-1.2.3_rc4", p1.installId());
+
+ Properties props2 = createExpectedProps(false);
+ BuildToolPackage p2 = new BuildToolPackageFakeArchive(props2);
+ assertEquals("build-tools-1.2.3", p2.installId());
+ }
+}
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
index 4cb0f99..b82637c 100755
--- 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
@@ -16,8 +16,6 @@
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;
@@ -26,7 +24,7 @@
@Override
public void testCreate() {
- Properties props = createProps();
+ Properties props = createExpectedProps();
MockExtraPackage p = new MockExtraPackage(
null, //source
@@ -37,8 +35,6 @@
null, //license
null, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
LOCAL_ARCHIVE_PATH
);
@@ -47,31 +43,29 @@
@Override
public void testSaveProperties() {
- Properties props = createProps();
+ Properties expected = createExpectedProps();
MockExtraPackage p = new MockExtraPackage(
null, //source
- props,
+ expected,
"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);
+ Properties actual = new Properties();
+ p.saveProperties(actual);
- assertEquals(props2, props);
+ assertEquals(expected, actual);
}
@Override
- protected Properties createProps() {
- Properties props = super.createProps();
+ protected Properties createExpectedProps() {
+ Properties props = super.createExpectedProps();
props.setProperty(PkgProps.EXTRA_VENDOR_ID, "vendor");
props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, "vendor");
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 802fde7..3d92d48 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
@@ -16,9 +16,6 @@
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.packages.ExtraPackage;
import com.android.sdklib.repository.PkgProps;
import java.io.File;
@@ -43,8 +40,6 @@
null, //license
null, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
"/local/archive/path" //archiveOsPath
);
return p;
@@ -52,7 +47,7 @@
/** Properties used to "load" the package. When saved, they become different. */
private Properties createLoadedProps() {
- Properties props = super.createProps();
+ Properties props = super.createExpectedProps();
// ExtraPackage properties
props.setProperty(PkgProps.EXTRA_VENDOR, "vendor");
@@ -67,7 +62,7 @@
/** Properties saved by the package. They differ from loaded ones in name and vendor. */
private Properties createSavedProps() {
- Properties props = super.createProps();
+ Properties props = super.createExpectedProps();
// ExtraPackage properties
props.setProperty(PkgProps.EXTRA_VENDOR_ID, "vendor");
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 2c0fe15..4bd1e1b 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
@@ -16,9 +16,6 @@
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.packages.ExtraPackage;
import com.android.sdklib.repository.PkgProps;
import java.io.File;
@@ -43,16 +40,14 @@
null, //license
null, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
"/local/archive/path" //archiveOsPath
);
return p;
}
@Override
- protected Properties createProps() {
- Properties props = super.createProps();
+ protected Properties createExpectedProps() {
+ Properties props = super.createExpectedProps();
// ExtraPackage properties
props.setProperty(PkgProps.EXTRA_VENDOR_ID, "the_vendor");
@@ -86,7 +81,7 @@
@Override
public final void testCreate() {
- Properties props = createProps();
+ Properties props = createExpectedProps();
ExtraPackage p = createExtraPackage(props);
testCreatedExtraPackage(p);
@@ -94,17 +89,17 @@
@Override
public void testSaveProperties() {
- Properties props = createProps();
- ExtraPackage p = createExtraPackage(props);
+ Properties expected = createExpectedProps();
+ ExtraPackage p = createExtraPackage(expected);
- Properties props2 = new Properties();
- p.saveProperties(props2);
+ Properties actual = new Properties();
+ p.saveProperties(actual);
- assertEquals(props2, props);
+ assertEquals(expected, actual);
}
public void testSameItemAs() {
- Properties props1 = createProps();
+ Properties props1 = createExpectedProps();
ExtraPackage p1 = createExtraPackage(props1);
assertTrue(p1.sameItemAs(p1));
@@ -154,7 +149,7 @@
}
public void testInstallId() {
- Properties props = createProps();
+ Properties props = createExpectedProps();
ExtraPackage p = createExtraPackage(props);
assertEquals("extra-the_vendor-the_path", p.installId());
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
index adffe2e..87533e3 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
@@ -17,12 +17,11 @@
package com.android.sdklib.internal.repository.packages;
import com.android.sdklib.SdkManager;
-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.MinToolsPackage;
-import com.android.sdklib.internal.repository.packages.Package;
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.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import java.io.File;
import java.util.Properties;
@@ -38,8 +37,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
super(source,
props,
@@ -47,8 +44,6 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
}
@@ -76,11 +71,18 @@
public String installId() {
return ""; //$NON-NLS-1$
}
+
+ @Override
+ public IPkgDesc getPkgDesc() {
+ return PkgDesc.Builder.newTool(
+ new FullRevision(1, 2, 3, 4),
+ FullRevision.NOT_SPECIFIED).create();
+ }
}
@Override
public void testCreate() {
- Properties props = createProps();
+ Properties props = createExpectedProps();
MockMinToolsPackage p = new MockMinToolsPackage(
null, //source
@@ -89,8 +91,6 @@
null, //license
null, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
LOCAL_ARCHIVE_PATH
);
@@ -99,29 +99,27 @@
@Override
public void testSaveProperties() {
- Properties props = createProps();
+ Properties expected = createExpectedProps();
MockMinToolsPackage p = new MockMinToolsPackage(
null, //source
- props,
+ expected,
-1, //revision
null, //license
null, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
LOCAL_ARCHIVE_PATH
);
- Properties props2 = new Properties();
- p.saveProperties(props2);
+ Properties actual = new Properties();
+ p.saveProperties(actual);
- assertEquals(props2, props);
+ assertEquals(expected, actual);
}
@Override
- protected Properties createProps() {
- Properties props = super.createProps();
+ protected Properties createExpectedProps() {
+ Properties props = super.createExpectedProps();
// MinToolsPackage properties
props.setProperty(PkgProps.MIN_TOOLS_REV, "3.0.1");
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBrokenPackage.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBrokenPackage.java
index e78e796..51f9e35 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBrokenPackage.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBrokenPackage.java
@@ -17,6 +17,8 @@
package com.android.sdklib.internal.repository.packages;
import com.android.sdklib.internal.repository.packages.BrokenPackage;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.descriptors.PkgDesc;
/**
@@ -52,6 +54,9 @@
longDescription,
minApiLevel,
exactApiLevel,
- "/sdk/broken/package" /*osPath*/);
+ "/sdk/broken/package" /*osPath*/,
+ PkgDesc.Builder.newTool(
+ new FullRevision(1, 2, 3, 4),
+ FullRevision.NOT_SPECIFIED).create());
}
}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBuildToolPackage.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBuildToolPackage.java
index 3150fb2..eb9f69c 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBuildToolPackage.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBuildToolPackage.java
@@ -16,8 +16,6 @@
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;
@@ -52,8 +50,6 @@
null, // license,
"desc", // description,
"url", // descUrl,
- Os.getCurrentOs(), // archiveOs,
- Arch.getCurrentArch(), // archiveArch,
"foo" // archiveOsPath
);
}
@@ -72,8 +68,6 @@
null, // license,
"desc", // description,
"url", // descUrl,
- Os.getCurrentOs(), // archiveOs,
- Arch.getCurrentArch(), // archiveArch,
source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
);
}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
index c9a69fa..ad15ef0 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
@@ -18,10 +18,10 @@
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.archives.Archive;
-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.sources.SdkSource;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import java.io.File;
import java.util.Properties;
@@ -46,8 +46,6 @@
null /*license*/,
null /*description*/,
null /*descUrl*/,
- Os.ANY /*archiveOs*/,
- Arch.ANY /*archiveArch*/,
"/sdk/tmp/empty_pkg" /*archiveOsPath*/
);
mTestHandle = testHandle;
@@ -67,8 +65,6 @@
null /*license*/,
null /*description*/,
null /*descUrl*/,
- Os.ANY /*archiveOs*/,
- Arch.ANY /*archiveArch*/,
"/sdk/tmp/empty_pkg" /*archiveOsPath*/
);
mTestHandle = testHandle;
@@ -88,8 +84,6 @@
null /*license*/,
null /*description*/,
null /*descUrl*/,
- Os.ANY /*archiveOs*/,
- Arch.ANY /*archiveArch*/,
"/sdk/tmp/empty_pkg" /*archiveOsPath*/
);
mTestHandle = testHandle;
@@ -110,8 +104,6 @@
null /*license*/,
null /*description*/,
null /*descUrl*/,
- Os.ANY /*archiveOs*/,
- Arch.ANY /*archiveArch*/,
"/sdk/tmp/empty_pkg" /*archiveOsPath*/
);
mTestHandle = testHandle;
@@ -120,11 +112,9 @@
@Override
protected Archive[] initializeArchives(
Properties props,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
return new Archive[] {
- new Archive(this, props, archiveOs, archiveArch, archiveOsPath) {
+ new Archive(this, props, archiveOsPath) {
@Override
public String toString() {
return mTestHandle;
@@ -132,6 +122,13 @@
} };
}
+ @Override
+ public IPkgDesc getPkgDesc() {
+ return PkgDesc.Builder.newTool(
+ new FullRevision(1, 2, 3, 4),
+ FullRevision.NOT_SPECIFIED).create();
+ }
+
public Archive getLocalArchive() {
return getArchives()[0];
}
@@ -148,7 +145,8 @@
@Override
public String getListDescription() {
- return this.getClass().getSimpleName();
+ String ld = super.getListDisplay();
+ return ld.isEmpty() ? this.getClass().getSimpleName() : ld;
}
@Override
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 4a9f0a4..3b9cd89 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
@@ -16,8 +16,6 @@
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.PkgProps;
@@ -49,8 +47,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
super(
source,
@@ -61,8 +57,6 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
}
@@ -81,8 +75,6 @@
null, // license,
"desc", // description,
"url", // descUrl,
- Os.getCurrentOs(), // archiveOs,
- Arch.getCurrentArch(), // archiveArch,
source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
);
}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
index 734a596..79af428 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
@@ -16,8 +16,6 @@
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;
@@ -52,8 +50,6 @@
null, // license,
"desc", // description,
"url", // descUrl,
- Os.getCurrentOs(), // archiveOs,
- Arch.getCurrentArch(), // archiveArch,
source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
);
}
@@ -72,8 +68,6 @@
null, // license,
"desc", // description,
"url", // descUrl,
- Os.getCurrentOs(), // archiveOs,
- Arch.getCurrentArch(), // archiveArch,
source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
);
}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSourcePackage.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSourcePackage.java
index 4c129a3..d5785ce 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSourcePackage.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSourcePackage.java
@@ -23,7 +23,7 @@
/**
- * A mock {@link SystemImagePackage} for testing.
+ * A mock {@link MockSourcePackage} for testing.
*
* By design, this package contains one and only one archive.
*/
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockToolPackage.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockToolPackage.java
index 3503621..19fcf54 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockToolPackage.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockToolPackage.java
@@ -16,9 +16,6 @@
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.packages.ToolPackage;
import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.PkgProps;
@@ -56,8 +53,6 @@
null, // license,
"desc", // description,
"url", // descUrl,
- Os.getCurrentOs(), // archiveOs,
- Arch.getCurrentArch(), // archiveArch,
source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
);
}
@@ -79,8 +74,6 @@
null, // license,
"desc", // description,
"url", // descUrl,
- Os.getCurrentOs(), // archiveOs,
- Arch.getCurrentArch(), // archiveArch,
source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
);
}
@@ -102,8 +95,6 @@
null, // license,
"desc", // description,
"url", // descUrl,
- Os.getCurrentOs(), // archiveOs,
- Arch.getCurrentArch(), // archiveArch,
source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
);
}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PackageTest.java
index 590f4d3..eb9819c 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PackageTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PackageTest.java
@@ -17,14 +17,14 @@
package com.android.sdklib.internal.repository.packages;
import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.archives.ArchFilter;
import com.android.sdklib.internal.repository.archives.Archive;
-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.BrokenPackage;
-import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.sources.SdkRepoSource;
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.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
import java.io.File;
import java.util.ArrayList;
@@ -47,8 +47,6 @@
String license,
String description,
String descUrl,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
super(source,
props,
@@ -56,8 +54,6 @@
license,
description,
descUrl,
- archiveOs,
- archiveArch,
archiveOsPath);
}
@@ -85,10 +81,17 @@
public String installId() {
return "mock-pkg"; //$NON-NLS-1$
}
+
+ @Override
+ public IPkgDesc getPkgDesc() {
+ return PkgDesc.Builder.newTool(
+ new FullRevision(1, 2, 3, 4),
+ FullRevision.NOT_SPECIFIED).create();
+ }
}
public void testCreate() throws Exception {
- Properties props = createProps();
+ Properties props = createExpectedProps();
Package p = new MockPackage(
null, //source
@@ -97,8 +100,6 @@
null, //license
null, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
LOCAL_ARCHIVE_PATH //archiveOsPath
);
@@ -106,24 +107,22 @@
}
public void testSaveProperties() throws Exception {
- Properties props = createProps();
+ Properties expected = createExpectedProps();
Package p = new MockPackage(
null, //source
- props,
+ expected,
-1, //revision
null, //license
null, //description
null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
LOCAL_ARCHIVE_PATH //archiveOsPath
);
- Properties props2 = new Properties();
- p.saveProperties(props2);
+ Properties actual = new Properties();
+ p.saveProperties(actual);
- assertEquals(props2, props);
+ assertEquals(expected, actual);
}
/**
@@ -132,23 +131,32 @@
* This is protected and used by derived classes to perform
* a similar creation test.
*/
- protected Properties createProps() {
+ protected Properties createExpectedProps() {
+ return createDefaultProps();
+ }
+
+ /**
+ * Similar to {@link #createExpectedProps()} but static so that
+ * it can be reused by test not deriving from {@link PackageTest}.
+ */
+ public static Properties createDefaultProps() {
Properties props = new Properties();
// Package properties
- props.setProperty(PkgProps.PKG_REVISION, "42");
- props.setProperty(PkgProps.PKG_LICENSE, "The License");
- props.setProperty(PkgProps.PKG_DESC, "Some description.");
- props.setProperty(PkgProps.PKG_DESC_URL, "http://description/url");
+ props.setProperty(PkgProps.PKG_REVISION, "42");
+ props.setProperty(PkgProps.PKG_LICENSE, "The License");
+ props.setProperty(PkgProps.PKG_DESC, "Some description.");
+ props.setProperty(PkgProps.PKG_DESC_URL, "http://description/url");
+ props.setProperty(PkgProps.PKG_LIST_DISPLAY, "Some description.");
props.setProperty(PkgProps.PKG_RELEASE_NOTE, "Release Note");
- props.setProperty(PkgProps.PKG_RELEASE_URL, "http://release/note");
- props.setProperty(PkgProps.PKG_SOURCE_URL, "http://source/url");
- props.setProperty(PkgProps.PKG_OBSOLETE, "true");
+ props.setProperty(PkgProps.PKG_RELEASE_URL, "http://release/note");
+ props.setProperty(PkgProps.PKG_SOURCE_URL, "http://source/url");
+ props.setProperty(PkgProps.PKG_OBSOLETE, "true");
return props;
}
/**
- * Tests the values set via {@link #createProps()} after the
+ * Tests the values set via {@link #createExpectedProps()} after the
* package has been created in {@link #testCreate()}.
* This is protected and used by derived classes to perform
* a similar creation test.
@@ -168,8 +176,7 @@
assertEquals(1, p.getArchives().length);
Archive a = p.getArchives()[0];
assertNotNull(a);
- assertEquals(Os.ANY, a.getOs());
- assertEquals(Arch.ANY, a.getArch());
+ assertEquals(new ArchFilter(null), a.getArchFilter());
assertEquals(LOCAL_ARCHIVE_PATH, a.getLocalOsPath());
}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
index acb57ee..fcdc5b2 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
@@ -19,21 +19,12 @@
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.internal.repository.MockPlatformTarget;
import com.android.sdklib.internal.repository.archives.Archive;
-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.PlatformPackage;
import com.android.sdklib.repository.PkgProps;
import java.util.Properties;
public class PlatformPackageTest extends MinToolsPackageTest {
- /**
- * PlatformPackage implicitly generates a local archive wrapper
- * that matches the current platform OS and architecture. Since this
- * is not convenient for testing, this class overrides it to always
- * create archives for any OS and any architecture.
- */
private static class PlatformPackageWithFakeArchive extends PlatformPackage {
protected PlatformPackageWithFakeArchive(IAndroidTarget target, Properties props) {
super(target, props);
@@ -42,12 +33,8 @@
@Override
protected Archive[] initializeArchives(
Properties props,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
- assert archiveOs == Os.getCurrentOs();
- assert archiveArch == Arch.getCurrentArch();
- return super.initializeArchives(props, Os.ANY, Arch.ANY, LOCAL_ARCHIVE_PATH);
+ return super.initializeArchives(props, LOCAL_ARCHIVE_PATH);
}
}
@@ -60,8 +47,8 @@
}
@Override
- protected Properties createProps() {
- Properties props = super.createProps();
+ protected Properties createExpectedProps() {
+ Properties props = super.createExpectedProps();
// PlatformPackage properties
props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
@@ -83,7 +70,7 @@
@Override
public final void testCreate() {
- Properties props = createProps();
+ Properties props = createExpectedProps();
PlatformPackage p = createPlatformPackage(props);
testCreatedPlatformPackage(p);
@@ -91,18 +78,17 @@
@Override
public void testSaveProperties() {
- Properties props = createProps();
- PlatformPackage p = createPlatformPackage(props);
+ Properties expected = createExpectedProps();
+ PlatformPackage p = createPlatformPackage(expected);
- Properties props2 = new Properties();
- p.saveProperties(props2);
+ Properties actual = new Properties();
+ p.saveProperties(actual);
- assertEquals(props2.toString(), props.toString());
- assertEquals(props2, props);
+ assertEquals(expected, actual);
}
public void testInstallId() {
- Properties props = createProps();
+ Properties props = createExpectedProps();
PlatformPackage p = createPlatformPackage(props);
assertEquals("android-5", p.installId());
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformSystemImagePackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformSystemImagePackageTest.java
new file mode 100755
index 0000000..b6852e6
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformSystemImagePackageTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.sdklib.internal.repository.packages;
+
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.AndroidVersion.AndroidVersionException;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.repository.PkgProps;
+
+import java.util.Properties;
+
+public class PlatformSystemImagePackageTest extends PackageTest {
+
+ private static class SysImgPackageFakeArchive extends SystemImagePackage {
+ protected SysImgPackageFakeArchive(
+ AndroidVersion platformVersion,
+ int revision,
+ String abi,
+ Properties props) {
+ super(platformVersion,
+ revision,
+ abi,
+ props,
+ String.format("/sdk/system-images/android-%s/%s",
+ platformVersion.getApiString(), abi));
+ }
+
+ @Override
+ protected Archive[] initializeArchives(
+ Properties props,
+ String archiveOsPath) {
+ return super.initializeArchives(props, LOCAL_ARCHIVE_PATH);
+ }
+ }
+
+ private SystemImagePackage createSystemImagePackage(Properties props)
+ throws AndroidVersionException {
+ SystemImagePackage p = new SysImgPackageFakeArchive(
+ new AndroidVersion(props),
+ 1 /*revision*/,
+ null /*abi*/,
+ props);
+ return p;
+ }
+
+ @Override
+ protected Properties createExpectedProps() {
+ Properties props = super.createExpectedProps();
+
+ // SystemImagePackage properties
+ props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
+ props.setProperty(PkgProps.SYS_IMG_ABI, "armeabi-v7a");
+ props.setProperty(PkgProps.SYS_IMG_TAG_ID, "default");
+ props.setProperty(PkgProps.SYS_IMG_TAG_DISPLAY, "Default");
+
+ return props;
+ }
+
+ protected void testCreatedSystemImagePackage(SystemImagePackage p) {
+ super.testCreatedPackage(p);
+
+ // SystemImagePackage properties
+ assertEquals("API 5", p.getAndroidVersion().toString());
+ assertEquals("armeabi-v7a", p.getAbi());
+ }
+
+ // ----
+
+ @Override
+ public final void testCreate() throws Exception {
+ Properties props = createExpectedProps();
+ SystemImagePackage p = createSystemImagePackage(props);
+
+ testCreatedSystemImagePackage(p);
+ }
+
+ @Override
+ public void testSaveProperties() throws Exception {
+ Properties expected = createExpectedProps();
+ SystemImagePackage p = createSystemImagePackage(expected);
+
+ Properties actual = new Properties();
+ p.saveProperties(actual);
+
+ assertEquals(expected, actual);
+ }
+
+ public void testSameItemAs() throws Exception {
+ Properties props1 = createExpectedProps();
+ SystemImagePackage p1 = createSystemImagePackage(props1);
+ assertTrue(p1.sameItemAs(p1));
+
+ // different abi, same version
+ Properties props2 = new Properties(props1);
+ props2.setProperty(PkgProps.SYS_IMG_ABI, "x86");
+ SystemImagePackage p2 = createSystemImagePackage(props2);
+ assertFalse(p1.sameItemAs(p2));
+ assertFalse(p2.sameItemAs(p1));
+
+ // different abi, different version
+ props2.setProperty(PkgProps.VERSION_API_LEVEL, "6");
+ p2 = createSystemImagePackage(props2);
+ assertFalse(p1.sameItemAs(p2));
+ assertFalse(p2.sameItemAs(p1));
+
+ // same abi, different version
+ Properties props3 = new Properties(props1);
+ props3.setProperty(PkgProps.VERSION_API_LEVEL, "6");
+ SystemImagePackage p3 = createSystemImagePackage(props3);
+ assertFalse(p1.sameItemAs(p3));
+ assertFalse(p3.sameItemAs(p1));
+ }
+
+ public void testInstallId() throws Exception {
+ Properties props = createExpectedProps();
+ SystemImagePackage p = createSystemImagePackage(props);
+
+ assertEquals("sys-img-armeabi-v7a-android-5", p.installId());
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformToolPackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformToolPackageTest.java
new file mode 100755
index 0000000..d82b5f9
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformToolPackageTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.PkgProps;
+
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public class PlatformToolPackageTest extends TestCase {
+
+ private static class PlatformToolPackageFakeArchive extends PlatformToolPackage {
+ protected PlatformToolPackageFakeArchive(Properties props) {
+ super(null, // source
+ props,
+ 0, //ignored, the one from props will be used
+ null, //license
+ null, //description
+ null, //descUrl
+ "/sdk/tools");
+ }
+
+ @Override
+ protected Archive[] initializeArchives(
+ Properties props,
+ String archiveOsPath) {
+ return super.initializeArchives(props, PackageTest.LOCAL_ARCHIVE_PATH);
+ }
+ }
+
+ private Properties createExpectedProps(boolean isPreview) {
+ Properties props = PackageTest.createDefaultProps();
+
+ props.setProperty(PkgProps.PKG_REVISION,
+ new FullRevision(1, 2, 3, isPreview ? 4 : 0).toShortString());
+ props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
+
+ return props;
+ }
+
+ // ----
+
+ public void testInstallId() throws Exception {
+ Properties props1 = createExpectedProps(true);
+ PlatformToolPackage p1 = new PlatformToolPackageFakeArchive(props1);
+ assertEquals("platform-tools-preview", p1.installId());
+
+ Properties props2 = createExpectedProps(false);
+ PlatformToolPackage p2 = new PlatformToolPackageFakeArchive(props2);
+ assertEquals("platform-tools", p2.installId());
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SourcePackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
index d87eab3..08d1ef9 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
@@ -19,21 +19,12 @@
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.AndroidVersion.AndroidVersionException;
import com.android.sdklib.internal.repository.archives.Archive;
-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.SourcePackage;
import com.android.sdklib.repository.PkgProps;
import java.util.Properties;
public class SourcePackageTest extends PackageTest {
- /**
- * SourcePackageTest implicitly generates a local archive wrapper
- * that matches the current platform OS and architecture. Since this
- * is not convenient for testing, this class overrides it to always
- * create archives for any OS and any architecture.
- */
private static class SourcePackageFakeArchive extends SourcePackage {
protected SourcePackageFakeArchive(
AndroidVersion platformVersion,
@@ -48,12 +39,8 @@
@Override
protected Archive[] initializeArchives(
Properties props,
- Os archiveOs,
- Arch archiveArch,
String archiveOsPath) {
- assert archiveOs == Os.getCurrentOs();
- assert archiveArch == Arch.getCurrentArch();
- return super.initializeArchives(props, Os.ANY, Arch.ANY, LOCAL_ARCHIVE_PATH);
+ return super.initializeArchives(props, LOCAL_ARCHIVE_PATH);
}
}
@@ -66,8 +53,8 @@
}
@Override
- protected Properties createProps() {
- Properties props = super.createProps();
+ protected Properties createExpectedProps() {
+ Properties props = super.createExpectedProps();
// SourcePackageTest properties
props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
@@ -86,7 +73,7 @@
@Override
public final void testCreate() throws Exception {
- Properties props = createProps();
+ Properties props = createExpectedProps();
SourcePackage p = createSourcePackageTest(props);
testCreatedSourcePackageTest(p);
@@ -94,17 +81,17 @@
@Override
public void testSaveProperties() throws Exception {
- Properties props = createProps();
- SourcePackage p = createSourcePackageTest(props);
+ Properties expected = createExpectedProps();
+ SourcePackage p = createSourcePackageTest(expected);
- Properties props2 = new Properties();
- p.saveProperties(props2);
+ Properties actual = new Properties();
+ p.saveProperties(actual);
- assertEquals(props2, props);
+ assertEquals(expected, actual);
}
public void testSameItemAs() throws Exception {
- Properties props1 = createProps();
+ Properties props1 = createExpectedProps();
SourcePackage p1 = createSourcePackageTest(props1);
assertTrue(p1.sameItemAs(p1));
@@ -117,7 +104,7 @@
}
public void testInstallId() throws Exception {
- Properties props = createProps();
+ Properties props = createExpectedProps();
SourcePackage p = createSourcePackageTest(props);
assertEquals("source-5", p.installId());
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java
deleted file mode 100755
index c32b81f..0000000
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java
+++ /dev/null
@@ -1,145 +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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.internal.repository.archives.Archive;
-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.SystemImagePackage;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-public class SystemImagePackageTest extends PackageTest {
-
- /**
- * SystemImagePackage implicitly generates a local archive wrapper
- * that matches the current platform OS and architecture. Since this
- * is not convenient for testing, this class overrides it to always
- * create archives for any OS and any architecture.
- */
- private static class SysImgPackageFakeArchive extends SystemImagePackage {
- protected SysImgPackageFakeArchive(
- AndroidVersion platformVersion,
- int revision,
- String abi,
- Properties props) {
- super(platformVersion,
- revision,
- abi,
- props,
- String.format("/sdk/system-images/android-%s/%s",
- platformVersion.getApiString(), abi));
- }
-
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- assert archiveOs == Os.getCurrentOs();
- assert archiveArch == Arch.getCurrentArch();
- return super.initializeArchives(props, Os.ANY, Arch.ANY, LOCAL_ARCHIVE_PATH);
- }
- }
-
- private SystemImagePackage createSystemImagePackage(Properties props)
- throws AndroidVersionException {
- SystemImagePackage p = new SysImgPackageFakeArchive(
- new AndroidVersion(props),
- 1 /*revision*/,
- null /*abi*/,
- props);
- return p;
- }
-
- @Override
- protected Properties createProps() {
- Properties props = super.createProps();
-
- // SystemImagePackage properties
- props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
- props.setProperty(PkgProps.SYS_IMG_ABI, "armeabi-v7a");
-
- return props;
- }
-
- protected void testCreatedSystemImagePackage(SystemImagePackage p) {
- super.testCreatedPackage(p);
-
- // SystemImagePackage properties
- assertEquals("API 5", p.getAndroidVersion().toString());
- assertEquals("armeabi-v7a", p.getAbi());
- }
-
- // ----
-
- @Override
- public final void testCreate() throws Exception {
- Properties props = createProps();
- SystemImagePackage p = createSystemImagePackage(props);
-
- testCreatedSystemImagePackage(p);
- }
-
- @Override
- public void testSaveProperties() throws Exception {
- Properties props = createProps();
- SystemImagePackage p = createSystemImagePackage(props);
-
- Properties props2 = new Properties();
- p.saveProperties(props2);
-
- assertEquals(props2, props);
- }
-
- public void testSameItemAs() throws Exception {
- Properties props1 = createProps();
- SystemImagePackage p1 = createSystemImagePackage(props1);
- assertTrue(p1.sameItemAs(p1));
-
- // different abi, same version
- Properties props2 = new Properties(props1);
- props2.setProperty(PkgProps.SYS_IMG_ABI, "x86");
- SystemImagePackage p2 = createSystemImagePackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // different abi, different version
- props2.setProperty(PkgProps.VERSION_API_LEVEL, "6");
- p2 = createSystemImagePackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // same abi, different version
- Properties props3 = new Properties(props1);
- props3.setProperty(PkgProps.VERSION_API_LEVEL, "6");
- SystemImagePackage p3 = createSystemImagePackage(props3);
- assertFalse(p1.sameItemAs(p3));
- assertFalse(p3.sameItemAs(p1));
- }
-
- public void testInstallId() throws Exception {
- Properties props = createProps();
- SystemImagePackage p = createSystemImagePackage(props);
-
- assertEquals("sysimg-5", p.installId());
- }
-}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ToolPackageTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ToolPackageTest.java
new file mode 100755
index 0000000..8e9f49a
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ToolPackageTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.PkgProps;
+
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public class ToolPackageTest extends TestCase {
+
+ private static class ToolPackageFakeArchive extends ToolPackage {
+ protected ToolPackageFakeArchive(Properties props) {
+ super(null, // source
+ props,
+ 0, //ignored, the one from props will be used
+ null, //license
+ null, //description
+ null, //descUrl
+ "/sdk/tools");
+ }
+
+ @Override
+ protected Archive[] initializeArchives(
+ Properties props,
+ String archiveOsPath) {
+ return super.initializeArchives(props, PackageTest.LOCAL_ARCHIVE_PATH);
+ }
+ }
+
+ private Properties createExpectedProps(boolean isPreview) {
+ Properties props = PackageTest.createDefaultProps();
+
+ props.setProperty(PkgProps.PKG_REVISION,
+ new FullRevision(1, 2, 3, isPreview ? 4 : 0).toShortString());
+ props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
+
+ return props;
+ }
+
+ // ----
+
+ public void testInstallId() throws Exception {
+ Properties props1 = createExpectedProps(true);
+ ToolPackage p1 = new ToolPackageFakeArchive(props1);
+ assertEquals("tools-preview", p1.installId());
+
+ Properties props2 = createExpectedProps(false);
+ ToolPackage p2 = new ToolPackageFakeArchive(props2);
+ assertEquals("tools", p2.installId());
+ }
+}
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 f281a98..f03aed9 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
@@ -226,6 +226,20 @@
"SDK/extras/g/usb_driver, " +
"SDK/extras/vendor0000005f/extra0000005f]").replace('/', File.separatorChar),
Arrays.toString(extraInstall.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[This add-on has no libraries, " +
+ "My Second add-on, " +
+ "My First add-on, " +
+ "Android Vendor Extra API Dep (Obsolete), " +
+ "G USB Driver (Obsolete), " +
+ "Unknown Extra (Obsolete)]",
+ Arrays.toString(listDescs.toArray()));
}
/**
@@ -320,6 +334,20 @@
"SDK/extras/g/usb_driver, " +
"SDK/extras/vendor0000005f/extra0000005f]").replace('/', File.separatorChar),
Arrays.toString(extraInstall.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[This add-on has no libraries, " +
+ "My Second add-on, " +
+ "My First add-on, " +
+ "Android Vendor Extra API Dep (Obsolete), " +
+ "G USB Driver (Obsolete), " +
+ "Unknown Extra (Obsolete)]",
+ Arrays.toString(listDescs.toArray()));
}
/**
@@ -429,6 +457,20 @@
"[], " +
"[]]",
Arrays.toString(extraFilePaths.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[This add-on has no libraries, " +
+ "My Second add-on, " +
+ "My First add-on, " +
+ "Android Vendor Extra API Dep (Obsolete), " +
+ "G USB Driver (Obsolete), " +
+ "Unknown Extra (Obsolete)]",
+ Arrays.toString(listDescs.toArray()));
}
/**
@@ -562,6 +604,20 @@
"[v8/veggies_8.jar, root.jar, dir1/dir 2 with space/mylib.jar], " +
"[]]",
Arrays.toString(extraFilePaths.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[This add-on has no libraries, " +
+ "My Second add-on, " +
+ "My First add-on, " +
+ ". -..- - .-. .- (Obsolete), " +
+ "Yet another extra, by Android, " +
+ "Random name, not an id! (Obsolete)]",
+ Arrays.toString(listDescs.toArray()));
}
/**
@@ -710,6 +766,20 @@
"Yet another extra, by Android: 3, " +
"Random name, not an id! (Obsolete): 3.2.1 rc42]",
Arrays.toString(minToolsRevs.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[This add-on has no libraries, " +
+ "My Second add-on, " +
+ "My First add-on, " +
+ ". -..- - .-. .- (Obsolete), " +
+ "Yet another extra, by Android, " +
+ "Random name, not an id! (Obsolete)]",
+ Arrays.toString(listDescs.toArray()));
}
/**
@@ -858,9 +928,196 @@
"Yet another extra, by Android: 3, " +
"Random name, not an id! (Obsolete): 3.2.1 rc42]",
Arrays.toString(minToolsRevs.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[This add-on has no libraries, " +
+ "My Second add-on, " +
+ "My First add-on, " +
+ ". -..- - .-. .- (Obsolete), " +
+ "Yet another extra, by Android, " +
+ "Random name, not an id! (Obsolete)]",
+ Arrays.toString(listDescs.toArray()));
}
/**
+ * Validate we can load a valid add-on schema version 6
+ */
+ public void testLoadAddonXml_7() throws Exception {
+ InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_7.xml");
+
+ // guess the version from the XML document
+ int version = mSource._getXmlSchemaVersion(xmlStream);
+ assertEquals(7, 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(7), 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 for API 5, rev 0, revision 1\n" +
+ "Found My Second Add-on for API 2.r42, 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 Another extra with min-API 42, revision 2.0.1\n" +
+ "Found Extra '____' for API _$1_, by _%2_, 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(
+ "[Extra '____' for API _$1_, by _%2_ (Obsolete): 3.0.1, " +
+ "Another extra with min-API 42: 3, " +
+ "Random name, not an id! (Obsolete): 3.2.1 rc42]",
+ Arrays.toString(minToolsRevs.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[This add-on has no libraries, " +
+ "My Second Add-on for API 2.r42, " +
+ "My First Add-on for API 5, rev 0, " +
+ "Extra '____' for API _$1_, by _%2_ (Obsolete), " +
+ "Another extra with min-API 42, " +
+ "Random name, not an id! (Obsolete)]",
+ Arrays.toString(listDescs.toArray()));
+ }
+
+ /**
+ * Validate there isn't a next-version we haven't tested yet
+ */
+ public void testLoadAddonXml_8() throws Exception {
+ InputStream xmlStream = xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_8.xml");
+ assertNull("There is a sample for addon-8.xsd but there is not corresponding unit test", xmlStream);
+ }
+
+
+ // ---- helpers ----
+
+ /**
* 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.
@@ -872,7 +1129,9 @@
*/
private ByteArrayInputStream getTestResource(String filename) throws IOException {
InputStream xmlStream = this.getClass().getResourceAsStream(filename);
-
+ if (xmlStream == null) {
+ return null;
+ }
try {
byte[] data = new byte[8192];
int offset = 0;
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
index 8efa1eb..596e2d5 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
@@ -114,6 +114,8 @@
protected void setUp() throws Exception {
super.setUp();
+ System.setProperty("THROW_DEEP_EXCEPTION_DURING_TESTING", "1");
+
mSource = new MockSdkRepoSource();
}
@@ -183,7 +185,7 @@
*/
public void testFindAlternateToolsXml_3() throws Exception {
InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_3.xml");
+ "/com/android/sdklib/testdata/repository_sample_03.xml");
Pair<Document, String> result = mSource._findAlternateToolsXml(xmlStream);
assertNotNull(result.getFirst());
@@ -211,9 +213,10 @@
/**
* Validate we can still load an old repository in schema version 1
*/
- public void testLoadXml_1() throws Exception {
- InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_1.xml");
+ public void testLoadRepoXml_01() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_01.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
// guess the version from the XML document
int version = mSource._getXmlSchemaVersion(xmlStream);
@@ -289,9 +292,10 @@
/**
* Validate we can still load an old repository in schema version 2
*/
- public void testLoadXml_2() throws Exception {
- InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_2.xml");
+ public void testLoadRepoXml_02() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_02.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
// guess the version from the XML document
int version = mSource._getXmlSchemaVersion(xmlStream);
@@ -369,9 +373,10 @@
/**
* Validate what we can load from repository in schema version 3
*/
- public void testLoadXml_3() throws Exception {
- InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_3.xml");
+ public void testLoadRepoXml_03() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_03.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
// guess the version from the XML document
int version = mSource._getXmlSchemaVersion(xmlStream);
@@ -448,9 +453,10 @@
/**
* Validate what we can load from repository in schema version 4
*/
- public void testLoadXml_4() throws Exception {
- InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_4.xml");
+ public void testLoadRepoXml_04() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_04.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
// guess the version from the XML document
int version = mSource._getXmlSchemaVersion(xmlStream);
@@ -555,9 +561,10 @@
/**
* Validate what we can load from repository in schema version 5
*/
- public void testLoadXml_5() throws Exception {
- InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_5.xml");
+ public void testLoadRepoXml_05() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_05.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
// guess the version from the XML document
int version = mSource._getXmlSchemaVersion(xmlStream);
@@ -708,9 +715,10 @@
/**
* Validate what we can load from repository in schema version 6
*/
- public void testLoadXml_6() throws Exception {
- InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_6.xml");
+ public void testLoadRepoXml_06() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_06.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
// guess the version from the XML document
int version = mSource._getXmlSchemaVersion(xmlStream);
@@ -851,9 +859,10 @@
/**
* Validate what we can load from repository in schema version 7
*/
- public void testLoadXml_7() throws Exception {
- InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_7.xml");
+ public void testLoadRepoXml_07() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_07.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
// guess the version from the XML document
int version = mSource._getXmlSchemaVersion(xmlStream);
@@ -1028,9 +1037,10 @@
/**
* Validate what we can load from repository in schema version 8
*/
- public void testLoadXml_8() throws Exception {
- InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_8.xml");
+ public void testLoadRepoXml_08() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_08.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
// guess the version from the XML document
int version = mSource._getXmlSchemaVersion(xmlStream);
@@ -1204,9 +1214,471 @@
"[Android SDK Tools: 4, " +
"Android SDK Tools: 4 rc5]",
Arrays.toString(minPlatToolsRevs.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[Android SDK Tools, " +
+ "Android SDK Tools, " +
+ "Android SDK Platform-tools, " +
+ "Android SDK Build-tools, " +
+ "Android SDK Build-tools, " +
+ "Android SDK Build-tools, " +
+ "Android SDK Build-tools, " +
+ "Documentation for Android SDK, " +
+ "Documentation for Android SDK, " +
+ "SDK Platform Android Pastry Preview, " +
+ "SDK Platform Android 1.1, " +
+ "SDK Platform Android 1.0, " +
+ "Samples for SDK API 14 (Obsolete), " +
+ "Samples for SDK API 14 (Obsolete), " +
+ "ARM EABI System Image, " +
+ "MIPS System Image, " +
+ "ARM EABI v7a System Image, " +
+ "Intel x86 Atom System Image, " +
+ "Sources for Android SDK, " +
+ "Sources for Android SDK, " +
+ "Sources for Android SDK]",
+ Arrays.toString(listDescs.toArray()));
}
/**
+ * Validate what we can load from repository in schema version 9
+ */
+ public void testLoadRepoXml_09() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_09.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
+
+ // guess the version from the XML document
+ int version = mSource._getXmlSchemaVersion(xmlStream);
+ assertEquals(9, version);
+
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
+
+ String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
+ assertEquals(Boolean.TRUE, validatorFound[0]);
+ assertEquals(null, validationError[0]);
+ assertEquals(SdkRepoConstants.getSchemaUri(9), 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));
+
+ // Verbose log order matches the XML order and not the sorted display order.
+ assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
+ "Found Documentation for Android SDK, API 1, revision 1\n" +
+ "Found Sources for Android SDK, API 1, revision 1\n" +
+ "Found SDK Platform Android 1.1, API 2, revision 12\n" +
+ "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
+ "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
+ "Found Custom Thing ARM EABI v7a System Image, Android API 2, revision 2\n" +
+ "Found Sources for Android SDK, API 2, revision 2\n" +
+ "Found SDK Platform Android Pastry Preview, revision 3\n" +
+ "Found Android SDK Tools, revision 1.2.3 rc4\n" +
+ "Found Android SDK Build-tools, revision 3 rc5\n" +
+ "Found Android SDK Build-tools, revision 3.0.1\n" +
+ "Found Documentation for Android SDK, API 2, revision 42\n" +
+ "Found Android SDK Tools, revision 42\n" +
+ "Found Android SDK Platform-tools, revision 3 rc5\n" +
+ "Found Android SDK Build-tools, revision 3\n" +
+ "Found Samples for SDK API 14, revision 24 (Obsolete)\n" +
+ "Found Samples for SDK API 14, revision 25 (Obsolete)\n" +
+ "Found Variant 1 ARM EABI System Image, Android API 42, revision 12\n" +
+ "Found Variant 1 MIPS System Image, Android API 42, revision 12\n" +
+ "Found Variant 2 MIPS System Image, Android API 42, revision 12\n" +
+ "Found Sources for Android SDK, API 42, revision 12\n" +
+ "Found Android SDK Build-tools, revision 12.13.14\n",
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
+
+ // check the packages we found... we expected to find 13 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.
+ // Order is defined by
+ // com.android.sdklib.internal.repository.packages.Package.comparisonKey()
+ Package[] pkgs = mSource.getPackages();
+
+ assertEquals(23, pkgs.length);
+ for (Package p : pkgs) {
+ assertTrue(p.getArchives().length >= 1);
+ }
+
+ // Check the layoutlib & included-abi of the platform packages.
+ ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
+ ArrayList<String> includedAbi = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof PlatformPackage) {
+ layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
+ String abi = ((PlatformPackage) p).getIncludedAbi();
+ includedAbi.add(abi == null ? "(null)" : abi);
+ }
+ }
+ assertEquals(
+ "[Pair [first=1, second=0], " + // platform API 5 preview
+ "Pair [first=5, second=31415], " + // platform API 2
+ "Pair [first=5, second=0]]", // platform API 1
+ Arrays.toString(layoutlibVers.toArray()));
+ assertEquals(
+ "[(null), " + // platform API 5 preview
+ "x86, " + // platform API 2
+ "armeabi]", // platform API 1
+ Arrays.toString(includedAbi.toArray()));
+
+ // Check the extra packages path, vendor, install folder, project-files, 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);
+ }
+ }
+
+ // There are no extra packages anymore in repository-6
+ assertEquals("[]", Arrays.toString(extraPaths.toArray()));
+ assertEquals("[]", Arrays.toString(extraVendors.toArray()));
+ assertEquals("[]", Arrays.toString(extraInstall.toArray()));
+ assertEquals("[]", Arrays.toString(extraFilePaths.toArray()));
+
+
+ // Check the system-image packages
+ ArrayList<String> sysImgInfo = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof SystemImagePackage) {
+ SystemImagePackage sip = (SystemImagePackage) p;
+ sysImgInfo.add(String.format("%1$s %2$s: %3$s", //$NON-NLS-1$
+ sip.getAndroidVersion().getApiString(),
+ sip.getAbi(),
+ sip.getTag()));
+ }
+ }
+ assertEquals(
+ "[42 armeabi: variant-1 [Variant 1], " +
+ "42 mips: variant-1 [Variant 1], " +
+ "42 mips: variant-2 [Variant 2], " +
+ "2 armeabi-v7a: coolThing [Custom Thing], " +
+ "2 armeabi-v7a: default [Default], " +
+ "2 x86: default [Default]]",
+ Arrays.toString(sysImgInfo.toArray()));
+
+
+ // Check the source packages
+ ArrayList<String> sourceVersion = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof SourcePackage) {
+ SourcePackage sp = (SourcePackage) p;
+ String v = sp.getAndroidVersion().getApiString();
+ sourceVersion.add(v);
+ }
+ }
+ assertEquals(
+ "[42, 2, 1]",
+ Arrays.toString(sourceVersion.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(
+ "[SDK Platform Android Pastry Preview: 0, " +
+ "SDK Platform Android 1.1: 0, " +
+ "SDK Platform Android 1.0: 2.0.1, " +
+ "Samples for SDK API 14 (Obsolete): 5, " +
+ "Samples for SDK API 14 (Obsolete): 5.1.2 rc3]",
+ Arrays.toString(minToolsRevs.toArray()));
+
+
+ // Check the min-platform-tools-rev
+ ArrayList<String> minPlatToolsRevs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof IMinPlatformToolsDependency) {
+ minPlatToolsRevs.add(p.getListDescription() + ": " +
+ ((IMinPlatformToolsDependency) p).getMinPlatformToolsRevision().toShortString());
+ }
+ }
+ assertEquals(
+ "[Android SDK Tools: 4, " +
+ "Android SDK Tools: 4 rc5]",
+ Arrays.toString(minPlatToolsRevs.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[Android SDK Tools, " +
+ "Android SDK Tools, " +
+ "Android SDK Platform-tools, " +
+ "Android SDK Build-tools, " +
+ "Android SDK Build-tools, " +
+ "Android SDK Build-tools, " +
+ "Android SDK Build-tools, " +
+ "Documentation for Android SDK, " +
+ "Documentation for Android SDK, " +
+ "SDK Platform Android Pastry Preview, " +
+ "SDK Platform Android 1.1, " +
+ "SDK Platform Android 1.0, " +
+ "Samples for SDK API 14 (Obsolete), " +
+ "Samples for SDK API 14 (Obsolete), " +
+ "Variant 1 ARM EABI System Image, " +
+ "Variant 1 MIPS System Image, " +
+ "Variant 2 MIPS System Image, " +
+ "Custom Thing ARM EABI v7a System Image, " +
+ "ARM EABI v7a System Image, " +
+ "Intel x86 Atom System Image, " +
+ "Sources for Android SDK, " +
+ "Sources for Android SDK, " +
+ "Sources for Android SDK]",
+ Arrays.toString(listDescs.toArray()));
+ }
+
+ /**
+ * Validate what we can load from repository in schema version 10
+ */
+ public void testLoadRepoXml_10() throws Exception {
+ String filename = "/com/android/sdklib/testdata/repository_sample_10.xml";
+ InputStream xmlStream = getTestResource(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
+
+ // guess the version from the XML document
+ int version = mSource._getXmlSchemaVersion(xmlStream);
+ assertEquals(10, version);
+
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
+
+ String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
+ assertEquals(Boolean.TRUE, validatorFound[0]);
+ assertEquals(null, validationError[0]);
+ assertEquals(SdkRepoConstants.getSchemaUri(10), 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));
+
+ // Verbose log order matches the XML order and not the sorted display order.
+ assertEquals("Found The first Android platform ever, revision 3\n" + // list-display
+ "Found Doc for first platform, revision 1\n" + // list-display
+ "Found Sources for first platform, revision 1\n" + // list-display
+ "Found SDK Platform Android 1.1, API 2, revision 12\n" +
+ "Found Sources for Android SDK, API 2, revision 2\n" +
+ "Found SDK Platform Android Pastry Preview, revision 3\n" +
+ "Found Tools in version 1.2.3.4, revision 1.2.3 rc4\n" + // list-display
+ "Found Build tools v3 (preview 5), revision 3 rc5\n" + // list-display
+ "Found Android SDK Build-tools, revision 3.0.1\n" +
+ "Found Documentation for Android SDK, API 2, revision 42\n" +
+ "Found Android SDK Tools, revision 42\n" +
+ "Found Android SDK Platform-tools, revision 3 rc5\n" +
+ "Found Android SDK Build-tools, revision 3\n" +
+ "Found Samples from Android 14, revision 24 (Obsolete)\n" + // list-display
+ "Found Samples for SDK API 14, revision 25 (Obsolete)\n" +
+ "Found Sources for Android SDK, API 42, revision 12\n" +
+ "Found Android SDK Build-tools, revision 12.13.14\n",
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
+
+ // check the packages we found... we expected to find 13 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.
+ // Order is defined by
+ // com.android.sdklib.internal.repository.packages.Package.comparisonKey()
+ Package[] pkgs = mSource.getPackages();
+
+ assertEquals(17, pkgs.length);
+ for (Package p : pkgs) {
+ assertTrue(p.getArchives().length >= 1);
+ }
+
+ // Check the layoutlib & included-abi of the platform packages.
+ ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
+ ArrayList<String> includedAbi = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof PlatformPackage) {
+ layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
+ String abi = ((PlatformPackage) p).getIncludedAbi();
+ includedAbi.add(abi == null ? "(null)" : abi);
+ }
+ }
+ assertEquals(
+ "[Pair [first=1, second=0], " + // platform API 5 preview
+ "Pair [first=5, second=31415], " + // platform API 2
+ "Pair [first=5, second=0]]", // platform API 1
+ Arrays.toString(layoutlibVers.toArray()));
+ assertEquals(
+ "[(null), " + // platform API 5 preview
+ "x86, " + // platform API 2
+ "armeabi]", // platform API 1
+ Arrays.toString(includedAbi.toArray()));
+
+ // Check the extra packages path, vendor, install folder, project-files, 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);
+ }
+ }
+
+ // There are no extra packages anymore in repository-6
+ assertEquals("[]", Arrays.toString(extraPaths.toArray()));
+ assertEquals("[]", Arrays.toString(extraVendors.toArray()));
+ assertEquals("[]", Arrays.toString(extraInstall.toArray()));
+ assertEquals("[]", Arrays.toString(extraFilePaths.toArray()));
+
+
+ // Check the system-image packages -- there can't be any in schema 10 anymore
+ ArrayList<String> sysImgInfo = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof SystemImagePackage) {
+ SystemImagePackage sip = (SystemImagePackage) p;
+ sysImgInfo.add(String.format("%1$s %2$s: %3$s", //$NON-NLS-1$
+ sip.getAndroidVersion().getApiString(),
+ sip.getAbi(),
+ sip.getTag()));
+ }
+ }
+ assertEquals("[]", Arrays.toString(sysImgInfo.toArray()));
+
+
+ // Check the source packages
+ ArrayList<String> sourceVersion = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof SourcePackage) {
+ SourcePackage sp = (SourcePackage) p;
+ String v = sp.getAndroidVersion().getApiString();
+ sourceVersion.add(v);
+ }
+ }
+ assertEquals(
+ "[42, 2, 1]",
+ Arrays.toString(sourceVersion.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(
+ "[SDK Platform Android Pastry Preview: 0, " +
+ "SDK Platform Android 1.1: 0, " +
+ "The first Android platform ever: 2.0.1, " + // list-display
+ "Samples from Android 14 (Obsolete): 5, " + // list-display
+ "Samples for SDK API 14 (Obsolete): 5.1.2 rc3]",
+ Arrays.toString(minToolsRevs.toArray()));
+
+
+ // Check the min-platform-tools-rev
+ ArrayList<String> minPlatToolsRevs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof IMinPlatformToolsDependency) {
+ minPlatToolsRevs.add(p.getListDescription() + ": " +
+ ((IMinPlatformToolsDependency) p).getMinPlatformToolsRevision().toShortString());
+ }
+ }
+ assertEquals(
+ "[Tools in version 1.2.3.4: 4, " + // list-display
+ "Android SDK Tools: 4 rc5]",
+ Arrays.toString(minPlatToolsRevs.toArray()));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[Tools in version 1.2.3.4, " + // list-display
+ "Android SDK Tools, " +
+ "Android SDK Platform-tools, " +
+ "Android SDK Build-tools, " +
+ "Android SDK Build-tools, " +
+ "Android SDK Build-tools, " +
+ "Build tools v3 (preview 5), " + // list-display
+ "Documentation for Android SDK, " +
+ "Doc for first platform, " + // list-display
+ "SDK Platform Android Pastry Preview, " +
+ "SDK Platform Android 1.1, " +
+ "The first Android platform ever, " + // list-display
+ "Samples from Android 14 (Obsolete), " + // list-display
+ "Samples for SDK API 14 (Obsolete), " +
+ "Sources for Android SDK, " +
+ "Sources for Android SDK, " +
+ "Sources for first platform]", // list-display
+ Arrays.toString(listDescs.toArray()));
+ }
+
+ /**
+ * Validate there isn't a next-version we haven't tested yet
+ */
+ public void testLoadRepoXml_11() throws Exception {
+ InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/repository_sample_11.xml");
+ assertNull("There is a sample for repository-11.xsd but there is not corresponding unit test", xmlStream);
+ }
+
+ // ---- helper ---
+
+ /**
* 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.
@@ -1218,9 +1690,9 @@
*/
private ByteArrayInputStream getTestResource(String filename) throws IOException {
InputStream xmlStream = this.getClass().getResourceAsStream(filename);
-
- assertNotNull("Missing test file: " + filename, xmlStream);
-
+ if (xmlStream == null) {
+ return null;
+ }
try {
byte[] data = new byte[8192];
int offset = 0;
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSysImgSourceTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSysImgSourceTest.java
index c080d11..d78a262 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSysImgSourceTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSysImgSourceTest.java
@@ -20,11 +20,13 @@
import com.android.sdklib.internal.repository.MockMonitor;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.packages.SystemImagePackage;
+import com.android.sdklib.io.FileOp;
import com.android.sdklib.repository.SdkSysImgConstants;
import org.w3c.dom.Document;
import java.io.ByteArrayInputStream;
+import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -168,6 +170,317 @@
"2 x86]",
Arrays.toString(sysImgVersionAbi.toArray()));
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[ARM EABI System Image, " +
+ "MIPS System Image, " +
+ "ARM EABI v7a System Image, " +
+ "Intel x86 Atom System Image]",
+ Arrays.toString(listDescs.toArray()));
+ }
+
+ /**
+ * Validate we can load a valid schema version 2
+ */
+ public void testLoadSysImgXml_2() throws Exception {
+ InputStream xmlStream =
+ getTestResource("/com/android/sdklib/testdata/sys_img_sample_2.xml");
+
+ // guess the version from the XML document
+ int version = mSource._getXmlSchemaVersion(xmlStream);
+ assertEquals(2, version);
+
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ String url = "not-a-valid-url://" + SdkSysImgConstants.URL_DEFAULT_FILENAME;
+
+ String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
+ assertEquals(Boolean.TRUE, validatorFound[0]);
+ assertEquals(null, validationError[0]);
+ assertEquals(SdkSysImgConstants.getSchemaUri(2), 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));
+
+ // Verbose log order matches the XML order and not the sorted display order.
+ assertEquals(
+ "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
+ "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
+ "Found Another tag name ARM EABI v7a System Image, Android API 2, revision 2\n" +
+ "Found ARM EABI System Image, Android API 42, revision 12\n" +
+ "Found MIPS System Image, Android API 42, revision 12\n" +
+ "Found This is an arbitrary string, MIPS System Image, Android API 44, revision 14\n" +
+ "Found Tag name is Sanitized if Display is Missing MIPS System Image, Android API 45, revision 15\n",
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
+
+ // check the packages we found...
+ // 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.
+ // Order is defined by
+ // com.android.sdklib.internal.repository.packages.SystemImagePackage.comparisonKey()
+
+ Package[] pkgs = mSource.getPackages();
+
+ assertEquals(7, pkgs.length);
+ for (Package p : pkgs) {
+ // We expected to find packages with each at least one archive.
+ assertTrue(p.getArchives().length >= 1);
+ // And only system images are supported by this source
+ assertTrue(p instanceof SystemImagePackage);
+ }
+
+ // Check the system-image packages
+ ArrayList<String> sysImgInfo = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof SystemImagePackage) {
+ SystemImagePackage sip = (SystemImagePackage) p;
+ sysImgInfo.add(String.format("%1$s %2$s: %3$s", //$NON-NLS-1$
+ sip.getAndroidVersion().getApiString(),
+ sip.getAbi(),
+ sip.getTag()));
+ }
+ }
+ assertEquals(
+ "[45 mips: tag-name---is-Sanitized----if-Display-is-Missing [Tag name is Sanitized if Display is Missing], " +
+ "44 mips: mips-only [This is an arbitrary string,], " +
+ "42 armeabi: default [Default], " +
+ "42 mips: default [Default], " +
+ "2 armeabi-v7a: default [Ignored in description for default tag], " +
+ "2 x86: default [Default], " +
+ "2 armeabi-v7a: other [Another tag name]]",
+ Arrays.toString(sysImgInfo.toArray()));
+
+ // Check the default install-paths of the packages
+ ArrayList<File> sysImgPath = new ArrayList<File>();
+ for (Package p : pkgs) {
+ if (p instanceof SystemImagePackage) {
+ SystemImagePackage sip = (SystemImagePackage) p;
+ sysImgPath.add(sip.getInstallFolder("root", null /*sdkManager*/)); //$NON-NLS-1$
+ }
+ }
+ assertEquals(Arrays.toString(new File[] {
+ FileOp.append("root", "system-images", "android-45", "tag-name-is-sanitized-if-display-is-missing", "mips"),
+ FileOp.append("root", "system-images", "android-44", "mips-only", "mips"),
+ FileOp.append("root", "system-images", "android-42", "default", "armeabi"),
+ FileOp.append("root", "system-images", "android-42", "default", "mips"),
+ FileOp.append("root", "system-images", "android-2" , "default", "armeabi-v7a"),
+ FileOp.append("root", "system-images", "android-2" , "default", "x86"),
+ FileOp.append("root", "system-images", "android-2" , "other", "armeabi-v7a"),
+ }).replace(File.separatorChar, '/'),
+ Arrays.toString(sysImgPath.toArray()).replace(File.separatorChar, '/'));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[Tag name is Sanitized if Display is Missing MIPS System Image, " +
+ "This is an arbitrary string, " +
+ "MIPS System Image, " +
+ "ARM EABI System Image, " +
+ "MIPS System Image, " +
+ "ARM EABI v7a System Image, " +
+ "Intel x86 Atom System Image, " +
+ "Another tag name ARM EABI v7a System Image]",
+ Arrays.toString(listDescs.toArray()));
+ }
+
+ /**
+ * Validate we can load a valid schema version 3
+ */
+ public void testLoadSysImgXml_3() throws Exception {
+ InputStream xmlStream =
+ getTestResource("/com/android/sdklib/testdata/sys_img_sample_3.xml");
+
+ // guess the version from the XML document
+ int version = mSource._getXmlSchemaVersion(xmlStream);
+ assertEquals(3, version);
+
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ String url = "not-a-valid-url://" + SdkSysImgConstants.URL_DEFAULT_FILENAME;
+
+ String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
+ assertEquals(Boolean.TRUE, validatorFound[0]);
+ assertEquals(null, validationError[0]);
+ assertEquals(SdkSysImgConstants.getSchemaUri(3), 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));
+
+ // Verbose log order matches the XML order and not the sorted display order.
+ assertEquals(
+ "Found System Image for x86 CPU for API 2, Android API 2, revision 1\n" +
+ "Found System Image for x86-64 CPU for API 2, Android API 2, revision 1\n" +
+ "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
+ "Found ARM 64 v8a System Image, Android API 2, revision 2\n" +
+ "Found Another tag name ARM EABI v7a System Image, Android API 2, revision 2\n" +
+ "Found ARM EABI System Image, Android API 42, revision 12\n" +
+ "Found MIPS64 System Image, Android API 42, revision 12\n" +
+ "Found MIPS system image for tag MIPS-only, Android API 44, revision 14\n" +
+ "Found Tag name is Sanitized if Display is Missing MIPS System Image, Android API 45, revision 15\n" +
+ "Found x86 System Image for some add-on, Acme Vendor Inc. API 2, revision 1\n" +
+ "Found Some Add-on ARM EABI v7a System Image, Acme Vendor Inc. API 2, revision 2\n" +
+ "Found Some Add-on Intel x86 Atom_64 System Image, Acme Vendor Inc. API 2, revision 3\n" +
+ "Found Some Add-on ARM 64 v8a System Image, Acme Vendor Inc. API 2, revision 4\n" +
+ "Found Some Add-on MIPS System Image, Acme Vendor Inc. API 2, revision 5\n" +
+ "Found Some Add-on MIPS64 System Image, Acme Vendor Inc. API 2, revision 6\n",
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
+
+ // check the packages we found...
+ // 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.
+ // Order is defined by
+ // com.android.sdklib.internal.repository.packages.SystemImagePackage.comparisonKey()
+
+ Package[] pkgs = mSource.getPackages();
+
+ assertEquals(15, pkgs.length);
+ for (Package p : pkgs) {
+ // We expected to find packages with each at least one archive.
+ assertTrue(p.getArchives().length >= 1);
+ // And only system images are supported by this source
+ assertTrue(p instanceof SystemImagePackage);
+ }
+
+ // Check the system-image packages
+ ArrayList<String> sysImgInfo = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof SystemImagePackage) {
+ SystemImagePackage sip = (SystemImagePackage) p;
+ sysImgInfo.add(String.format("%1$s %2$s: %3$s", //$NON-NLS-1$
+ sip.getAndroidVersion().getApiString(),
+ sip.getAbi(),
+ sip.getTag()));
+ }
+ }
+ assertEquals(
+ "[45 mips: tag-name---is-Sanitized----if-Display-is-Missing [Tag name is Sanitized if Display is Missing]\n" +
+ "44 mips: mips-only [This is an arbitrary string,]\n" +
+ "42 armeabi: default [Default]\n" +
+ "42 mips64: default [Default]\n" +
+ "2 arm64-v8a: default [Ignored in description for default tag]\n" +
+ "2 armeabi-v7a: default [Ignored in description for default tag]\n" +
+ "2 x86_64: default [Default]\n" +
+ "2 x86: default [Default]\n" +
+ "2 armeabi-v7a: other [Another tag name]\n" +
+ "2 arm64-v8a: some-addon [Some Add-on]\n" +
+ "2 armeabi-v7a: some-addon [Some Add-on]\n" +
+ "2 x86_64: some-addon [Some Add-on]\n" +
+ "2 x86: some-addon [Some Add-on]\n" +
+ "2 mips64: some-addon [Some Add-on]\n" +
+ "2 mips: some-addon [Some Add-on]]",
+ Arrays.toString(sysImgInfo.toArray()).replace(", ", "\n"));
+
+ // Check the default install-paths of the packages
+ ArrayList<File> sysImgPath = new ArrayList<File>();
+ for (Package p : pkgs) {
+ if (p instanceof SystemImagePackage) {
+ SystemImagePackage sip = (SystemImagePackage) p;
+ sysImgPath.add(sip.getInstallFolder("root", null /*sdkManager*/)); //$NON-NLS-1$
+ }
+ }
+ assertEquals(Arrays.toString(new File[] {
+ FileOp.append("root", "system-images", "android-45", "tag-name-is-sanitized-if-display-is-missing", "mips"),
+ FileOp.append("root", "system-images", "android-44", "mips-only", "mips"),
+ FileOp.append("root", "system-images", "android-42", "default", "armeabi"),
+ FileOp.append("root", "system-images", "android-42", "default", "mips64"),
+ FileOp.append("root", "system-images", "android-2" , "default", "arm64-v8a"),
+ FileOp.append("root", "system-images", "android-2" , "default", "armeabi-v7a"),
+ FileOp.append("root", "system-images", "android-2" , "default", "x86_64"),
+ FileOp.append("root", "system-images", "android-2" , "default", "x86"),
+ FileOp.append("root", "system-images", "android-2" , "other", "armeabi-v7a"),
+ FileOp.append("root", "system-images", "android-2" , "some-addon", "arm64-v8a"),
+ FileOp.append("root", "system-images", "android-2" , "some-addon", "armeabi-v7a"),
+ FileOp.append("root", "system-images", "android-2" , "some-addon", "x86_64"),
+ FileOp.append("root", "system-images", "android-2" , "some-addon", "x86"),
+ FileOp.append("root", "system-images", "android-2" , "some-addon", "mips64"),
+ FileOp.append("root", "system-images", "android-2" , "some-addon", "mips"),
+ }).replace(File.separatorChar, '/').replace(", ", "\n"),
+ Arrays.toString(sysImgPath.toArray()).replace(File.separatorChar, '/').replace(", ", "\n"));
+
+ // Check the list display of the packages
+ ArrayList<String> listDescs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ listDescs.add(p.getListDescription());
+ }
+ assertEquals(
+ "[Tag name is Sanitized if Display is Missing MIPS System Image\n" +
+ "MIPS system image for tag MIPS-only\n" + // list-display override
+ "ARM EABI System Image\n" +
+ "MIPS64 System Image\n" +
+ "ARM 64 v8a System Image\n" +
+ "ARM EABI v7a System Image\n" +
+ "System Image for x86-64 CPU for API 2\n" + // list-display override
+ "System Image for x86 CPU for API 2\n" + // list-display override
+ "Another tag name ARM EABI v7a System Image\n" +
+ "Some Add-on ARM 64 v8a System Image\n" +
+ "Some Add-on ARM EABI v7a System Image\n" +
+ "Some Add-on Intel x86 Atom_64 System Image\n" +
+ "x86 System Image for some add-on\n" + // list-display override
+ "Some Add-on MIPS64 System Image\n" +
+ "Some Add-on MIPS System Image]",
+ Arrays.toString(listDescs.toArray()).replace(", ", "\n"));
+
+ // Check platfomr vs add-ons system-images
+ ArrayList<String> addonProps = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof SystemImagePackage) {
+ SystemImagePackage sip = (SystemImagePackage) p;
+ String s = sip.isPlatform() ? "Platform" : "Addon: ";
+ if (!sip.isPlatform()) {
+ s += sip.getTag().toString() + " by " + sip.getAddonVendor().toString();
+ }
+ addonProps.add(s);
+ }
+ }
+ assertEquals(
+ "[Platform\n" +
+ "Platform\n" +
+ "Platform\n" +
+ "Platform\n" +
+ "Platform\n" +
+ "Platform\n" +
+ "Platform\n" +
+ "Platform\n" +
+ "Platform\n" +
+ "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
+ "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
+ "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
+ "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
+ "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
+ "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]]",
+ Arrays.toString(addonProps.toArray()).replace(", ", "\n"));
+
+ }
+
+ /**
+ * Validate there isn't a next-version we haven't tested yet
+ */
+ public void testLoadSysImgXml_4() throws Exception {
+ InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/sys_img_sample_4.xml");
+ assertNull("There is a sample for sys-img-4.xsd but there is not corresponding unit test", xmlStream);
}
@@ -185,7 +498,9 @@
*/
private ByteArrayInputStream getTestResource(String filename) throws IOException {
InputStream xmlStream = this.getClass().getResourceAsStream(filename);
-
+ if (xmlStream == null) {
+ return null;
+ }
try {
byte[] data = new byte[8192];
int offset = 0;
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/SettingsControllerTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/SettingsControllerTest.java
new file mode 100755
index 0000000..adfb0e9
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/SettingsControllerTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.updater;
+
+import com.android.sdklib.AndroidLocationTestCase;
+import com.android.sdklib.internal.repository.updater.ISettingsPage.SettingsChangedCallback;
+import com.android.sdklib.internal.repository.updater.SettingsController.OnChangedListener;
+import com.android.sdklib.internal.repository.updater.SettingsController.Settings;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.mock.MockLog;
+
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class SettingsControllerTest extends AndroidLocationTestCase {
+
+ private IFileOp mFileOp;
+ private MockLog mMockLog;
+ private SettingsController m;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mFileOp = new FileOp();
+ mMockLog = new MockLog();
+ m = new SettingsController(mFileOp, mMockLog);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public final void testDefaultSettings() {
+ m.loadSettings();
+ Settings s = m.getSettings();
+ assertFalse(s.getAskBeforeAdbRestart());
+ assertFalse(s.getEnablePreviews());
+ assertFalse(s.getForceHttp());
+ assertTrue (s.getShowUpdateOnly());
+ assertTrue (s.getUseDownloadCache());
+ assertEquals(-1, s.getMonitorDensity());
+ }
+
+ public final void testSetSettings() {
+ m.loadSettings();
+
+ Settings s1 = m.getSettings();
+ assertFalse(s1.getAskBeforeAdbRestart());
+ assertFalse(s1.getEnablePreviews());
+ assertFalse(s1.getForceHttp());
+ assertTrue (s1.getShowUpdateOnly());
+ assertTrue (s1.getUseDownloadCache());
+ assertEquals(-1, s1.getMonitorDensity());
+
+ m.setSetting(ISettingsPage.KEY_ASK_ADB_RESTART, true);
+ m.setSetting(ISettingsPage.KEY_ENABLE_PREVIEWS, true);
+ m.setSetting(ISettingsPage.KEY_FORCE_HTTP, true);
+ m.setShowUpdateOnly(false);
+ m.setSetting(ISettingsPage.KEY_USE_DOWNLOAD_CACHE, false);
+ m.setMonitorDensity(320);
+
+ Settings s2 = m.getSettings();
+ assertSame(s2, s1);
+ assertTrue (s2.getAskBeforeAdbRestart());
+ assertTrue (s2.getEnablePreviews());
+ assertTrue (s2.getForceHttp());
+ assertFalse(s2.getShowUpdateOnly());
+ assertFalse(s2.getUseDownloadCache());
+ assertEquals(320, s2.getMonitorDensity());
+
+ m.saveSettings();
+
+ // create a new instance
+ SettingsController m3 = new SettingsController(mFileOp, mMockLog);
+ m3.loadSettings();
+
+ Settings s3 = m3.getSettings();
+ assertNotSame(s3, s1);
+ assertTrue (s3.getAskBeforeAdbRestart());
+ assertTrue (s3.getEnablePreviews());
+ assertTrue (s3.getForceHttp());
+ assertFalse(s3.getShowUpdateOnly());
+ assertFalse(s3.getUseDownloadCache());
+ assertEquals(320, s3.getMonitorDensity());
+ }
+
+ public final void testSettingsPage() {
+ final AtomicBoolean pageLoadSettingsCalled = new AtomicBoolean(false);
+ final AtomicBoolean pageRetrieveSettingsCalled = new AtomicBoolean(false);
+ final AtomicBoolean pageSetOnSettingsChangedCalled = new AtomicBoolean(false);
+ final AtomicReference<SettingsChangedCallback> pageSettingsChangedCallback =
+ new AtomicReference<ISettingsPage.SettingsChangedCallback>();
+
+ ISettingsPage mockPage = new ISettingsPage() {
+ @Override
+ public void loadSettings(Properties inSettings) {
+ pageLoadSettingsCalled.set(true);
+ }
+
+ @Override
+ public void setOnSettingsChanged(SettingsChangedCallback settingsChangedCallback) {
+ pageSetOnSettingsChangedCalled.set(true);
+ pageSettingsChangedCallback.set(settingsChangedCallback);
+ }
+
+ @Override
+ public void retrieveSettings(Properties outSettings) {
+ pageRetrieveSettingsCalled.set(true);
+ }
+ };
+
+ // Setting the page loads settings into it and then registers a changed-callback
+ // that will call m.onSettingsChanged.
+ m.setSettingsPage(mockPage);
+ assertTrue(pageLoadSettingsCalled.get());
+ assertTrue(pageSetOnSettingsChangedCalled.get());
+ assertFalse(pageRetrieveSettingsCalled.get());
+
+ final AtomicBoolean listener1Called = new AtomicBoolean(false);
+
+ OnChangedListener listener1 = new OnChangedListener() {
+ @Override
+ public void onSettingsChanged(SettingsController controller, Settings oldSettings) {
+ listener1Called.set(true);
+ }
+ };
+
+ final AtomicBoolean listener2Called = new AtomicBoolean(false);
+
+ OnChangedListener listener2 = new OnChangedListener() {
+ @Override
+ public void onSettingsChanged(SettingsController controller, Settings oldSettings) {
+ listener1Called.set(true);
+ }
+ };
+
+ m.registerOnChangedListener(listener1);
+ m.registerOnChangedListener(listener2);
+ m.unregisterOnChangedListener(listener2);
+ m.unregisterOnChangedListener(listener2);
+ assertFalse(listener1Called.get());
+ assertFalse(listener2Called.get());
+
+ // When the settings page changes, it calls the callback that it was given
+ // (which we captured earlier)
+ assertNotNull(pageSettingsChangedCallback.get());
+ pageSettingsChangedCallback.get().onSettingsChanged(mockPage);
+ // That triggers SettingsController.onSettingsChanged which calls retrieve() on the
+ // page to get the settings and save them and then call all the registered listeners
+ // with the settings.
+ assertTrue(pageRetrieveSettingsCalled.get());
+ assertTrue(listener1Called.get());
+ assertFalse(listener2Called.get());
+
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/UpdaterDataTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/UpdaterDataTest.java
index e646c31..4a2bf9b 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/UpdaterDataTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/UpdaterDataTest.java
@@ -131,13 +131,13 @@
data.acceptLicense(infos , acceptLicenses, 3);
assertEquals(
"[P -------------------------------\n" +
- "License id: sdk-license-dddb8a39\n" +
- "Used by: \n" +
+ ", P License id: sdk-license-dddb8a39\n" +
+ ", P Used by: \n" +
" - MockEmptyPackage 'test'\n" +
- "-------------------------------\n" +
- "This is the license text.\n" +
- "Etc etc.\n" +
+ ", P -------------------------------\n" +
"\n" +
+ ", P This is the license text.\n" +
+ "Etc etc.\n" +
"\n" +
", P Do you accept the license 'sdk-license-dddb8a39' [y/n]: , P \n" +
", P Unknown response ''.\n" +
@@ -170,13 +170,13 @@
data.acceptLicense(infos , acceptLicenses, 3);
assertEquals(
"[P -------------------------------\n" +
- "License id: sdk-license-dddb8a39\n" +
- "Used by: \n" +
+ ", P License id: sdk-license-dddb8a39\n" +
+ ", P Used by: \n" +
" - MockEmptyPackage 'test'\n" +
- "-------------------------------\n" +
- "This is the license text.\n" +
- "Etc etc.\n" +
+ ", P -------------------------------\n" +
"\n" +
+ ", P This is the license text.\n" +
+ "Etc etc.\n" +
"\n" +
", P Do you accept the license 'sdk-license-dddb8a39' [y/n]: , P \n" +
"]", Arrays.toString(inputLog.getMessages().toArray()));
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 557a51f..e2c5d14 100755
--- a/sdklib/src/test/java/com/android/sdklib/io/MockFileOp.java
+++ b/sdklib/src/test/java/com/android/sdklib/io/MockFileOp.java
@@ -68,6 +68,7 @@
public void reset() {
mExistingFiles.clear();
mExistingFolders.clear();
+ mOutputStreams.clear();
}
@NonNull
@@ -80,7 +81,7 @@
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
// Try to convert the windows-looking path to a unix-looking one
path = path.replace('\\', '/');
- path = path.replace("C:", ""); //$NON-NLS-1$ //$NON-NLS-2$
+ path = path.replaceAll("^[A-Z]:", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
return path;
}
@@ -90,7 +91,7 @@
* Parent folders are not automatically created.
*/
public void recordExistingFile(@NonNull File file) {
- recordExistingFile(getAgnosticAbsPath(file), 0, null);
+ recordExistingFile(getAgnosticAbsPath(file), 0, (byte[])null);
}
/**
@@ -102,7 +103,7 @@
* @param absFilePath A unix-like file path, e.g. "/dir/file"
*/
public void recordExistingFile(@NonNull String absFilePath) {
- recordExistingFile(absFilePath, 0, null);
+ recordExistingFile(absFilePath, 0, (byte[])null);
}
/**
@@ -150,6 +151,22 @@
}
/**
+ * 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,
+ long lastModified,
+ @NonNull String content) {
+ recordExistingFile(absFilePath, lastModified, content.getBytes(Charsets.UTF_8));
+ }
+
+ /**
* Records a new absolute folder path.
* Parent folders are not automatically created.
*/
@@ -170,6 +187,20 @@
}
/**
+ * Returns true if a file with the given path has been recorded.
+ */
+ public boolean hasRecordedExistingFile(File file) {
+ return mExistingFiles.containsKey(getAgnosticAbsPath(file));
+ }
+
+ /**
+ * Returns true if a folder with the given path has been recorded.
+ */
+ public boolean hasRecordedExistingFolder(File folder) {
+ return mExistingFolders.contains(getAgnosticAbsPath(folder));
+ }
+
+ /**
* Returns the list of paths added using {@link #recordExistingFile(String)}
* and eventually updated by {@link #delete(File)} operations.
* <p/>
@@ -484,15 +515,14 @@
* records the write rather than actually performing it.</em>
*/
@Override
- public boolean saveProperties(@NonNull File file, @NonNull Properties props,
- @NonNull String comments) {
+ public void saveProperties(
+ @NonNull File file,
+ @NonNull Properties props,
+ @NonNull String comments) throws IOException {
OutputStream fos = null;
try {
fos = newFileOutputStream(file);
-
props.store(fos, comments);
- return true;
- } catch (IOException ignore) {
} finally {
if (fos != null) {
try {
@@ -501,8 +531,6 @@
}
}
}
-
- return false;
}
/**
diff --git a/sdklib/src/test/java/com/android/sdklib/io/MockFileOpTest.java b/sdklib/src/test/java/com/android/sdklib/io/MockFileOpTest.java
index 8fb1784..9739058 100755
--- a/sdklib/src/test/java/com/android/sdklib/io/MockFileOpTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/io/MockFileOpTest.java
@@ -17,6 +17,7 @@
package com.android.sdklib.io;
import java.io.File;
+import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
@@ -188,4 +189,61 @@
"</dir1/dir2/forgot to close: (stream not closed properly)>]",
Arrays.toString(m.getOutputStreams()));
}
+
+ public void testMakeRelative() throws Exception {
+ assertEquals("dir3",
+ FileOp.makeRelativeImpl("/dir1/dir2",
+ "/dir1/dir2/dir3",
+ false, "/"));
+
+ assertEquals("../../../dir3",
+ FileOp.makeRelativeImpl("/dir1/dir2/dir4/dir5/dir6",
+ "/dir1/dir2/dir3",
+ false, "/"));
+
+ assertEquals("dir3/dir4/dir5/dir6",
+ FileOp.makeRelativeImpl("/dir1/dir2/",
+ "/dir1/dir2/dir3/dir4/dir5/dir6",
+ false, "/"));
+
+ // case-sensitive on non-Windows.
+ assertEquals("../DIR2/dir3/DIR4/dir5/DIR6",
+ FileOp.makeRelativeImpl("/dir1/dir2/",
+ "/dir1/DIR2/dir3/DIR4/dir5/DIR6",
+ false, "/"));
+
+ // same path: empty result.
+ assertEquals("",
+ FileOp.makeRelativeImpl("/dir1/dir2/dir3",
+ "/dir1/dir2/dir3",
+ false, "/"));
+
+ // same drive letters on Windows
+ assertEquals("..\\..\\..\\dir3",
+ FileOp.makeRelativeImpl("C:\\dir1\\dir2\\dir4\\dir5\\dir6",
+ "C:\\dir1\\dir2\\dir3",
+ true, "\\"));
+
+ // not case-sensitive on Windows, results will be mixed.
+ assertEquals("dir3/DIR4/dir5/DIR6",
+ FileOp.makeRelativeImpl("/DIR1/dir2/",
+ "/dir1/DIR2/dir3/DIR4/dir5/DIR6",
+ true, "/"));
+
+ // UNC path on Windows
+ assertEquals("..\\..\\..\\dir3",
+ FileOp.makeRelativeImpl("\\\\myserver.domain\\dir1\\dir2\\dir4\\dir5\\dir6",
+ "\\\\myserver.domain\\dir1\\dir2\\dir3",
+ true, "\\"));
+
+ // different drive letters are not supported
+ try {
+ FileOp.makeRelativeImpl("C:\\dir1\\dir2\\dir4\\dir5\\dir6",
+ "D:\\dir1\\dir2\\dir3",
+ true, "\\");
+ fail("Expected: IOException. Actual: no exception.");
+ } catch (IOException e) {
+ assertEquals("makeRelative: incompatible drive letters", e.getMessage());
+ }
+ }
}
diff --git a/sdklib/src/test/java/com/android/sdklib/local/LocalSdkTest.java b/sdklib/src/test/java/com/android/sdklib/local/LocalSdkTest.java
deleted file mode 100755
index b183abb..0000000
--- a/sdklib/src/test/java/com/android/sdklib/local/LocalSdkTest.java
+++ /dev/null
@@ -1,662 +0,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.
- */
-
-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/mock/MockLog.java b/sdklib/src/test/java/com/android/sdklib/mock/MockLog.java
index 1cd912b..5b14d60 100644
--- a/sdklib/src/test/java/com/android/sdklib/mock/MockLog.java
+++ b/sdklib/src/test/java/com/android/sdklib/mock/MockLog.java
@@ -63,7 +63,26 @@
@Override
public String toString() {
- return mMessages.toString();
+ StringBuilder sb = new StringBuilder();
+ // Each line starts with [WPVE] + space + actual content.
+ // When writing the types, collapse the W/P/V/E qualifiers and only specify one per line.
+ char lastType = 0;
+ for (String s : mMessages) {
+ if (s.isEmpty()) {
+ continue;
+ }
+ char type = s.charAt(0);
+ if (type != lastType) {
+ sb.append(s);
+ lastType = type;
+ } else if (s.length() > 2) {
+ sb.append(s.substring(2));
+ }
+ if (s.endsWith("\n")) {
+ lastType = 0;
+ }
+ }
+ return sb.toString();
}
@NonNull
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 88fec01..471a4e8 100755
--- a/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonXmlTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonXmlTest.java
@@ -31,12 +31,10 @@
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
-import junit.framework.TestCase;
-
/**
* Tests local validation of an SDK Addon sample XMLs using an XML Schema validator.
*/
-public class ValidateAddonXmlTest extends TestCase {
+public class ValidateAddonXmlTest extends ValidateTestCase {
private static String OPEN_TAG_ADDON =
"<r:sdk-addon xmlns:r=\"http://schemas.android.com/sdk/android/addon/" +
@@ -44,40 +42,6 @@
"\">";
private static String CLOSE_TAG_ADDON = "</r:sdk-addon>";
- // --- Helpers ------------
-
- /**
- * Helper method that returns a validator for our Addon XSD
- *
- * @param version The version number, in range {@code 1..NS_LATEST_VERSION}
- * @param handler A {@link CaptureErrorHandler}. If null the default will be used,
- * which will most likely print errors to stderr.
- */
- private Validator getAddonValidator(int version, @Nullable CaptureErrorHandler handler)
- throws SAXException {
- Validator validator = null;
- InputStream xsdStream = SdkAddonConstants.getXsdStream(version);
- if (xsdStream != null) {
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- Schema schema = factory.newSchema(new StreamSource(xsdStream));
- validator = schema.newValidator();
- if (handler != null) {
- validator.setErrorHandler(handler);
- }
- }
-
- return validator;
- }
-
- /** An helper that validates a string against an expected regexp. */
- private void assertRegex(String expectedRegexp, String actualString) {
- assertNotNull(actualString);
- assertTrue(
- String.format("Regexp Assertion Failed:\nExpected: %s\nActual: %s\n",
- expectedRegexp, actualString),
- actualString.matches(expectedRegexp));
- }
-
// --- Tests ------------
/** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
@@ -96,6 +60,11 @@
getAddonValidator(SdkAddonConstants.NS_LATEST_VERSION + 1, handler));
}
+ /** Validate the XSD version 1 */
+ public void testValidateAddonXsd1() throws Exception {
+ validateXsd(SdkAddonConstants.getXsdStream(1));
+ }
+
/** Validate a valid sample using namespace version 1 using an InputStream */
public void testValidateLocalAddonFile1() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
@@ -108,6 +77,11 @@
handler.verify();
}
+ /** Validate the XSD version 2 */
+ public void testValidateAddonXsd2() throws Exception {
+ validateXsd(SdkAddonConstants.getXsdStream(2));
+ }
+
/** Validate a valid sample using namespace version 2 using an InputStream */
public void testValidateLocalAddonFile2() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
@@ -120,6 +94,11 @@
handler.verify();
}
+ /** Validate the XSD version 3 */
+ public void testValidateAddonXsd3() throws Exception {
+ validateXsd(SdkAddonConstants.getXsdStream(3));
+ }
+
/** Validate a valid sample using namespace version 3 using an InputStream */
public void testValidateLocalAddonFile3() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
@@ -132,6 +111,11 @@
handler.verify();
}
+ /** Validate the XSD version 4 */
+ public void testValidateAddonXsd4() throws Exception {
+ validateXsd(SdkAddonConstants.getXsdStream(4));
+ }
+
/** Validate a valid sample using namespace version 4 using an InputStream */
public void testValidateLocalAddonFile4() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
@@ -144,6 +128,11 @@
handler.verify();
}
+ /** Validate the XSD version 5 */
+ public void testValidateAddonXsd5() throws Exception {
+ validateXsd(SdkAddonConstants.getXsdStream(5));
+ }
+
/** Validate a valid sample using namespace version 5 using an InputStream */
public void testValidateLocalAddonFile5() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
@@ -156,6 +145,11 @@
handler.verify();
}
+ /** Validate the XSD version 6 */
+ public void testValidateAddonXsd6() throws Exception {
+ validateXsd(SdkAddonConstants.getXsdStream(6));
+ }
+
/** Validate a valid sample using namespace version 6 using an InputStream */
public void testValidateLocalAddonFile6() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
@@ -168,6 +162,30 @@
handler.verify();
}
+ /** Validate the XSD version 7 */
+ public void testValidateAddonXsd7() throws Exception {
+ validateXsd(SdkAddonConstants.getXsdStream(7));
+ }
+
+ /** Validate a valid sample using namespace version 7 using an InputStream */
+ public void testValidateLocalAddonFile7() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/addon_sample_7.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getAddonValidator(7, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
+ /** Make sure we don't have a next-version sample that is not validated yet */
+ public void testValidateLocalAddonFile8() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/addon_sample_8.xml");
+ assertNull(xmlStream);
+ }
+
// IMPORTANT: each time you add a test here, you should add a corresponding
// test in SdkAddonSourceTest to validate the XML content is parsed correctly.
@@ -233,4 +251,29 @@
// If we get here, the validator has not failed as we expected it to.
fail();
}
+
+ // --- Helpers ------------
+
+ /**
+ * Helper method that returns a validator for our Addon XSD
+ *
+ * @param version The version number, in range {@code 1..NS_LATEST_VERSION}
+ * @param handler A {@link CaptureErrorHandler}. If null the default will be used,
+ * which will most likely print errors to stderr.
+ */
+ private Validator getAddonValidator(int version, @Nullable CaptureErrorHandler handler)
+ throws SAXException {
+ Validator validator = null;
+ InputStream xsdStream = SdkAddonConstants.getXsdStream(version);
+ if (xsdStream != null) {
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ Schema schema = factory.newSchema(new StreamSource(xsdStream));
+ validator = schema.newValidator();
+ if (handler != null) {
+ validator.setErrorHandler(handler);
+ }
+ }
+
+ return validator;
+ }
}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonsListXmlTest.java b/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonsListXmlTest.java
index 0461c67..8bb335d 100755
--- a/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonsListXmlTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonsListXmlTest.java
@@ -29,12 +29,72 @@
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
-import junit.framework.TestCase;
-
/**
* Tests local validation of an SDK Addon-List sample XMLs using an XML Schema validator.
*/
-public class ValidateAddonsListXmlTest extends TestCase {
+public class ValidateAddonsListXmlTest extends ValidateTestCase {
+
+ // --- Tests ------------
+
+ /** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
+ public void testAddonLatestVersionNumber() throws Exception {
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+
+ // There should be a schema matching NS_LATEST_VERSION
+ assertNotNull(getValidator(SdkAddonsListConstants.NS_LATEST_VERSION, handler));
+
+ // There should NOT be a schema with NS_LATEST_VERSION+1
+ assertNull(
+ String.format(
+ "There's an ADDON XSD at version %d but SdkAddonsListConstants.NS_LATEST_VERSION is still set to %d.",
+ SdkAddonsListConstants.NS_LATEST_VERSION + 1,
+ SdkAddonsListConstants.NS_LATEST_VERSION),
+ getValidator(SdkAddonsListConstants.NS_LATEST_VERSION + 1, handler));
+ }
+
+ /** Validate the XSD version 1 */
+ public void testValidateAddonsListXsd1() throws Exception {
+ validateXsd(SdkAddonsListConstants.getXsdStream(1));
+ }
+
+ /** Validate a valid sample using namespace version 1 using an InputStream */
+ public void testValidateLocalAddonsListFile1() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/addons_list_sample_1.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getValidator(1, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
+ /** Validate the XSD version 2 */
+ public void testValidateAddonsListXsd2() throws Exception {
+ validateXsd(SdkAddonsListConstants.getXsdStream(2));
+ }
+
+ /** Validate a valid sample using namespace version 2 using an InputStream */
+ public void testValidateLocalAddonsListFile2() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/addons_list_sample_2.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getValidator(2, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
+ /** Make sure we don't have a next-version sample that is not validated yet */
+ public void testValidateLocalAddonsListFile3() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/addons_list_sample_3.xml");
+ assertNull(xmlStream);
+ }
+
+ // IMPORTANT: each time you add a test here, you should add a corresponding
+ // test in AddonsListFetcherTest to validate the XML content is parsed correctly.
// --- Helpers ------------
@@ -60,49 +120,4 @@
return validator;
}
-
- // --- Tests ------------
-
- /** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
- public void testAddonLatestVersionNumber() throws Exception {
- CaptureErrorHandler handler = new CaptureErrorHandler();
-
- // There should be a schema matching NS_LATEST_VERSION
- assertNotNull(getValidator(SdkAddonsListConstants.NS_LATEST_VERSION, handler));
-
- // There should NOT be a schema with NS_LATEST_VERSION+1
- assertNull(
- String.format(
- "There's an ADDON XSD at version %d but SdkAddonsListConstants.NS_LATEST_VERSION is still set to %d.",
- SdkAddonsListConstants.NS_LATEST_VERSION + 1,
- SdkAddonsListConstants.NS_LATEST_VERSION),
- getValidator(SdkAddonsListConstants.NS_LATEST_VERSION + 1, handler));
- }
-
- /** Validate a valid sample using namespace version 1 using an InputStream */
- public void testValidateLocalAddonFile1() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addons_list_sample_1.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getValidator(1, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate a valid sample using namespace version 2 using an InputStream */
- public void testValidateLocalAddonFile2() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addons_list_sample_2.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getValidator(2, handler);
- validator.validate(source);
- handler.verify();
- }
-
- // IMPORTANT: each time you add a test here, you should add a corresponding
- // test in AddonsListFetcherTest to validate the XML content is parsed correctly.
}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/ValidateRepositoryXmlTest.java b/sdklib/src/test/java/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
index c5667ec..20fd409 100755
--- a/sdklib/src/test/java/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
@@ -31,15 +31,13 @@
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
-import junit.framework.TestCase;
-
/**
* Tests local validation of an SDK Repository sample XMLs using an XML Schema validator.
*
* References:
* http://www.ibm.com/developerworks/xml/library/x-javaxmlvalidapi.html
*/
-public class ValidateRepositoryXmlTest extends TestCase {
+public class ValidateRepositoryXmlTest extends ValidateTestCase {
private static String OPEN_TAG_REPO =
"<r:sdk-repository xmlns:r=\"http://schemas.android.com/sdk/android/repository/" +
@@ -47,41 +45,6 @@
"\">";
private static String CLOSE_TAG_REPO = "</r:sdk-repository>";
- // --- Helpers ------------
-
- /**
- * Helper method that returns a validator for our Repository XSD
- *
- * @param version The version number, in range {@code 1..NS_LATEST_VERSION}
- * @param handler A {@link CaptureErrorHandler}. If null the default will be used,
- * which will most likely print errors to stderr.
- */
- private Validator getRepoValidator(int version, @Nullable CaptureErrorHandler handler)
- throws SAXException {
- Validator validator = null;
- InputStream xsdStream = SdkRepoConstants.getXsdStream(version);
- if (xsdStream != null) {
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- Schema schema = factory.newSchema(new StreamSource(xsdStream));
- validator = schema.newValidator();
-
- if (handler != null) {
- validator.setErrorHandler(handler);
- }
- }
-
- return validator;
- }
-
- /** An helper that validates a string against an expected regexp. */
- private void assertRegex(String expectedRegexp, String actualString) {
- assertNotNull(actualString);
- assertTrue(
- String.format("Regexp Assertion Failed:\nExpected: %s\nActual: %s\n",
- expectedRegexp, actualString),
- actualString.matches(expectedRegexp));
- }
-
// --- Tests ------------
/** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
@@ -100,10 +63,15 @@
getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION + 1, handler));
}
+ /** Validate the XSD version 1 */
+ public void testValidateRepositoryXsd1() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(1));
+ }
+
/** Validate a valid sample using namespace version 1 using an InputStream */
public void testValidateLocalRepositoryFile1() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_1.xml");
+ "/com/android/sdklib/testdata/repository_sample_01.xml");
Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler();
@@ -112,10 +80,15 @@
handler.verify();
}
+ /** Validate the XSD version 2 */
+ public void testValidateRepositoryXsd2() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(2));
+ }
+
/** Validate a valid sample using namespace version 2 using an InputStream */
public void testValidateLocalRepositoryFile2() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_2.xml");
+ "/com/android/sdklib/testdata/repository_sample_02.xml");
Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler();
@@ -124,10 +97,15 @@
handler.verify();
}
+ /** Validate the XSD version 3 */
+ public void testValidateRepositoryXsd3() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(3));
+ }
+
/** Validate a valid sample using namespace version 3 using an InputStream */
public void testValidateLocalRepositoryFile3() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_3.xml");
+ "/com/android/sdklib/testdata/repository_sample_03.xml");
Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler();
@@ -136,69 +114,130 @@
handler.verify();
}
+ /** Validate the XSD version 4 */
+ public void testValidateRepositoryXsd4() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(4));
+ }
+
/** Validate a valid sample using namespace version 4 using an InputStream */
public void testValidateLocalRepositoryFile4() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_4.xml");
+ "/com/android/sdklib/testdata/repository_sample_04.xml");
Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler();
Validator validator = getRepoValidator(4, handler);
validator.validate(source);
handler.verify();
+ }
+ /** Validate the XSD version 5 */
+ public void testValidateRepositoryXsd5() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(5));
}
/** Validate a valid sample using namespace version 5 using an InputStream */
public void testValidateLocalRepositoryFile5() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_5.xml");
+ "/com/android/sdklib/testdata/repository_sample_05.xml");
Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler();
Validator validator = getRepoValidator(5, handler);
validator.validate(source);
handler.verify();
-
}
- /** Validate a valid sample using namespace version 5 using an InputStream */
+ /** Validate the XSD version 6 */
+ public void testValidateRepositoryXsd6() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(6));
+ }
+
+ /** Validate a valid sample using namespace version 6 using an InputStream */
public void testValidateLocalRepositoryFile6() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_6.xml");
+ "/com/android/sdklib/testdata/repository_sample_06.xml");
Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler();
Validator validator = getRepoValidator(6, handler);
validator.validate(source);
handler.verify();
-
}
- /** Validate a valid sample using namespace version 5 using an InputStream */
+ /** Validate the XSD version 7 */
+ public void testValidateRepositoryXsd7() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(7));
+ }
+
+ /** Validate a valid sample using namespace version 7 using an InputStream */
public void testValidateLocalRepositoryFile7() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_7.xml");
+ "/com/android/sdklib/testdata/repository_sample_07.xml");
Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler();
Validator validator = getRepoValidator(7, handler);
validator.validate(source);
handler.verify();
-
}
- /** Validate a valid sample using namespace version 5 using an InputStream */
+ /** Validate the XSD version 8 */
+ public void testValidateRepositoryXsd8() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(8));
+ }
+
+ /** Validate a valid sample using namespace version 8 using an InputStream */
public void testValidateLocalRepositoryFile8() throws Exception {
InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_8.xml");
+ "/com/android/sdklib/testdata/repository_sample_08.xml");
Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler();
Validator validator = getRepoValidator(8, handler);
validator.validate(source);
handler.verify();
+ }
+ /** Validate the XSD version 9 */
+ public void testValidateRepositoryXsd9() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(9));
+ }
+
+ /** Validate a valid sample using namespace version 9 using an InputStream */
+ public void testValidateLocalRepositoryFile9() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/repository_sample_09.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getRepoValidator(9, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
+ /** Validate the XSD version 10 */
+ public void testValidateRepositoryXsd10() throws Exception {
+ validateXsd(SdkRepoConstants.getXsdStream(10));
+ }
+
+ /** Validate a valid sample using namespace version 10 using an InputStream */
+ public void testValidateLocalRepositoryFile10() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/repository_sample_10.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getRepoValidator(10, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
+ /** Make sure we don't have a next-version sample that is not validated yet */
+ public void testValidateLocalRepositoryFile11() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/repository_sample_11.xml");
+ assertNull(xmlStream);
}
// IMPORTANT: each time you add a test here, you should add a corresponding
@@ -317,7 +356,7 @@
"<r:license id=\"lic1\"> some license </r:license> " +
"<r:tool> <r:uses-license ref=\"lic2\" /> <r:revision> <r:major>1</r:major> </r:revision> " +
"<r:min-platform-tools-rev> <r:major>1</r:major> </r:min-platform-tools-rev> " +
- "<r:archives> <r:archive os=\"any\"> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
+ "<r:archives> <r:archive> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
"<r:url>url</r:url> </r:archive> </r:archives> </r:tool>" +
CLOSE_TAG_REPO;
@@ -342,7 +381,7 @@
String document = "<?xml version=\"1.0\"?>" +
OPEN_TAG_REPO +
"<r:extra> <r:revision>1</r:revision> <r:path>path</r:path> " +
- "<r:archives> <r:archive os=\"any\"> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
+ "<r:archives> <r:archive> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
"<r:url>url</r:url> </r:archive> </r:archives> </r:extra>" +
CLOSE_TAG_REPO;
@@ -361,4 +400,30 @@
// If we get here, the validator has not failed as we expected it to.
fail();
}
+
+ // --- Helpers ------------
+
+ /**
+ * Helper method that returns a validator for our Repository XSD
+ *
+ * @param version The version number, in range {@code 1..NS_LATEST_VERSION}
+ * @param handler A {@link CaptureErrorHandler}. If null the default will be used,
+ * which will most likely print errors to stderr.
+ */
+ private Validator getRepoValidator(int version, @Nullable CaptureErrorHandler handler)
+ throws SAXException {
+ Validator validator = null;
+ InputStream xsdStream = SdkRepoConstants.getXsdStream(version);
+ if (xsdStream != null) {
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ Schema schema = factory.newSchema(new StreamSource(xsdStream));
+ validator = schema.newValidator();
+
+ if (handler != null) {
+ validator.setErrorHandler(handler);
+ }
+ }
+
+ return validator;
+ }
}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/ValidateSysImgXmlTest.java b/sdklib/src/test/java/com/android/sdklib/repository/ValidateSysImgXmlTest.java
index fe7b1e8..6fecada 100755
--- a/sdklib/src/test/java/com/android/sdklib/repository/ValidateSysImgXmlTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/repository/ValidateSysImgXmlTest.java
@@ -29,15 +29,93 @@
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
-import junit.framework.TestCase;
-
/**
* Tests local validation of an SDK Repository sample XMLs using an XML Schema validator.
*
* References:
* http://www.ibm.com/developerworks/xml/library/x-javaxmlvalidapi.html
*/
-public class ValidateSysImgXmlTest extends TestCase {
+public class ValidateSysImgXmlTest extends ValidateTestCase {
+
+ // --- Tests ------------
+
+ /** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
+ public void testSysImgLatestVersionNumber() throws Exception {
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+
+ // There should be a schema matching NS_LATEST_VERSION
+ assertNotNull(getValidator(SdkSysImgConstants.NS_LATEST_VERSION, handler));
+
+ // There should NOT be a schema with NS_LATEST_VERSION+1
+ assertNull(
+ String.format(
+ "There's a REPO XSD at version %d but SdkSysImgConstants.NS_LATEST_VERSION is still set to %d.",
+ SdkSysImgConstants.NS_LATEST_VERSION + 1,
+ SdkSysImgConstants.NS_LATEST_VERSION),
+ getValidator(SdkSysImgConstants.NS_LATEST_VERSION + 1, handler));
+ }
+
+ /** Validate the XSD version 1 */
+ public void testValidateSysImgXsd1() throws Exception {
+ validateXsd(SdkSysImgConstants.getXsdStream(1));
+ }
+
+ /** Validate a valid sample using namespace version 1 using an InputStream */
+ public void testValidateLocalSysImgFile1() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/sys_img_sample_1.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getValidator(1, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
+ /** Validate the XSD version 2 */
+ public void testValidateSysImgXsd2() throws Exception {
+ validateXsd(SdkSysImgConstants.getXsdStream(2));
+ }
+
+ /** Validate a valid sample using namespace version 2 using an InputStream */
+ public void testValidateLocalSysImgFile2() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/sys_img_sample_2.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getValidator(2, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
+ /** Validate the XSD version 3 */
+ public void testValidateSysImgXsd3() throws Exception {
+ validateXsd(SdkSysImgConstants.getXsdStream(3));
+ }
+
+ /** Validate a valid sample using namespace version 3 using an InputStream */
+ public void testValidateLocalSysImgFile3() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/sys_img_sample_3.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getValidator(3, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
+ /** Make sure we don't have a next-version sample that is not validated yet */
+ public void testValidateLocalSysImgFile4() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/sys_img_sample_4.xml");
+ assertNull(xmlStream);
+ }
+
+ // IMPORTANT: each time you add a test here, you should add a corresponding
+ // test in SdkSysImgSourceTest to validate the XML content is parsed correctly.
+
// --- Helpers ------------
@@ -64,38 +142,4 @@
return validator;
}
-
- // --- Tests ------------
-
- /** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
- public void testSysImgLatestVersionNumber() throws Exception {
- CaptureErrorHandler handler = new CaptureErrorHandler();
-
- // There should be a schema matching NS_LATEST_VERSION
- assertNotNull(getValidator(SdkSysImgConstants.NS_LATEST_VERSION, handler));
-
- // There should NOT be a schema with NS_LATEST_VERSION+1
- assertNull(
- String.format(
- "There's a REPO XSD at version %d but SdkSysImgConstants.NS_LATEST_VERSION is still set to %d.",
- SdkSysImgConstants.NS_LATEST_VERSION + 1,
- SdkSysImgConstants.NS_LATEST_VERSION),
- getValidator(SdkSysImgConstants.NS_LATEST_VERSION + 1, handler));
- }
-
- /** Validate a valid sample using namespace version 1 using an InputStream */
- public void testValidateLocalRepositoryFile1() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/sys_img_sample_1.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getValidator(1, handler);
- validator.validate(source);
- handler.verify();
- }
-
- // IMPORTANT: each time you add a test here, you should add a corresponding
- // test in SdkSysImgSourceTest to validate the XML content is parsed correctly.
-
}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/ValidateTestCase.java b/sdklib/src/test/java/com/android/sdklib/repository/ValidateTestCase.java
new file mode 100755
index 0000000..26d031e
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/ValidateTestCase.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 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.sdklib.repository;
+
+import org.w3c.dom.ls.LSInput;
+import org.w3c.dom.ls.LSResourceResolver;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import javax.xml.XMLConstants;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+
+import junit.framework.TestCase;
+
+abstract class ValidateTestCase extends TestCase {
+
+ /**
+ * Validates an XSD stream against the w3.org XSD schema.
+ */
+ protected void validateXsd(InputStream repoXsdStream) throws SAXException, IOException {
+ final Class<? extends ValidateTestCase> clazz = this.getClass();
+ InputStream xsdXsdStream = clazz.getResourceAsStream(
+ "/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.xsd");
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ factory.setResourceResolver(new LSResourceResolver() {
+ @Override
+ public LSInput resolveResource(
+ String type,
+ String namespaceURI,
+ final String publicId,
+ final String systemId,
+ final String baseURI) {
+ if (systemId != null) {
+ String resName = "/com/android/sdklib/testdata/www.w3.org/2001/";
+ int pos = systemId.lastIndexOf('/');
+ if (pos < 0) {
+ resName += systemId;
+ } else {
+ resName += systemId.substring(pos + 1);
+ }
+ final InputStream stream = clazz.getResourceAsStream(resName);
+ if (stream == null) {
+ fail("XSD validation requires missing file: " + resName);
+ }
+ return new LSInput() {
+ @SuppressWarnings("hiding")
+ @Override
+ public void setSystemId(String systemId) {}
+
+ @Override
+ public void setStringData(String stringData) {}
+
+ @SuppressWarnings("hiding")
+ @Override
+ public void setPublicId(String publicId) {}
+
+ @Override
+ public void setEncoding(String encoding) {}
+
+ @Override
+ public void setCharacterStream(Reader characterStream) {}
+
+ @Override
+ public void setCertifiedText(boolean certifiedText) {}
+
+ @Override
+ public void setByteStream(InputStream byteStream) {}
+
+ @SuppressWarnings("hiding")
+ @Override
+ public void setBaseURI(String baseURI) {}
+
+ @Override
+ public String getSystemId() {
+ return systemId;
+ }
+
+ @Override
+ public String getStringData() {
+ return null;
+ }
+
+ @Override
+ public String getPublicId() {
+ return publicId;
+ }
+
+ @Override
+ public String getEncoding() {
+ return null;
+ }
+
+ @Override
+ public Reader getCharacterStream() {
+ return null;
+ }
+
+ @Override
+ public boolean getCertifiedText() {
+ return false;
+ }
+
+ @Override
+ public InputStream getByteStream() {
+ return stream;
+ }
+
+ @Override
+ public String getBaseURI() {
+ return baseURI;
+ }
+ };
+ }
+ return null;
+ }
+ });
+ Schema schema = factory.newSchema(new StreamSource(xsdXsdStream));
+ Validator validator = schema.newValidator();
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ validator.setErrorHandler(handler);
+
+ validator.validate(new StreamSource(repoXsdStream));
+ handler.verify();
+ }
+
+ /** An helper that validates a string against an expected regexp. */
+ protected void assertRegex(String expectedRegexp, String actualString) {
+ assertNotNull(actualString);
+ assertTrue(
+ String.format("Regexp Assertion Failed:\nExpected: %s\nActual: %s\n",
+ expectedRegexp, actualString),
+ actualString.matches(expectedRegexp));
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgDescTest.java b/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgDescTest.java
new file mode 100755
index 0000000..42557c3
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgDescTest.java
@@ -0,0 +1,931 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.descriptors;
+
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
+
+import java.io.File;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+public class PkgDescTest extends TestCase {
+
+ public final File mRoot = new File("/sdk");
+
+ public final void testPkgDescTool_NotPreview() {
+ IPkgDesc p = PkgDesc.Builder.newTool(
+ new FullRevision(1, 2, 3),
+ new FullRevision(5, 6, 7, 8)).create();
+
+ assertEquals(PkgType.PKG_TOOLS, p.getType());
+
+ assertTrue (p.hasFullRevision());
+ assertEquals(new FullRevision(1, 2, 3), p.getFullRevision());
+ assertFalse (p.getFullRevision().isPreview());
+
+ assertFalse(p.hasMajorRevision());
+ assertNull (p.getMajorRevision());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertTrue (p.hasMinPlatformToolsRev());
+ assertEquals(new FullRevision(5, 6, 7, 8), p.getMinPlatformToolsRev());
+
+ assertEquals("tools", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "tools"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=tools FullRev=1.2.3 MinPlatToolsRev=5.6.7 rc8>", p.toString());
+ assertEquals("Android SDK Tools 1.2.3", p.getListDescription());
+ }
+
+ public final void testPkgDescTool_Preview() {
+ IPkgDesc p = PkgDesc.Builder.newTool(
+ new FullRevision(1, 2, 3, 4),
+ new FullRevision(5, 6, 7, 8)).create();
+
+ assertEquals(PkgType.PKG_TOOLS, p.getType());
+
+ assertTrue (p.hasFullRevision());
+ assertEquals(new FullRevision(1, 2, 3, 4), p.getFullRevision());
+ assertTrue (p.getFullRevision().isPreview());
+
+ assertFalse(p.hasMajorRevision());
+ assertNull (p.getMajorRevision());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertTrue (p.hasMinPlatformToolsRev());
+ assertEquals(new FullRevision(5, 6, 7, 8), p.getMinPlatformToolsRev());
+
+ assertEquals("tools-preview", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "tools"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=tools FullRev=1.2.3 rc4 MinPlatToolsRev=5.6.7 rc8>", p.toString());
+ assertEquals("Android SDK Tools 1.2.3 rc4", p.getListDescription());
+ }
+
+ public final void testPkgDescTool_Update() {
+ final FullRevision min5670 = new FullRevision(5, 6, 7, 0);
+ final IPkgDesc f123 =
+ PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 0), min5670).create();
+ final IPkgDesc f123b =
+ PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 0), min5670).create();
+
+ // can't update itself
+ assertFalse(f123 .isUpdateFor(f123b));
+ assertFalse(f123b.isUpdateFor(f123));
+ assertTrue (f123 .compareTo(f123b) == 0);
+ assertTrue (f123b.compareTo(f123 ) == 0);
+
+ // min-platform-tools-rev isn't used for updates checks
+ final FullRevision min5680 = new FullRevision(5, 6, 8, 0);
+ final IPkgDesc f123c =
+ PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 0), min5680).create();
+ assertFalse(f123c.isUpdateFor(f123));
+ // but it's used for comparisons
+ assertTrue (f123c.compareTo(f123) > 0);
+
+ // full revision is used for updated checks
+ final IPkgDesc f124 =
+ PkgDesc.Builder.newTool(new FullRevision(1, 2, 4, 0), min5670).create();
+ assertTrue (f124.isUpdateFor(f123));
+ assertFalse(f123.isUpdateFor(f124));
+ assertTrue (f124.compareTo(f123) > 0);
+
+ final IPkgDesc f122 =
+ PkgDesc.Builder.newTool(new FullRevision(1, 2, 2, 0), min5670).create();
+ assertTrue (f123.isUpdateFor(f122));
+ assertFalse(f122.isUpdateFor(f123));
+ assertTrue (f122.compareTo(f123) < 0);
+
+ // previews are not updated by final packages
+ final FullRevision min5671 = new FullRevision(5, 6, 7, 1);
+ final IPkgDesc p1231 =
+ PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 1), min5671).create();
+ assertFalse(p1231.isUpdateFor(f122));
+ assertFalse(f122 .isUpdateFor(p1231));
+ // but previews are used for comparisons
+ assertTrue (p1231.compareTo(f122 ) > 0);
+ assertTrue (f123 .compareTo(p1231) > 0);
+
+ final IPkgDesc p1232 =
+ PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 2), min5671).create();
+ assertTrue (p1232.isUpdateFor(p1231));
+ assertFalse(p1231.isUpdateFor(p1232));
+ assertTrue (p1232.compareTo(p1231) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescPlatformTool_NotPreview() {
+ IPkgDesc p = PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3)).create();
+
+ assertEquals(PkgType.PKG_PLATFORM_TOOLS, p.getType());
+
+ assertTrue (p.hasFullRevision());
+ assertEquals(new FullRevision(1, 2, 3), p.getFullRevision());
+ assertFalse (p.getFullRevision().isPreview());
+
+ assertFalse(p.hasMajorRevision());
+ assertNull (p.getMajorRevision());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("platform-tools", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "platform-tools"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=platform_tools FullRev=1.2.3>", p.toString());
+ assertEquals("Android SDK Platform-Tools 1.2.3", p.getListDescription());
+ }
+
+ public final void testPkgDescPlatformTool_Preview() {
+ IPkgDesc p = PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 4)).create();
+
+ assertEquals(PkgType.PKG_PLATFORM_TOOLS, p.getType());
+
+ assertTrue (p.hasFullRevision());
+ assertEquals(new FullRevision(1, 2, 3, 4), p.getFullRevision());
+ assertTrue (p.getFullRevision().isPreview());
+
+ assertFalse(p.hasMajorRevision());
+ assertNull (p.getMajorRevision());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("platform-tools-preview", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "platform-tools"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=platform_tools FullRev=1.2.3 rc4>", p.toString());
+ assertEquals("Android SDK Platform-Tools 1.2.3 rc4", p.getListDescription());
+ }
+
+ public final void testPkgDescPlatformTool_Update() {
+ final IPkgDesc f123 =
+ PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 0)).create();
+ final IPkgDesc f123b =
+ PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 0)).create();
+
+ // can't update itself
+ assertFalse(f123 .isUpdateFor(f123b));
+ assertFalse(f123b.isUpdateFor(f123));
+ assertTrue (f123 .compareTo(f123b) == 0);
+ assertTrue (f123b.compareTo(f123 ) == 0);
+
+ // full revision is used for updated checks
+ final IPkgDesc f124 =
+ PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 4, 0)).create();
+ assertTrue (f124.isUpdateFor(f123));
+ assertFalse(f123.isUpdateFor(f124));
+ assertTrue (f124.compareTo(f123) > 0);
+
+ final IPkgDesc f122 =
+ PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 2, 0)).create();
+ assertTrue (f123.isUpdateFor(f122));
+ assertFalse(f122.isUpdateFor(f123));
+ assertTrue (f122.compareTo(f123) < 0);
+
+ // previews are not updated by final packages
+ final IPkgDesc p1231 =
+ PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 1)).create();
+ assertFalse(p1231.isUpdateFor(f122));
+ assertFalse(f122 .isUpdateFor(p1231));
+ // but previews are used for comparisons
+ assertTrue (p1231.compareTo(f122 ) > 0);
+ assertTrue (f123 .compareTo(p1231) > 0);
+
+ final IPkgDesc p1232 =
+ PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 2)).create();
+ assertTrue (p1232.isUpdateFor(p1231));
+ assertFalse(p1231.isUpdateFor(p1232));
+ assertTrue (p1232.compareTo(p1231) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescDoc() throws Exception {
+ IPkgDesc p =
+ PkgDesc.Builder.newDoc(new AndroidVersion("19"), new MajorRevision(1)).create();
+
+ assertEquals(PkgType.PKG_DOC, p.getType());
+
+ assertFalse(p.hasFullRevision());
+ assertNull(p.getFullRevision());
+
+ assertTrue(p.hasMajorRevision());
+ assertEquals(new MajorRevision(1), p.getMajorRevision());
+
+ assertTrue(p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull(p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull(p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull(p.getMinPlatformToolsRev());
+
+ assertEquals("doc-19", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "docs"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=doc Android=API 19 MajorRev=1>", p.toString());
+ assertEquals("Documentation for Android SDK 19", p.getListDescription());
+ }
+
+ public final void testPkgDescDoc_Update() throws Exception {
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final MajorRevision rev1 = new MajorRevision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newDoc(api19, rev1).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newDoc(api19, rev1).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ final IPkgDesc p19_2 = PkgDesc.Builder.newDoc(api19, new MajorRevision(2)).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ final IPkgDesc p18_1 = PkgDesc.Builder.newDoc(new AndroidVersion("18"), rev1).create();
+ assertTrue (p19_1.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_1));
+ assertTrue (p19_1.compareTo(p18_1) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescBuildTool_NotPreview() {
+ IPkgDesc p = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3)).create();
+
+ assertEquals(PkgType.PKG_BUILD_TOOLS, p.getType());
+
+ assertTrue (p.hasFullRevision());
+ assertEquals(new FullRevision(1, 2, 3), p.getFullRevision());
+ assertFalse (p.getFullRevision().isPreview());
+
+ assertFalse(p.hasMajorRevision());
+ assertNull (p.getMajorRevision());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("build-tools-1.2.3", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "build-tools", "build-tools-1.2.3"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=build_tools FullRev=1.2.3>", p.toString());
+ assertEquals("Android SDK Build-Tools 1.2.3", p.getListDescription());
+ }
+
+ public final void testPkgDescBuildTool_Preview() {
+ IPkgDesc p = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 4)).create();
+
+ assertEquals(PkgType.PKG_BUILD_TOOLS, p.getType());
+
+ assertTrue (p.hasFullRevision());
+ assertEquals(new FullRevision(1, 2, 3, 4), p.getFullRevision());
+ assertTrue (p.getFullRevision().isPreview());
+
+ assertFalse(p.hasMajorRevision());
+ assertNull (p.getMajorRevision());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("build-tools-1.2.3_rc4", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "build-tools", "build-tools-1.2.3_rc4"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=build_tools FullRev=1.2.3 rc4>", p.toString());
+ assertEquals("Android SDK Build-Tools 1.2.3 rc4", p.getListDescription());
+ }
+
+ public final void testPkgDescBuildTool_Update() {
+ final IPkgDesc f123 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 0)).create();
+ final IPkgDesc f123b = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 0)).create();
+
+ // can't update itself
+ assertFalse(f123 .isUpdateFor(f123b));
+ assertFalse(f123b.isUpdateFor(f123));
+ assertTrue (f123 .compareTo(f123b) == 0);
+ assertTrue (f123b.compareTo(f123 ) == 0);
+
+ // build-tools is different as full revisions are installed side by side
+ // so they don't update each other (except for the preview bit, see below.)
+ final IPkgDesc f124 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 4, 0)).create();
+ assertFalse(f124.isUpdateFor(f123));
+ assertFalse(f123.isUpdateFor(f124));
+ // comparison is still done on the full revision.
+ assertTrue (f124.compareTo(f123) > 0);
+
+ final IPkgDesc f122 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 2, 0)).create();
+ assertFalse(f123.isUpdateFor(f122));
+ assertFalse(f122.isUpdateFor(f123));
+ assertTrue (f122.compareTo(f123) < 0);
+
+ // previews are not updated by final packages
+ final IPkgDesc p1231 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 1)).create();
+ assertFalse(p1231.isUpdateFor(f122));
+ assertFalse(f122 .isUpdateFor(p1231));
+ // but previews are used for comparisons
+ assertTrue (p1231.compareTo(f122 ) > 0);
+ assertTrue (f123 .compareTo(p1231) > 0);
+
+ // previews do update other packages that have the same major.minor.micro.
+ final IPkgDesc p1232 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 2)).create()
+ ;
+ assertTrue (p1232.isUpdateFor(p1231));
+ assertFalse(p1231.isUpdateFor(p1232));
+ assertTrue (p1232.compareTo(p1231) > 0);
+
+ final IPkgDesc p1222 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 2, 2)).create();
+ assertFalse(p1232.isUpdateFor(p1222));
+ }
+
+ //----
+
+ public final void testPkgDescExtra() {
+ IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
+ IPkgDesc p = PkgDesc.Builder
+ .newExtra(vendor,
+ "extra_path",
+ "My Extra",
+ new String[] { "old_path1", "old_path2" },
+ new NoPreviewRevision(1, 2, 3))
+ .create();
+
+ assertEquals(PkgType.PKG_EXTRA, p.getType());
+
+ assertTrue (p.hasFullRevision());
+ assertEquals(new FullRevision(1, 2, 3), p.getFullRevision());
+
+ assertFalse(p.hasMajorRevision());
+ assertNull (p.getMajorRevision());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertTrue (p.hasPath());
+ assertEquals("extra_path", p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("extra-vendor-extra_path", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "extras", "vendor", "extra_path"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=extra Vendor=vendor [The Vendor] Path=extra_path FullRev=1.2.3>", p.toString());
+ assertEquals("My Extra, rev 1.2.3", p.getListDescription());
+
+ IPkgDescExtra e = (IPkgDescExtra) p;
+ assertEquals("vendor [The Vendor]", e.getVendor().toString());
+ assertEquals("extra_path", e.getPath());
+ assertEquals("[old_path1, old_path2]", Arrays.toString(e.getOldPaths()));
+ }
+
+ public final void testPkgDescExtra_Update() {
+ IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
+ final NoPreviewRevision rev123 = new NoPreviewRevision(1, 2, 3);
+ final IPkgDesc p123 = PkgDesc.Builder
+ .newExtra(vendor, "extra_path", "My Extra", new String[0], rev123)
+ .create();
+ final IPkgDesc p123b = PkgDesc.Builder
+ .newExtra(vendor, "extra_path", "My Extra", new String[0], rev123)
+ .create();
+
+ // can't update itself
+ assertFalse(p123 .isUpdateFor(p123b));
+ assertFalse(p123b.isUpdateFor(p123));
+ assertTrue (p123 .compareTo(p123b) == 0);
+ assertTrue (p123b.compareTo(p123 ) == 0);
+
+ // updates a lesser revision of the same vendor/path
+ final NoPreviewRevision rev124 = new NoPreviewRevision(1, 2, 4);
+ final IPkgDesc p124 = PkgDesc.Builder
+ .newExtra(vendor, "extra_path", "My Extra", new String[0], rev124)
+ .create();
+ assertTrue (p124.isUpdateFor(p123));
+ assertTrue (p124.compareTo(p123) > 0);
+
+ // does not update a different vendor
+ IdDisplay vendor2 = new IdDisplay("different-vendor", "Not the same Vendor");
+ final IPkgDesc a124 = PkgDesc.Builder
+ .newExtra(vendor2, "extra_path", "My Extra", new String[0], rev124)
+ .create();
+ assertFalse(a124.isUpdateFor(p123));
+ assertTrue (a124.compareTo(p123) < 0);
+
+ // does not update a different extra path
+ final IPkgDesc n124 = PkgDesc.Builder
+ .newExtra(vendor, "no_va", "Oye Como Va", new String[0], rev124)
+ .create();
+ assertFalse(n124.isUpdateFor(p123));
+ assertTrue (n124.compareTo(p123) > 0);
+ // unless the old_paths mechanism is used to provide a way to update the path
+ final IPkgDesc o124 = PkgDesc.Builder
+ .newExtra(vendor, "no_va", "Oye Como Va", new String[] { "extra_path" }, rev124)
+ .create();
+ assertTrue (o124.isUpdateFor(p123));
+ assertTrue (o124.compareTo(p123) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescSource() throws Exception {
+ IPkgDesc p =
+ PkgDesc.Builder.newSource(new AndroidVersion("19"), new MajorRevision(1)).create();
+
+ assertEquals(PkgType.PKG_SOURCE, p.getType());
+
+ assertFalse(p.hasFullRevision());
+ assertNull (p.getFullRevision());
+
+ assertTrue (p.hasMajorRevision());
+ assertEquals(new MajorRevision(1), p.getMajorRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("source-19", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "sources", "android-19"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=source Android=API 19 MajorRev=1>", p.toString());
+ assertEquals("Sources for Android 19", p.getListDescription());
+ }
+
+ public final void testPkgDescSource_Update() throws Exception {
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final MajorRevision rev1 = new MajorRevision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newSource(api19, rev1).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newSource(api19, rev1).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // updates a lesser revision of the same API
+ final IPkgDesc p19_2 = PkgDesc.Builder.newSource(api19, new MajorRevision(2)).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final IPkgDesc p18_1 = PkgDesc.Builder.newSource(new AndroidVersion("18"), rev1).create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescSample() throws Exception {
+ IPkgDesc p = PkgDesc.Builder.newSample(new AndroidVersion("19"),
+ new MajorRevision(1),
+ new FullRevision(5, 6, 7, 8)).create();
+
+ assertEquals(PkgType.PKG_SAMPLE, p.getType());
+
+ assertFalse(p.hasFullRevision());
+ assertNull (p.getFullRevision());
+
+ assertTrue (p.hasMajorRevision());
+ assertEquals(new MajorRevision(1), p.getMajorRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertTrue (p.hasMinToolsRev());
+ assertEquals(new FullRevision(5, 6, 7, 8), p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("sample-19", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "samples", "android-19"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=sample Android=API 19 MajorRev=1 MinToolsRev=5.6.7 rc8>",
+ p.toString());
+ assertEquals("Samples for Android 19", p.getListDescription());
+ }
+
+ public final void testPkgDescSample_Update() throws Exception {
+ final FullRevision min5670 = new FullRevision(5, 6, 7, 0);
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final MajorRevision rev1 = new MajorRevision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newSample(api19, rev1, min5670).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newSample(api19, rev1, min5670).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // min-tools-rev isn't used for updates checks
+ final FullRevision min5680 = new FullRevision(5, 6, 8, 0);
+ final IPkgDesc p19_1c = PkgDesc.Builder.newSample(api19, rev1, min5680).create();
+ assertFalse(p19_1c.isUpdateFor(p19_1));
+ // but it's used for comparisons
+ assertTrue (p19_1c.compareTo(p19_1) > 0);
+
+ // updates a lesser revision of the same API
+ final IPkgDesc p19_2 =
+ PkgDesc.Builder.newSample(api19, new MajorRevision(2), min5670).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final IPkgDesc p18_1 =
+ PkgDesc.Builder.newSample(new AndroidVersion("18"), rev1, min5670).create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescPlatform() throws Exception {
+ IPkgDesc p = PkgDesc.Builder.newPlatform(new AndroidVersion("19"),
+ new MajorRevision(1),
+ new FullRevision(5, 6, 7, 8)).create();
+
+ assertEquals(PkgType.PKG_PLATFORM, p.getType());
+
+ assertFalse(p.hasFullRevision());
+ assertNull (p.getFullRevision());
+
+ assertTrue (p.hasMajorRevision());
+ assertEquals(new MajorRevision(1), p.getMajorRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertTrue (p.hasPath());
+ assertEquals("android-19", p.getPath());
+
+ assertTrue (p.hasMinToolsRev());
+ assertEquals(new FullRevision(5, 6, 7, 8), p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("android-19", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "platforms", "android-19"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=platform Android=API 19 Path=android-19 MajorRev=1 MinToolsRev=5.6.7 rc8>",
+ p.toString());
+ assertEquals("Android SDK Platform 19", p.getListDescription());
+ }
+
+ public final void testPkgDescPlatform_Update() throws Exception {
+ final FullRevision min5670 = new FullRevision(5, 6, 7, 0);
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final MajorRevision rev1 = new MajorRevision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newPlatform(api19, rev1, min5670).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newPlatform(api19, rev1, min5670).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // min-tools-rev isn't used for updates checks
+ final FullRevision min5680 = new FullRevision(5, 6, 8, 0);
+ final IPkgDesc p19_1c = PkgDesc.Builder.newPlatform(api19, rev1, min5680).create();
+ assertFalse(p19_1c.isUpdateFor(p19_1));
+ // but it's used for comparisons
+ assertTrue (p19_1c.compareTo(p19_1) > 0);
+
+ // updates a lesser revision of the same API
+ final IPkgDesc p19_2 =
+ PkgDesc.Builder.newPlatform(api19, new MajorRevision(2), min5670).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final IPkgDesc p18_1 =
+ PkgDesc.Builder.newPlatform(new AndroidVersion("18"), rev1, min5670).create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescAddon() throws Exception {
+ IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
+ IdDisplay name = new IdDisplay("addon_name", "The Add-on");
+ IPkgDesc p1 = PkgDesc.Builder
+ .newAddon(new AndroidVersion("19"), new MajorRevision(1), vendor, name)
+ .create();
+
+ assertEquals(PkgType.PKG_ADDON, p1.getType());
+
+ assertFalse(p1.hasFullRevision());
+ assertNull (p1.getFullRevision());
+
+ assertTrue (p1.hasMajorRevision());
+ assertEquals(new MajorRevision(1), p1.getMajorRevision());
+
+ assertTrue (p1.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p1.getAndroidVersion());
+
+ assertTrue (p1.hasPath());
+ assertEquals("The Vendor:The Add-on:19", p1.getPath());
+
+ assertFalse(p1.hasMinToolsRev());
+ assertNull (p1.getMinToolsRev());
+
+ assertFalse(p1.hasMinPlatformToolsRev());
+ assertNull (p1.getMinPlatformToolsRev());
+
+ assertTrue(p1.hasVendor());
+ assertEquals(new IdDisplay("vendor", "only the id is compared with"), p1.getVendor());
+
+ assertEquals(new IdDisplay("addon_name", "ignored"), ((IPkgDescAddon) p1).getName());
+
+ assertEquals("addon-addon_name-vendor-19", p1.getInstallId());
+ assertEquals(FileOp.append(mRoot, "add-ons", "addon-addon_name-vendor-19"),
+ p1.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=addon Android=API 19 Vendor=vendor [The Vendor] Path=The Vendor:The Add-on:19 MajorRev=1>",
+ p1.toString());
+ assertEquals("The Add-on, Android 19", p1.getListDescription());
+ }
+
+ public final void testPkgDescAddon_Update() throws Exception {
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final MajorRevision rev1 = new MajorRevision(1);
+ IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
+ IdDisplay name = new IdDisplay("addon_name", "The Add-on");
+ final IPkgDesc p19_1 = PkgDesc.Builder.newAddon(api19, rev1, vendor, name)
+ .create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newAddon(api19, rev1, vendor, name)
+ .create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // updates a lesser revision of the same API
+ final MajorRevision rev2 = new MajorRevision(2);
+ final IPkgDesc p19_2 = PkgDesc.Builder.newAddon(api19, rev2, vendor, name)
+ .create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final AndroidVersion api18 = new AndroidVersion("18");
+ final IPkgDesc p18_1 = PkgDesc.Builder.newAddon(api18, rev2, vendor, name)
+ .create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+
+ // does not update a different vendor
+ IdDisplay vendor2 = new IdDisplay("another_vendor", "Another Vendor");
+ final IPkgDesc a19_2 = PkgDesc.Builder.newAddon(api19, rev2, vendor2, name)
+ .create();
+ assertFalse(a19_2.isUpdateFor(p19_1));
+ assertTrue (a19_2.compareTo(p19_1) < 0);
+
+ // does not update a different add-on name
+ IdDisplay name2 = new IdDisplay("another_name", "Another Add-on");
+ final IPkgDesc n19_2 = PkgDesc.Builder.newAddon(api19, rev2, vendor, name2)
+ .create();
+ assertFalse(n19_2.isUpdateFor(p19_1));
+ assertTrue (n19_2.compareTo(p19_1) < 0);
+ }
+
+ //----
+
+ public final void testPkgDescSysImg_Platform() throws Exception {
+ IdDisplay tag = new IdDisplay("tag", "My Tag");
+ IPkgDesc p = PkgDesc.Builder.newSysImg(
+ new AndroidVersion("19"),
+ tag,
+ "eabi",
+ new MajorRevision(1)).create();
+
+ assertEquals(PkgType.PKG_SYS_IMAGE, p.getType());
+
+ assertFalse(p.hasFullRevision());
+ assertNull (p.getFullRevision());
+
+ assertTrue (p.hasMajorRevision());
+ assertEquals(new MajorRevision(1), p.getMajorRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertTrue (p.hasPath());
+ assertEquals("eabi", p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("sys-img-eabi-tag-19", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "system-images", "android-19", "tag", "eabi"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=sys_image Android=API 19 Tag=tag [My Tag] Path=eabi MajorRev=1>",
+ p.toString());
+ assertEquals("eabi System Image, Android 19", p.getListDescription());
+ }
+
+ public final void testPkgDescSysImg_Platform_Update() throws Exception {
+ IdDisplay tag1 = new IdDisplay("tag1", "My Tag 1");
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final MajorRevision rev1 = new MajorRevision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newSysImg(api19, tag1, "eabi", rev1).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newSysImg(api19, tag1, "eabi", rev1).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // updates a lesser revision of the same API
+ final IPkgDesc p19_2 =
+ PkgDesc.Builder.newSysImg(api19, tag1, "eabi", new MajorRevision(2)).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final IPkgDesc p18_1 =
+ PkgDesc.Builder.newSysImg(new AndroidVersion("18"), tag1, "eabi", rev1).create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+
+ // does not update a different ABI
+ final IPkgDesc p19_2c =
+ PkgDesc.Builder.newSysImg(api19, tag1, "ppc", new MajorRevision(2)).create();
+ assertFalse(p19_2c.isUpdateFor(p19_1));
+ assertTrue (p19_2c.compareTo(p19_1) > 0);
+
+ // does not update a different tag
+ IdDisplay tag2 = new IdDisplay("tag2", "My Tag 2");
+ final IPkgDesc p19_t2 =
+ PkgDesc.Builder.newSysImg(api19, tag2, "eabi", new MajorRevision(2)).create();
+ assertFalse(p19_t2.isUpdateFor(p19_1));
+ assertTrue (p19_t2.compareTo(p19_1) > 0);
+ }
+
+ public final void testPkgDescSysImg_Addon() throws Exception {
+ IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
+ IdDisplay name = new IdDisplay("addon_name", "The Add-on");
+ IPkgDesc p = PkgDesc.Builder.newAddonSysImg(
+ new AndroidVersion("19"),
+ vendor,
+ name,
+ "eabi",
+ new MajorRevision(1)).create();
+
+ assertEquals(PkgType.PKG_ADDON_SYS_IMAGE, p.getType());
+
+ assertFalse(p.hasFullRevision());
+ assertNull (p.getFullRevision());
+
+ assertTrue (p.hasMajorRevision());
+ assertEquals(new MajorRevision(1), p.getMajorRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertTrue (p.hasPath());
+ assertEquals("eabi", p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertTrue(p.hasVendor());
+ assertEquals(new IdDisplay("vendor", "only the id is compared with"), p.getVendor());
+
+ assertTrue(p.hasTag());
+ assertEquals(new IdDisplay("addon_name", "ignored"), p.getTag());
+
+ assertEquals("sys-img-eabi-addon-addon_name-vendor-19", p.getInstallId());
+ assertEquals(FileOp.append(mRoot, "system-images", "addon-addon_name-vendor-19", "eabi"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=addon_sys_image Android=API 19 Vendor=vendor [The Vendor] Tag=addon_name [The Add-on] Path=eabi MajorRev=1>",
+ p.toString());
+ assertEquals("The Vendor eabi System Image, Android 19", p.getListDescription());
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgTypeTest.java b/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgTypeTest.java
new file mode 100755
index 0000000..0103d0d
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgTypeTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.descriptors;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+public class PkgTypeTest extends TestCase {
+
+ public final void testPkgTypeTool() {
+ IPkgCapabilities p = PkgType.PKG_TOOLS;
+ assertFalse(p.hasMajorRevision());
+ assertTrue (p.hasFullRevision());
+ assertFalse(p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertTrue (p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypePlatformTool() {
+ IPkgCapabilities p = PkgType.PKG_PLATFORM_TOOLS;
+ assertFalse(p.hasMajorRevision());
+ assertTrue (p.hasFullRevision());
+ assertFalse(p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeDoc() {
+ IPkgCapabilities p = PkgType.PKG_DOC;
+ assertTrue (p.hasMajorRevision());
+ assertFalse(p.hasFullRevision());
+ assertTrue (p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeBuildTool() {
+ IPkgCapabilities p = PkgType.PKG_BUILD_TOOLS;
+
+ assertTrue (p.hasFullRevision());
+ assertFalse(p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeExtra() {
+ IPkgCapabilities p = PkgType.PKG_EXTRA;
+
+ assertFalse(p.hasMajorRevision());
+ assertTrue (p.hasFullRevision());
+ assertFalse(p.hasAndroidVersion());
+ assertTrue (p.hasPath());
+ assertFalse(p.hasTag());
+ assertTrue (p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeSource() throws Exception {
+ IPkgCapabilities p = PkgType.PKG_SOURCE;
+
+ assertTrue (p.hasMajorRevision());
+ assertFalse(p.hasFullRevision());
+ assertTrue (p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeSample() throws Exception {
+ IPkgCapabilities p = PkgType.PKG_SAMPLE;
+
+ assertTrue (p.hasMajorRevision());
+ assertFalse(p.hasFullRevision());
+ assertTrue (p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertTrue (p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypePlatform() throws Exception {
+ IPkgCapabilities p = PkgType.PKG_PLATFORM;
+
+ assertTrue (p.hasMajorRevision());
+ assertFalse(p.hasFullRevision());
+ assertTrue (p.hasAndroidVersion());
+ assertTrue (p.hasPath()); // platform path is its hash string
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertTrue (p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeAddon() throws Exception {
+ IPkgCapabilities p = PkgType.PKG_ADDON;
+
+ assertTrue (p.hasMajorRevision());
+ assertFalse(p.hasFullRevision());
+ assertTrue (p.hasAndroidVersion());
+ assertTrue (p.hasPath()); // add-on path is its hash string
+ assertFalse(p.hasTag());
+ assertTrue (p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeSysImg() throws Exception {
+ IPkgCapabilities p = PkgType.PKG_SYS_IMAGE;
+
+ assertTrue (p.hasMajorRevision());
+ assertFalse(p.hasFullRevision());
+ assertTrue (p.hasAndroidVersion());
+ assertTrue (p.hasPath()); // sys-img path is its ABI string
+ assertTrue (p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeAddonSysImg() throws Exception {
+ IPkgCapabilities p = PkgType.PKG_ADDON_SYS_IMAGE;
+
+ assertTrue (p.hasMajorRevision());
+ assertFalse(p.hasFullRevision());
+ assertTrue (p.hasAndroidVersion());
+ assertTrue (p.hasPath()); // sys-img path is its ABI string
+ assertTrue (p.hasTag());
+ assertTrue (p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgType_UniqueIntValues() {
+ // Check all types have a unique int value
+ Map<Integer, PkgType> ints = new HashMap<Integer, PkgType>();
+ for (PkgType type : PkgType.values()) {
+ Integer i = type.getIntValue();
+ if (ints.containsKey(i)) {
+ fail(String.format("Int value 0x%04x defined by both PkgType.%s and PkgType.%s",
+ i, type, ints.get(i)));
+ }
+ ints.put(i, type);
+ }
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/local/LocalDirInfoTest.java b/sdklib/src/test/java/com/android/sdklib/repository/local/LocalDirInfoTest.java
new file mode 100755
index 0000000..dea1c2e
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/local/LocalDirInfoTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.sdklib.io.FileOp;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+
+import junit.framework.TestCase;
+
+public class LocalDirInfoTest extends TestCase {
+
+ private final FileOp mFOp = new FileOp();
+ private File mTempDir;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTempDir = File.createTempFile("test", "dir");
+ assertTrue(mTempDir.delete());
+ assertTrue(mTempDir.mkdirs());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mFOp.deleteFileOrFolder(mTempDir);
+ mTempDir = null;
+ }
+
+ // Test: start with empty directory, removing the dir marks it as changed.
+ public final void testHasChanged_Empty() {
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Removing the dir marks it as changed
+ mFOp.deleteFileOrFolder(mTempDir);
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: start with empty directory, adding a file marks it as changed.
+ public final void testHasChanged_AddFile() throws Exception {
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Adding a file inside the dir marks it as changed
+ createFileContent(mTempDir, "some_file.txt", "whatever content");
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: removing any file marks it as changed.
+ public final void testHasChanged_RemoveFile() throws Exception {
+ File f = createFileContent(mTempDir, "some_file.txt", "whatever content");
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Removing a file inside the dir marks it as changed
+ mFOp.deleteFileOrFolder(f);
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: start with empty directory, adding a directory marks it as changed.
+ public final void testHasChanged_AddDir() throws Exception {
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Adding a file inside the dir marks it as changed
+ File dir = new File(mTempDir, "some_dir");
+ assertTrue(dir.mkdirs());
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: removing any directory marks it as changed.
+ public final void testHasChanged_RemoveDir() throws Exception {
+ File dir = new File(mTempDir, "some_dir");
+ assertTrue(dir.mkdirs());
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Removing a dir marks it as changed
+ mFOp.deleteFileOrFolder(dir);
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: directory that contains a source.properties, change source.properties's content
+ public final void testHasChanged_SourceProps_Changed() throws Exception {
+ createFileContent(mTempDir, "source.properties", "key=value");
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Change source.properties's content
+ createFileContent(mTempDir, "source.properties", "other_key=other_value");
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: directory that contains a source.properties, change source.properties's timestamp
+ public final void testHasChanged_SourceProps_Timestamp() throws Exception {
+ createFileContent(mTempDir, "source.properties", "key=value");
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Recreate source.properties with the same content, this changes its timestamp.
+ // Note: the last-modified resolution on Linux is 1 second on ext3/ext4 file systems
+ // so we need at least 1 second in between both edits other they will have the same
+ // last-modified value.
+ Thread.sleep(1100);
+ createFileContent(mTempDir, "source.properties", "key=value");
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: directory that contains a source.properties, change delete source.properties
+ public final void testHasChanged_SourceProps_Deleted() throws Exception {
+ File sp = createFileContent(mTempDir, "source.properties", "key=value");
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Removing the source.properties marks it as changed
+ mFOp.deleteFileOrFolder(sp);
+ assertTrue(di.hasChanged());
+ }
+
+ //---- Helpers
+
+ // Creates a new file with the specified name, in the specified
+ // parent directory with the given UTF-8 content.
+ private static File createFileContent(File parentDir,
+ String fileName,
+ String fileContent) throws IOException {
+ File f = new File(parentDir, fileName);
+ OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(f),
+ Charset.forName("UTF-8"));
+ try {
+ fw.write(fileContent);
+ } finally {
+ fw.close();
+ }
+ return f;
+ }
+
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/local/LocalSdkTest.java b/sdklib/src/test/java/com/android/sdklib/repository/local/LocalSdkTest.java
new file mode 100755
index 0000000..477490e
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/local/LocalSdkTest.java
@@ -0,0 +1,848 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.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 com.android.sdklib.repository.descriptors.PkgType;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.regex.Pattern;
+
+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_allPkgTypes() {
+ // Make sure getPkgInfo() can handle all defined package types.
+ for(PkgType type : PkgType.values()) {
+ mLS.getPkgsInfos(EnumSet.of(type));
+ }
+
+ // And do the same thing differently, using PKG_ALL
+ assertNotNull(mLS.getPkgsInfos(PkgType.PKG_ALL));
+ }
+
+ 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(PkgType.PKG_TOOLS));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.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\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.androidCmdName(), "placeholder");
+ mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.FN_EMULATOR, "placeholder");
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.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.getDesc().getFullRevision());
+ assertEquals(
+ "<LocalToolPkgInfo <PkgDesc Type=tools FullRev=22.3.4 MinPlatToolsRev=18.0.0>>",
+ pi.toString());
+ assertEquals("Android SDK Tools 22.3.4", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+
+ 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(PkgType.PKG_PLATFORM_TOOLS));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.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\\://example.com/repository-8.xml");
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.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.getDesc().getFullRevision());
+ assertEquals("<LocalPlatformToolPkgInfo <PkgDesc Type=platform_tools FullRev=18.19.20>>", pi.toString());
+ assertEquals("Android SDK Platform-Tools 18.19.20", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+
+ Package pkg = pi.getPackage();
+ assertNotNull(pkg);
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Docs() {
+ // check empty
+ assertNull(mLS.getPkgInfo(PkgType.PKG_DOC));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.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\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/docs/index.html", "placeholder");
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_DOC);
+ 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.getDesc().getMajorRevision());
+ assertEquals("<LocalDocPkgInfo <PkgDesc Type=doc Android=API 18 MajorRev=2>>", pi.toString());
+ assertEquals("Documentation for Android SDK 18, rev 2", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+
+ 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(PkgType.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\\://example.com/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(PkgType.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\\://example.com/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\\://example.com/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\\://example.com/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(PkgType.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.getDesc().getFullRevision());
+ assertEquals("Android SDK Build-Tools 18.1.2", pi.getListDescription());
+
+ Package pkg = pi.getPackage();
+ assertNotNull(pkg);
+
+ // -- get all build-tools and iterate, sorted by revision.
+
+ assertEquals("[<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=12.2.3>>, " +
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=17.0.0>>, " +
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=18.1.2>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_BUILD_TOOLS)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Extra() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_EXTRA)));
+ assertNull(mLS.getPkgInfo(PkgType.PKG_EXTRA, "vendor1", "path1"));
+ assertNull(mLS.getExtra("vendor1", "path1"));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.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=First 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=First 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(PkgType.PKG_EXTRA, "vendor1", "path1");
+ assertNotNull(pi1);
+ assertTrue(pi1 instanceof LocalExtraPkgInfo);
+ assertEquals(
+ "vendor1 [First Vendor]",
+ ((LocalExtraPkgInfo)pi1).getDesc().getVendor().toString());
+ assertEquals(
+ "path1",
+ ((LocalExtraPkgInfo)pi1).getDesc().getPath());
+ assertEquals(new File("/sdk/extras/vendor1/path1"), pi1.getLocalDir());
+ assertSame(mLS, pi1.getLocalSdk());
+ assertEquals(null, pi1.getLoadError());
+ assertEquals(new FullRevision(11), pi1.getDesc().getFullRevision());
+ assertEquals("Android Support Library, rev 11", pi1.getListDescription());
+ assertSame(pi1, mLS.getPkgInfo(pi1.getDesc()));
+
+ 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 <PkgDesc Type=extra Vendor=vendor1 [First Vendor] Path=path1 FullRev=11.0.0>>, " +
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=vendor1 [First Vendor] Path=path2 FullRev=21.0.0>>, " +
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=vendor2 [Another Vendor] Path=path1 FullRev=21.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_EXTRA)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Sources() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SOURCE)));
+ assertNull(mLS.getPkgInfo(PkgType.PKG_SOURCE, new AndroidVersion(18, null)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.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(PkgType.PKG_SOURCE, new AndroidVersion(18, null));
+ assertNotNull(pi18);
+ assertTrue(pi18 instanceof LocalSourcePkgInfo);
+ assertSame(mLS, pi18.getLocalSdk());
+ assertEquals(null, pi18.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi18.getDesc().getAndroidVersion());
+ assertEquals(new MajorRevision(2), pi18.getDesc().getMajorRevision());
+ assertEquals("Sources for Android 18, rev 2", pi18.getListDescription());
+
+ Package pkg = pi18.getPackage();
+ assertNotNull(pkg);
+
+ LocalPkgInfo pi1 = mLS.getPkgInfo(PkgType.PKG_SOURCE, new AndroidVersion(3, "CUPCAKE"));
+ assertNotNull(pi1);
+ assertEquals(new AndroidVersion(3, "CUPCAKE"), pi1.getDesc().getAndroidVersion());
+ assertEquals(new MajorRevision(1), pi1.getDesc().getMajorRevision());
+ assertEquals("Sources for Android CUPCAKE", pi1.getListDescription());
+ assertSame(pi1, mLS.getPkgInfo(pi1.getDesc()));
+
+ // -- get all extras and iterate, sorted by revision.
+
+ assertEquals("[<LocalSourcePkgInfo <PkgDesc Type=source Android=API 3, CUPCAKE preview MajorRev=1>>, " +
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 MajorRev=2>>, " +
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 42 MajorRev=3>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SOURCE)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Samples() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SAMPLE)));
+ assertNull(mLS.getPkgInfo(PkgType.PKG_SAMPLE, new AndroidVersion(18, null)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.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(PkgType.PKG_SAMPLE, new AndroidVersion(18, null));
+ assertNotNull(pi18);
+ assertTrue(pi18 instanceof LocalSamplePkgInfo);
+ assertSame(mLS, pi18.getLocalSdk());
+ assertEquals(null, pi18.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi18.getDesc().getAndroidVersion());
+ assertEquals(new MajorRevision(2), pi18.getDesc().getMajorRevision());
+ assertEquals("Samples for Android 18, rev 2", pi18.getListDescription());
+ assertSame(pi18, mLS.getPkgInfo(pi18.getDesc()));
+
+ Package pkg = pi18.getPackage();
+ assertNotNull(pkg);
+
+ // -- get all extras and iterate, sorted by revision.
+
+ assertEquals(
+ "[<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 18 MajorRev=2 MinToolsRev=0.0.0>>, " +
+ "<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 42 MajorRev=3 MinToolsRev=0.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SAMPLE)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_SysImages() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.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/armeabi");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/mips");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/somedir/armeabi-v7a");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-1/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips/skins");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips/skins/skinA");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips/skins/skinB");
+ // without tags
+ 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");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/armeabi-v7a/source.properties",
+ "Pkg.Revision=5\n" +
+ "SystemImage.Abi=armeabi-v7a\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ // with tags
+ mFOp.recordExistingFile("/sdk/system-images/android-42/somedir/armeabi-v7a/source.properties",
+ "Pkg.Revision=6\n" +
+ "SystemImage.TagId=default\n" + // Prop TagId is used instead of the "somedir" name
+ "SystemImage.Abi=armeabi-v7a\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/tag-1/x86/source.properties",
+ "Pkg.Revision=7\n" +
+ "SystemImage.TagId=tag-1\n" +
+ "SystemImage.TagDisplay=My Tag 1\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/tag-2/mips/source.properties",
+ "Pkg.Revision=8\n" +
+ "SystemImage.TagId=tag-2\n" +
+ "SystemImage.TagDisplay=My Tag 2\n" +
+ "SystemImage.Abi=mips\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/tag-2/mips/skins/skinA/layout",
+ "part {\n" +
+ "}\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/tag-2/mips/skins/skinB/layout",
+ "part {\n" +
+ "}\n");
+
+ assertEquals("[<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=armeabi-v7a MajorRev=1>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=x86 MajorRev=2>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=default [Default] Path=armeabi-v7a MajorRev=6>>, " +
+ // Tag=default Path=armeabi-v7a MajorRev=5 is overriden by the MajorRev=6 above
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=default [Default] Path=mips MajorRev=4>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=default [Default] Path=x86 MajorRev=3>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=tag-1 [My Tag 1] Path=x86 MajorRev=7>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=tag-2 [My Tag 2] Path=mips MajorRev=8>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)));
+
+ LocalPkgInfo pi = mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)[0];
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalSysImgPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new MajorRevision(1), pi.getDesc().getMajorRevision());
+ assertEquals("armeabi-v7a", pi.getDesc().getPath());
+ assertEquals("armeabi-v7a System Image, Android 18", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+
+ Package pkg = pi.getPackage();
+ assertNull(pkg);
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Platforms() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_PLATFORM)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_PLATFORM)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalPlatformPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
+ assertEquals(new MajorRevision(1), pi.getDesc().getMajorRevision());
+ assertEquals("Android SDK Platform 18", pi.getListDescription());
+
+ Package pkg = pi.getPackage();
+ assertNotNull(pkg);
+
+ IAndroidTarget t1 = ((LocalPlatformPkgInfo)pi).getAndroidTarget();
+ assertNotNull(t1);
+
+ LocalPkgInfo pi2 = mLS.getPkgInfo(PkgType.PKG_PLATFORM, "android-18");
+ assertSame(pi, pi2);
+
+ IAndroidTarget t2 = mLS.getTargetFromHashString("android-18");
+ assertSame(t1, t2);
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Platforms_SysImages_Skins() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+
+ mFOp.recordExistingFolder("/sdk/system-images");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-1/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips/skins");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips/skins/skinA");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips/skins/skinB");
+ mFOp.recordExistingFile("/sdk/system-images/android-18/tag-1/x86/source.properties",
+ "Pkg.Revision=7\n" +
+ "SystemImage.TagId=tag-1\n" +
+ "SystemImage.TagDisplay=My Tag 1\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-18/tag-2/mips/source.properties",
+ "Pkg.Revision=8\n" +
+ "SystemImage.TagId=tag-2\n" +
+ "SystemImage.TagDisplay=My Tag 2\n" +
+ "SystemImage.Abi=mips\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/tag-2/mips/skins/skinA/layout",
+ "part {\n" +
+ "}\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-18/tag-2/mips/skins/skinB/layout",
+ "part {\n" +
+ "}\n");
+
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [My Tag 1] Path=x86 MajorRev=7>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=tag-2 [My Tag 2] Path=mips MajorRev=8>>]",
+ Arrays.toString(
+ mLS.getPkgsInfos(EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_SYS_IMAGE))));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalPlatformPkgInfo);
+
+ IAndroidTarget t = ((LocalPlatformPkgInfo)pi).getAndroidTarget();
+ assertNotNull(t);
+
+ assertEquals(
+ "[SystemImage tag=tag-1, ABI=x86, location in system image='/sdk/system-images/android-18/tag-1/x86', " +
+ "SystemImage tag=tag-2, ABI=mips, location in system image='/sdk/system-images/android-18/tag-2/mips']",
+ sanitizePath(Arrays.toString(t.getSystemImages())));
+
+ assertEquals("/sdk/platforms/android-18/skins/WVGA800",
+ sanitizePath(t.getDefaultSkin().toString()));
+
+ assertEquals(
+ "[/sdk/system-images/android-18/tag-2/mips/skins/skinA, " +
+ "/sdk/system-images/android-18/tag-2/mips/skins/skinB]",
+ sanitizePath(Arrays.toString(t.getSkins())));
+
+ // check the skins paths from the system image also match what's in the platform
+ assertEquals(
+ "[/sdk/system-images/android-18/tag-2/mips/skins/skinA, " +
+ "/sdk/system-images/android-18/tag-2/mips/skins/skinB]",
+ sanitizePath(Arrays.toString(t.getSystemImages()[1].getSkins())));
+
+ assertEquals("Android SDK Platform 18", pi.getListDescription());
+ }
+
+ private String sanitizePath(String path) {
+ // On Windows the "/sdk" paths get transformed into an absolute "C:\\sdk"
+ // so we sanitize them back to "/sdk". On Linux/Mac, this is mostly a no-op.
+ String sdk = mLS.getLocation().getAbsolutePath();
+ path = path.replaceAll(Pattern.quote(sdk), "/sdk");
+ path = path.replace(File.separatorChar, '/');
+ return path;
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Platforms_Sources() {
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>]",
+ Arrays.toString(
+ mLS.getPkgsInfos(EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_SOURCE))));
+
+ // By default, IAndroidTarget returns the legacy path to a platform source,
+ // whether that directory exist or not.
+ LocalPkgInfo pi1 = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
+ IAndroidTarget t1 = ((LocalPlatformPkgInfo)pi1).getAndroidTarget();
+ assertEquals("/sdk/platforms/android-18/sources",
+ mFOp.getAgnosticAbsPath(t1.getPath(IAndroidTarget.SOURCES)));
+ assertEquals("Android SDK Platform 18", pi1.getListDescription());
+ assertSame(pi1, mLS.getPkgInfo(pi1.getDesc()));
+
+ // However if a separate sources package folder is installed, it is returned instead.
+ mLS.clearLocalPkg(PkgType.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(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
+ IAndroidTarget t2 = ((LocalPlatformPkgInfo)pi2).getAndroidTarget();
+ assertEquals("[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>, " +
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 MajorRev=2>>]",
+ Arrays.toString(mLS.getPkgsInfos(
+ EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_SOURCE))));
+ assertEquals("Android SDK Platform 18", pi2.getListDescription());
+ assertSame(pi2, mLS.getPkgInfo(pi2.getDesc()));
+
+ assertEquals("/sdk/sources/android-18",
+ mFOp.getAgnosticAbsPath(t2.getPath(IAndroidTarget.SOURCES)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Addons() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.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 <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>, " +
+ "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ALL)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_ADDON, "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.getDesc().getAndroidVersion());
+ assertEquals(new MajorRevision(2), pi.getDesc().getMajorRevision());
+ assertEquals("Some Vendor:Some Name:18", pi.getDesc().getPath());
+ assertEquals("Some Name, Android 18, rev 2", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+
+ 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/local/UpdateTest.java b/sdklib/src/test/java/com/android/sdklib/repository/local/UpdateTest.java
new file mode 100755
index 0000000..90fdc26
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/local/UpdateTest.java
@@ -0,0 +1,811 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.repository.local;
+
+import com.android.SdkConstants;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.io.MockFileOp;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repository.remote.RemotePkgInfo;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.TreeMultimap;
+
+import java.io.File;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+public class UpdateTest extends TestCase {
+
+ private MockFileOp mFOp;
+ private LocalSdk mLS;
+ private Multimap<PkgType, RemotePkgInfo> mRemotePkgs;
+ private IDescription mSource;
+
+ @Override
+ protected void setUp() {
+ mFOp = new MockFileOp();
+ mLS = new LocalSdk(mFOp);
+ mRemotePkgs = TreeMultimap.create();
+ mSource = new IDescription() {
+ @Override
+ public String getShortDescription() {
+ return "source";
+ }
+
+ @Override
+ public String getLongDescription() {
+ return "mock sdk repository source";
+ }
+ };
+
+ mLS.setLocation(new File("/sdk"));
+ }
+
+ public final void testComputeUpdates_Tools() throws Exception {
+ addLocalTool("22.3.4", "18");
+ addRemoteTool(new FullRevision(23), new FullRevision(19));
+ addRemoteTool(new FullRevision(23, 0, 1, 2), new FullRevision(19));
+
+ addLocalPlatformTool("1.0.2");
+ addRemotePlatformTool(new FullRevision(1, 0, 3));
+ addRemotePlatformTool(new FullRevision(2, 0, 4, 5));
+
+ addLocalBuildTool("18.0.0");
+ addLocalBuildTool("19.0.0");
+ addRemoteBuildTool(new FullRevision(18, 0, 1));
+ addRemoteBuildTool(new FullRevision(19, 1, 2));
+
+ LocalPkgInfo[] allLocalPkgs = mLS.getPkgsInfos(PkgType.PKG_ALL);
+
+ UpdateResult result = Update.computeUpdates(allLocalPkgs, mRemotePkgs);
+
+ assertNotNull(result);
+ assertEquals(
+ "[" +
+ "<LocalToolPkgInfo <PkgDesc Type=tools FullRev=22.3.4 MinPlatToolsRev=18.0.0> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=tools FullRev=23.0.0 MinPlatToolsRev=19.0.0>>>\n" +
+
+ "<LocalPlatformToolPkgInfo <PkgDesc Type=platform_tools FullRev=1.0.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=platform_tools FullRev=1.0.3>>>\n" +
+
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=18.0.0>>\n" +
+
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=19.0.0>>" +
+ "]",
+ Arrays.toString(allLocalPkgs).replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<LocalToolPkgInfo <PkgDesc Type=tools FullRev=22.3.4 MinPlatToolsRev=18.0.0> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=tools FullRev=23.0.0 MinPlatToolsRev=19.0.0>>>\n" +
+
+ "<LocalPlatformToolPkgInfo <PkgDesc Type=platform_tools FullRev=1.0.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=platform_tools FullRev=1.0.3>>>" +
+ "]",
+ result.getUpdatedPkgs().toString().replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=tools FullRev=23.0.1 rc2 MinPlatToolsRev=19.0.0>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=platform_tools FullRev=2.0.4 rc5>>\n" +
+
+ "<RemotePkgInfo Source:source <PkgDesc Type=build_tools FullRev=18.0.1>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=build_tools FullRev=19.1.2>>" +
+ "]",
+ result.getNewPkgs().toString().replace(", ", "\n"));
+ }
+
+ public final void testComputeUpdates_DocExtras() throws Exception {
+ final AndroidVersion api18 = new AndroidVersion("18");
+ final AndroidVersion api19 = new AndroidVersion("19");
+
+ final IdDisplay vendor = new IdDisplay("android", "The Android");
+
+ addLocalDoc (api18, "1");
+ addRemoteDoc(api18, new MajorRevision(2));
+ addRemoteDoc(api19, new MajorRevision(3));
+
+ addLocalExtra("18.0.1", vendor, "support");
+ addLocalExtra("18.0.2", vendor, "compat");
+ addRemoteExtra(new NoPreviewRevision(18, 3, 4), vendor, "support", "The Support Lib");
+ addRemoteExtra(new NoPreviewRevision(18, 5, 6), vendor, "compat", "The Compat Lib");
+ addRemoteExtra(new NoPreviewRevision(19, 7, 8), vendor, "whatever", "The Whatever Lib");
+
+ LocalPkgInfo[] allLocalPkgs = mLS.getPkgsInfos(PkgType.PKG_ALL);
+
+ UpdateResult result = Update.computeUpdates(allLocalPkgs, mRemotePkgs);
+
+ assertNotNull(result);
+ assertEquals(
+ "[" +
+ "<LocalDocPkgInfo <PkgDesc Type=doc Android=API 18 MajorRev=1> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=doc Android=API 19 MajorRev=3>>>\n" +
+
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=android [The Android] Path=compat FullRev=18.0.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=compat FullRev=18.5.6>>>\n" +
+
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=android [The Android] Path=support FullRev=18.0.1> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=support FullRev=18.3.4>>>" +
+ "]",
+ Arrays.toString(allLocalPkgs).replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<LocalDocPkgInfo <PkgDesc Type=doc Android=API 18 MajorRev=1> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=doc Android=API 19 MajorRev=3>>>\n" +
+
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=android [The Android] Path=compat FullRev=18.0.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=compat FullRev=18.5.6>>>\n" +
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=android [The Android] Path=support FullRev=18.0.1> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=support FullRev=18.3.4>>>" +
+ "]",
+ result.getUpdatedPkgs().toString().replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=whatever FullRev=19.7.8>>" +
+ "]",
+ result.getNewPkgs().toString().replace(", ", "\n"));
+ }
+
+ public final void testComputeUpdates_Platforms() throws Exception {
+ final AndroidVersion api18 = new AndroidVersion("18");
+ final AndroidVersion api19 = new AndroidVersion("19");
+
+ final IdDisplay tagDefault = new IdDisplay("default", "Default");
+ final IdDisplay tag1 = new IdDisplay("tag-1", "Tag 1");
+ final IdDisplay tag2 = new IdDisplay("tag-2", "Tag 2");
+
+ addLocalPlatform (api18, "2", "22.1.2");
+ addLocalSource (api18, "3");
+ addLocalSample (api18, "4", "22.1.2");
+ addLocalSysImg (api18, "5", null, "eabi");
+ addLocalSysImg (api18, "6", tag1, "eabi");
+ addRemotePlatform(api18, new MajorRevision(12), new FullRevision(22));
+ addRemoteSource (api18, new MajorRevision(13));
+ addRemoteSample (api18, new MajorRevision(14), new FullRevision(22));
+ addRemoteSysImg (api18, new MajorRevision(15), tagDefault, "eabi");
+ addRemoteSysImg (api18, new MajorRevision(16), tag1, "eabi");
+
+ addRemotePlatform(api19, new MajorRevision(22), new FullRevision(23));
+ addRemoteSource (api19, new MajorRevision(23));
+ addRemoteSample (api19, new MajorRevision(24), new FullRevision(23));
+ addRemoteSysImg (api19, new MajorRevision(25), tagDefault, "eabi");
+ addRemoteSysImg (api19, new MajorRevision(26), tag1, "eabi");
+ addRemoteSysImg (api19, new MajorRevision(27), tag2, "eabi");
+
+ LocalPkgInfo[] allLocalPkgs = mLS.getPkgsInfos(PkgType.PKG_ALL);
+
+ UpdateResult result = Update.computeUpdates(allLocalPkgs, mRemotePkgs);
+
+ assertNotNull(result);
+ assertEquals(
+ "[" +
+ "<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=2 MinToolsRev=22.1.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=12 MinToolsRev=22.0.0>>>\n" +
+
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=eabi MajorRev=5> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=eabi MajorRev=15>>>\n" +
+
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [Tag 1] Path=eabi MajorRev=6> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [Tag 1] Path=eabi MajorRev=16>>>\n" +
+
+ "<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 18 MajorRev=4 MinToolsRev=22.1.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sample Android=API 18 MajorRev=14 MinToolsRev=22.0.0>>>\n" +
+
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 MajorRev=3> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=source Android=API 18 MajorRev=13>>>" +
+ "]",
+ Arrays.toString(allLocalPkgs).replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=2 MinToolsRev=22.1.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=12 MinToolsRev=22.0.0>>>\n" +
+
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=eabi MajorRev=5> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=eabi MajorRev=15>>>\n" +
+
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [Tag 1] Path=eabi MajorRev=6> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [Tag 1] Path=eabi MajorRev=16>>>\n" +
+
+ "<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 18 MajorRev=4 MinToolsRev=22.1.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sample Android=API 18 MajorRev=14 MinToolsRev=22.0.0>>>\n" +
+
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 MajorRev=3> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=source Android=API 18 MajorRev=13>>>" +
+ "]",
+ result.getUpdatedPkgs().toString().replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=platform Android=API 19 Path=android-19 MajorRev=22 MinToolsRev=23.0.0>>\n" +
+
+ "<RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 19 Tag=default [Default] Path=eabi MajorRev=25>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 19 Tag=tag-1 [Tag 1] Path=eabi MajorRev=26>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 19 Tag=tag-2 [Tag 2] Path=eabi MajorRev=27>>\n" +
+
+ "<RemotePkgInfo Source:source <PkgDesc Type=sample Android=API 19 MajorRev=24 MinToolsRev=23.0.0>>\n" +
+
+ "<RemotePkgInfo Source:source <PkgDesc Type=source Android=API 19 MajorRev=23>>" +
+ "]",
+ result.getNewPkgs().toString().replace(", ", "\n"));
+ }
+
+ public final void testComputeUpdates_Addons() throws Exception {
+ final AndroidVersion api18 = new AndroidVersion("18");
+ final AndroidVersion api19 = new AndroidVersion("19");
+
+ final IdDisplay vendor1 = new IdDisplay("android", "The Android");
+ final IdDisplay name1 = new IdDisplay("cool_addon", "The Add-on");
+
+ final IdDisplay vendor2 = new IdDisplay("vendor2", "Vendor Too");
+ final IdDisplay name2 = new IdDisplay("addon-2", "Add-on Too");
+
+ addLocalAddOn (api18, "7", vendor1, name1);
+ addLocalAddonSysImg (api18, "8", vendor1, name1, "abi32");
+ addLocalAddonSysImg (api18, "9", vendor1, name1, "abi64"); // no update
+ addRemoteAddOn (api18, new MajorRevision(17), vendor1, name1);
+ addRemoteAddonSysImg(api18, new MajorRevision(18), vendor1, name1, "abi32");
+ addRemoteAddonSysImg(api18, new MajorRevision(19), vendor1, name1, "abi96"); //wrong abi
+ // these remote sys-img do not match the right vendor/name.
+ addRemoteAddonSysImg(api18, new MajorRevision(18), vendor2, name1, "abi64");
+ addRemoteAddonSysImg(api18, new MajorRevision(18), vendor1, name2, "abi64");
+ addRemoteAddonSysImg(api18, new MajorRevision(18), vendor2, name2, "abi64");
+
+ addRemoteAddOn (api19, new MajorRevision(27), vendor1, name1);
+ addRemoteAddonSysImg(api19, new MajorRevision(28), vendor1, name1, "abi32");
+ addRemoteAddonSysImg(api19, new MajorRevision(29), vendor1, name1, "abi64");
+
+ LocalPkgInfo[] allLocalPkgs = mLS.getPkgsInfos(PkgType.PKG_ALL);
+
+ UpdateResult result = Update.computeUpdates(allLocalPkgs, mRemotePkgs);
+
+ assertNotNull(result);
+ assertEquals(
+ "[" +
+ "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=android [The Android] Path=The Android:The Add-on:18 MajorRev=7> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=addon Android=API 18 Vendor=android [The Android] Path=The Android:The Add-on:18 MajorRev=17>>>\n" +
+ "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=8> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=18>>>\n" +
+ "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi64 MajorRev=9>>" +
+ "]",
+ Arrays.toString(allLocalPkgs).replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=android [The Android] Path=The Android:The Add-on:18 MajorRev=7> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=addon Android=API 18 Vendor=android [The Android] Path=The Android:The Add-on:18 MajorRev=17>>>\n" +
+ "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=8> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=18>>>" +
+ "]",
+ result.getUpdatedPkgs().toString().replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon Android=API 19 Vendor=android [The Android] Path=The Android:The Add-on:19 MajorRev=27>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=addon-2 [Add-on Too] Path=abi64 MajorRev=18>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi96 MajorRev=19>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=vendor2 [Vendor Too] Tag=addon-2 [Add-on Too] Path=abi64 MajorRev=18>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=vendor2 [Vendor Too] Tag=cool_addon [The Add-on] Path=abi64 MajorRev=18>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 19 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=28>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 19 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi64 MajorRev=29>>" +
+ "]",
+ result.getNewPkgs().toString().replace(", ", "\n"));
+
+ }
+
+ // --- all of the above at the same time
+
+ public final void testComputeUpdates_All() throws Exception {
+ final AndroidVersion api18 = new AndroidVersion("18");
+ final AndroidVersion api19 = new AndroidVersion("19");
+
+ final IdDisplay tagDefault = new IdDisplay("default", "Default");
+ final IdDisplay tag1 = new IdDisplay("tag-1", "Tag 1");
+ final IdDisplay tag2 = new IdDisplay("tag-2", "Tag 2");
+
+ final IdDisplay vendor = new IdDisplay("android", "The Android");
+ final IdDisplay vendor1 = new IdDisplay("android", "The Android");
+ final IdDisplay name1 = new IdDisplay("cool_addon", "The Add-on");
+
+ final IdDisplay vendor2 = new IdDisplay("vendor2", "Vendor Too");
+ final IdDisplay name2 = new IdDisplay("addon-2", "Add-on Too");
+
+ //---
+ addLocalTool("22.3.4", "18");
+ addRemoteTool(new FullRevision(23), new FullRevision(19));
+ addRemoteTool(new FullRevision(23, 0, 1, 2), new FullRevision(19));
+
+ addLocalPlatformTool("1.0.2");
+ addRemotePlatformTool(new FullRevision(1, 0, 3));
+ addRemotePlatformTool(new FullRevision(2, 0, 4, 5));
+
+ addLocalBuildTool("18.0.0");
+ addLocalBuildTool("19.0.0");
+ addRemoteBuildTool(new FullRevision(18, 0, 1));
+ addRemoteBuildTool(new FullRevision(19, 1, 2));
+
+ //---
+ addLocalDoc (api18, "1");
+ addRemoteDoc(api18, new MajorRevision(2));
+ addRemoteDoc(api19, new MajorRevision(3));
+
+ addLocalExtra("18.0.1", vendor, "support");
+ addLocalExtra("18.0.2", vendor, "compat");
+ addRemoteExtra(new NoPreviewRevision(18, 3, 4), vendor, "support", "The Support Lib");
+ addRemoteExtra(new NoPreviewRevision(18, 5, 6), vendor, "compat", "The Compat Lib");
+ addRemoteExtra(new NoPreviewRevision(19, 7, 8), vendor, "whatever", "The Whatever Lib");
+
+ //---
+
+ addLocalPlatform (api18, "2", "22.1.2");
+ addLocalSource (api18, "3");
+ addLocalSample (api18, "4", "22.1.2");
+ addLocalSysImg (api18, "5", null, "eabi");
+ addLocalSysImg (api18, "6", tag1, "eabi");
+ addRemotePlatform(api18, new MajorRevision(12), new FullRevision(22));
+ addRemoteSource (api18, new MajorRevision(13));
+ addRemoteSample (api18, new MajorRevision(14), new FullRevision(22));
+ addRemoteSysImg (api18, new MajorRevision(15), tagDefault, "eabi");
+ addRemoteSysImg (api18, new MajorRevision(16), tag1, "eabi");
+
+ addRemotePlatform(api19, new MajorRevision(22), new FullRevision(23));
+ addRemoteSource (api19, new MajorRevision(23));
+ addRemoteSample (api19, new MajorRevision(24), new FullRevision(23));
+ addRemoteSysImg (api19, new MajorRevision(25), tagDefault, "eabi");
+ addRemoteSysImg (api19, new MajorRevision(26), tag1, "eabi");
+ addRemoteSysImg (api19, new MajorRevision(27), tag2, "eabi");
+
+ //---
+ addLocalAddOn (api18, "7", vendor1, name1);
+ addLocalAddonSysImg (api18, "8", vendor1, name1, "abi32");
+ addLocalAddonSysImg (api18, "9", vendor1, name1, "abi64"); // no update
+ addRemoteAddOn (api18, new MajorRevision(17), vendor1, name1);
+ addRemoteAddonSysImg(api18, new MajorRevision(18), vendor1, name1, "abi32");
+ addRemoteAddonSysImg(api18, new MajorRevision(19), vendor1, name1, "abi96"); //wrong abi
+ // these remote sys-img do not match the right vendor/name.
+ addRemoteAddonSysImg(api18, new MajorRevision(18), vendor2, name1, "abi64");
+ addRemoteAddonSysImg(api18, new MajorRevision(18), vendor1, name2, "abi64");
+ addRemoteAddonSysImg(api18, new MajorRevision(18), vendor2, name2, "abi64");
+
+ addRemoteAddOn (api19, new MajorRevision(27), vendor1, name1);
+ addRemoteAddonSysImg(api19, new MajorRevision(28), vendor1, name1, "abi32");
+ addRemoteAddonSysImg(api19, new MajorRevision(29), vendor1, name1, "abi64");
+
+
+ //---
+
+ LocalPkgInfo[] allLocalPkgs = mLS.getPkgsInfos(PkgType.PKG_ALL);
+
+ UpdateResult result = Update.computeUpdates(allLocalPkgs, mRemotePkgs);
+
+ assertNotNull(result);
+ assertEquals(
+ "[" +
+ "<LocalToolPkgInfo <PkgDesc Type=tools FullRev=22.3.4 MinPlatToolsRev=18.0.0> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=tools FullRev=23.0.0 MinPlatToolsRev=19.0.0>>>\n" +
+
+ "<LocalPlatformToolPkgInfo <PkgDesc Type=platform_tools FullRev=1.0.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=platform_tools FullRev=1.0.3>>>\n" +
+
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=18.0.0>>\n" +
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=19.0.0>>\n" +
+ //---
+ "<LocalDocPkgInfo <PkgDesc Type=doc Android=API 18 MajorRev=1> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=doc Android=API 19 MajorRev=3>>>\n" +
+ //---
+ "<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=2 MinToolsRev=22.1.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=12 MinToolsRev=22.0.0>>>\n" +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=eabi MajorRev=5> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=eabi MajorRev=15>>>\n" +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [Tag 1] Path=eabi MajorRev=6> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [Tag 1] Path=eabi MajorRev=16>>>\n" +
+ //---
+ "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=android [The Android] Path=The Android:The Add-on:18 MajorRev=7> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=addon Android=API 18 Vendor=android [The Android] Path=The Android:The Add-on:18 MajorRev=17>>>\n" +
+ "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=8> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=18>>>\n" +
+ "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi64 MajorRev=9>>\n" +
+ //---
+ "<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 18 MajorRev=4 MinToolsRev=22.1.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sample Android=API 18 MajorRev=14 MinToolsRev=22.0.0>>>\n" +
+
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 MajorRev=3> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=source Android=API 18 MajorRev=13>>>\n" +
+ //---
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=android [The Android] Path=compat FullRev=18.0.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=compat FullRev=18.5.6>>>\n" +
+
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=android [The Android] Path=support FullRev=18.0.1> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=support FullRev=18.3.4>>>" +
+ "]",
+ Arrays.toString(allLocalPkgs).replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<LocalToolPkgInfo <PkgDesc Type=tools FullRev=22.3.4 MinPlatToolsRev=18.0.0> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=tools FullRev=23.0.0 MinPlatToolsRev=19.0.0>>>\n" +
+
+ "<LocalPlatformToolPkgInfo <PkgDesc Type=platform_tools FullRev=1.0.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=platform_tools FullRev=1.0.3>>>\n" +
+ //---
+ "<LocalDocPkgInfo <PkgDesc Type=doc Android=API 18 MajorRev=1> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=doc Android=API 19 MajorRev=3>>>\n" +
+ //---
+ "<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=2 MinToolsRev=22.1.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=12 MinToolsRev=22.0.0>>>\n" +
+
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=eabi MajorRev=5> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=eabi MajorRev=15>>>\n" +
+
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [Tag 1] Path=eabi MajorRev=6> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [Tag 1] Path=eabi MajorRev=16>>>\n" +
+ //---
+ "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=android [The Android] Path=The Android:The Add-on:18 MajorRev=7> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=addon Android=API 18 Vendor=android [The Android] Path=The Android:The Add-on:18 MajorRev=17>>>\n" +
+ "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=8> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=18>>>\n" +
+ //---
+ "<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 18 MajorRev=4 MinToolsRev=22.1.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=sample Android=API 18 MajorRev=14 MinToolsRev=22.0.0>>>\n" +
+
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 MajorRev=3> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=source Android=API 18 MajorRev=13>>>\n" +
+ //---
+
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=android [The Android] Path=compat FullRev=18.0.2> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=compat FullRev=18.5.6>>>\n" +
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=android [The Android] Path=support FullRev=18.0.1> " +
+ "Updated by: <RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=support FullRev=18.3.4>>>" +
+ "]",
+ result.getUpdatedPkgs().toString().replace(", ", "\n"));
+ assertEquals(
+ "[" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=tools FullRev=23.0.1 rc2 MinPlatToolsRev=19.0.0>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=platform_tools FullRev=2.0.4 rc5>>\n" +
+
+ "<RemotePkgInfo Source:source <PkgDesc Type=build_tools FullRev=18.0.1>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=build_tools FullRev=19.1.2>>\n" +
+ //---
+ "<RemotePkgInfo Source:source <PkgDesc Type=platform Android=API 19 Path=android-19 MajorRev=22 MinToolsRev=23.0.0>>\n" +
+
+ "<RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 19 Tag=default [Default] Path=eabi MajorRev=25>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 19 Tag=tag-1 [Tag 1] Path=eabi MajorRev=26>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=sys_image Android=API 19 Tag=tag-2 [Tag 2] Path=eabi MajorRev=27>>\n" +
+ //---
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon Android=API 19 Vendor=android [The Android] Path=The Android:The Add-on:19 MajorRev=27>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=addon-2 [Add-on Too] Path=abi64 MajorRev=18>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi96 MajorRev=19>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=vendor2 [Vendor Too] Tag=addon-2 [Add-on Too] Path=abi64 MajorRev=18>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 18 Vendor=vendor2 [Vendor Too] Tag=cool_addon [The Add-on] Path=abi64 MajorRev=18>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 19 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi32 MajorRev=28>>\n" +
+ "<RemotePkgInfo Source:source <PkgDesc Type=addon_sys_image Android=API 19 Vendor=android [The Android] Tag=cool_addon [The Add-on] Path=abi64 MajorRev=29>>\n" +
+ //---
+ "<RemotePkgInfo Source:source <PkgDesc Type=sample Android=API 19 MajorRev=24 MinToolsRev=23.0.0>>\n" +
+
+ "<RemotePkgInfo Source:source <PkgDesc Type=source Android=API 19 MajorRev=23>>\n" +
+ //---
+ "<RemotePkgInfo Source:source <PkgDesc Type=extra Vendor=android [The Android] Path=whatever FullRev=19.7.8>>" +
+ "]",
+ result.getNewPkgs().toString().replace(", ", "\n"));
+
+ }
+
+ //---
+
+ private void addLocalTool(String fullRev, String minPlatToolsRev) {
+ mFOp.recordExistingFolder("/sdk/tools");
+ mFOp.recordExistingFile("/sdk/tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=" + fullRev + "\n" +
+ "Platform.MinPlatformToolsRev=" + minPlatToolsRev + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.androidCmdName(), "placeholder");
+ mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.FN_EMULATOR, "placeholder");
+ }
+
+ private void addLocalPlatformTool(String fullRev) {
+ mFOp.recordExistingFolder("/sdk/platform-tools");
+ mFOp.recordExistingFile("/sdk/platform-tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=" + fullRev + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ }
+
+ private void addLocalDoc(AndroidVersion version, String majorRev) {
+ mFOp.recordExistingFolder("/sdk/docs");
+ mFOp.recordExistingFile("/sdk/docs/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=" + version.getApiString() + "\n" +
+ "Pkg.Revision=" + majorRev + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/docs/index.html", "placeholder");
+ }
+
+ private void addLocalBuildTool(String fullRev) {
+ mFOp.recordExistingFolder("/sdk/build-tools");
+ mFOp.recordExistingFolder("/sdk/build-tools/" + fullRev);
+ mFOp.recordExistingFile("/sdk/build-tools/" + fullRev + "/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=" + fullRev + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ }
+
+ private void addLocalExtra(String fullRev, IdDisplay vendor, String path) {
+ mFOp.recordExistingFolder("/sdk/extras");
+ mFOp.recordExistingFolder("/sdk/extras/" + vendor.getId());
+ mFOp.recordExistingFolder("/sdk/extras/" + vendor.getId() + "/" + path);
+ mFOp.recordExistingFile("/sdk/extras/" + vendor.getId() + "/" + path + "/source.properties",
+ "Extra.NameDisplay=Android Support Library\n" +
+ "Extra.VendorDisplay=" + vendor.getDisplay() + "\n" +
+ "Extra.VendorId=" + vendor.getId() + "\n" +
+ "Extra.Path=" + path + "\n" +
+ "Extra.OldPaths=compatibility\n" +
+ "Archive.Os=ANY\n" +
+ "Pkg.Revision=" + fullRev + "\n" +
+ "Archive.Arch=ANY\n");
+ }
+
+ private void addLocalSource(AndroidVersion version, String majorRev) {
+ String api = version.getApiString();
+ mFOp.recordExistingFolder("/sdk/sources");
+ mFOp.recordExistingFolder("/sdk/sources/android-" + api);
+ mFOp.recordExistingFile("/sdk/sources/android-" + api + "/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=" + api + "\n" +
+ "AndroidVersion.CodeName=\n" +
+ "Pkg.Revision=" + majorRev + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+ }
+
+ private void addLocalSample(AndroidVersion version, String majorRev, String minToolsRev) {
+ String api = version.getApiString();
+ mFOp.recordExistingFolder("/sdk/samples");
+ mFOp.recordExistingFolder("/sdk/samples/android-" + api);
+ mFOp.recordExistingFile("/sdk/samples/android-" + api + "/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=" + api + "\n" +
+ "AndroidVersion.CodeName=\n" +
+ "Pkg.Revision=" + majorRev + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Platform.MinToolsRev=" + minToolsRev + "\n" +
+ "Archive.Arch=ANY\n");
+ }
+
+ private void addLocalSysImg(AndroidVersion version, String majorRev, IdDisplay tag, String abi) {
+ String api = version.getApiString();
+ String tagDir = (tag == null ? "" : "/" + tag.getId());
+ mFOp.recordExistingFolder("/sdk/system-images");
+ mFOp.recordExistingFolder("/sdk/system-images/android-" + api + tagDir);
+ mFOp.recordExistingFolder("/sdk/system-images/android-" + api + tagDir + "/" + abi);
+ mFOp.recordExistingFile ("/sdk/system-images/android-" + api + tagDir + "/" + abi +"/source.properties",
+ "SystemImage.Abi=" + abi + "\n" +
+ (tag == null ? "" : ("SystemImage.TagId=" + tag.getId())) + "\n" +
+ (tag == null ? "" : ("SystemImage.TagDisplay=" + tag.getDisplay())) + "\n" +
+ "Pkg.Revision=" + majorRev + "\n" +
+ "AndroidVersion.ApiLevel=" + api + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ }
+
+ private void addLocalAddonSysImg(AndroidVersion version, String majorRev, IdDisplay vendor, IdDisplay tag, String abi) {
+ String api = version.getApiString();
+ String addon_dir = "addon-" + vendor.getId() + "-" + tag.getId();
+ mFOp.recordExistingFolder("/sdk/system-images");
+ mFOp.recordExistingFolder("/sdk/system-images/" + addon_dir);
+ mFOp.recordExistingFolder("/sdk/system-images/" + addon_dir + "/" + abi);
+ mFOp.recordExistingFile ("/sdk/system-images/" + addon_dir + "/" + abi + "/source.properties",
+ "SystemImage.Abi=" + abi + "\n" +
+ "SystemImage.TagId=" + tag.getId() + "\n" +
+ "SystemImage.TagDisplay=" + tag.getDisplay() + "\n" +
+ "Addon.VendorId=" + vendor.getId() + "\n" +
+ "Addon.VendorDisplay=" + vendor.getDisplay() + "\n" +
+ "Pkg.Revision=" + majorRev + "\n" +
+ "AndroidVersion.ApiLevel=" + api + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ }
+
+ private void addLocalPlatform(AndroidVersion version, String majorRev, String minToolsRev) {
+ String api = version.getApiString();
+ mFOp.recordExistingFolder("/sdk/platforms");
+ mFOp.recordExistingFolder("/sdk/platforms/android-" + api);
+ mFOp.recordExistingFile("/sdk/platforms/android-" + api + "/android.jar");
+ mFOp.recordExistingFile("/sdk/platforms/android-" + api + "/framework.aidl");
+ mFOp.recordExistingFile("/sdk/platforms/android-" + api + "/source.properties",
+ "Pkg.Revision=" + majorRev + "\n" +
+ "AndroidVersion.ApiLevel=" + api + "\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Layoutlib.Api=10\n" +
+ "Layoutlib.Revision=1\n" +
+ "Platform.MinToolsRev=" + minToolsRev + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/platforms/android-" + api + "/sdk.properties",
+ "sdk.ant.templates.revision=1\n" +
+ "sdk.skin.default=WVGA800\n");
+ mFOp.recordExistingFile("/sdk/platforms/android-" + api + "/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=" + api + "\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");
+ }
+
+ private void addLocalAddOn(AndroidVersion version, String majorRev, IdDisplay vendor, IdDisplay name) {
+ String api = version.getApiString();
+ String addon_dir = "addon-" + vendor.getId() + "-" + name;
+ mFOp.recordExistingFolder("/sdk/add-ons");
+ mFOp.recordExistingFolder("/sdk/add-ons/" + addon_dir);
+ mFOp.recordExistingFile("/sdk/add-ons/" + addon_dir + "/source.properties",
+ "Pkg.Revision=" + majorRev + "\n" +
+ "Addon.VendorId=" + vendor.getId() + "\n" +
+ "Addon.VendorDisplay=" + vendor.getDisplay() + "\n" +
+ "Addon.NameId=" + name.getId() + "\n" +
+ "Addon.NameDisplay=" + name.getDisplay() + "\n" +
+ "AndroidVersion.ApiLevel=" + api + "\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/add-ons/" + addon_dir + "/manifest.ini",
+ "Pkg.Revision=" + majorRev + "\n" +
+ "name=" + name.getDisplay() + "\n" +
+ "name-id=" + name.getId() + "\n" +
+ "vendor=" + vendor.getDisplay() + "\n" +
+ "vendor-id=" + vendor.getId() + "\n" +
+ "api=" + api + "\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");
+ }
+
+ //---
+
+ private void addRemoteTool(FullRevision revision, FullRevision minPlatformToolsRev) {
+ IPkgDesc d = PkgDesc.Builder.newTool(revision, minPlatformToolsRev).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemotePlatformTool(FullRevision revision) {
+ IPkgDesc d = PkgDesc.Builder.newPlatformTool(revision).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemoteDoc(AndroidVersion version, MajorRevision revision) {
+ IPkgDesc d = PkgDesc.Builder.newDoc(version, revision).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemoteBuildTool(FullRevision revision) {
+ IPkgDesc d = PkgDesc.Builder.newBuildTool(revision).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemoteExtra(NoPreviewRevision revision,
+ IdDisplay vendor,
+ String path,
+ String name) {
+ IPkgDesc d = PkgDesc.Builder.newExtra(vendor, path, name, null, revision).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemoteSource(AndroidVersion version, MajorRevision revision) {
+ IPkgDesc d = PkgDesc.Builder.newSource(version, revision).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemoteSample(AndroidVersion version,
+ MajorRevision revision,
+ FullRevision minToolsRev) {
+ IPkgDesc d = PkgDesc.Builder.newSample(version, revision, minToolsRev).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemoteSysImg(AndroidVersion version,
+ MajorRevision revision,
+ IdDisplay tag,
+ String abi) {
+ IPkgDesc d = PkgDesc.Builder.newSysImg(version, tag, abi, revision).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemoteAddonSysImg(AndroidVersion version,
+ MajorRevision revision,
+ IdDisplay vendor,
+ IdDisplay tag,
+ String abi) {
+ IPkgDesc d = PkgDesc.Builder.newAddonSysImg(version, vendor, tag, abi, revision).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemotePlatform(AndroidVersion version,
+ MajorRevision revision,
+ FullRevision minToolsRev) {
+ IPkgDesc d = PkgDesc.Builder.newPlatform(version, revision, minToolsRev).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+ private void addRemoteAddOn(AndroidVersion version,
+ MajorRevision revision,
+ IdDisplay vendor,
+ IdDisplay name) {
+ IPkgDesc d = PkgDesc.Builder.newAddon(version, revision, vendor, name).create();
+ RemotePkgInfo r = new RemotePkgInfo(d, mSource);
+ mRemotePkgs.put(d.getType(), r);
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_7.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_7.xml
new file mode 100755
index 0000000..e9618b7
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_7.xml
@@ -0,0 +1,245 @@
+<?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/7">
+
+ <!-- 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:list-display>My First Add-on for API 5, rev 0</sdk:list-display>
+
+ <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>
+ <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:list-display>My Second Add-on for API 2.r42</sdk:list-display>
+
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>42</sdk:revision>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>64</sdk:host-bits>
+ <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>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:host-bits>64</sdk:host-bits>
+ <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>
+
+ <!-- No list-display element. It is optional. -->
+
+ <sdk:uses-license ref="license2" />
+ <sdk:api-level>4</sdk:api-level>
+ <sdk:revision>3</sdk:revision>
+ <sdk:archives>
+ <sdk:archive>
+ <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>
+
+ <!-- No list-display element. It is optional. -->
+
+ <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>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>32</sdk:host-bits>
+ <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:list-display>Another extra with min-API 42</sdk:list-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>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>64</sdk:host-bits>
+ <sdk:jvm-bits>32</sdk:jvm-bits>
+ <sdk:min-jvm-version>1.7</sdk:min-jvm-version>
+ <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:list-display>Extra '____' for API _$1_, by _%2_</sdk:list-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>
+ <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/src/test/java/com/android/sdklib/testdata/repository_sample_1.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_01.xml
similarity index 100%
rename from sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_1.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_01.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_2.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_02.xml
similarity index 100%
rename from sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_2.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_02.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_3.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_03.xml
similarity index 100%
rename from sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_3.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_03.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_4.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_04.xml
similarity index 100%
rename from sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_4.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_04.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_5.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_05.xml
similarity index 100%
rename from sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_5.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_05.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_6.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_06.xml
similarity index 100%
rename from sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_6.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_06.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_7.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_07.xml
similarity index 100%
rename from sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_7.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_07.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_8.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_08.xml
similarity index 100%
rename from sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_8.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_08.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_09.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_09.xml
new file mode 100755
index 0000000..75201be
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_09.xml
@@ -0,0 +1,509 @@
+<?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-repository
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/9">
+
+ <!-- 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:platform>
+ <sdk:version>1.0</sdk:version>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>3</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+ <sdk:release-note>This is an optional release note
+ for this package. It's a free multi-line text.
+ </sdk:release-note>
+ <sdk:release-url>http://some/url/for/the/release/note.html</sdk:release-url>
+ <sdk:min-tools-rev>
+ <sdk:major>2</sdk:major>
+ <!-- minor is missing and equivalent to 0 -->
+ <sdk:micro>1</sdk:micro>
+ </sdk:min-tools-rev>
+ <!-- The archives node is mandatory and it cannot be empty. -->
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:layoutlib>
+ <sdk:api>5</sdk:api>
+ <sdk:revision>0</sdk:revision>
+ </sdk:layoutlib>
+ <sdk:included-abi>armeabi</sdk:included-abi>
+ </sdk:platform>
+
+ <sdk:doc>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <!-- the license element is not mandatory. -->
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/docs.html</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/docs/docs1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:doc>
+
+ <sdk:source>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/sources1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:source>
+
+ <sdk:platform>
+ <sdk:version>1.1</sdk:version>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <!-- sdk:description and sdk:desc-url are optional -->
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <!-- arch attribute is optional -->
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-win.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-mac.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx" arch="ppc">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-mac.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux" arch="x86">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux" arch="x86_64">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:layoutlib>
+ <sdk:api>5</sdk:api>
+ <sdk:revision>31415</sdk:revision>
+ </sdk:layoutlib>
+ <sdk:included-abi>x86</sdk:included-abi>
+ </sdk:platform>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:abi>x86</sdk:abi>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:abi>armeabi-v7a</sdk:abi>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/armv7/image2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:abi>armeabi-v7a</sdk:abi>
+ <sdk:tag-id>coolThing</sdk:tag-id>
+ <sdk:tag-display>Custom Thing</sdk:tag-display>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65533</sdk:size>
+ <sdk:checksum type="sha1">1235ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/armv7/image3.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:source>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/sources2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:source>
+
+ <sdk:platform>
+ <sdk:version>Pastry</sdk:version>
+ <sdk:api-level>5</sdk:api-level>
+ <sdk:codename>Pastry</sdk:codename>
+ <sdk:revision>3</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:description>Preview version for Pastry</sdk:description>
+ <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+ <!-- The archives node is mandatory and it cannot be empty. -->
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:layoutlib>
+ <sdk:api>1</sdk:api>
+ </sdk:layoutlib>
+ </sdk:platform>
+
+ <sdk:tool>
+ <sdk:revision>
+ <sdk:major>1</sdk:major>
+ <sdk:minor>2</sdk:minor>
+ <sdk:micro>3</sdk:micro>
+ <sdk:preview>4</sdk:preview>
+ </sdk:revision>
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/tools.html</sdk:desc-url>
+ <sdk:uses-license ref="license1" />
+ <sdk:min-platform-tools-rev>
+ <sdk:major>4</sdk:major>
+ </sdk:min-platform-tools-rev>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/tools1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:tool>
+
+ <sdk:build-tool>
+ <sdk:revision>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ <sdk:preview>5</sdk:preview>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b122ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b222ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b322ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:build-tool>
+
+ <sdk:build-tool>
+ <sdk:revision>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>1</sdk:micro>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b122ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b222ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b322ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:build-tool>
+
+ <sdk:doc>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>42</sdk:revision>
+ <sdk:uses-license ref="license2" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:doc>
+
+ <sdk:tool>
+ <sdk:revision>
+ <sdk:major>42</sdk:major>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:min-platform-tools-rev>
+ <sdk:major>4</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ <sdk:preview>5</sdk:preview>
+ </sdk:min-platform-tools-rev>
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:tool>
+
+ <sdk:platform-tool>
+ <sdk:revision>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ <sdk:preview>5</sdk:preview>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:platform-tool>
+
+ <sdk:build-tool>
+ <sdk:revision>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b122ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools/3.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b222ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools3-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b322ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools3-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:build-tool>
+
+ <sdk:sample>
+ <sdk:api-level>14</sdk:api-level>
+ <sdk:revision>24</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65537</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/sample_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some sample package</sdk:description>
+ <sdk:desc-url>http://www.example.com/sample.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>5</sdk:major>
+ </sdk:min-tools-rev>
+ <sdk:obsolete>This is obsolete</sdk:obsolete>
+ </sdk:sample>
+
+ <sdk:sample>
+ <sdk:api-level>14</sdk:api-level>
+ <sdk:revision>25</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65537</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/sample_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some sample package</sdk:description>
+ <sdk:desc-url>http://www.example.com/sample.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>5</sdk:major>
+ <sdk:minor>1</sdk:minor>
+ <sdk:micro>2</sdk:micro>
+ <sdk:preview>3</sdk:preview>
+ </sdk:min-tools-rev>
+ <sdk:obsolete>This is obsolete</sdk:obsolete>
+ </sdk:sample>
+
+ <sdk:system-image>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:abi>armeabi</sdk:abi>
+ <sdk:tag-id>variant-1</sdk:tag-id>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>1234</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/86/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:abi>mips</sdk:abi>
+ <sdk:tag-id>variant-1</sdk:tag-id>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>12345</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/mips/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:abi>mips</sdk:abi>
+ <sdk:tag-id>variant-2</sdk:tag-id>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>12345</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/mips/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:source>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>1234</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/source12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:source>
+
+ <sdk:build-tool>
+ <sdk:revision>
+ <sdk:major>12</sdk:major>
+ <sdk:minor>13</sdk:minor>
+ <sdk:micro>14</sdk:micro>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b122ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools/12.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b222ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b322ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools12-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:build-tool>
+
+</sdk:sdk-repository>
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_10.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_10.xml
new file mode 100755
index 0000000..8534b29
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_10.xml
@@ -0,0 +1,489 @@
+<?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-repository
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/10">
+
+ <!-- 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:platform>
+ <sdk:list-display>The first Android platform ever</sdk:list-display>
+ <sdk:version>1.0</sdk:version>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>3</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+ <sdk:release-note>This is an optional release note
+ for this package. It's a free multi-line text.
+ </sdk:release-note>
+ <sdk:release-url>http://some/url/for/the/release/note.html</sdk:release-url>
+ <sdk:min-tools-rev>
+ <sdk:major>2</sdk:major>
+ <!-- minor is missing and equivalent to 0 -->
+ <sdk:micro>1</sdk:micro>
+ </sdk:min-tools-rev>
+ <!-- The archives node is mandatory and it cannot be empty. -->
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:layoutlib>
+ <sdk:api>5</sdk:api>
+ <sdk:revision>0</sdk:revision>
+ </sdk:layoutlib>
+ <sdk:included-abi>armeabi</sdk:included-abi>
+ </sdk:platform>
+
+ <sdk:doc>
+ <sdk:list-display>Doc for first platform</sdk:list-display>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <!-- the license element is not mandatory. -->
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/docs.html</sdk:desc-url>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/docs/docs1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:doc>
+
+ <sdk:source>
+ <sdk:list-display>Sources for first platform</sdk:list-display>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/sources1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:source>
+
+ <sdk:platform>
+ <sdk:version>1.1</sdk:version>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <!-- sdk:description and sdk:desc-url are optional -->
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>32</sdk:host-bits>
+ <sdk:jvm-bits>32</sdk:jvm-bits>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d94545251</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-win32.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>64</sdk:host-bits>
+ <sdk:jvm-bits>32</sdk:jvm-bits>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d94545252</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-win32+64.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>64</sdk:host-bits>
+ <sdk:jvm-bits>64</sdk:jvm-bits>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d94545253</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-win64.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>64</sdk:host-bits>
+ <sdk:jvm-bits>64</sdk:jvm-bits>
+ <sdk:min-jvm-version>1.6</sdk:min-jvm-version>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d94545254</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-win64-jvm6.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>64</sdk:host-bits>
+ <sdk:jvm-bits>64</sdk:jvm-bits>
+ <sdk:min-jvm-version>1.7</sdk:min-jvm-version>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d94545255</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-win64-jvm7.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>macosx</sdk:host-os>
+ <sdk:min-jvm-version>1.6</sdk:min-jvm-version>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d94545256</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-mac.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:host-bits>32</sdk:host-bits>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d94545257</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:host-bits>64</sdk:host-bits>
+ <sdk:min-jvm-version>1.7</sdk:min-jvm-version>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d94545258</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:layoutlib>
+ <sdk:api>5</sdk:api>
+ <sdk:revision>31415</sdk:revision>
+ </sdk:layoutlib>
+ <sdk:included-abi>x86</sdk:included-abi>
+ </sdk:platform>
+
+ <sdk:source>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/sources2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:source>
+
+ <sdk:platform>
+ <sdk:version>Pastry</sdk:version>
+ <sdk:api-level>5</sdk:api-level>
+ <sdk:codename>Pastry</sdk:codename>
+ <sdk:revision>3</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:description>Preview version for Pastry</sdk:description>
+ <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+ <!-- The archives node is mandatory and it cannot be empty. -->
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:layoutlib>
+ <sdk:api>1</sdk:api>
+ </sdk:layoutlib>
+ </sdk:platform>
+
+ <sdk:tool>
+ <sdk:list-display>Tools in version 1.2.3.4</sdk:list-display>
+ <sdk:revision>
+ <sdk:major>1</sdk:major>
+ <sdk:minor>2</sdk:minor>
+ <sdk:micro>3</sdk:micro>
+ <sdk:preview>4</sdk:preview>
+ </sdk:revision>
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/tools.html</sdk:desc-url>
+ <sdk:uses-license ref="license1" />
+ <sdk:min-platform-tools-rev>
+ <sdk:major>4</sdk:major>
+ </sdk:min-platform-tools-rev>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/tools1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:tool>
+
+ <sdk:build-tool>
+ <sdk:list-display>Build tools v3 (preview 5)</sdk:list-display>
+ <sdk:revision>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ <sdk:preview>5</sdk:preview>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b122ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b222ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>macosx</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b322ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:build-tool>
+
+ <sdk:build-tool>
+ <sdk:revision>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>1</sdk:micro>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b122ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b222ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>macosx</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b322ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:build-tool>
+
+ <sdk:doc>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>42</sdk:revision>
+ <sdk:uses-license ref="license2" />
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>macosx</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:doc>
+
+ <sdk:tool>
+ <sdk:revision>
+ <sdk:major>42</sdk:major>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:min-platform-tools-rev>
+ <sdk:major>4</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ <sdk:preview>5</sdk:preview>
+ </sdk:min-platform-tools-rev>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>macosx</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:tool>
+
+ <sdk:platform-tool>
+ <sdk:revision>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ <sdk:preview>5</sdk:preview>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>macosx</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:platform-tool>
+
+ <sdk:build-tool>
+ <sdk:revision>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b122ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools/3.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b222ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools3-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>macosx</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b322ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools3-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:build-tool>
+
+ <sdk:sample>
+ <sdk:list-display>Samples from Android 14</sdk:list-display>
+ <sdk:api-level>14</sdk:api-level>
+ <sdk:revision>24</sdk:revision>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65537</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/sample_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some sample package</sdk:description>
+ <sdk:desc-url>http://www.example.com/sample.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>5</sdk:major>
+ </sdk:min-tools-rev>
+ <sdk:obsolete>This is obsolete</sdk:obsolete>
+ </sdk:sample>
+
+ <sdk:sample>
+ <sdk:api-level>14</sdk:api-level>
+ <sdk:revision>25</sdk:revision>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65537</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/sample_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some sample package</sdk:description>
+ <sdk:desc-url>http://www.example.com/sample.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>5</sdk:major>
+ <sdk:minor>1</sdk:minor>
+ <sdk:micro>2</sdk:micro>
+ <sdk:preview>3</sdk:preview>
+ </sdk:min-tools-rev>
+ <sdk:obsolete>This is obsolete</sdk:obsolete>
+ </sdk:sample>
+
+ <sdk:source>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>1234</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/source12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:source>
+
+ <sdk:build-tool>
+ <sdk:revision>
+ <sdk:major>12</sdk:major>
+ <sdk:minor>13</sdk:minor>
+ <sdk:micro>14</sdk:micro>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b122ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools/12.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>linux</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b222ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive>
+ <sdk:host-os>macosx</sdk:host-os>
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">b322ae37115ebf13412bbef91339ee0d9454525b</sdk:checksum>
+ <sdk:url>distrib/build-tools12-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:build-tool>
+
+</sdk:sdk-repository>
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_2.xml b/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_2.xml
new file mode 100755
index 0000000..2efc1ac
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_2.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0"?>
+<!--
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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-sys-img
+ xmlns:sdk="http://schemas.android.com/sdk/android/sys-img/2">
+
+ <!-- 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>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:abi>x86</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:tag-display>Ignored in description for default tag</sdk:tag-display>
+ <sdk:abi>armeabi-v7a</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/armv7/image2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:tag-id>other</sdk:tag-id>
+ <sdk:tag-display>Another tag name</sdk:tag-display>
+ <sdk:abi>armeabi-v7a</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/armv7/image2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:abi>armeabi</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>1234</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/86/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:abi>mips</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>12345</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/mips/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>44</sdk:api-level>
+ <sdk:revision>14</sdk:revision>
+ <sdk:tag-id>mips-only</sdk:tag-id>
+ <sdk:tag-display>This is an arbitrary string,</sdk:tag-display>
+ <sdk:abi>mips</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>12345</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/mips/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>45</sdk:api-level>
+ <sdk:revision>15</sdk:revision>
+ <sdk:tag-id>tag-name---is-Sanitized----if-Display-is-Missing</sdk:tag-id>
+ <sdk:abi>mips</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>12345</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/mips/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+</sdk:sdk-sys-img>
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_3.xml b/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_3.xml
new file mode 100755
index 0000000..8acd602
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_3.xml
@@ -0,0 +1,293 @@
+<?xml version="1.0"?>
+<!--
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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-sys-img
+ xmlns:sdk="http://schemas.android.com/sdk/android/sys-img/3">
+
+ <!-- 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>
+
+ <sdk:system-image>
+ <sdk:list-display>System Image for x86 CPU for API 2</sdk:list-display>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:abi>x86</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>32</sdk:host-bits>
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:list-display>System Image for x86-64 CPU for API 2</sdk:list-display>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:abi>x86_64</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>32</sdk:host-bits>
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541264</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86_64/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <!-- No list-display here. -->
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:tag-display>Ignored in description for default tag</sdk:tag-display>
+ <sdk:abi>armeabi-v7a</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/armv7/image2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <!-- No list-display here. -->
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:tag-display>Ignored in description for default tag</sdk:tag-display>
+ <sdk:abi>arm64-v8a</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541264</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/armv64/image2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <!-- No list-display here. -->
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:tag-id>other</sdk:tag-id>
+ <sdk:tag-display>Another tag name</sdk:tag-display>
+ <sdk:abi>armeabi-v7a</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/armv7/image2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <!-- No list-display here. -->
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:abi>armeabi</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>1234</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/86/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <!-- No list-display here. -->
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:tag-id>default</sdk:tag-id>
+ <sdk:abi>mips64</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>12345</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541264</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/mips64/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:list-display>MIPS system image for tag MIPS-only</sdk:list-display>
+ <sdk:api-level>44</sdk:api-level>
+ <sdk:revision>14</sdk:revision>
+ <sdk:tag-id>mips-only</sdk:tag-id>
+ <sdk:tag-display>This is an arbitrary string,</sdk:tag-display>
+ <sdk:abi>mips</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>12345</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/mips/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <!-- No list-display here. -->
+ <sdk:api-level>45</sdk:api-level>
+ <sdk:revision>15</sdk:revision>
+ <sdk:tag-id>tag-name---is-Sanitized----if-Display-is-Missing</sdk:tag-id>
+ <sdk:abi>mips</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>12345</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/mips/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <!-- add-on -->
+
+ <sdk:system-image>
+ <sdk:list-display>x86 System Image for some add-on</sdk:list-display>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:tag-id>some-addon</sdk:tag-id>
+ <sdk:tag-display>Some Add-on</sdk:tag-display>
+ <sdk:add-on>
+ <sdk:vendor-id>some-vendor</sdk:vendor-id>
+ <sdk:vendor-display>Acme Vendor Inc.</sdk:vendor-display>
+ </sdk:add-on>
+ <sdk:abi>x86</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:host-os>windows</sdk:host-os>
+ <sdk:host-bits>32</sdk:host-bits>
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:tag-id>some-addon</sdk:tag-id>
+ <sdk:tag-display>Some Add-on</sdk:tag-display>
+ <sdk:add-on>
+ <sdk:vendor-id>some-vendor</sdk:vendor-id>
+ <sdk:vendor-display>Acme Vendor Inc.</sdk:vendor-display>
+ </sdk:add-on>
+ <sdk:abi>armeabi-v7a</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>3</sdk:revision>
+ <sdk:tag-id>some-addon</sdk:tag-id>
+ <sdk:tag-display>Some Add-on</sdk:tag-display>
+ <sdk:add-on>
+ <sdk:vendor-id>some-vendor</sdk:vendor-id>
+ <sdk:vendor-display>Acme Vendor Inc.</sdk:vendor-display>
+ </sdk:add-on>
+ <sdk:abi>x86_64</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>4</sdk:revision>
+ <sdk:tag-id>some-addon</sdk:tag-id>
+ <sdk:tag-display>Some Add-on</sdk:tag-display>
+ <sdk:add-on>
+ <sdk:vendor-id>some-vendor</sdk:vendor-id>
+ <sdk:vendor-display>Acme Vendor Inc.</sdk:vendor-display>
+ </sdk:add-on>
+ <sdk:abi>arm64-v8a</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>5</sdk:revision>
+ <sdk:tag-id>some-addon</sdk:tag-id>
+ <sdk:tag-display>Some Add-on</sdk:tag-display>
+ <sdk:add-on>
+ <sdk:vendor-id>some-vendor</sdk:vendor-id>
+ <sdk:vendor-display>Acme Vendor Inc.</sdk:vendor-display>
+ </sdk:add-on>
+ <sdk:abi>mips</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>6</sdk:revision>
+ <sdk:tag-id>some-addon</sdk:tag-id>
+ <sdk:tag-display>Some Add-on</sdk:tag-display>
+ <sdk:add-on>
+ <sdk:vendor-id>some-vendor</sdk:vendor-id>
+ <sdk:vendor-display>Acme Vendor Inc.</sdk:vendor-display>
+ </sdk:add-on>
+ <sdk:abi>mips64</sdk:abi>
+ <sdk:archives>
+ <sdk:archive>
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+</sdk:sdk-sys-img>
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.dtd b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.dtd
new file mode 100644
index 0000000..e8e8f76
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.dtd
@@ -0,0 +1,402 @@
+<!-- DTD for XML Schemas: Part 1: Structures
+ Public Identifier: "-//W3C//DTD XMLSCHEMA 200102//EN"
+ Official Location: http://www.w3.org/2001/XMLSchema.dtd -->
+<!-- $Id: XMLSchema.dtd,v 1.31 2001/10/24 15:50:16 ht Exp $ -->
+<!-- Note this DTD is NOT normative, or even definitive. --> <!--d-->
+<!-- prose copy in the structures REC is the definitive version --> <!--d-->
+<!-- (which shouldn't differ from this one except for this --> <!--d-->
+<!-- comment and entity expansions, but just in case) --> <!--d-->
+<!-- With the exception of cases with multiple namespace
+ prefixes for the XML Schema namespace, any XML document which is
+ not valid per this DTD given redefinitions in its internal subset of the
+ 'p' and 's' parameter entities below appropriate to its namespace
+ declaration of the XML Schema namespace is almost certainly not
+ a valid schema. -->
+
+<!-- The simpleType element and its constituent parts
+ are defined in XML Schema: Part 2: Datatypes -->
+<!ENTITY % xs-datatypes PUBLIC 'datatypes' 'datatypes.dtd' >
+
+<!ENTITY % p 'xs:'> <!-- can be overriden in the internal subset of a
+ schema document to establish a different
+ namespace prefix -->
+<!ENTITY % s ':xs'> <!-- if %p is defined (e.g. as foo:) then you must
+ also define %s as the suffix for the appropriate
+ namespace declaration (e.g. :foo) -->
+<!ENTITY % nds 'xmlns%s;'>
+
+<!-- Define all the element names, with optional prefix -->
+<!ENTITY % schema "%p;schema">
+<!ENTITY % complexType "%p;complexType">
+<!ENTITY % complexContent "%p;complexContent">
+<!ENTITY % simpleContent "%p;simpleContent">
+<!ENTITY % extension "%p;extension">
+<!ENTITY % element "%p;element">
+<!ENTITY % unique "%p;unique">
+<!ENTITY % key "%p;key">
+<!ENTITY % keyref "%p;keyref">
+<!ENTITY % selector "%p;selector">
+<!ENTITY % field "%p;field">
+<!ENTITY % group "%p;group">
+<!ENTITY % all "%p;all">
+<!ENTITY % choice "%p;choice">
+<!ENTITY % sequence "%p;sequence">
+<!ENTITY % any "%p;any">
+<!ENTITY % anyAttribute "%p;anyAttribute">
+<!ENTITY % attribute "%p;attribute">
+<!ENTITY % attributeGroup "%p;attributeGroup">
+<!ENTITY % include "%p;include">
+<!ENTITY % import "%p;import">
+<!ENTITY % redefine "%p;redefine">
+<!ENTITY % notation "%p;notation">
+
+<!-- annotation elements -->
+<!ENTITY % annotation "%p;annotation">
+<!ENTITY % appinfo "%p;appinfo">
+<!ENTITY % documentation "%p;documentation">
+
+<!-- Customisation entities for the ATTLIST of each element type.
+ Define one of these if your schema takes advantage of the
+ anyAttribute='##other' in the schema for schemas -->
+
+<!ENTITY % schemaAttrs ''>
+<!ENTITY % complexTypeAttrs ''>
+<!ENTITY % complexContentAttrs ''>
+<!ENTITY % simpleContentAttrs ''>
+<!ENTITY % extensionAttrs ''>
+<!ENTITY % elementAttrs ''>
+<!ENTITY % groupAttrs ''>
+<!ENTITY % allAttrs ''>
+<!ENTITY % choiceAttrs ''>
+<!ENTITY % sequenceAttrs ''>
+<!ENTITY % anyAttrs ''>
+<!ENTITY % anyAttributeAttrs ''>
+<!ENTITY % attributeAttrs ''>
+<!ENTITY % attributeGroupAttrs ''>
+<!ENTITY % uniqueAttrs ''>
+<!ENTITY % keyAttrs ''>
+<!ENTITY % keyrefAttrs ''>
+<!ENTITY % selectorAttrs ''>
+<!ENTITY % fieldAttrs ''>
+<!ENTITY % includeAttrs ''>
+<!ENTITY % importAttrs ''>
+<!ENTITY % redefineAttrs ''>
+<!ENTITY % notationAttrs ''>
+<!ENTITY % annotationAttrs ''>
+<!ENTITY % appinfoAttrs ''>
+<!ENTITY % documentationAttrs ''>
+
+<!ENTITY % complexDerivationSet "CDATA">
+ <!-- #all or space-separated list drawn from derivationChoice -->
+<!ENTITY % blockSet "CDATA">
+ <!-- #all or space-separated list drawn from
+ derivationChoice + 'substitution' -->
+
+<!ENTITY % mgs '%all; | %choice; | %sequence;'>
+<!ENTITY % cs '%choice; | %sequence;'>
+<!ENTITY % formValues '(qualified|unqualified)'>
+
+
+<!ENTITY % attrDecls '((%attribute;| %attributeGroup;)*,(%anyAttribute;)?)'>
+
+<!ENTITY % particleAndAttrs '((%mgs; | %group;)?, %attrDecls;)'>
+
+<!-- This is used in part2 -->
+<!ENTITY % restriction1 '((%mgs; | %group;)?)'>
+
+%xs-datatypes;
+
+<!-- the duplication below is to produce an unambiguous content model
+ which allows annotation everywhere -->
+<!ELEMENT %schema; ((%include; | %import; | %redefine; | %annotation;)*,
+ ((%simpleType; | %complexType;
+ | %element; | %attribute;
+ | %attributeGroup; | %group;
+ | %notation; ),
+ (%annotation;)*)* )>
+<!ATTLIST %schema;
+ targetNamespace %URIref; #IMPLIED
+ version CDATA #IMPLIED
+ %nds; %URIref; #FIXED 'http://www.w3.org/2001/XMLSchema'
+ xmlns CDATA #IMPLIED
+ finalDefault %complexDerivationSet; ''
+ blockDefault %blockSet; ''
+ id ID #IMPLIED
+ elementFormDefault %formValues; 'unqualified'
+ attributeFormDefault %formValues; 'unqualified'
+ xml:lang CDATA #IMPLIED
+ %schemaAttrs;>
+<!-- Note the xmlns declaration is NOT in the Schema for Schemas,
+ because at the Infoset level where schemas operate,
+ xmlns(:prefix) is NOT an attribute! -->
+<!-- The declaration of xmlns is a convenience for schema authors -->
+
+<!-- The id attribute here and below is for use in external references
+ from non-schemas using simple fragment identifiers.
+ It is NOT used for schema-to-schema reference, internal or
+ external. -->
+
+<!-- a type is a named content type specification which allows attribute
+ declarations-->
+<!-- -->
+
+<!ELEMENT %complexType; ((%annotation;)?,
+ (%simpleContent;|%complexContent;|
+ %particleAndAttrs;))>
+
+<!ATTLIST %complexType;
+ name %NCName; #IMPLIED
+ id ID #IMPLIED
+ abstract %boolean; #IMPLIED
+ final %complexDerivationSet; #IMPLIED
+ block %complexDerivationSet; #IMPLIED
+ mixed (true|false) 'false'
+ %complexTypeAttrs;>
+
+<!-- particleAndAttrs is shorthand for a root type -->
+<!-- mixed is disallowed if simpleContent, overriden if complexContent
+ has one too. -->
+
+<!-- If anyAttribute appears in one or more referenced attributeGroups
+ and/or explicitly, the intersection of the permissions is used -->
+
+<!ELEMENT %complexContent; ((%annotation;)?, (%restriction;|%extension;))>
+<!ATTLIST %complexContent;
+ mixed (true|false) #IMPLIED
+ id ID #IMPLIED
+ %complexContentAttrs;>
+
+<!-- restriction should use the branch defined above, not the simple
+ one from part2; extension should use the full model -->
+
+<!ELEMENT %simpleContent; ((%annotation;)?, (%restriction;|%extension;))>
+<!ATTLIST %simpleContent;
+ id ID #IMPLIED
+ %simpleContentAttrs;>
+
+<!-- restriction should use the simple branch from part2, not the
+ one defined above; extension should have no particle -->
+
+<!ELEMENT %extension; ((%annotation;)?, (%particleAndAttrs;))>
+<!ATTLIST %extension;
+ base %QName; #REQUIRED
+ id ID #IMPLIED
+ %extensionAttrs;>
+
+<!-- an element is declared by either:
+ a name and a type (either nested or referenced via the type attribute)
+ or a ref to an existing element declaration -->
+
+<!ELEMENT %element; ((%annotation;)?, (%complexType;| %simpleType;)?,
+ (%unique; | %key; | %keyref;)*)>
+<!-- simpleType or complexType only if no type|ref attribute -->
+<!-- ref not allowed at top level -->
+<!ATTLIST %element;
+ name %NCName; #IMPLIED
+ id ID #IMPLIED
+ ref %QName; #IMPLIED
+ type %QName; #IMPLIED
+ minOccurs %nonNegativeInteger; #IMPLIED
+ maxOccurs CDATA #IMPLIED
+ nillable %boolean; #IMPLIED
+ substitutionGroup %QName; #IMPLIED
+ abstract %boolean; #IMPLIED
+ final %complexDerivationSet; #IMPLIED
+ block %blockSet; #IMPLIED
+ default CDATA #IMPLIED
+ fixed CDATA #IMPLIED
+ form %formValues; #IMPLIED
+ %elementAttrs;>
+<!-- type and ref are mutually exclusive.
+ name and ref are mutually exclusive, one is required -->
+<!-- In the absence of type AND ref, type defaults to type of
+ substitutionGroup, if any, else the ur-type, i.e. unconstrained -->
+<!-- default and fixed are mutually exclusive -->
+
+<!ELEMENT %group; ((%annotation;)?,(%mgs;)?)>
+<!ATTLIST %group;
+ name %NCName; #IMPLIED
+ ref %QName; #IMPLIED
+ minOccurs %nonNegativeInteger; #IMPLIED
+ maxOccurs CDATA #IMPLIED
+ id ID #IMPLIED
+ %groupAttrs;>
+
+<!ELEMENT %all; ((%annotation;)?, (%element;)*)>
+<!ATTLIST %all;
+ minOccurs (1) #IMPLIED
+ maxOccurs (1) #IMPLIED
+ id ID #IMPLIED
+ %allAttrs;>
+
+<!ELEMENT %choice; ((%annotation;)?, (%element;| %group;| %cs; | %any;)*)>
+<!ATTLIST %choice;
+ minOccurs %nonNegativeInteger; #IMPLIED
+ maxOccurs CDATA #IMPLIED
+ id ID #IMPLIED
+ %choiceAttrs;>
+
+<!ELEMENT %sequence; ((%annotation;)?, (%element;| %group;| %cs; | %any;)*)>
+<!ATTLIST %sequence;
+ minOccurs %nonNegativeInteger; #IMPLIED
+ maxOccurs CDATA #IMPLIED
+ id ID #IMPLIED
+ %sequenceAttrs;>
+
+<!-- an anonymous grouping in a model, or
+ a top-level named group definition, or a reference to same -->
+
+<!-- Note that if order is 'all', group is not allowed inside.
+ If order is 'all' THIS group must be alone (or referenced alone) at
+ the top level of a content model -->
+<!-- If order is 'all', minOccurs==maxOccurs==1 on element/any inside -->
+<!-- Should allow minOccurs=0 inside order='all' . . . -->
+
+<!ELEMENT %any; (%annotation;)?>
+<!ATTLIST %any;
+ namespace CDATA '##any'
+ processContents (skip|lax|strict) 'strict'
+ minOccurs %nonNegativeInteger; '1'
+ maxOccurs CDATA '1'
+ id ID #IMPLIED
+ %anyAttrs;>
+
+<!-- namespace is interpreted as follows:
+ ##any - - any non-conflicting WFXML at all
+
+ ##other - - any non-conflicting WFXML from namespace other
+ than targetNamespace
+
+ ##local - - any unqualified non-conflicting WFXML/attribute
+ one or - - any non-conflicting WFXML from
+ more URI the listed namespaces
+ references
+
+ ##targetNamespace ##local may appear in the above list,
+ with the obvious meaning -->
+
+<!ELEMENT %anyAttribute; (%annotation;)?>
+<!ATTLIST %anyAttribute;
+ namespace CDATA '##any'
+ processContents (skip|lax|strict) 'strict'
+ id ID #IMPLIED
+ %anyAttributeAttrs;>
+<!-- namespace is interpreted as for 'any' above -->
+
+<!-- simpleType only if no type|ref attribute -->
+<!-- ref not allowed at top level, name iff at top level -->
+<!ELEMENT %attribute; ((%annotation;)?, (%simpleType;)?)>
+<!ATTLIST %attribute;
+ name %NCName; #IMPLIED
+ id ID #IMPLIED
+ ref %QName; #IMPLIED
+ type %QName; #IMPLIED
+ use (prohibited|optional|required) #IMPLIED
+ default CDATA #IMPLIED
+ fixed CDATA #IMPLIED
+ form %formValues; #IMPLIED
+ %attributeAttrs;>
+<!-- type and ref are mutually exclusive.
+ name and ref are mutually exclusive, one is required -->
+<!-- default for use is optional when nested, none otherwise -->
+<!-- default and fixed are mutually exclusive -->
+<!-- type attr and simpleType content are mutually exclusive -->
+
+<!-- an attributeGroup is a named collection of attribute decls, or a
+ reference thereto -->
+<!ELEMENT %attributeGroup; ((%annotation;)?,
+ (%attribute; | %attributeGroup;)*,
+ (%anyAttribute;)?) >
+<!ATTLIST %attributeGroup;
+ name %NCName; #IMPLIED
+ id ID #IMPLIED
+ ref %QName; #IMPLIED
+ %attributeGroupAttrs;>
+
+<!-- ref iff no content, no name. ref iff not top level -->
+
+<!-- better reference mechanisms -->
+<!ELEMENT %unique; ((%annotation;)?, %selector;, (%field;)+)>
+<!ATTLIST %unique;
+ name %NCName; #REQUIRED
+ id ID #IMPLIED
+ %uniqueAttrs;>
+
+<!ELEMENT %key; ((%annotation;)?, %selector;, (%field;)+)>
+<!ATTLIST %key;
+ name %NCName; #REQUIRED
+ id ID #IMPLIED
+ %keyAttrs;>
+
+<!ELEMENT %keyref; ((%annotation;)?, %selector;, (%field;)+)>
+<!ATTLIST %keyref;
+ name %NCName; #REQUIRED
+ refer %QName; #REQUIRED
+ id ID #IMPLIED
+ %keyrefAttrs;>
+
+<!ELEMENT %selector; ((%annotation;)?)>
+<!ATTLIST %selector;
+ xpath %XPathExpr; #REQUIRED
+ id ID #IMPLIED
+ %selectorAttrs;>
+<!ELEMENT %field; ((%annotation;)?)>
+<!ATTLIST %field;
+ xpath %XPathExpr; #REQUIRED
+ id ID #IMPLIED
+ %fieldAttrs;>
+
+<!-- Schema combination mechanisms -->
+<!ELEMENT %include; (%annotation;)?>
+<!ATTLIST %include;
+ schemaLocation %URIref; #REQUIRED
+ id ID #IMPLIED
+ %includeAttrs;>
+
+<!ELEMENT %import; (%annotation;)?>
+<!ATTLIST %import;
+ namespace %URIref; #IMPLIED
+ schemaLocation %URIref; #IMPLIED
+ id ID #IMPLIED
+ %importAttrs;>
+
+<!ELEMENT %redefine; (%annotation; | %simpleType; | %complexType; |
+ %attributeGroup; | %group;)*>
+<!ATTLIST %redefine;
+ schemaLocation %URIref; #REQUIRED
+ id ID #IMPLIED
+ %redefineAttrs;>
+
+<!ELEMENT %notation; (%annotation;)?>
+<!ATTLIST %notation;
+ name %NCName; #REQUIRED
+ id ID #IMPLIED
+ public CDATA #REQUIRED
+ system %URIref; #IMPLIED
+ %notationAttrs;>
+
+<!-- Annotation is either application information or documentation -->
+<!-- By having these here they are available for datatypes as well
+ as all the structures elements -->
+
+<!ELEMENT %annotation; (%appinfo; | %documentation;)*>
+<!ATTLIST %annotation; %annotationAttrs;>
+
+<!-- User must define annotation elements in internal subset for this
+ to work -->
+<!ELEMENT %appinfo; ANY> <!-- too restrictive -->
+<!ATTLIST %appinfo;
+ source %URIref; #IMPLIED
+ id ID #IMPLIED
+ %appinfoAttrs;>
+<!ELEMENT %documentation; ANY> <!-- too restrictive -->
+<!ATTLIST %documentation;
+ source %URIref; #IMPLIED
+ id ID #IMPLIED
+ xml:lang CDATA #IMPLIED
+ %documentationAttrs;>
+
+<!NOTATION XMLSchemaStructures PUBLIC
+ 'structures' 'http://www.w3.org/2001/XMLSchema.xsd' >
+<!NOTATION XML PUBLIC
+ 'REC-xml-1998-0210' 'http://www.w3.org/TR/1998/REC-xml-19980210' >
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.xsd b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.xsd
new file mode 100644
index 0000000..2e9a272
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.xsd
@@ -0,0 +1,2534 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- XML Schema schema for XML Schemas: Part 1: Structures -->
+<!-- Note this schema is NOT the normative structures schema. -->
+<!-- The prose copy in the structures REC is the normative -->
+<!-- version (which shouldn't differ from this one except for -->
+<!-- this comment and entity expansions, but just in case -->
+<!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" [
+
+<!-- provide ID type information even for parsers which only read the
+ internal subset -->
+<!ATTLIST xs:schema id ID #IMPLIED>
+<!ATTLIST xs:complexType id ID #IMPLIED>
+<!ATTLIST xs:complexContent id ID #IMPLIED>
+<!ATTLIST xs:simpleContent id ID #IMPLIED>
+<!ATTLIST xs:extension id ID #IMPLIED>
+<!ATTLIST xs:element id ID #IMPLIED>
+<!ATTLIST xs:group id ID #IMPLIED>
+<!ATTLIST xs:all id ID #IMPLIED>
+<!ATTLIST xs:choice id ID #IMPLIED>
+<!ATTLIST xs:sequence id ID #IMPLIED>
+<!ATTLIST xs:any id ID #IMPLIED>
+<!ATTLIST xs:anyAttribute id ID #IMPLIED>
+<!ATTLIST xs:attribute id ID #IMPLIED>
+<!ATTLIST xs:attributeGroup id ID #IMPLIED>
+<!ATTLIST xs:unique id ID #IMPLIED>
+<!ATTLIST xs:key id ID #IMPLIED>
+<!ATTLIST xs:keyref id ID #IMPLIED>
+<!ATTLIST xs:selector id ID #IMPLIED>
+<!ATTLIST xs:field id ID #IMPLIED>
+<!ATTLIST xs:include id ID #IMPLIED>
+<!ATTLIST xs:import id ID #IMPLIED>
+<!ATTLIST xs:redefine id ID #IMPLIED>
+<!ATTLIST xs:notation id ID #IMPLIED>
+<!--
+ keep this schema XML1.0 DTD valid
+ -->
+ <!ENTITY % schemaAttrs 'xmlns:hfp CDATA #IMPLIED'>
+
+ <!ELEMENT hfp:hasFacet EMPTY>
+ <!ATTLIST hfp:hasFacet
+ name NMTOKEN #REQUIRED>
+
+ <!ELEMENT hfp:hasProperty EMPTY>
+ <!ATTLIST hfp:hasProperty
+ name NMTOKEN #REQUIRED
+ value CDATA #REQUIRED>
+<!--
+ Make sure that processors that do not read the external
+ subset will know about the various IDs we declare
+ -->
+ <!ATTLIST xs:simpleType id ID #IMPLIED>
+ <!ATTLIST xs:maxExclusive id ID #IMPLIED>
+ <!ATTLIST xs:minExclusive id ID #IMPLIED>
+ <!ATTLIST xs:maxInclusive id ID #IMPLIED>
+ <!ATTLIST xs:minInclusive id ID #IMPLIED>
+ <!ATTLIST xs:totalDigits id ID #IMPLIED>
+ <!ATTLIST xs:fractionDigits id ID #IMPLIED>
+ <!ATTLIST xs:length id ID #IMPLIED>
+ <!ATTLIST xs:minLength id ID #IMPLIED>
+ <!ATTLIST xs:maxLength id ID #IMPLIED>
+ <!ATTLIST xs:enumeration id ID #IMPLIED>
+ <!ATTLIST xs:pattern id ID #IMPLIED>
+ <!ATTLIST xs:appinfo id ID #IMPLIED>
+ <!ATTLIST xs:documentation id ID #IMPLIED>
+ <!ATTLIST xs:list id ID #IMPLIED>
+ <!ATTLIST xs:union id ID #IMPLIED>
+ ]>
+<xs:schema targetNamespace="http://www.w3.org/2001/XMLSchema" blockDefault="#all" elementFormDefault="qualified" version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="EN" xmlns:hfp="http://www.w3.org/2001/XMLSchema-hasFacetAndProperty">
+ <xs:annotation>
+ <xs:documentation>
+ Part 1 version: Id: structures.xsd,v 1.2 2004/01/15 11:34:25 ht Exp
+ Part 2 version: Id: datatypes.xsd,v 1.3 2004/01/23 18:11:13 ht Exp
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/2004/PER-xmlschema-1-20040318/structures.html">
+ The schema corresponding to this document is normative,
+ with respect to the syntactic constraints it expresses in the
+ XML Schema language. The documentation (within <documentation> elements)
+ below, is not normative, but rather highlights important aspects of
+ the W3C Recommendation of which this is a part</xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>
+ The simpleType element and all of its members are defined
+ towards the end of this schema document</xs:documentation>
+ </xs:annotation>
+
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd">
+ <xs:annotation>
+ <xs:documentation>
+ Get access to the xml: attribute groups for xml:lang
+ as declared on 'schema' and 'documentation' below
+ </xs:documentation>
+ </xs:annotation>
+ </xs:import>
+
+ <xs:complexType name="openAttrs">
+ <xs:annotation>
+ <xs:documentation>
+ This type is extended by almost all schema types
+ to allow attributes from other namespaces to be
+ added to user schemas.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:restriction base="xs:anyType">
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="annotated">
+ <xs:annotation>
+ <xs:documentation>
+ This type is extended by all types which allow annotation
+ other than <schema> itself
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:openAttrs">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="schemaTop">
+ <xs:annotation>
+ <xs:documentation>
+ This group is for the
+ elements which occur freely at the top level of schemas.
+ All of their types are based on the "annotated" type by extension.</xs:documentation>
+ </xs:annotation>
+ <xs:choice>
+ <xs:group ref="xs:redefinable"/>
+ <xs:element ref="xs:element"/>
+ <xs:element ref="xs:attribute"/>
+ <xs:element ref="xs:notation"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:group name="redefinable">
+ <xs:annotation>
+ <xs:documentation>
+ This group is for the
+ elements which can self-redefine (see <redefine> below).</xs:documentation>
+ </xs:annotation>
+ <xs:choice>
+ <xs:element ref="xs:simpleType"/>
+ <xs:element ref="xs:complexType"/>
+ <xs:element ref="xs:group"/>
+ <xs:element ref="xs:attributeGroup"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:simpleType name="formChoice">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="qualified"/>
+ <xs:enumeration value="unqualified"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="reducedDerivationControl">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:derivationControl">
+ <xs:enumeration value="extension"/>
+ <xs:enumeration value="restriction"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="derivationSet">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ <xs:documentation>
+ #all or (possibly empty) subset of {extension, restriction}</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list itemType="xs:reducedDerivationControl"/>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:simpleType name="typeDerivationControl">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:derivationControl">
+ <xs:enumeration value="extension"/>
+ <xs:enumeration value="restriction"/>
+ <xs:enumeration value="list"/>
+ <xs:enumeration value="union"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="fullDerivationSet">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ <xs:documentation>
+ #all or (possibly empty) subset of {extension, restriction, list, union}</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list itemType="xs:typeDerivationControl"/>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:element name="schema" id="schema">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-schema"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:openAttrs">
+ <xs:sequence>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xs:include"/>
+ <xs:element ref="xs:import"/>
+ <xs:element ref="xs:redefine"/>
+ <xs:element ref="xs:annotation"/>
+ </xs:choice>
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:group ref="xs:schemaTop"/>
+ <xs:element ref="xs:annotation" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:sequence>
+ <xs:attribute name="targetNamespace" type="xs:anyURI"/>
+ <xs:attribute name="version" type="xs:token"/>
+ <xs:attribute name="finalDefault" type="xs:fullDerivationSet" use="optional" default=""/>
+ <xs:attribute name="blockDefault" type="xs:blockSet" use="optional" default=""/>
+ <xs:attribute name="attributeFormDefault" type="xs:formChoice" use="optional" default="unqualified"/>
+ <xs:attribute name="elementFormDefault" type="xs:formChoice" use="optional" default="unqualified"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ <xs:attribute ref="xml:lang"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:key name="element">
+ <xs:selector xpath="xs:element"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="attribute">
+ <xs:selector xpath="xs:attribute"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="type">
+ <xs:selector xpath="xs:complexType|xs:simpleType"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="group">
+ <xs:selector xpath="xs:group"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="attributeGroup">
+ <xs:selector xpath="xs:attributeGroup"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="notation">
+ <xs:selector xpath="xs:notation"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="identityConstraint">
+ <xs:selector xpath=".//xs:key|.//xs:unique|.//xs:keyref"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ </xs:element>
+
+ <xs:simpleType name="allNNI">
+ <xs:annotation><xs:documentation>
+ for maxOccurs</xs:documentation></xs:annotation>
+ <xs:union memberTypes="xs:nonNegativeInteger">
+ <xs:simpleType>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="unbounded"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:attributeGroup name="occurs">
+ <xs:annotation><xs:documentation>
+ for all particles</xs:documentation></xs:annotation>
+ <xs:attribute name="minOccurs" type="xs:nonNegativeInteger" use="optional" default="1"/>
+ <xs:attribute name="maxOccurs" type="xs:allNNI" use="optional" default="1"/>
+ </xs:attributeGroup>
+
+ <xs:attributeGroup name="defRef">
+ <xs:annotation><xs:documentation>
+ for element, group and attributeGroup,
+ which both define and reference</xs:documentation></xs:annotation>
+ <xs:attribute name="name" type="xs:NCName"/>
+ <xs:attribute name="ref" type="xs:QName"/>
+ </xs:attributeGroup>
+
+ <xs:group name="typeDefParticle">
+ <xs:annotation>
+ <xs:documentation>
+ 'complexType' uses this</xs:documentation></xs:annotation>
+ <xs:choice>
+ <xs:element name="group" type="xs:groupRef"/>
+ <xs:element ref="xs:all"/>
+ <xs:element ref="xs:choice"/>
+ <xs:element ref="xs:sequence"/>
+ </xs:choice>
+ </xs:group>
+
+
+
+ <xs:group name="nestedParticle">
+ <xs:choice>
+ <xs:element name="element" type="xs:localElement"/>
+ <xs:element name="group" type="xs:groupRef"/>
+ <xs:element ref="xs:choice"/>
+ <xs:element ref="xs:sequence"/>
+ <xs:element ref="xs:any"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:group name="particle">
+ <xs:choice>
+ <xs:element name="element" type="xs:localElement"/>
+ <xs:element name="group" type="xs:groupRef"/>
+ <xs:element ref="xs:all"/>
+ <xs:element ref="xs:choice"/>
+ <xs:element ref="xs:sequence"/>
+ <xs:element ref="xs:any"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:complexType name="attribute">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:element name="simpleType" minOccurs="0" type="xs:localSimpleType"/>
+ </xs:sequence>
+ <xs:attributeGroup ref="xs:defRef"/>
+ <xs:attribute name="type" type="xs:QName"/>
+ <xs:attribute name="use" use="optional" default="optional">
+ <xs:simpleType>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="prohibited"/>
+ <xs:enumeration value="optional"/>
+ <xs:enumeration value="required"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:attribute name="default" type="xs:string"/>
+ <xs:attribute name="fixed" type="xs:string"/>
+ <xs:attribute name="form" type="xs:formChoice"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="topLevelAttribute">
+ <xs:complexContent>
+ <xs:restriction base="xs:attribute">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:element name="simpleType" minOccurs="0" type="xs:localSimpleType"/>
+ </xs:sequence>
+ <xs:attribute name="ref" use="prohibited"/>
+ <xs:attribute name="form" use="prohibited"/>
+ <xs:attribute name="use" use="prohibited"/>
+ <xs:attribute name="name" use="required" type="xs:NCName"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="attrDecls">
+ <xs:sequence>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="attribute" type="xs:attribute"/>
+ <xs:element name="attributeGroup" type="xs:attributeGroupRef"/>
+ </xs:choice>
+ <xs:element ref="xs:anyAttribute" minOccurs="0"/>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:element name="anyAttribute" type="xs:wildcard" id="anyAttribute">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-anyAttribute"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:group name="complexTypeModel">
+ <xs:choice>
+ <xs:element ref="xs:simpleContent"/>
+ <xs:element ref="xs:complexContent"/>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:documentation>
+ This branch is short for
+ <complexContent>
+ <restriction base="xs:anyType">
+ ...
+ </restriction>
+ </complexContent></xs:documentation>
+ </xs:annotation>
+ <xs:group ref="xs:typeDefParticle" minOccurs="0"/>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ </xs:choice>
+ </xs:group>
+
+ <xs:complexType name="complexType" abstract="true">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:complexTypeModel"/>
+ <xs:attribute name="name" type="xs:NCName">
+ <xs:annotation>
+ <xs:documentation>
+ Will be restricted to required or forbidden</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="mixed" type="xs:boolean" use="optional" default="false">
+ <xs:annotation>
+ <xs:documentation>
+ Not allowed if simpleContent child is chosen.
+ May be overriden by setting on complexContent child.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="abstract" type="xs:boolean" use="optional" default="false"/>
+ <xs:attribute name="final" type="xs:derivationSet"/>
+ <xs:attribute name="block" type="xs:derivationSet"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="topLevelComplexType">
+ <xs:complexContent>
+ <xs:restriction base="xs:complexType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:complexTypeModel"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:NCName" use="required"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="localComplexType">
+ <xs:complexContent>
+ <xs:restriction base="xs:complexType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:complexTypeModel"/>
+ </xs:sequence>
+ <xs:attribute name="name" use="prohibited"/>
+ <xs:attribute name="abstract" use="prohibited"/>
+ <xs:attribute name="final" use="prohibited"/>
+ <xs:attribute name="block" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="restrictionType">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:choice minOccurs="0">
+ <xs:group ref="xs:typeDefParticle"/>
+ <xs:group ref="xs:simpleRestrictionModel"/>
+ </xs:choice>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:attribute name="base" type="xs:QName" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="complexRestrictionType">
+ <xs:complexContent>
+ <xs:restriction base="xs:restrictionType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>This choice is added simply to
+ make this a valid restriction per the REC</xs:documentation>
+ </xs:annotation>
+ <xs:group ref="xs:typeDefParticle"/>
+ </xs:choice>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="extensionType">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:group ref="xs:typeDefParticle" minOccurs="0"/>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:attribute name="base" type="xs:QName" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="complexContent" id="complexContent">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-complexContent"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:choice>
+ <xs:element name="restriction" type="xs:complexRestrictionType"/>
+ <xs:element name="extension" type="xs:extensionType"/>
+ </xs:choice>
+ <xs:attribute name="mixed" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation>
+ Overrides any setting on complexType parent.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="simpleRestrictionType">
+ <xs:complexContent>
+ <xs:restriction base="xs:restrictionType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>This choice is added simply to
+ make this a valid restriction per the REC</xs:documentation>
+ </xs:annotation>
+ <xs:group ref="xs:simpleRestrictionModel"/>
+ </xs:choice>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="simpleExtensionType">
+ <xs:complexContent>
+ <xs:restriction base="xs:extensionType">
+ <xs:sequence>
+ <xs:annotation>
+ <xs:documentation>
+ No typeDefParticle group reference</xs:documentation>
+ </xs:annotation>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="simpleContent" id="simpleContent">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-simpleContent"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:choice>
+ <xs:element name="restriction" type="xs:simpleRestrictionType"/>
+ <xs:element name="extension" type="xs:simpleExtensionType"/>
+ </xs:choice>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="complexType" type="xs:topLevelComplexType" id="complexType">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-complexType"/>
+ </xs:annotation>
+ </xs:element>
+
+
+ <xs:simpleType name="blockSet">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ <xs:documentation>
+ #all or (possibly empty) subset of {substitution, extension,
+ restriction}</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list>
+ <xs:simpleType>
+ <xs:restriction base="xs:derivationControl">
+ <xs:enumeration value="extension"/>
+ <xs:enumeration value="restriction"/>
+ <xs:enumeration value="substitution"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:list>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:complexType name="element" abstract="true">
+ <xs:annotation>
+ <xs:documentation>
+ The element element can be used either
+ at the top level to define an element-type binding globally,
+ or within a content model to either reference a globally-defined
+ element or type or declare an element-type binding locally.
+ The ref form is not allowed at the top level.</xs:documentation>
+ </xs:annotation>
+
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:choice minOccurs="0">
+ <xs:element name="simpleType" type="xs:localSimpleType"/>
+ <xs:element name="complexType" type="xs:localComplexType"/>
+ </xs:choice>
+ <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attributeGroup ref="xs:defRef"/>
+ <xs:attribute name="type" type="xs:QName"/>
+ <xs:attribute name="substitutionGroup" type="xs:QName"/>
+ <xs:attributeGroup ref="xs:occurs"/>
+ <xs:attribute name="default" type="xs:string"/>
+ <xs:attribute name="fixed" type="xs:string"/>
+ <xs:attribute name="nillable" type="xs:boolean" use="optional" default="false"/>
+ <xs:attribute name="abstract" type="xs:boolean" use="optional" default="false"/>
+ <xs:attribute name="final" type="xs:derivationSet"/>
+ <xs:attribute name="block" type="xs:blockSet"/>
+ <xs:attribute name="form" type="xs:formChoice"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="topLevelElement">
+ <xs:complexContent>
+ <xs:restriction base="xs:element">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:element name="simpleType" type="xs:localSimpleType"/>
+ <xs:element name="complexType" type="xs:localComplexType"/>
+ </xs:choice>
+ <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="ref" use="prohibited"/>
+ <xs:attribute name="form" use="prohibited"/>
+ <xs:attribute name="minOccurs" use="prohibited"/>
+ <xs:attribute name="maxOccurs" use="prohibited"/>
+ <xs:attribute name="name" use="required" type="xs:NCName"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="localElement">
+ <xs:complexContent>
+ <xs:restriction base="xs:element">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:element name="simpleType" type="xs:localSimpleType"/>
+ <xs:element name="complexType" type="xs:localComplexType"/>
+ </xs:choice>
+ <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="substitutionGroup" use="prohibited"/>
+ <xs:attribute name="final" use="prohibited"/>
+ <xs:attribute name="abstract" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="element" type="xs:topLevelElement" id="element">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-element"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="group" abstract="true">
+ <xs:annotation>
+ <xs:documentation>
+ group type for explicit groups, named top-level groups and
+ group references</xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:particle" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:attributeGroup ref="xs:defRef"/>
+ <xs:attributeGroup ref="xs:occurs"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="realGroup">
+ <xs:complexContent>
+ <xs:restriction base="xs:group">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0" maxOccurs="1">
+ <xs:element ref="xs:all"/>
+ <xs:element ref="xs:choice"/>
+ <xs:element ref="xs:sequence"/>
+ </xs:choice>
+ </xs:sequence>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="namedGroup">
+ <xs:complexContent>
+ <xs:restriction base="xs:realGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="1" maxOccurs="1">
+ <xs:element name="all">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:restriction base="xs:all">
+ <xs:group ref="xs:allModel"/>
+ <xs:attribute name="minOccurs" use="prohibited"/>
+ <xs:attribute name="maxOccurs" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="choice" type="xs:simpleExplicitGroup"/>
+ <xs:element name="sequence" type="xs:simpleExplicitGroup"/>
+ </xs:choice>
+ </xs:sequence>
+ <xs:attribute name="name" use="required" type="xs:NCName"/>
+ <xs:attribute name="ref" use="prohibited"/>
+ <xs:attribute name="minOccurs" use="prohibited"/>
+ <xs:attribute name="maxOccurs" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="groupRef">
+ <xs:complexContent>
+ <xs:restriction base="xs:realGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="ref" use="required" type="xs:QName"/>
+ <xs:attribute name="name" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="explicitGroup">
+ <xs:annotation>
+ <xs:documentation>
+ group type for the three kinds of group</xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:restriction base="xs:group">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:nestedParticle" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:NCName" use="prohibited"/>
+ <xs:attribute name="ref" type="xs:QName" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="simpleExplicitGroup">
+ <xs:complexContent>
+ <xs:restriction base="xs:explicitGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:nestedParticle" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="minOccurs" use="prohibited"/>
+ <xs:attribute name="maxOccurs" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="allModel">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:annotation>
+ <xs:documentation>This choice with min/max is here to
+ avoid a pblm with the Elt:All/Choice/Seq
+ Particle derivation constraint</xs:documentation>
+ </xs:annotation>
+ <xs:element name="element" type="xs:narrowMaxMin"/>
+ </xs:choice>
+ </xs:sequence>
+ </xs:group>
+
+
+ <xs:complexType name="narrowMaxMin">
+ <xs:annotation>
+ <xs:documentation>restricted max/min</xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:restriction base="xs:localElement">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:element name="simpleType" type="xs:localSimpleType"/>
+ <xs:element name="complexType" type="xs:localComplexType"/>
+ </xs:choice>
+ <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="minOccurs" use="optional" default="1">
+ <xs:simpleType>
+ <xs:restriction base="xs:nonNegativeInteger">
+ <xs:enumeration value="0"/>
+ <xs:enumeration value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:attribute name="maxOccurs" use="optional" default="1">
+ <xs:simpleType>
+ <xs:restriction base="xs:allNNI">
+ <xs:enumeration value="0"/>
+ <xs:enumeration value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="all">
+ <xs:annotation>
+ <xs:documentation>
+ Only elements allowed inside</xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:restriction base="xs:explicitGroup">
+ <xs:group ref="xs:allModel"/>
+ <xs:attribute name="minOccurs" use="optional" default="1">
+ <xs:simpleType>
+ <xs:restriction base="xs:nonNegativeInteger">
+ <xs:enumeration value="0"/>
+ <xs:enumeration value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:attribute name="maxOccurs" use="optional" default="1">
+ <xs:simpleType>
+ <xs:restriction base="xs:allNNI">
+ <xs:enumeration value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="all" id="all" type="xs:all">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-all"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="choice" type="xs:explicitGroup" id="choice">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-choice"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="sequence" type="xs:explicitGroup" id="sequence">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-sequence"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="group" type="xs:namedGroup" id="group">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-group"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="wildcard">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="namespace" type="xs:namespaceList" use="optional" default="##any"/>
+ <xs:attribute name="processContents" use="optional" default="strict">
+ <xs:simpleType>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="skip"/>
+ <xs:enumeration value="lax"/>
+ <xs:enumeration value="strict"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="any" id="any">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-any"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:wildcard">
+ <xs:attributeGroup ref="xs:occurs"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:annotation>
+ <xs:documentation>
+ simple type for the value of the 'namespace' attr of
+ 'any' and 'anyAttribute'</xs:documentation>
+ </xs:annotation>
+ <xs:annotation>
+ <xs:documentation>
+ Value is
+ ##any - - any non-conflicting WFXML/attribute at all
+
+ ##other - - any non-conflicting WFXML/attribute from
+ namespace other than targetNS
+
+ ##local - - any unqualified non-conflicting WFXML/attribute
+
+ one or - - any non-conflicting WFXML/attribute from
+ more URI the listed namespaces
+ references
+ (space separated)
+
+ ##targetNamespace or ##local may appear in the above list, to
+ refer to the targetNamespace of the enclosing
+ schema or an absent targetNamespace respectively</xs:documentation>
+ </xs:annotation>
+
+ <xs:simpleType name="namespaceList">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="##any"/>
+ <xs:enumeration value="##other"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list>
+ <xs:simpleType>
+ <xs:union memberTypes="xs:anyURI">
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="##targetNamespace"/>
+ <xs:enumeration value="##local"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+ </xs:list>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:element name="attribute" type="xs:topLevelAttribute" id="attribute">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-attribute"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="attributeGroup" abstract="true">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:attrDecls"/>
+ <xs:attributeGroup ref="xs:defRef"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="namedAttributeGroup">
+ <xs:complexContent>
+ <xs:restriction base="xs:attributeGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:attribute name="name" use="required" type="xs:NCName"/>
+ <xs:attribute name="ref" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="attributeGroupRef">
+ <xs:complexContent>
+ <xs:restriction base="xs:attributeGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="ref" use="required" type="xs:QName"/>
+ <xs:attribute name="name" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="attributeGroup" type="xs:namedAttributeGroup" id="attributeGroup">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-attributeGroup"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="include" id="include">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-include"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="schemaLocation" type="xs:anyURI" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="redefine" id="redefine">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-redefine"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:openAttrs">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xs:annotation"/>
+ <xs:group ref="xs:redefinable"/>
+ </xs:choice>
+ <xs:attribute name="schemaLocation" type="xs:anyURI" use="required"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="import" id="import">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-import"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="namespace" type="xs:anyURI"/>
+ <xs:attribute name="schemaLocation" type="xs:anyURI"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="selector" id="selector">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-selector"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="xpath" use="required">
+ <xs:simpleType>
+ <xs:annotation>
+ <xs:documentation>A subset of XPath expressions for use
+in selectors</xs:documentation>
+ <xs:documentation>A utility type, not for public
+use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:annotation>
+ <xs:documentation>The following pattern is intended to allow XPath
+ expressions per the following EBNF:
+ Selector ::= Path ( '|' Path )*
+ Path ::= ('.//')? Step ( '/' Step )*
+ Step ::= '.' | NameTest
+ NameTest ::= QName | '*' | NCName ':' '*'
+ child:: is also allowed
+ </xs:documentation>
+ </xs:annotation>
+ <xs:pattern value="(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*(\|(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*)*">
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="field" id="field">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-field"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="xpath" use="required">
+ <xs:simpleType>
+ <xs:annotation>
+ <xs:documentation>A subset of XPath expressions for use
+in fields</xs:documentation>
+ <xs:documentation>A utility type, not for public
+use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:annotation>
+ <xs:documentation>The following pattern is intended to allow XPath
+ expressions per the same EBNF as for selector,
+ with the following change:
+ Path ::= ('.//')? ( Step '/' )* ( Step | '@' NameTest )
+ </xs:documentation>
+ </xs:annotation>
+ <xs:pattern value="(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|((attribute::|@)((\i\c*:)?(\i\c*|\*))))(\|(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|((attribute::|@)((\i\c*:)?(\i\c*|\*)))))*">
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="keybase">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:element ref="xs:selector"/>
+ <xs:element ref="xs:field" minOccurs="1" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:NCName" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="identityConstraint">
+ <xs:annotation>
+ <xs:documentation>The three kinds of identity constraints, all with
+ type of or derived from 'keybase'.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice>
+ <xs:element ref="xs:unique"/>
+ <xs:element ref="xs:key"/>
+ <xs:element ref="xs:keyref"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:element name="unique" type="xs:keybase" id="unique">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-unique"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="key" type="xs:keybase" id="key">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-key"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="keyref" id="keyref">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-keyref"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:keybase">
+ <xs:attribute name="refer" type="xs:QName" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="notation" id="notation">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-notation"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="name" type="xs:NCName" use="required"/>
+ <xs:attribute name="public" type="xs:public"/>
+ <xs:attribute name="system" type="xs:anyURI"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:simpleType name="public">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ <xs:documentation>
+ A public identifier, per ISO 8879</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token"/>
+ </xs:simpleType>
+
+ <xs:element name="appinfo" id="appinfo">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-appinfo"/>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:any processContents="lax"/>
+ </xs:sequence>
+ <xs:attribute name="source" type="xs:anyURI"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="documentation" id="documentation">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-documentation"/>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:any processContents="lax"/>
+ </xs:sequence>
+ <xs:attribute name="source" type="xs:anyURI"/>
+ <xs:attribute ref="xml:lang"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="annotation" id="annotation">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-annotation"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:openAttrs">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xs:appinfo"/>
+ <xs:element ref="xs:documentation"/>
+ </xs:choice>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:annotation>
+ <xs:documentation>
+ notations for use within XML Schema schemas</xs:documentation>
+ </xs:annotation>
+
+ <xs:notation name="XMLSchemaStructures" public="structures" system="http://www.w3.org/2000/08/XMLSchema.xsd"/>
+ <xs:notation name="XML" public="REC-xml-19980210" system="http://www.w3.org/TR/1998/REC-xml-19980210"/>
+
+ <xs:complexType name="anyType" mixed="true">
+ <xs:annotation>
+ <xs:documentation>
+ Not the real urType, but as close an approximation as we can
+ get in the XML representation</xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
+ </xs:sequence>
+ <xs:anyAttribute processContents="lax"/>
+ </xs:complexType>
+
+ <xs:annotation>
+ <xs:documentation>
+ First the built-in primitive datatypes. These definitions are for
+ information only, the real built-in definitions are magic.
+ </xs:documentation>
+
+ <xs:documentation>
+ For each built-in datatype in this schema (both primitive and
+ derived) can be uniquely addressed via a URI constructed
+ as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype
+
+ For example, to address the int datatype, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int
+
+ Additionally, each facet definition element can be uniquely
+ addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the facet
+
+ For example, to address the maxInclusive facet, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#maxInclusive
+
+ Additionally, each facet usage in a built-in datatype definition
+ can be uniquely addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype, followed
+ by a period (".") followed by the name of the facet
+
+ For example, to address the usage of the maxInclusive facet in
+ the definition of int, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int.maxInclusive
+
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:simpleType name="string" id="string">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality" value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#string"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="preserve" id="string.preserve"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="boolean" id="boolean">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#boolean"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="boolean.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="float" id="float">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="total"/>
+ <hfp:hasProperty name="bounded" value="true"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ <hfp:hasProperty name="numeric" value="true"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#float"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="float.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="double" id="double">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="total"/>
+ <hfp:hasProperty name="bounded" value="true"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ <hfp:hasProperty name="numeric" value="true"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#double"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="double.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="decimal" id="decimal">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="totalDigits"/>
+ <hfp:hasFacet name="fractionDigits"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="total"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="true"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#decimal"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="decimal.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="duration" id="duration">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#duration"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="duration.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="dateTime" id="dateTime">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#dateTime"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="dateTime.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="time" id="time">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#time"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="time.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="date" id="date">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#date"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="date.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gYearMonth" id="gYearMonth">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gYearMonth"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gYearMonth.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gYear" id="gYear">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gYear"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gYear.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gMonthDay" id="gMonthDay">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gMonthDay"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gMonthDay.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gDay" id="gDay">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gDay"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gDay.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gMonth" id="gMonth">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gMonth"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gMonth.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="hexBinary" id="hexBinary">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#binary"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="hexBinary.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="base64Binary" id="base64Binary">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#base64Binary"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="base64Binary.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="anyURI" id="anyURI">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#anyURI"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="anyURI.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="QName" id="QName">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#QName"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="QName.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="NOTATION" id="NOTATION">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#NOTATION"/>
+ <xs:documentation>
+ NOTATION cannot be used directly in a schema; rather a type
+ must be derived from it by specifying at least one enumeration
+ facet whose value is the name of a NOTATION declared in the
+ schema.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="NOTATION.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:annotation>
+ <xs:documentation>
+ Now the derived primitive types
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:simpleType name="normalizedString" id="normalizedString">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#normalizedString"/>
+ </xs:annotation>
+ <xs:restriction base="xs:string">
+ <xs:whiteSpace value="replace"
+ id="normalizedString.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="token" id="token">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#token"/>
+ </xs:annotation>
+ <xs:restriction base="xs:normalizedString">
+ <xs:whiteSpace value="collapse" id="token.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="language" id="language">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#language"/>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:pattern
+ value="[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*"
+ id="language.pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.ietf.org/rfc/rfc3066.txt">
+ pattern specifies the content of section 2.12 of XML 1.0e2
+ and RFC 3066 (Revised version of RFC 1766).
+ </xs:documentation>
+ </xs:annotation>
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="IDREFS" id="IDREFS">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#IDREFS"/>
+ </xs:annotation>
+ <xs:restriction>
+ <xs:simpleType>
+ <xs:list itemType="xs:IDREF"/>
+ </xs:simpleType>
+ <xs:minLength value="1" id="IDREFS.minLength"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="ENTITIES" id="ENTITIES">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#ENTITIES"/>
+ </xs:annotation>
+ <xs:restriction>
+ <xs:simpleType>
+ <xs:list itemType="xs:ENTITY"/>
+ </xs:simpleType>
+ <xs:minLength value="1" id="ENTITIES.minLength"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="NMTOKEN" id="NMTOKEN">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#NMTOKEN"/>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:pattern value="\c+" id="NMTOKEN.pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/REC-xml#NT-Nmtoken">
+ pattern matches production 7 from the XML spec
+ </xs:documentation>
+ </xs:annotation>
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="NMTOKENS" id="NMTOKENS">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#NMTOKENS"/>
+ </xs:annotation>
+ <xs:restriction>
+ <xs:simpleType>
+ <xs:list itemType="xs:NMTOKEN"/>
+ </xs:simpleType>
+ <xs:minLength value="1" id="NMTOKENS.minLength"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="Name" id="Name">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#Name"/>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:pattern value="\i\c*" id="Name.pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/REC-xml#NT-Name">
+ pattern matches production 5 from the XML spec
+ </xs:documentation>
+ </xs:annotation>
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="NCName" id="NCName">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#NCName"/>
+ </xs:annotation>
+ <xs:restriction base="xs:Name">
+ <xs:pattern value="[\i-[:]][\c-[:]]*" id="NCName.pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/REC-xml-names/#NT-NCName">
+ pattern matches production 4 from the Namespaces in XML spec
+ </xs:documentation>
+ </xs:annotation>
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="ID" id="ID">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#ID"/>
+ </xs:annotation>
+ <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:simpleType name="IDREF" id="IDREF">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#IDREF"/>
+ </xs:annotation>
+ <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:simpleType name="ENTITY" id="ENTITY">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#ENTITY"/>
+ </xs:annotation>
+ <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:simpleType name="integer" id="integer">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#integer"/>
+ </xs:annotation>
+ <xs:restriction base="xs:decimal">
+ <xs:fractionDigits value="0" fixed="true" id="integer.fractionDigits"/>
+ <xs:pattern value="[\-+]?[0-9]+"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="nonPositiveInteger" id="nonPositiveInteger">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#nonPositiveInteger"/>
+ </xs:annotation>
+ <xs:restriction base="xs:integer">
+ <xs:maxInclusive value="0" id="nonPositiveInteger.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="negativeInteger" id="negativeInteger">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#negativeInteger"/>
+ </xs:annotation>
+ <xs:restriction base="xs:nonPositiveInteger">
+ <xs:maxInclusive value="-1" id="negativeInteger.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="long" id="long">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasProperty name="bounded" value="true"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#long"/>
+ </xs:annotation>
+ <xs:restriction base="xs:integer">
+ <xs:minInclusive value="-9223372036854775808" id="long.minInclusive"/>
+ <xs:maxInclusive value="9223372036854775807" id="long.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="int" id="int">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#int"/>
+ </xs:annotation>
+ <xs:restriction base="xs:long">
+ <xs:minInclusive value="-2147483648" id="int.minInclusive"/>
+ <xs:maxInclusive value="2147483647" id="int.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="short" id="short">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#short"/>
+ </xs:annotation>
+ <xs:restriction base="xs:int">
+ <xs:minInclusive value="-32768" id="short.minInclusive"/>
+ <xs:maxInclusive value="32767" id="short.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="byte" id="byte">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#byte"/>
+ </xs:annotation>
+ <xs:restriction base="xs:short">
+ <xs:minInclusive value="-128" id="byte.minInclusive"/>
+ <xs:maxInclusive value="127" id="byte.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="nonNegativeInteger" id="nonNegativeInteger">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#nonNegativeInteger"/>
+ </xs:annotation>
+ <xs:restriction base="xs:integer">
+ <xs:minInclusive value="0" id="nonNegativeInteger.minInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="unsignedLong" id="unsignedLong">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasProperty name="bounded" value="true"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#unsignedLong"/>
+ </xs:annotation>
+ <xs:restriction base="xs:nonNegativeInteger">
+ <xs:maxInclusive value="18446744073709551615"
+ id="unsignedLong.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="unsignedInt" id="unsignedInt">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#unsignedInt"/>
+ </xs:annotation>
+ <xs:restriction base="xs:unsignedLong">
+ <xs:maxInclusive value="4294967295"
+ id="unsignedInt.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="unsignedShort" id="unsignedShort">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#unsignedShort"/>
+ </xs:annotation>
+ <xs:restriction base="xs:unsignedInt">
+ <xs:maxInclusive value="65535"
+ id="unsignedShort.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="unsignedByte" id="unsignedByte">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#unsignedByte"/>
+ </xs:annotation>
+ <xs:restriction base="xs:unsignedShort">
+ <xs:maxInclusive value="255" id="unsignedByte.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="positiveInteger" id="positiveInteger">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#positiveInteger"/>
+ </xs:annotation>
+ <xs:restriction base="xs:nonNegativeInteger">
+ <xs:minInclusive value="1" id="positiveInteger.minInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="derivationControl">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="substitution"/>
+ <xs:enumeration value="extension"/>
+ <xs:enumeration value="restriction"/>
+ <xs:enumeration value="list"/>
+ <xs:enumeration value="union"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:group name="simpleDerivation">
+ <xs:choice>
+ <xs:element ref="xs:restriction"/>
+ <xs:element ref="xs:list"/>
+ <xs:element ref="xs:union"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:simpleType name="simpleDerivationSet">
+ <xs:annotation>
+ <xs:documentation>
+ #all or (possibly empty) subset of {restriction, union, list}
+ </xs:documentation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list>
+ <xs:simpleType>
+ <xs:restriction base="xs:derivationControl">
+ <xs:enumeration value="list"/>
+ <xs:enumeration value="union"/>
+ <xs:enumeration value="restriction"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:list>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:complexType name="simpleType" abstract="true">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:simpleDerivation"/>
+ <xs:attribute name="final" type="xs:simpleDerivationSet"/>
+ <xs:attribute name="name" type="xs:NCName">
+ <xs:annotation>
+ <xs:documentation>
+ Can be restricted to required or forbidden
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="topLevelSimpleType">
+ <xs:complexContent>
+ <xs:restriction base="xs:simpleType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:simpleDerivation"/>
+ </xs:sequence>
+ <xs:attribute name="name" use="required"
+ type="xs:NCName">
+ <xs:annotation>
+ <xs:documentation>
+ Required at the top level
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="localSimpleType">
+ <xs:complexContent>
+ <xs:restriction base="xs:simpleType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:simpleDerivation"/>
+ </xs:sequence>
+ <xs:attribute name="name" use="prohibited">
+ <xs:annotation>
+ <xs:documentation>
+ Forbidden when nested
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="final" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="simpleType" type="xs:topLevelSimpleType" id="simpleType">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-simpleType"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:group name="facets">
+ <xs:annotation>
+ <xs:documentation>
+ We should use a substitution group for facets, but
+ that's ruled out because it would allow users to
+ add their own, which we're not ready for yet.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice>
+ <xs:element ref="xs:minExclusive"/>
+ <xs:element ref="xs:minInclusive"/>
+ <xs:element ref="xs:maxExclusive"/>
+ <xs:element ref="xs:maxInclusive"/>
+ <xs:element ref="xs:totalDigits"/>
+ <xs:element ref="xs:fractionDigits"/>
+ <xs:element ref="xs:length"/>
+ <xs:element ref="xs:minLength"/>
+ <xs:element ref="xs:maxLength"/>
+ <xs:element ref="xs:enumeration"/>
+ <xs:element ref="xs:whiteSpace"/>
+ <xs:element ref="xs:pattern"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:group name="simpleRestrictionModel">
+ <xs:sequence>
+ <xs:element name="simpleType" type="xs:localSimpleType" minOccurs="0"/>
+ <xs:group ref="xs:facets" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:element name="restriction" id="restriction">
+ <xs:complexType>
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-restriction">
+ base attribute and simpleType child are mutually
+ exclusive, but one or other is required
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:simpleRestrictionModel"/>
+ <xs:attribute name="base" type="xs:QName" use="optional"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="list" id="list">
+ <xs:complexType>
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-list">
+ itemType attribute and simpleType child are mutually
+ exclusive, but one or other is required
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:element name="simpleType" type="xs:localSimpleType"
+ minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="itemType" type="xs:QName" use="optional"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="union" id="union">
+ <xs:complexType>
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-union">
+ memberTypes attribute must be non-empty or there must be
+ at least one simpleType child
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:element name="simpleType" type="xs:localSimpleType"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="memberTypes" use="optional">
+ <xs:simpleType>
+ <xs:list itemType="xs:QName"/>
+ </xs:simpleType>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="facet">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="value" use="required"/>
+ <xs:attribute name="fixed" type="xs:boolean" use="optional"
+ default="false"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="noFixedFacet">
+ <xs:complexContent>
+ <xs:restriction base="xs:facet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="fixed" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="minExclusive" id="minExclusive" type="xs:facet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-minExclusive"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="minInclusive" id="minInclusive" type="xs:facet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-minInclusive"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="maxExclusive" id="maxExclusive" type="xs:facet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-maxExclusive"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="maxInclusive" id="maxInclusive" type="xs:facet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-maxInclusive"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="numFacet">
+ <xs:complexContent>
+ <xs:restriction base="xs:facet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="value" type="xs:nonNegativeInteger" use="required"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="totalDigits" id="totalDigits">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-totalDigits"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:restriction base="xs:numFacet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="value" type="xs:positiveInteger" use="required"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="fractionDigits" id="fractionDigits" type="xs:numFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-fractionDigits"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="length" id="length" type="xs:numFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-length"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="minLength" id="minLength" type="xs:numFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-minLength"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="maxLength" id="maxLength" type="xs:numFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-maxLength"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="enumeration" id="enumeration" type="xs:noFixedFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-enumeration"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="whiteSpace" id="whiteSpace">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-whiteSpace"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:restriction base="xs:facet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="value" use="required">
+ <xs:simpleType>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="preserve"/>
+ <xs:enumeration value="replace"/>
+ <xs:enumeration value="collapse"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="pattern" id="pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-pattern"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:restriction base="xs:noFixedFacet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="value" type="xs:string" use="required"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+</xs:schema>
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/datatypes.dtd b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/datatypes.dtd
new file mode 100644
index 0000000..8e48553
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/datatypes.dtd
@@ -0,0 +1,203 @@
+<!--
+ DTD for XML Schemas: Part 2: Datatypes
+ $Id: datatypes.dtd,v 1.23 2001/03/16 17:36:30 ht Exp $
+ Note this DTD is NOT normative, or even definitive. - - the
+ prose copy in the datatypes REC is the definitive version
+ (which shouldn't differ from this one except for this comment
+ and entity expansions, but just in case)
+ -->
+
+<!--
+ This DTD cannot be used on its own, it is intended
+ only for incorporation in XMLSchema.dtd, q.v.
+ -->
+
+<!-- Define all the element names, with optional prefix -->
+<!ENTITY % simpleType "%p;simpleType">
+<!ENTITY % restriction "%p;restriction">
+<!ENTITY % list "%p;list">
+<!ENTITY % union "%p;union">
+<!ENTITY % maxExclusive "%p;maxExclusive">
+<!ENTITY % minExclusive "%p;minExclusive">
+<!ENTITY % maxInclusive "%p;maxInclusive">
+<!ENTITY % minInclusive "%p;minInclusive">
+<!ENTITY % totalDigits "%p;totalDigits">
+<!ENTITY % fractionDigits "%p;fractionDigits">
+<!ENTITY % length "%p;length">
+<!ENTITY % minLength "%p;minLength">
+<!ENTITY % maxLength "%p;maxLength">
+<!ENTITY % enumeration "%p;enumeration">
+<!ENTITY % whiteSpace "%p;whiteSpace">
+<!ENTITY % pattern "%p;pattern">
+
+<!--
+ Customisation entities for the ATTLIST of each element
+ type. Define one of these if your schema takes advantage
+ of the anyAttribute='##other' in the schema for schemas
+ -->
+
+<!ENTITY % simpleTypeAttrs "">
+<!ENTITY % restrictionAttrs "">
+<!ENTITY % listAttrs "">
+<!ENTITY % unionAttrs "">
+<!ENTITY % maxExclusiveAttrs "">
+<!ENTITY % minExclusiveAttrs "">
+<!ENTITY % maxInclusiveAttrs "">
+<!ENTITY % minInclusiveAttrs "">
+<!ENTITY % totalDigitsAttrs "">
+<!ENTITY % fractionDigitsAttrs "">
+<!ENTITY % lengthAttrs "">
+<!ENTITY % minLengthAttrs "">
+<!ENTITY % maxLengthAttrs "">
+<!ENTITY % enumerationAttrs "">
+<!ENTITY % whiteSpaceAttrs "">
+<!ENTITY % patternAttrs "">
+
+<!-- Define some entities for informative use as attribute
+ types -->
+<!ENTITY % URIref "CDATA">
+<!ENTITY % XPathExpr "CDATA">
+<!ENTITY % QName "NMTOKEN">
+<!ENTITY % QNames "NMTOKENS">
+<!ENTITY % NCName "NMTOKEN">
+<!ENTITY % nonNegativeInteger "NMTOKEN">
+<!ENTITY % boolean "(true|false)">
+<!ENTITY % simpleDerivationSet "CDATA">
+<!--
+ #all or space-separated list drawn from derivationChoice
+ -->
+
+<!--
+ Note that the use of 'facet' below is less restrictive
+ than is really intended: There should in fact be no
+ more than one of each of minInclusive, minExclusive,
+ maxInclusive, maxExclusive, totalDigits, fractionDigits,
+ length, maxLength, minLength within datatype,
+ and the min- and max- variants of Inclusive and Exclusive
+ are mutually exclusive. On the other hand, pattern and
+ enumeration may repeat.
+ -->
+<!ENTITY % minBound "(%minInclusive; | %minExclusive;)">
+<!ENTITY % maxBound "(%maxInclusive; | %maxExclusive;)">
+<!ENTITY % bounds "%minBound; | %maxBound;">
+<!ENTITY % numeric "%totalDigits; | %fractionDigits;">
+<!ENTITY % ordered "%bounds; | %numeric;">
+<!ENTITY % unordered
+ "%pattern; | %enumeration; | %whiteSpace; | %length; |
+ %maxLength; | %minLength;">
+<!ENTITY % facet "%ordered; | %unordered;">
+<!ENTITY % facetAttr
+ "value CDATA #REQUIRED
+ id ID #IMPLIED">
+<!ENTITY % fixedAttr "fixed %boolean; #IMPLIED">
+<!ENTITY % facetModel "(%annotation;)?">
+<!ELEMENT %simpleType;
+ ((%annotation;)?, (%restriction; | %list; | %union;))>
+<!ATTLIST %simpleType;
+ name %NCName; #IMPLIED
+ final %simpleDerivationSet; #IMPLIED
+ id ID #IMPLIED
+ %simpleTypeAttrs;>
+<!-- name is required at top level -->
+<!ELEMENT %restriction; ((%annotation;)?,
+ (%restriction1; |
+ ((%simpleType;)?,(%facet;)*)),
+ (%attrDecls;))>
+<!ATTLIST %restriction;
+ base %QName; #IMPLIED
+ id ID #IMPLIED
+ %restrictionAttrs;>
+<!--
+ base and simpleType child are mutually exclusive,
+ one is required.
+
+ restriction is shared between simpleType and
+ simpleContent and complexContent (in XMLSchema.xsd).
+ restriction1 is for the latter cases, when this
+ is restricting a complex type, as is attrDecls.
+ -->
+<!ELEMENT %list; ((%annotation;)?,(%simpleType;)?)>
+<!ATTLIST %list;
+ itemType %QName; #IMPLIED
+ id ID #IMPLIED
+ %listAttrs;>
+<!--
+ itemType and simpleType child are mutually exclusive,
+ one is required
+ -->
+<!ELEMENT %union; ((%annotation;)?,(%simpleType;)*)>
+<!ATTLIST %union;
+ id ID #IMPLIED
+ memberTypes %QNames; #IMPLIED
+ %unionAttrs;>
+<!--
+ At least one item in memberTypes or one simpleType
+ child is required
+ -->
+
+<!ELEMENT %maxExclusive; %facetModel;>
+<!ATTLIST %maxExclusive;
+ %facetAttr;
+ %fixedAttr;
+ %maxExclusiveAttrs;>
+<!ELEMENT %minExclusive; %facetModel;>
+<!ATTLIST %minExclusive;
+ %facetAttr;
+ %fixedAttr;
+ %minExclusiveAttrs;>
+
+<!ELEMENT %maxInclusive; %facetModel;>
+<!ATTLIST %maxInclusive;
+ %facetAttr;
+ %fixedAttr;
+ %maxInclusiveAttrs;>
+<!ELEMENT %minInclusive; %facetModel;>
+<!ATTLIST %minInclusive;
+ %facetAttr;
+ %fixedAttr;
+ %minInclusiveAttrs;>
+
+<!ELEMENT %totalDigits; %facetModel;>
+<!ATTLIST %totalDigits;
+ %facetAttr;
+ %fixedAttr;
+ %totalDigitsAttrs;>
+<!ELEMENT %fractionDigits; %facetModel;>
+<!ATTLIST %fractionDigits;
+ %facetAttr;
+ %fixedAttr;
+ %fractionDigitsAttrs;>
+
+<!ELEMENT %length; %facetModel;>
+<!ATTLIST %length;
+ %facetAttr;
+ %fixedAttr;
+ %lengthAttrs;>
+<!ELEMENT %minLength; %facetModel;>
+<!ATTLIST %minLength;
+ %facetAttr;
+ %fixedAttr;
+ %minLengthAttrs;>
+<!ELEMENT %maxLength; %facetModel;>
+<!ATTLIST %maxLength;
+ %facetAttr;
+ %fixedAttr;
+ %maxLengthAttrs;>
+
+<!-- This one can be repeated -->
+<!ELEMENT %enumeration; %facetModel;>
+<!ATTLIST %enumeration;
+ %facetAttr;
+ %enumerationAttrs;>
+
+<!ELEMENT %whiteSpace; %facetModel;>
+<!ATTLIST %whiteSpace;
+ %facetAttr;
+ %fixedAttr;
+ %whiteSpaceAttrs;>
+
+<!-- This one can be repeated -->
+<!ELEMENT %pattern; %facetModel;>
+<!ATTLIST %pattern;
+ %facetAttr;
+ %patternAttrs;>
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/xml.xsd b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/xml.xsd
new file mode 100644
index 0000000..d662b42
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/xml.xsd
@@ -0,0 +1,117 @@
+<?xml version='1.0'?>
+<!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" >
+<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
+
+ <xs:annotation>
+ <xs:documentation>
+ See http://www.w3.org/XML/1998/namespace.html and
+ http://www.w3.org/TR/REC-xml for information about this namespace.
+
+ This schema document describes the XML namespace, in a form
+ suitable for import by other schema documents.
+
+ Note that local names in this namespace are intended to be defined
+ only by the World Wide Web Consortium or its subgroups. The
+ following names are currently defined in this namespace and should
+ not be used with conflicting semantics by any Working Group,
+ specification, or document instance:
+
+ base (as an attribute name): denotes an attribute whose value
+ provides a URI to be used as the base for interpreting any
+ relative URIs in the scope of the element on which it
+ appears; its value is inherited. This name is reserved
+ by virtue of its definition in the XML Base specification.
+
+ lang (as an attribute name): denotes an attribute whose value
+ is a language code for the natural language of the content of
+ any element; its value is inherited. This name is reserved
+ by virtue of its definition in the XML specification.
+
+ space (as an attribute name): denotes an attribute whose
+ value is a keyword indicating what whitespace processing
+ discipline is intended for the content of the element; its
+ value is inherited. This name is reserved by virtue of its
+ definition in the XML specification.
+
+ Father (in any context at all): denotes Jon Bosak, the chair of
+ the original XML Working Group. This name is reserved by
+ the following decision of the W3C XML Plenary and
+ XML Coordination groups:
+
+ In appreciation for his vision, leadership and dedication
+ the W3C XML Plenary on this 10th day of February, 2000
+ reserves for Jon Bosak in perpetuity the XML name
+ xml:Father
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>This schema defines attributes and an attribute group
+ suitable for use by
+ schemas wishing to allow xml:base, xml:lang or xml:space attributes
+ on elements they define.
+
+ To enable this, such a schema must import this schema
+ for the XML namespace, e.g. as follows:
+ <schema . . .>
+ . . .
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
+
+ Subsequently, qualified reference to any of the attributes
+ or the group defined below will have the desired effect, e.g.
+
+ <type . . .>
+ . . .
+ <attributeGroup ref="xml:specialAttrs"/>
+
+ will define a type which will schema-validate an instance
+ element with any of those attributes</xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>In keeping with the XML Schema WG's standard versioning
+ policy, this schema document will persist at
+ http://www.w3.org/2001/03/xml.xsd.
+ At the date of issue it can also be found at
+ http://www.w3.org/2001/xml.xsd.
+ The schema document at that URI may however change in the future,
+ in order to remain compatible with the latest version of XML Schema
+ itself. In other words, if the XML Schema namespace changes, the version
+ of this document at
+ http://www.w3.org/2001/xml.xsd will change
+ accordingly; the version at
+ http://www.w3.org/2001/03/xml.xsd will not change.
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:attribute name="lang" type="xs:language">
+ <xs:annotation>
+ <xs:documentation>In due course, we should install the relevant ISO 2- and 3-letter
+ codes as the enumerated possible values . . .</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
+ <xs:attribute name="space" default="preserve">
+ <xs:simpleType>
+ <xs:restriction base="xs:NCName">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="preserve"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+
+ <xs:attribute name="base" type="xs:anyURI">
+ <xs:annotation>
+ <xs:documentation>See http://www.w3.org/TR/xmlbase/ for
+ information about this attribute.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
+ <xs:attributeGroup name="specialAttrs">
+ <xs:attribute ref="xml:base"/>
+ <xs:attribute ref="xml:lang"/>
+ <xs:attribute ref="xml:space"/>
+ </xs:attributeGroup>
+
+</xs:schema>
diff --git a/sdklib/test.gradle b/sdklib/test.gradle
index de568fa..db7c213 100755
--- a/sdklib/test.gradle
+++ b/sdklib/test.gradle
@@ -1,36 +1,18 @@
apply plugin: 'java'
-apply plugin: 'distrib'
apply plugin: 'maven'
-evaluationDependsOn(':dvlib')
+evaluationDependsOn(':base:dvlib')
group = 'com.android.tools'
archivesBaseName = 'sdklib-test'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':sdklib')
- compile project(':dvlib').sourceSets.test.output
+ compile project(':base:sdklib')
+ compile project(':base:dvlib').sourceSets.test.output
compile 'junit:junit:3.8.1'
}
sourceSets {
main.resources.srcDirs = [ 'src/test/java' ]
}
-
-javadoc {
- enabled = false
- dependsOn = []
-}
-
-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
deleted file mode 100644
index e75c7ad..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,46 +0,0 @@
-include 'ant-tasks'
-include 'archquery'
-include 'asset-studio'
-include 'common'
-include 'ddmlib'
-include 'draw9patch'
-include 'dvlib'
-include 'fat32lib'
-include 'jobb'
-include 'layoutlib-api'
-include 'lint'
-include 'lint-api'
-include 'lint-checks'
-include 'manifest-merger'
-include 'ninepatch'
-include 'rule-api'
-include 'screenshot2'
-include 'sdk-common'
-include 'sdklib'
-include 'sdklib-test'
-include 'testutils'
-
-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/NOTICE b/templates/NOTICE
new file mode 100644
index 0000000..06a9081
--- /dev/null
+++ b/templates/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2014, The Android Open 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/templates/activities/BlankActivity/globals.xml.ftl b/templates/activities/BlankActivity/globals.xml.ftl
index 0dca7d3..3475cf0 100644
--- a/templates/activities/BlankActivity/globals.xml.ftl
+++ b/templates/activities/BlankActivity/globals.xml.ftl
@@ -1,13 +1,11 @@
<?xml version="1.0"?>
<globals>
- <global id="projectOut" value="." />
<global id="manifestOut" value="${manifestDir}" />
- <global id="appCompat" value="${(minApiLevel lt 14)?string('1','')}" />
+ <global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
<!-- 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)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/activities/BlankActivity/recipe.xml.ftl b/templates/activities/BlankActivity/recipe.xml.ftl
index e44e643..cc4e5b5 100644
--- a/templates/activities/BlankActivity/recipe.xml.ftl
+++ b/templates/activities/BlankActivity/recipe.xml.ftl
@@ -1,9 +1,7 @@
<?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>
+ <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
@@ -19,64 +17,12 @@
<merge from="res/values-w820dp/dimens.xml"
to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
- <!-- 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/layout/activity_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
- <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" />
-
- <#elseif navType == 'drawer'>
- <instantiate from="res/layout/activity_drawer.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
- <instantiate from="res/layout/fragment_navigation_drawer.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/fragment_navigation_drawer.xml" />
-
- <#else>
- <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" || navType == "pager">
- <instantiate from="src/app_package/TabsAndPagerActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <#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 == "spinner">
- <instantiate from="src/app_package/DropdownActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- </#if>
+ <instantiate from="src/app_package/SimpleActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</recipe>
diff --git a/templates/activities/BlankActivity/root/AndroidManifest.xml.ftl b/templates/activities/BlankActivity/root/AndroidManifest.xml.ftl
index 98d8d7b..af1d2d6 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="${packageName}.${activityClass}"
+ <activity android:name="${relativePackage}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
diff --git a/templates/activities/BlankActivity/root/res/layout/activity_drawer.xml.ftl b/templates/activities/BlankActivity/root/res/layout/activity_drawer.xml.ftl
deleted file mode 100644
index c23b77d..0000000
--- a/templates/activities/BlankActivity/root/res/layout/activity_drawer.xml.ftl
+++ /dev/null
@@ -1,30 +0,0 @@
-<!-- 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.ftl b/templates/activities/BlankActivity/root/res/layout/activity_fragment_container.xml.ftl
deleted file mode 100644
index da3a7e4..0000000
--- a/templates/activities/BlankActivity/root/res/layout/activity_fragment_container.xml.ftl
+++ /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="${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
deleted file mode 100644
index bdaf98c..0000000
--- a/templates/activities/BlankActivity/root/res/layout/activity_pager.xml.ftl
+++ /dev/null
@@ -1,6 +0,0 @@
-<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- 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
new file mode 100644
index 0000000..e522310
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/layout/activity_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="${relativePackage}.${activityClass}">
+
+ <TextView
+ android:text="@string/hello_world"
+ 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
deleted file mode 100644
index 62c1224..0000000
--- a/templates/activities/BlankActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
+++ /dev/null
@@ -1,9 +0,0 @@
-<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
deleted file mode 100644
index db8cc12..0000000
--- a/templates/activities/BlankActivity/root/res/layout/fragment_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="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
deleted file mode 100644
index d9d95cd..0000000
--- a/templates/activities/BlankActivity/root/res/menu/global.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<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 054a3dc..27f6aaa 100644
--- a/templates/activities/BlankActivity/root/res/menu/main.xml.ftl
+++ b/templates/activities/BlankActivity/root/res/menu/main.xml.ftl
@@ -1,12 +1,9 @@
-<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat?has_content>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
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>
+ tools:context="${relativePackage}.${activityClass}" >
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
- ${(appCompat?has_content)?string('app','android')}:showAsAction="never" />
+ ${(appCompat)?string('app','android')}:showAsAction="never" />
</menu>
diff --git a/templates/activities/BlankActivity/root/res/values/dimens.xml.ftl b/templates/activities/BlankActivity/root/res/values/dimens.xml.ftl
index 74c7299..47c8224 100644
--- a/templates/activities/BlankActivity/root/res/values/dimens.xml.ftl
+++ b/templates/activities/BlankActivity/root/res/values/dimens.xml.ftl
@@ -2,10 +2,4 @@
<!-- 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 bcb8586..721bd51 100644
--- a/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
@@ -3,19 +3,7 @@
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
</#if>
- <#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
deleted file mode 100644
index bc28bb9..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/DrawerActivity.java.ftl
+++ /dev/null
@@ -1,83 +0,0 @@
-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
deleted file mode 100644
index eb2d740..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/DropdownActivity.java.ftl
+++ /dev/null
@@ -1,85 +0,0 @@
-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.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.widget.ArrayAdapter;
-import android.widget.TextView;
-
-public class ${activityClass} extends ${(appCompat?has_content)?string('ActionBar','')}Activity implements ActionBar.OnNavigationListener {
-
- /**
- * The serialization (saved instance state) Bundle key representing the
- * current dropdown 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 a dropdown list.
- final ActionBar actionBar = get${Support}ActionBar();
- actionBar.setDisplayShowTitleEnabled(false);
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
- <#if parentActivityClass != "">
- // Show the Up button in the action bar.
- actionBar.setDisplayHomeAsUpEnabled(true);
- </#if>
-
- // Set up the dropdown list navigation in the action bar.
- actionBar.setListNavigationCallbacks(
- // Specify a SpinnerAdapter to populate the dropdown list.
- new ArrayAdapter<String>(
- actionBar.getThemedContext(),
- android.R.layout.simple_list_item_1,
- android.R.id.text1,
- new String[] {
- getString(R.string.title_section1),
- getString(R.string.title_section2),
- getString(R.string.title_section3),
- }),
- this);
- }
-
- @Override
- public void onRestoreInstanceState(Bundle savedInstanceState) {
- // Restore the previously serialized current dropdown position.
- if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
- get${Support}ActionBar().setSelectedNavigationItem(
- savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- // Serialize the current dropdown position.
- outState.putInt(STATE_SELECTED_NAVIGATION_ITEM,
- get${Support}ActionBar().getSelectedNavigationIndex());
- }
-
- <#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.
- get${Support}FragmentManager().beginTransaction()
- .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
- .commit();
- return true;
- }
-
- <#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
deleted file mode 100644
index 3880362..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
+++ /dev/null
@@ -1,282 +0,0 @@
-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 417a5ed..52be392 100644
--- a/templates/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
+++ b/templates/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
@@ -1,32 +1,36 @@
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 <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
import android.os.Bundle;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.os.Build;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
-public class ${activityClass} extends ${(appCompat?has_content)?string('ActionBar','')}Activity {
+public class ${activityClass} extends ${(appCompat)?string('ActionBar','')}Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${layoutName});
-
- if (savedInstanceState == null) {
- get${Support}FragmentManager().beginTransaction()
- .add(R.id.container, new PlaceholderFragment())
- .commit();
- }
}
- <#include "include_options_menu.java.ftl">
- <#include "include_fragment.java.ftl">
+ @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;
+ }
+ @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/root/src/app_package/TabsAndPagerActivity.java.ftl b/templates/activities/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
deleted file mode 100644
index d89e4ac..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
+++ /dev/null
@@ -1,138 +0,0 @@
-package ${packageName};
-
-import java.util.Locale;
-
-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.view.ViewPager;
-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 ${(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 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;
-
- /**
- * The {@link ViewPager} that will host the section contents.
- */
- ViewPager mViewPager;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.${layoutName});
-
- <#if navType == 'tabs'>
- // Set up the action bar.
- 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 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 == '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.
- mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
- @Override
- public void onPageSelected(int position) {
- actionBar.setSelectedNavigationItem(position);
- }
- });
-
- // For each of the sections in the app, add a tab to the action bar.
- for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
- // Create a tab with text corresponding to the page title defined by
- // the adapter. Also specify this Activity object, which implements
- // the TabListener interface, as the callback (listener) for when
- // this tab is selected.
- actionBar.addTab(
- actionBar.newTab()
- .setText(mSectionsPagerAdapter.getPageTitle(i))
- .setTabListener(this));
- }
- </#if>
- }
-
- <#include "include_options_menu.java.ftl">
-
- <#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.
- mViewPager.setCurrentItem(tab.getPosition());
- }
-
- @Override
- public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
- }
-
- @Override
- public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
- }</#if>
-
- /**
- * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
- * one of the sections/tabs/pages.
- */
- public class SectionsPagerAdapter extends FragmentPagerAdapter {
-
- public SectionsPagerAdapter(FragmentManager fm) {
- super(fm);
- }
-
- @Override
- public Fragment getItem(int position) {
- // getItem is called to instantiate the fragment for the given page.
- // Return a PlaceholderFragment (defined as a static inner class below).
- return PlaceholderFragment.newInstance(position + 1);
- }
-
- @Override
- public int getCount() {
- // Show 3 total pages.
- return 3;
- }
-
- @Override
- public CharSequence getPageTitle(int position) {
- Locale l = Locale.getDefault();
- switch (position) {
- case 0:
- return getString(R.string.title_section1).toUpperCase(l);
- case 1:
- return getString(R.string.title_section2).toUpperCase(l);
- case 2:
- return getString(R.string.title_section3).toUpperCase(l);
- }
- return null;
- }
- }
-
- <#include "include_fragment.java.ftl">
-
-}
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
deleted file mode 100644
index c271435..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/include_fragment.java.ftl
+++ /dev/null
@@ -1,47 +0,0 @@
- /**
- * 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_options_menu.java.ftl b/templates/activities/BlankActivity/root/src/app_package/include_options_menu.java.ftl
deleted file mode 100644
index d99a3d2..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/include_options_menu.java.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-
- @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 a72292f..21861e5 100644
--- a/templates/activities/BlankActivity/template.xml
+++ b/templates/activities/BlankActivity/template.xml
@@ -5,9 +5,10 @@
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.">
+ description="Creates a new blank activity with an action bar.">
- <category value="Activities" />
+ <category value="Activity" />
+ <formfactor value="Mobile" />
<parameter
id="activityClass"
@@ -28,15 +29,6 @@
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"
@@ -61,19 +53,6 @@
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
<parameter
- id="navType"
- name="Navigation Type"
- type="enum"
- default="none"
- help="The type of navigation to use for the activity" >
- <option id="none" default="true">None</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
id="packageName"
name="Package name"
type="string"
@@ -84,12 +63,6 @@
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_blank_activity.png</thumb>
- <!-- 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="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/BlankActivityWithFragment/globals.xml.ftl b/templates/activities/BlankActivityWithFragment/globals.xml.ftl
new file mode 100644
index 0000000..3475cf0
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/globals.xml.ftl
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
+ <!-- e.g. getSupportActionBar vs. getActionBar -->
+ <global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="menuName" value="${classToResource(activityClass)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/activities/BlankActivityWithFragment/recipe.xml.ftl b/templates/activities/BlankActivityWithFragment/recipe.xml.ftl
new file mode 100644
index 0000000..227a92b
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/recipe.xml.ftl
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
+
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="res/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <instantiate from="res/layout/activity_fragment_container.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+
+ <instantiate from="res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
+ <instantiate from="src/app_package/SimpleActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+</recipe>
diff --git a/templates/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl b/templates/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..af1d2d6
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl
@@ -0,0 +1,24 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/templates/activities/BlankActivityWithFragment/root/res/layout/activity_fragment_container.xml.ftl b/templates/activities/BlankActivityWithFragment/root/res/layout/activity_fragment_container.xml.ftl
new file mode 100644
index 0000000..92b2b62
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/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="${relativePackage}.${activityClass}"
+ tools:ignore="MergeRootFrame" />
diff --git a/templates/activities/BlankActivityWithFragment/root/res/layout/fragment_simple.xml.ftl b/templates/activities/BlankActivityWithFragment/root/res/layout/fragment_simple.xml.ftl
new file mode 100644
index 0000000..8d5a2e5
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/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="${relativePackage}.${activityClass}$PlaceholderFragment">
+
+ <TextView
+ android:text="@string/hello_world"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/templates/activities/BlankActivityWithFragment/root/res/menu/main.xml.ftl b/templates/activities/BlankActivityWithFragment/root/res/menu/main.xml.ftl
new file mode 100644
index 0000000..27f6aaa
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/root/res/menu/main.xml.ftl
@@ -0,0 +1,9 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
+ xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="${relativePackage}.${activityClass}" >
+ <item android:id="@+id/action_settings"
+ android:title="@string/action_settings"
+ android:orderInCategory="100"
+ ${(appCompat)?string('app','android')}:showAsAction="never" />
+</menu>
diff --git a/templates/activities/BlankActivityWithFragment/root/res/values-w820dp/dimens.xml b/templates/activities/BlankActivityWithFragment/root/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/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/BlankActivityWithFragment/root/res/values/dimens.xml.ftl b/templates/activities/BlankActivityWithFragment/root/res/values/dimens.xml.ftl
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/root/res/values/dimens.xml.ftl
@@ -0,0 +1,5 @@
+<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/BlankActivityWithFragment/root/res/values/strings.xml.ftl b/templates/activities/BlankActivityWithFragment/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..991c89c
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/root/res/values/strings.xml.ftl
@@ -0,0 +1,7 @@
+<resources>
+ <#if !isNewProject>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
+ </#if>
+ <string name="hello_world">Hello world!</string>
+ <string name="action_settings">Settings</string>
+</resources>
diff --git a/templates/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl b/templates/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl
new file mode 100644
index 0000000..561f01d
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl
@@ -0,0 +1,32 @@
+package ${packageName};
+
+import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
+import android.<#if appCompat>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat>support.v4.</#if>app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.os.Build;
+
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
+
+public class ${activityClass} extends ${appCompat?string('ActionBar','')}Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+ if (savedInstanceState == null) {
+ get${Support}FragmentManager().beginTransaction()
+ .add(R.id.container, new PlaceholderFragment())
+ .commit();
+ }
+ }
+
+ <#include "include_options_menu.java.ftl">
+
+ <#include "include_fragment.java.ftl">
+}
diff --git a/templates/activities/BlankActivityWithFragment/root/src/app_package/include_fragment.java.ftl b/templates/activities/BlankActivityWithFragment/root/src/app_package/include_fragment.java.ftl
new file mode 100644
index 0000000..435b311
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/root/src/app_package/include_fragment.java.ftl
@@ -0,0 +1,15 @@
+ /**
+ * A placeholder fragment containing a simple view.
+ */
+ public static class PlaceholderFragment extends Fragment {
+
+ public PlaceholderFragment() {
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.${fragmentLayoutName}, container, false);
+ return rootView;
+ }
+ }
diff --git a/templates/activities/BlankActivityWithFragment/root/src/app_package/include_options_menu.java.ftl b/templates/activities/BlankActivityWithFragment/root/src/app_package/include_options_menu.java.ftl
new file mode 100644
index 0000000..5c4bb95
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/root/src/app_package/include_options_menu.java.ftl
@@ -0,0 +1,19 @@
+
+ @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;
+ }
+
+ @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/BlankActivityWithFragment/template.xml b/templates/activities/BlankActivityWithFragment/template.xml
new file mode 100644
index 0000000..9b26d2f
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/template.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<template
+ format="3"
+ revision="4"
+ name="Blank Activity with Fragment"
+ minApi="7"
+ minBuildApi="14"
+ description="Creates a new blank activity, with an action bar and a contained Fragment.">
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ 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"
+ constraints="nonempty"
+ default="MainActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_blank_activity_fragment.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/BlankActivityWithFragment/template_blank_activity_fragment.png b/templates/activities/BlankActivityWithFragment/template_blank_activity_fragment.png
new file mode 100644
index 0000000..53b310d
--- /dev/null
+++ b/templates/activities/BlankActivityWithFragment/template_blank_activity_fragment.png
Binary files differ
diff --git a/templates/activities/EmptyActivity/globals.xml.ftl b/templates/activities/EmptyActivity/globals.xml.ftl
new file mode 100644
index 0000000..4bf836f
--- /dev/null
+++ b/templates/activities/EmptyActivity/globals.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/activities/EmptyActivity/recipe.xml.ftl b/templates/activities/EmptyActivity/recipe.xml.ftl
new file mode 100644
index 0000000..41088d9
--- /dev/null
+++ b/templates/activities/EmptyActivity/recipe.xml.ftl
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<recipe>
+ <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/activity_simple.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="src/app_package/SimpleActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</recipe>
diff --git a/templates/activities/EmptyActivity/root/AndroidManifest.xml.ftl b/templates/activities/EmptyActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..8054909
--- /dev/null
+++ b/templates/activities/EmptyActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,20 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ >
+ <#if isLauncher>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/templates/activities/EmptyActivity/root/res/layout/activity_simple.xml b/templates/activities/EmptyActivity/root/res/layout/activity_simple.xml
new file mode 100644
index 0000000..39d12f0
--- /dev/null
+++ b/templates/activities/EmptyActivity/root/res/layout/activity_simple.xml
@@ -0,0 +1,12 @@
+<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"
+ tools:context="${relativePackage}.${activityClass}">
+
+ <TextView
+ android:text="@string/hello_world"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/templates/activities/EmptyActivity/root/res/values/strings.xml.ftl b/templates/activities/EmptyActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..a76fbc6
--- /dev/null
+++ b/templates/activities/EmptyActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,6 @@
+<resources>
+ <#if !isNewProject>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
+ </#if>
+ <string name="hello_world">Hello world!</string>
+</resources>
diff --git a/templates/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl b/templates/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl
new file mode 100644
index 0000000..2df6530
--- /dev/null
+++ b/templates/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl
@@ -0,0 +1,15 @@
+package ${packageName};
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+public class ${activityClass} extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+ }
+}
diff --git a/templates/activities/EmptyActivity/template.xml b/templates/activities/EmptyActivity/template.xml
new file mode 100644
index 0000000..73d7bc9
--- /dev/null
+++ b/templates/activities/EmptyActivity/template.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<template
+ format="3"
+ revision="4"
+ name="Empty Activity"
+ minApi="7"
+ minBuildApi="14"
+ description="Creates a new empty activity">
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="MainActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_blank_activity.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/EmptyActivity/template_blank_activity.png b/templates/activities/EmptyActivity/template_blank_activity.png
new file mode 100644
index 0000000..d6ace2c
--- /dev/null
+++ b/templates/activities/EmptyActivity/template_blank_activity.png
Binary files differ
diff --git a/templates/activities/FullscreenActivity/globals.xml.ftl b/templates/activities/FullscreenActivity/globals.xml.ftl
index d566fee..81a41ba 100644
--- a/templates/activities/FullscreenActivity/globals.xml.ftl
+++ b/templates/activities/FullscreenActivity/globals.xml.ftl
@@ -5,4 +5,5 @@
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="simpleName" value="${activityToLayout(activityClass)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/activities/FullscreenActivity/recipe.xml.ftl b/templates/activities/FullscreenActivity/recipe.xml.ftl
index 834a207..bdc8a19 100644
--- a/templates/activities/FullscreenActivity/recipe.xml.ftl
+++ b/templates/activities/FullscreenActivity/recipe.xml.ftl
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<recipe>
- <dependency mavenUrl="com.android.support:support-v4:18.0.0" />
+ <dependency mavenUrl="com.android.support:support-v4:19.+" />
<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 266df2f..4c5b799 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="${packageName}.${activityClass}"
+ <activity android:name="${relativePackage}.${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 000b639..31e0bf7 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="${packageName}.${activityClass}">
+ tools:context="${relativePackage}.${activityClass}">
<!-- The primary full-screen view. This can be replaced with whatever view
is needed to present your content, e.g. VideoView, SurfaceView,
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 9d6109b..d8db3c2 100644
--- a/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
+++ b/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
@@ -13,6 +13,7 @@
import android.view.MenuItem;
import android.support.v4.app.NavUtils;
</#if>
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* An example full-screen activity that shows and hides the system UI (i.e.
diff --git a/templates/activities/FullscreenActivity/template.xml b/templates/activities/FullscreenActivity/template.xml
index cf568ea..996fc6d 100644
--- a/templates/activities/FullscreenActivity/template.xml
+++ b/templates/activities/FullscreenActivity/template.xml
@@ -7,8 +7,8 @@
minApi="4"
minBuildApi="16">
<dependency name="android-support-v4" revision="8" />
-
- <category value="Activities" />
+ <category value="Activity" />
+ <formfactor value="Mobile" />
<parameter
id="activityClass"
diff --git a/templates/activities/GoogleMapsActivity/globals.xml.ftl b/templates/activities/GoogleMapsActivity/globals.xml.ftl
new file mode 100644
index 0000000..6b21e78
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/globals.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="projectOut" value="." />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="debugResOut" value="${projectOut}/src/debug/res" />
+ <global id="releaseResOut" value="${projectOut}/src/release/res" />
+ <global id="resOut" value="${resDir}" />
+ <global id="menuName" value="${classToResource(activityClass)}" />
+ <global id="simpleName" value="${activityToLayout(activityClass)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/activities/GoogleMapsActivity/recipe.xml.ftl b/templates/activities/GoogleMapsActivity/recipe.xml.ftl
new file mode 100644
index 0000000..f1694a6
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/recipe.xml.ftl
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
+ <dependency mavenUrl="com.android.support:appcompat-v7:19.+" />
+
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="res/layout/activity_map.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <merge from="res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="src/app_package/MapActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <merge from="debugRes/values/google_maps_api.xml.ftl"
+ to="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
+
+ <merge from="releaseRes/values/google_maps_api.xml.ftl"
+ to="${escapeXmlAttribute(releaseResOut)}/values/google_maps_api.xml" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <!-- Display the API key instructions. -->
+ <open file="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
+</recipe>
diff --git a/templates/activities/GoogleMapsActivity/root/AndroidManifest.xml.ftl b/templates/activities/GoogleMapsActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..6041dc2
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,31 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
+ <!-- The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
+ Google Maps Android API v2, but are recommended. -->
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+ <application>
+ <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
+ <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="@string/google_maps_key"/>
+
+ <activity android:name="${relativePackage}.${activityClass}"
+ android:label="@string/title_${simpleName}">
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+
+ </activity>
+ </application>
+
+</manifest>
diff --git a/templates/activities/GoogleMapsActivity/root/debugRes/values/google_maps_api.xml.ftl b/templates/activities/GoogleMapsActivity/root/debugRes/values/google_maps_api.xml.ftl
new file mode 100644
index 0000000..0c8c501
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/root/debugRes/values/google_maps_api.xml.ftl
@@ -0,0 +1,22 @@
+<resources>
+ <#--
+ NOTE: the merger does not merge comments (only NodeType == ELEMENT_NODE).
+ Wrap it in an element as a workaround. -->
+ <string name="google_maps_key_instructions" templateMergeStrategy="replace"><!--
+
+ TODO: Before you run your application, you need a Google Maps API key.
+
+ To get one, follow this link, follow the directions and press "Create" at the end:
+
+https://console.developers.google.com/flows/enableapi?apiid=maps_android_backend&keyType=CLIENT_SIDE_ANDROID&r=${debugKeystoreSha1}%3B${packageName}
+
+ You can also add your credentials to an existing key, using this line:
+ ${debugKeystoreSha1};${packageName}
+
+ Once you have your key (it starts with "AIza"), replace the "google_maps_key"
+ string in this file.
+ --></string>
+
+ <#-- Always preserve the existing key. -->
+ <string name="google_maps_key" templateMergeStrategy="preserve">YOUR_KEY_HERE</string>
+</resources>
diff --git a/templates/activities/GoogleMapsActivity/root/releaseRes/values/google_maps_api.xml.ftl b/templates/activities/GoogleMapsActivity/root/releaseRes/values/google_maps_api.xml.ftl
new file mode 100644
index 0000000..206463b
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/root/releaseRes/values/google_maps_api.xml.ftl
@@ -0,0 +1,22 @@
+<resources>
+ <#--
+ NOTE: the merger does not merge comments (only NodeType == ELEMENT_NODE).
+ Wrap it in an element as a workaround. -->
+ <string name="google_maps_key_instructions" templateMergeStrategy="replace"><!--
+
+ TODO: Before you release your application, you need a Google Maps API key.
+
+ To do this, you can either add your release key credentials to your existing
+ key, or create a new key.
+
+ Follow the directions here:
+
+https://developers.google.com/maps/documentation/android/start#get_an_android_certificate_and_the_google_maps_api_key
+
+ Once you have your key (it starts with "AIza"), replace the "google_maps_key"
+ string in this file.
+ --></string>
+
+ <#-- Always preserve the existing key. -->
+ <string name="google_maps_key" templateMergeStrategy="preserve">YOUR_KEY_HERE</string>
+</resources>
diff --git a/templates/activities/GoogleMapsActivity/root/res/layout/activity_map.xml.ftl b/templates/activities/GoogleMapsActivity/root/res/layout/activity_map.xml.ftl
new file mode 100644
index 0000000..3b6c9b8
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/root/res/layout/activity_map.xml.ftl
@@ -0,0 +1,7 @@
+<fragment 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:id="@+id/map"
+ tools:context="${relativePackage}.${activityClass}"
+ android:name="com.google.android.gms.maps.SupportMapFragment"/>
diff --git a/templates/activities/GoogleMapsActivity/root/res/values/strings.xml.ftl b/templates/activities/GoogleMapsActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..fae485a
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,3 @@
+<resources>
+ <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
+</resources>
diff --git a/templates/activities/GoogleMapsActivity/root/src/app_package/MapActivity.java.ftl b/templates/activities/GoogleMapsActivity/root/src/app_package/MapActivity.java.ftl
new file mode 100644
index 0000000..ce90b52
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/root/src/app_package/MapActivity.java.ftl
@@ -0,0 +1,65 @@
+package ${packageName};
+
+import android.support.v4.app.FragmentActivity;
+import android.os.Bundle;
+
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.SupportMapFragment;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.MarkerOptions;
+
+public class ${activityClass} extends FragmentActivity {
+
+ private GoogleMap mMap; // Might be null if Google Play services APK is not available.
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+ setUpMapIfNeeded();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ setUpMapIfNeeded();
+ }
+
+ /**
+ * Sets up the map if it is possible to do so (i.e., the Google Play services APK is correctly
+ * installed) and the map has not already been instantiated.. This will ensure that we only ever
+ * call {@link #setUpMap()} once when {@link #mMap} is not null.
+ * <p>
+ * If it isn't installed {@link SupportMapFragment} (and
+ * {@link com.google.android.gms.maps.MapView MapView}) will show a prompt for the user to
+ * install/update the Google Play services APK on their device.
+ * <p>
+ * A user can return to this FragmentActivity after following the prompt and correctly
+ * installing/updating/enabling the Google Play services. Since the FragmentActivity may not
+ * have been completely destroyed during this process (it is likely that it would only be
+ * stopped or paused), {@link #onCreate(Bundle)} may not be called again so we should call this
+ * method in {@link #onResume()} to guarantee that it will be called.
+ */
+ private void setUpMapIfNeeded() {
+ // Do a null check to confirm that we have not already instantiated the map.
+ if (mMap == null) {
+ // Try to obtain the map from the SupportMapFragment.
+ mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
+ .getMap();
+ // Check if we were successful in obtaining the map.
+ if (mMap != null) {
+ setUpMap();
+ }
+ }
+ }
+
+ /**
+ * This is where we can add markers or lines, add listeners or move the camera. In this case, we
+ * just add a marker near Africa.
+ * <p>
+ * This should only be called once and when we are sure that {@link #mMap} is not null.
+ */
+ private void setUpMap() {
+ mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
+ }
+}
diff --git a/templates/activities/GoogleMapsActivity/template.xml b/templates/activities/GoogleMapsActivity/template.xml
new file mode 100644
index 0000000..5db66fc
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/template.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ name="Google Maps Activity"
+ description="Creates a new activity with a Google Map"
+ minApi="9"
+ minBuildApi="14">
+
+ <dependency name="android-support-v4" revision="8" />
+
+ <category value="Google" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MapsActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_map"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="Map"
+ help="The name of the activity." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <thumbs>
+ <thumb>template_map_activity.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/GoogleMapsActivity/template_map_activity.png b/templates/activities/GoogleMapsActivity/template_map_activity.png
new file mode 100644
index 0000000..212b31e
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/template_map_activity.png
Binary files differ
diff --git a/templates/activities/GooglePlayServicesActivity/globals.xml.ftl b/templates/activities/GooglePlayServicesActivity/globals.xml.ftl
new file mode 100644
index 0000000..f753fc4
--- /dev/null
+++ b/templates/activities/GooglePlayServicesActivity/globals.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="projectOut" value="." />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/activities/GooglePlayServicesActivity/recipe.xml.ftl b/templates/activities/GooglePlayServicesActivity/recipe.xml.ftl
new file mode 100644
index 0000000..2a13c74
--- /dev/null
+++ b/templates/activities/GooglePlayServicesActivity/recipe.xml.ftl
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
+
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <merge from="res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="src/app_package/activity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+</recipe>
diff --git a/templates/activities/GooglePlayServicesActivity/root/AndroidManifest.xml.ftl b/templates/activities/GooglePlayServicesActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..0001419
--- /dev/null
+++ b/templates/activities/GooglePlayServicesActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,24 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ >
+ <#if isLauncher>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+
+ <meta-data
+ android:name="com.google.android.gms.version"
+ android:value="@integer/google_play_services_version" />
+ </application>
+
+</manifest>
diff --git a/templates/activities/GooglePlayServicesActivity/root/res/values/strings.xml.ftl b/templates/activities/GooglePlayServicesActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..24849de
--- /dev/null
+++ b/templates/activities/GooglePlayServicesActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,5 @@
+<resources>
+ <#if !isNewProject>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
+ </#if>
+</resources>
diff --git a/templates/activities/GooglePlayServicesActivity/root/src/app_package/activity.java.ftl b/templates/activities/GooglePlayServicesActivity/root/src/app_package/activity.java.ftl
new file mode 100644
index 0000000..0c0a9e1
--- /dev/null
+++ b/templates/activities/GooglePlayServicesActivity/root/src/app_package/activity.java.ftl
@@ -0,0 +1,202 @@
+package ${packageName};
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+<#if includeCast>
+import com.google.android.gms.cast.Cast;
+</#if>
+<#if includeDrive>
+import com.google.android.gms.drive.Drive;
+</#if>
+<#if includeGames>
+import com.google.android.gms.games.Games;
+</#if>
+<#if includePlus>
+import com.google.android.gms.plus.Plus;
+</#if>
+<#if includeWallet>
+import com.google.android.gms.wallet.Wallet;
+</#if>
+
+public class ${activityClass} extends Activity implements
+ GoogleApiClient.ConnectionCallbacks,
+ GoogleApiClient.OnConnectionFailedListener {
+
+ private static final String TAG = "${activityClass}";
+
+ private static final String KEY_IN_RESOLUTION = "is_in_resolution";
+
+ /**
+ * Request code for auto Google Play Services error resolution.
+ */
+ protected static final int REQUEST_CODE_RESOLUTION = 1;
+
+ /**
+ * Google API client.
+ */
+ private GoogleApiClient mGoogleApiClient;
+
+ /**
+ * Determines if the client is in a resolution state, and
+ * waiting for resolution intent to return.
+ */
+ private boolean mIsInResolution;
+
+ /**
+ * Called when the activity is starting. Restores the activity state.
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mIsInResolution = savedInstanceState.getBoolean(KEY_IN_RESOLUTION, false);
+ }
+ }
+
+ /**
+ * Called when the Activity is made visible.
+ * A connection to Play Services need to be initiated as
+ * soon as the activity is visible. Registers {@code ConnectionCallbacks}
+ * and {@code OnConnectionFailedListener} on the
+ * activities itself.
+ */
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (mGoogleApiClient == null) {
+ mGoogleApiClient = new GoogleApiClient.Builder(this)
+ <#if includeCast>
+ .addApi(Cast.API)
+ </#if>
+ <#if includeDrive>
+ .addApi(Drive.API)
+ </#if>
+ <#if includeGames>
+ .addApi(Games.API)
+ </#if>
+ <#if includePlus>
+ .addApi(Plus.API)
+ </#if>
+ <#if includeWallet>
+ .addApi(Wallet.API)
+ </#if>
+ <#if includeDrive>
+ .addScope(Drive.SCOPE_FILE)
+ </#if>
+ <#if includeGames>
+ .addScope(Games.SCOPE_GAMES)
+ </#if>
+ <#if includePlus>
+ .addScope(Plus.SCOPE_PLUS_LOGIN)
+ </#if>
+ // Optionally, add additional APIs and scopes if required.
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .build();
+ }
+ mGoogleApiClient.connect();
+ }
+
+ /**
+ * Called when activity gets invisible. Connection to Play Services needs to
+ * be disconnected as soon as an activity is invisible.
+ */
+ @Override
+ protected void onStop() {
+ if (mGoogleApiClient != null) {
+ mGoogleApiClient.disconnect();
+ }
+ super.onStop();
+ }
+
+ /**
+ * Saves the resolution state.
+ */
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_IN_RESOLUTION, mIsInResolution);
+ }
+
+ /**
+ * Handles Google Play Services resolution callbacks.
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case REQUEST_CODE_RESOLUTION:
+ retryConnecting();
+ break;
+ }
+ }
+
+ private void retryConnecting() {
+ mIsInResolution = false;
+ if (!mGoogleApiClient.isConnecting()) {
+ mGoogleApiClient.connect();
+ }
+ }
+
+ /**
+ * Called when {@code mGoogleApiClient} is connected.
+ */
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ Log.i(TAG, "GoogleApiClient connected");
+ // TODO: Start making API requests.
+ }
+
+ /**
+ * Called when {@code mGoogleApiClient} connection is suspended.
+ */
+ @Override
+ public void onConnectionSuspended(int cause) {
+ Log.i(TAG, "GoogleApiClient connection suspended");
+ retryConnecting();
+ }
+
+ /**
+ * Called when {@code mGoogleApiClient} is trying to connect but failed.
+ * Handle {@code result.getResolution()} if there is a resolution
+ * available.
+ */
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
+ if (!result.hasResolution()) {
+ // Show a localized error dialog.
+ GooglePlayServicesUtil.getErrorDialog(
+ result.getErrorCode(), this, 0, new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ retryConnecting();
+ }
+ }).show();
+ return;
+ }
+ // If there is an existing resolution error being displayed or a resolution
+ // activity has started before, do nothing and wait for resolution
+ // progress to be completed.
+ if (mIsInResolution) {
+ return;
+ }
+ mIsInResolution = true;
+ try {
+ result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
+ } catch (SendIntentException e) {
+ Log.e(TAG, "Exception while starting resolution activity", e);
+ retryConnecting();
+ }
+ }
+}
diff --git a/templates/activities/GooglePlayServicesActivity/template.xml b/templates/activities/GooglePlayServicesActivity/template.xml
new file mode 100644
index 0000000..0d467a6
--- /dev/null
+++ b/templates/activities/GooglePlayServicesActivity/template.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+<template
+ format="3"
+ revision="1"
+ name="Google Play Services Activity"
+ description="Creates a new activity that initiates and connects to the Google Play Services unified client."
+ minApi="7"
+ minBuildApi="7">
+
+ <category value="Google" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="GooglePlayServicesActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="GooglePlayServicesActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="includeGames"
+ name="Enable Google Play Games Services"
+ type="boolean"
+ default="false">
+ </parameter>
+
+ <parameter
+ id="includePlus"
+ name="Enable Google+ APIs"
+ type="boolean"
+ default="false">
+ </parameter>
+
+ <parameter
+ id="includeDrive"
+ name="Enable Google Drive APIs"
+ type="boolean"
+ default="false">
+ </parameter>
+
+ <parameter
+ id="includeCast"
+ name="Enable Google Cast APIs"
+ type="boolean"
+ default="false">
+ </parameter>
+
+ <parameter
+ id="includeWallet"
+ name="Enable Google Wallet Instant Buy APIs"
+ type="boolean"
+ default="false">
+ </parameter>
+
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_play_services_activity.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/GooglePlayServicesActivity/template_play_services_activity.png b/templates/activities/GooglePlayServicesActivity/template_play_services_activity.png
new file mode 100644
index 0000000..fffaac4
--- /dev/null
+++ b/templates/activities/GooglePlayServicesActivity/template_play_services_activity.png
Binary files differ
diff --git a/templates/activities/LoginActivity/globals.xml.ftl b/templates/activities/LoginActivity/globals.xml.ftl
index 05c9aad..eafa59c 100644
--- a/templates/activities/LoginActivity/globals.xml.ftl
+++ b/templates/activities/LoginActivity/globals.xml.ftl
@@ -6,4 +6,5 @@
<global id="resOut" value="${resDir}" />
<global id="menuName" value="${classToResource(activityClass)}" />
<global id="simpleName" value="${activityToLayout(activityClass)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/activities/LoginActivity/recipe.xml.ftl b/templates/activities/LoginActivity/recipe.xml.ftl
index bb232af..43198a0 100644
--- a/templates/activities/LoginActivity/recipe.xml.ftl
+++ b/templates/activities/LoginActivity/recipe.xml.ftl
@@ -1,16 +1,14 @@
<?xml version="1.0"?>
<recipe>
- <dependency mavenUrl="com.android.support:support-v4:18.0.0" />
+ <dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
+ <dependency mavenUrl="com.android.support:appcompat-v7:19.+" />
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <merge from="res/values/styles.xml"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
- <merge from="res/values-large/styles.xml"
- to="${escapeXmlAttribute(resOut)}/values-large/styles.xml" />
- <copy from="res/menu/activity_login.xml"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+ <merge from="res/values/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+
<instantiate from="res/layout/activity_login.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
@@ -20,5 +18,11 @@
<instantiate from="src/app_package/LoginActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ <#if includeGooglePlus>
+ <instantiate from="src/app_package/PlusBaseActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/PlusBaseActivity.java" />
+ </#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
</recipe>
diff --git a/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl b/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl
index 4f35c79..a206ed4 100644
--- a/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl
+++ b/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl
@@ -1,19 +1,37 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+<#if includeGooglePlus>
+ <!-- To access Google+ APIs: -->
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <!-- To retrieve OAuth 2.0 tokens or invalidate tokens to disconnect a user. This disconnect
+ option is required to comply with the Google+ Sign-In developer policies -->
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+
+ <!-- To retrieve the account name (email) as part of sign-in: -->
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" /></#if>
+
+ <!-- To auto-complete the email text field in the login form with the user's emails --><#if !includeGooglePlus>
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" /></#if>
+ <uses-permission android:name="android.permission.READ_PROFILE" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
<application>
- <activity android:name="${packageName}.${activityClass}"
+ <activity android:name=".${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
android:label="@string/title_${simpleName}"
</#if>
- android:windowSoftInputMode="adjustResize|stateVisible"
+ android:windowSoftInputMode="adjustResize|<#if includeGooglePlus>stateHidden<#else>stateVisible</#if>"
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
<#if parentActivityClass != "">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="${parentActivityClass}" />
</#if>
</activity>
+<#if includeGooglePlus>
+ <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
+</#if>
</application>
</manifest>
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 9f0fb73..b24781f 100644
--- a/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
+++ b/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
@@ -1,69 +1,110 @@
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- tools:context="${packageName}.${activityClass}">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context="${relativePackage}.${activityClass}">
<!-- Login progress -->
- <LinearLayout android:id="@+id/login_status"
- android:visibility="gone"
+ <ProgressBar
+ android:id="@+id/login_progress"
+ style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center_horizontal"
- android:orientation="vertical">
- <ProgressBar style="?android:attr/progressBarStyleLarge"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="8dp"/>
- <TextView
- android:id="@+id/login_status_message"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:fontFamily="sans-serif-light"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="16dp"
- android:text="@string/login_progress_signing_in" />
- </LinearLayout>
+ android:layout_marginBottom="8dp"
+ android:visibility="gone"/>
- <!-- Login form -->
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout style="@style/LoginFormContainer"
+ android:layout_height="match_parent"
+ >
+<#if includeGooglePlus>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:orientation="vertical">
- <EditText
- android:id="@+id/email"
- android:singleLine="true"
- android:maxLines="1"
+ <com.google.android.gms.common.SignInButton
+ android:id="@+id/plus_sign_in_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:inputType="textEmailAddress"
- android:hint="@string/prompt_email" />
+ android:layout_marginBottom="32dp"/>
- <EditText
- android:id="@+id/password"
- android:singleLine="true"
- android:maxLines="1"
+ <LinearLayout
+ android:id="@+id/plus_sign_out_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/prompt_password"
- android:inputType="textPassword"
- android:imeActionLabel="@string/action_sign_in_short"
- android:imeActionId="@+id/login"
- android:imeOptions="actionUnspecified" />
+ android:visibility="gone"
+ android:weightSum="2">
- <Button android:id="@+id/sign_in_button"
- android:layout_width="wrap_content"
+ <Button
+ android:id="@+id/plus_sign_out_button"
+ style="?android:textAppearanceSmall"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/plus_sign_out"/>
+
+ <Button
+ android:id="@+id/plus_disconnect_button"
+ style="?android:textAppearanceSmall"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/plus_disconnect"/>
+
+ </LinearLayout>
+</#if>
+
+ <LinearLayout
+ android:id="@+id/email_login_form"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:text="@string/action_sign_in_register"
- android:paddingLeft="32dp"
- android:paddingRight="32dp"
- android:layout_gravity="right" />
+ android:orientation="vertical">
+ <AutoCompleteTextView
+ android:id="@+id/email"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/prompt_email"
+ android:inputType="textEmailAddress"
+ android:maxLines="1"
+ android:singleLine="true"/>
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/prompt_password"
+ android:imeActionId="@+id/login"
+ android:imeActionLabel="@string/action_sign_in_short"
+ android:imeOptions="actionUnspecified"
+ android:inputType="textPassword"
+ android:maxLines="1"
+ android:singleLine="true"/>
+
+ <Button
+ android:id="@+id/email_sign_in_button"
+ style="?android:textAppearanceSmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/action_sign_in"
+ android:textStyle="bold"/>
+
+ </LinearLayout>
+<#if includeGooglePlus>
</LinearLayout>
-
+</#if>
</ScrollView>
-</merge>
+
+</LinearLayout>
+
+
+
diff --git a/templates/activities/LoginActivity/root/res/menu/activity_login.xml b/templates/activities/LoginActivity/root/res/menu/activity_login.xml
deleted file mode 100644
index f39c9a3..0000000
--- a/templates/activities/LoginActivity/root/res/menu/activity_login.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<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" />
-</menu>
diff --git a/templates/activities/LoginActivity/root/res/values-large/styles.xml b/templates/activities/LoginActivity/root/res/values-large/styles.xml
deleted file mode 100644
index 7b56acd..0000000
--- a/templates/activities/LoginActivity/root/res/values-large/styles.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<resources>
-
- <style name="LoginFormContainer">
- <item name="android:layout_width">400dp</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_gravity">center</item>
- <item name="android:padding">16dp</item>
- </style>
-
-</resources>
diff --git a/templates/activities/LoginActivity/root/res/values/dimens.xml b/templates/activities/LoginActivity/root/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/templates/activities/LoginActivity/root/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<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/LoginActivity/root/res/values/strings.xml.ftl b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
index def1b2f..97c23b6 100644
--- a/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
@@ -5,15 +5,11 @@
<!-- Strings related to login -->
<string name="prompt_email">Email</string>
- <string name="prompt_password">Password</string>
-
- <string name="action_sign_in_register"><b>Sign in</b> or register</string>
+ <string name="prompt_password">Password (optional)</string>
+ <string name="action_sign_in">Sign in or register</string>
<string name="action_sign_in_short">Sign in</string>
-
- <string name="action_forgot_password">Recover lost password</string>
-
- <string name="login_progress_signing_in">Signing in…</string>
-
+<#if includeGooglePlus> <string name="plus_sign_out">Switch Google+ account</string>
+ <string name="plus_disconnect">Disconnect from Google+</string></#if>
<string name="error_invalid_email">This email address is invalid</string>
<string name="error_invalid_password">This password is too short</string>
<string name="error_incorrect_password">This password is incorrect</string>
diff --git a/templates/activities/LoginActivity/root/res/values/styles.xml b/templates/activities/LoginActivity/root/res/values/styles.xml
deleted file mode 100644
index eaec28d..0000000
--- a/templates/activities/LoginActivity/root/res/values/styles.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<resources>
-
- <style name="LoginFormContainer">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:padding">16dp</item>
- </style>
-
-</resources>
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 4f28c21..866c673 100644
--- a/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
+++ b/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
@@ -3,70 +3,98 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
-import android.app.Activity;
+<#if !includeGooglePlus>import android.app.Activity;</#if>
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentResolver;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.AsyncTask;
+<#if minApiLevel lt 14>import android.os.Build.VERSION;</#if>
import android.os.Build;
import android.os.Bundle;
+import android.provider.ContactsContract;
import android.text.TextUtils;
import android.view.KeyEvent;
-import android.view.Menu;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
-<#if parentActivityClass != "">
-import android.view.MenuItem;
-import android.support.v4.app.NavUtils;
+<#if includeGooglePlus>
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.android.gms.common.SignInButton;
</#if>
+import java.util.ArrayList;
+import java.util.List;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
- * Activity which displays a login screen to the user, offering registration as
- * well.
+ * A login screen that offers login via email/password<#if includeGooglePlus> and via Google+ sign in</#if>.
+<#if includeGooglePlus> * <p/>
+ * ************ IMPORTANT SETUP NOTES: ************
+ * In order for Google+ sign in to work with your app, you must first go to:
+ * https://developers.google.com/+/mobile/android/getting-started#step_1_enable_the_google_api
+ * and follow the steps in "Step 1" to create an OAuth 2.0 client for your package.</#if>
*/
-public class ${activityClass} extends Activity {
+public class ${activityClass} extends <#if includeGooglePlus>PlusBase</#if>Activity implements LoaderCallbacks<Cursor>{
+
/**
* A dummy authentication store containing known user names and passwords.
* TODO: remove after connecting to a real authentication system.
*/
private static final String[] DUMMY_CREDENTIALS = new String[]{
- "foo@example.com:hello",
- "bar@example.com:world"
+ "foo@example.com:hello", "bar@example.com:world"
};
-
- /**
- * The default email to populate the email field with.
- */
- public static final String EXTRA_EMAIL = "com.example.android.authenticatordemo.extra.EMAIL";
-
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private UserLoginTask mAuthTask = null;
- // Values for email and password at the time of the login attempt.
- private String mEmail;
- private String mPassword;
-
// UI references.
- private EditText mEmailView;
+ private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
+ private View mProgressView;<#if includeGooglePlus>
+ private View mEmailLoginFormView;
+ private SignInButton mPlusSignInButton;
+ private View mSignOutButtons;</#if>
private View mLoginFormView;
- private View mLoginStatusView;
- private TextView mLoginStatusMessageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- setContentView(R.layout.${layoutName});
- <#if parentActivityClass != "">
+ setContentView(R.layout.activity_login);
+<#if parentActivityClass != "">
setupActionBar();
- </#if>
+</#if>
+<#if includeGooglePlus>
+
+ // Find the Google+ sign in button.
+ mPlusSignInButton = (SignInButton) findViewById(R.id.plus_sign_in_button);
+ if (supportsGooglePlayServices()) {
+ // Set a listener to connect the user when the G+ button is clicked.
+ mPlusSignInButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ signIn();
+ }
+ });
+ } else {
+ // Don't offer G+ sign in if the app's version is too low to support Google Play
+ // Services.
+ mPlusSignInButton.setVisibility(View.GONE);
+ return;
+ }
+</#if>
// Set up the login form.
- mEmail = getIntent().getStringExtra(EXTRA_EMAIL);
- mEmailView = (EditText) findViewById(R.id.email);
- mEmailView.setText(mEmail);
+ mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
+ populateAutoComplete();
mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@@ -80,16 +108,32 @@
}
});
- mLoginFormView = findViewById(R.id.login_form);
- mLoginStatusView = findViewById(R.id.login_status);
- mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message);
-
- findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener() {
+ Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
+ mEmailSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
attemptLogin();
}
});
+
+ mLoginFormView = findViewById(R.id.login_form);
+ mProgressView = findViewById(R.id.login_progress);<#if includeGooglePlus>
+ mEmailLoginFormView = findViewById(R.id.email_login_form);
+ mSignOutButtons = findViewById(R.id.plus_sign_out_buttons);</#if>
+ }
+
+ private void populateAutoComplete() {
+<#if minApiLevel gte 14>
+ getLoaderManager().initLoader(0, null, this);
+<#else>
+ if (VERSION.SDK_INT >= 14) {
+ // Use ContactsContract.Profile (API 14+)
+ getLoaderManager().initLoader(0, null, this);
+ } else if (VERSION.SDK_INT >= 8) {
+ // Use AccountManager (API 8+)
+ new SetupEmailAutoCompleteTask().execute(null, null);
+ }
+</#if>
}
<#if parentActivityClass != "">
@@ -103,34 +147,8 @@
getActionBar().setDisplayHomeAsUpEnabled(true);
}
}
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- 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);
- }
</#if>
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- getMenuInflater().inflate(R.menu.${menuName}, menu);
- return true;
- }
-
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
@@ -146,29 +164,26 @@
mPasswordView.setError(null);
// Store values at the time of the login attempt.
- mEmail = mEmailView.getText().toString();
- mPassword = mPasswordView.getText().toString();
+ String email = mEmailView.getText().toString();
+ String password = mPasswordView.getText().toString();
boolean cancel = false;
View focusView = null;
- // Check for a valid password.
- if (TextUtils.isEmpty(mPassword)) {
- mPasswordView.setError(getString(R.string.error_field_required));
- focusView = mPasswordView;
- cancel = true;
- } else if (mPassword.length() < 4) {
+
+ // Check for a valid password, if the user entered one.
+ if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid email address.
- if (TextUtils.isEmpty(mEmail)) {
+ if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
- } else if (!mEmail.contains("@")) {
+ } else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
@@ -181,58 +196,214 @@
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
- mLoginStatusMessageView.setText(R.string.login_progress_signing_in);
showProgress(true);
- mAuthTask = new UserLoginTask();
+ mAuthTask = new UserLoginTask(email, password);
mAuthTask.execute((Void) null);
}
}
+ private boolean isEmailValid(String email) {
+ //TODO: Replace this with your own logic
+ return email.contains("@");
+ }
+
+ private boolean isPasswordValid(String password) {
+ //TODO: Replace this with your own logic
+ return password.length() > 4;
+ }
/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
- private void showProgress(final boolean show) {
+ public void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
- mLoginStatusView.setVisibility(View.VISIBLE);
- mLoginStatusView.animate()
- .setDuration(shortAnimTime)
- .alpha(show ? 1 : 0)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
- }
- });
+ mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
+ mLoginFormView.animate().setDuration(shortAnimTime).alpha(
+ show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
+ }
+ });
- mLoginFormView.setVisibility(View.VISIBLE);
- mLoginFormView.animate()
- .setDuration(shortAnimTime)
- .alpha(show ? 0 : 1)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
- }
- });
+ mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
+ mProgressView.animate().setDuration(shortAnimTime).alpha(
+ show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+ });
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
- mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
+ mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
+<#if includeGooglePlus>
+
+ @Override
+ protected void onPlusClientSignIn() {
+ //Set up sign out and disconnect buttons.
+ Button signOutButton = (Button) findViewById(R.id.plus_sign_out_button);
+ signOutButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ signOut();
+ }
+ });
+ Button disconnectButton = (Button) findViewById(R.id.plus_disconnect_button);
+ disconnectButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ revokeAccess();
+ }
+ });
+ }
+
+ @Override
+ protected void onPlusClientBlockingUI(boolean show) {
+ showProgress(show);
+ }
+
+ @Override
+ protected void updateConnectButtonState() {
+ //TODO: Update this logic to also handle the user logged in by email.
+ boolean connected = getPlusClient().isConnected();
+
+ mSignOutButtons.setVisibility(connected ? View.VISIBLE : View.GONE);
+ mPlusSignInButton.setVisibility(connected ? View.GONE : View.VISIBLE);
+ mEmailLoginFormView.setVisibility(connected ? View.GONE : View.VISIBLE);
+ }
+
+ @Override
+ protected void onPlusClientRevokeAccess() {
+ // TODO: Access to the user's G+ account has been revoked. Per the developer terms, delete
+ // any stored user data here.
+ }
+
+ @Override
+ protected void onPlusClientSignOut() {
+
+ }
+
+ /**
+ * Check if the device supports Google Play Services. It's best
+ * practice to check first rather than handling this as an error case.
+ *
+ * @return whether the device supports Google Play Services
+ */
+ private boolean supportsGooglePlayServices() {
+ return GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) ==
+ ConnectionResult.SUCCESS;
+ }
+</#if>
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
+ return new CursorLoader(this,
+ // Retrieve data rows for the device user's 'profile' contact.
+ Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
+ ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
+
+ // Select only email addresses.
+ ContactsContract.Contacts.Data.MIMETYPE +
+ " = ?", new String[]{ContactsContract.CommonDataKinds.Email
+ .CONTENT_ITEM_TYPE},
+
+ // Show primary email addresses first. Note that there won't be
+ // a primary email address if the user hasn't specified one.
+ ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
+ List<String> emails = new ArrayList<String>();
+ cursor.moveToFirst();
+ while (!cursor.isAfterLast()) {
+ emails.add(cursor.getString(ProfileQuery.ADDRESS));
+ cursor.moveToNext();
+ }
+
+ addEmailsToAutoComplete(emails);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> cursorLoader) {
+
+ }
+
+ private interface ProfileQuery {
+ String[] PROJECTION = {
+ ContactsContract.CommonDataKinds.Email.ADDRESS,
+ ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
+ };
+
+ int ADDRESS = 0;
+ int IS_PRIMARY = 1;
+ }
+
+<#if minApiLevel lt 14>
+ /**
+ * Use an AsyncTask to fetch the user's email addresses on a background thread, and update
+ * the email text field with results on the main UI thread.
+ */
+ class SetupEmailAutoCompleteTask extends AsyncTask<Void, Void, List<String>> {
+
+ @Override
+ protected List<String> doInBackground(Void... voids) {
+ ArrayList<String> emailAddressCollection = new ArrayList<String>();
+
+ // Get all emails from the user's contacts and copy them to a list.
+ ContentResolver cr = getContentResolver();
+ Cursor emailCur = cr.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
+ null, null, null);
+ while (emailCur.moveToNext()) {
+ String email = emailCur.getString(emailCur.getColumnIndex(ContactsContract
+ .CommonDataKinds.Email.DATA));
+ emailAddressCollection.add(email);
+ }
+ emailCur.close();
+
+ return emailAddressCollection;
+ }
+
+ @Override
+ protected void onPostExecute(List<String> emailAddressCollection) {
+ addEmailsToAutoComplete(emailAddressCollection);
+ }
+ }
+</#if>
+
+ private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
+ //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
+ ArrayAdapter<String> adapter =
+ new ArrayAdapter<String>(LoginActivity.this,
+ android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
+
+ mEmailView.setAdapter(adapter);
+ }
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
+
+ private final String mEmail;
+ private final String mPassword;
+
+ UserLoginTask(String email, String password) {
+ mEmail = email;
+ mPassword = password;
+ }
+
@Override
protected Boolean doInBackground(Void... params) {
// TODO: attempt authentication against a network service.
@@ -276,3 +447,6 @@
}
}
}
+
+
+
diff --git a/templates/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl b/templates/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl
new file mode 100644
index 0000000..617a54d
--- /dev/null
+++ b/templates/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl
@@ -0,0 +1,283 @@
+package ${packageName};
+
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+<#if minApiLevel lt 14>import android.support.v7.app.ActionBarActivity;</#if>
+<#if minApiLevel gte 14>import android.app.Activity;</#if>
+import android.util.Log;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesClient;
+import com.google.android.gms.common.Scopes;
+import com.google.android.gms.plus.PlusClient;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
+
+/**
+ * A base class to wrap communication with the Google Play Services PlusClient.
+ */
+public abstract class PlusBaseActivity extends <#if minApiLevel lt 14>ActionBar</#if>Activity
+ implements GooglePlayServicesClient.ConnectionCallbacks,
+ GooglePlayServicesClient.OnConnectionFailedListener {
+
+ private static final String TAG = PlusBaseActivity.class.getSimpleName();
+
+ // A magic number we will use to know that our sign-in error resolution activity has completed
+ private static final int OUR_REQUEST_CODE = 49404;
+
+ // A flag to stop multiple dialogues appearing for the user
+ private boolean mAutoResolveOnFail;
+
+ // A flag to track when a connection is already in progress
+ public boolean mPlusClientIsConnecting = false;
+
+ // This is the helper object that connects to Google Play Services.
+ private PlusClient mPlusClient;
+
+ // The saved result from {@link #onConnectionFailed(ConnectionResult)}. If a connection
+ // attempt has been made, this is non-null.
+ // If this IS null, then the connect method is still running.
+ private ConnectionResult mConnectionResult;
+
+
+ /**
+ * Called when the {@link PlusClient} revokes access to this app.
+ */
+ protected abstract void onPlusClientRevokeAccess();
+
+ /**
+ * Called when the PlusClient is successfully connected.
+ */
+ protected abstract void onPlusClientSignIn();
+
+ /**
+ * Called when the {@link PlusClient} is disconnected.
+ */
+ protected abstract void onPlusClientSignOut();
+
+ /**
+ * Called when the {@link PlusClient} is blocking the UI. If you have a progress bar widget,
+ * this tells you when to show or hide it.
+ */
+ protected abstract void onPlusClientBlockingUI(boolean show);
+
+ /**
+ * Called when there is a change in connection state. If you have "Sign in"/ "Connect",
+ * "Sign out"/ "Disconnect", or "Revoke access" buttons, this lets you know when their states
+ * need to be updated.
+ */
+ protected abstract void updateConnectButtonState();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Initialize the PlusClient connection.
+ // Scopes indicate the information about the user your application will be able to access.
+ mPlusClient =
+ new PlusClient.Builder(this, this, this).setScopes(Scopes.PLUS_LOGIN,
+ Scopes.PLUS_ME).build();
+ }
+
+ /**
+ * Try to sign in the user.
+ */
+ public void signIn() {
+ if (!mPlusClient.isConnected()) {
+ // Show the dialog as we are now signing in.
+ setProgressBarVisible(true);
+ // Make sure that we will start the resolution (e.g. fire the intent and pop up a
+ // dialog for the user) for any errors that come in.
+ mAutoResolveOnFail = true;
+ // We should always have a connection result ready to resolve,
+ // so we can start that process.
+ if (mConnectionResult != null) {
+ startResolution();
+ } else {
+ // If we don't have one though, we can start connect in
+ // order to retrieve one.
+ initiatePlusClientConnect();
+ }
+ }
+
+ updateConnectButtonState();
+ }
+
+ /**
+ * Connect the {@link PlusClient} only if a connection isn't already in progress. This will
+ * call back to {@link #onConnected(android.os.Bundle)} or
+ * {@link #onConnectionFailed(com.google.android.gms.common.ConnectionResult)}.
+ */
+ private void initiatePlusClientConnect() {
+ if (!mPlusClient.isConnected() && !mPlusClient.isConnecting()) {
+ mPlusClient.connect();
+ }
+ }
+
+ /**
+ * Disconnect the {@link PlusClient} only if it is connected (otherwise, it can throw an error.)
+ * This will call back to {@link #onDisconnected()}.
+ */
+ private void initiatePlusClientDisconnect() {
+ if (mPlusClient.isConnected()) {
+ mPlusClient.disconnect();
+ }
+ }
+
+ /**
+ * Sign out the user (so they can switch to another account).
+ */
+ public void signOut() {
+
+ // We only want to sign out if we're connected.
+ if (mPlusClient.isConnected()) {
+ // Clear the default account in order to allow the user to potentially choose a
+ // different account from the account chooser.
+ mPlusClient.clearDefaultAccount();
+
+ // Disconnect from Google Play Services, then reconnect in order to restart the
+ // process from scratch.
+ initiatePlusClientDisconnect();
+
+ Log.v(TAG, "Sign out successful!");
+ }
+
+ updateConnectButtonState();
+ }
+
+ /**
+ * Revoke Google+ authorization completely.
+ */
+ public void revokeAccess() {
+
+ if (mPlusClient.isConnected()) {
+ // Clear the default account as in the Sign Out.
+ mPlusClient.clearDefaultAccount();
+
+ // Revoke access to this entire application. This will call back to
+ // onAccessRevoked when it is complete, as it needs to reach the Google
+ // authentication servers to revoke all tokens.
+ mPlusClient.revokeAccessAndDisconnect(new PlusClient.OnAccessRevokedListener() {
+ public void onAccessRevoked(ConnectionResult result) {
+ updateConnectButtonState();
+ onPlusClientRevokeAccess();
+ }
+ });
+ }
+
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ initiatePlusClientConnect();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ initiatePlusClientDisconnect();
+ }
+
+ public boolean isPlusClientConnecting() {
+ return mPlusClientIsConnecting;
+ }
+
+ private void setProgressBarVisible(boolean flag) {
+ mPlusClientIsConnecting = flag;
+ onPlusClientBlockingUI(flag);
+ }
+
+ /**
+ * A helper method to flip the mResolveOnFail flag and start the resolution
+ * of the ConnectionResult from the failed connect() call.
+ */
+ private void startResolution() {
+ try {
+ // Don't start another resolution now until we have a result from the activity we're
+ // about to start.
+ mAutoResolveOnFail = false;
+ // If we can resolve the error, then call start resolution and pass it an integer tag
+ // we can use to track.
+ // This means that when we get the onActivityResult callback we'll know it's from
+ // being started here.
+ mConnectionResult.startResolutionForResult(this, OUR_REQUEST_CODE);
+ } catch (IntentSender.SendIntentException e) {
+ // Any problems, just try to connect() again so we get a new ConnectionResult.
+ mConnectionResult = null;
+ initiatePlusClientConnect();
+ }
+ }
+
+ /**
+ * An earlier connection failed, and we're now receiving the result of the resolution attempt
+ * by PlusClient.
+ *
+ * @see #onConnectionFailed(ConnectionResult)
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
+ updateConnectButtonState();
+ if (requestCode == OUR_REQUEST_CODE && responseCode == RESULT_OK) {
+ // If we have a successful result, we will want to be able to resolve any further
+ // errors, so turn on resolution with our flag.
+ mAutoResolveOnFail = true;
+ // If we have a successful result, let's call connect() again. If there are any more
+ // errors to resolve we'll get our onConnectionFailed, but if not,
+ // we'll get onConnected.
+ initiatePlusClientConnect();
+ } else if (requestCode == OUR_REQUEST_CODE && responseCode != RESULT_OK) {
+ // If we've got an error we can't resolve, we're no longer in the midst of signing
+ // in, so we can stop the progress spinner.
+ setProgressBarVisible(false);
+ }
+ }
+
+ /**
+ * Successfully connected (called by PlusClient)
+ */
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ updateConnectButtonState();
+ setProgressBarVisible(false);
+ onPlusClientSignIn();
+ }
+
+ /**
+ * Successfully disconnected (called by PlusClient)
+ */
+ @Override
+ public void onDisconnected() {
+ updateConnectButtonState();
+ onPlusClientSignOut();
+ }
+
+ /**
+ * Connection failed for some reason (called by PlusClient)
+ * Try and resolve the result. Failure here is usually not an indication of a serious error,
+ * just that the user's input is needed.
+ *
+ * @see #onActivityResult(int, int, Intent)
+ */
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ updateConnectButtonState();
+
+ // Most of the time, the connection will fail with a user resolvable result. We can store
+ // that in our mConnectionResult property ready to be used when the user clicks the
+ // sign-in button.
+ if (result.hasResolution()) {
+ mConnectionResult = result;
+ if (mAutoResolveOnFail) {
+ // This is a local helper function that starts the resolution of the problem,
+ // which may be showing the user an account chooser or similar.
+ startResolution();
+ }
+ }
+ }
+
+ public PlusClient getPlusClient() {
+ return mPlusClient;
+ }
+
+}
diff --git a/templates/activities/LoginActivity/template.xml b/templates/activities/LoginActivity/template.xml
index 576c633..03ea755 100644
--- a/templates/activities/LoginActivity/template.xml
+++ b/templates/activities/LoginActivity/template.xml
@@ -1,15 +1,16 @@
<?xml version="1.0"?>
<template
format="4"
- revision="4"
+ revision="5"
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">
+ description="Creates a new login activity, allowing users to optionally sign in with Google+ or enter an email address and password to log in to or register with your application."
+ minApi="8"
+ minBuildApi="14">
<dependency name="android-support-v4" revision="8" />
- <category value="Activities" />
+ <category value="Activity" />
+ <formfactor value="Mobile" />
<parameter
id="activityClass"
@@ -51,6 +52,12 @@
constraints="package"
default="com.mycompany.myapp" />
+ <parameter
+ id="includeGooglePlus"
+ name="Include Google+ sign in"
+ type="boolean"
+ default="true" />
+
<thumbs>
<thumb>template_login_activity.png</thumb>
</thumbs>
diff --git a/templates/activities/MasterDetailFlow/globals.xml.ftl b/templates/activities/MasterDetailFlow/globals.xml.ftl
index 0d23f55..5858cec 100644
--- a/templates/activities/MasterDetailFlow/globals.xml.ftl
+++ b/templates/activities/MasterDetailFlow/globals.xml.ftl
@@ -1,5 +1,7 @@
<?xml version="1.0"?>
<globals>
+ <global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
+ <global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
<global id="projectOut" value="." />
<global id="manifestOut" value="${manifestDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
@@ -8,4 +10,5 @@
<global id="collection_name" value="${extractLetters(objectKind?lower_case)}_list" />
<global id="DetailName" value="${extractLetters(objectKind)}Detail" />
<global id="detail_name" value="${extractLetters(objectKind?lower_case)}_detail" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/activities/MasterDetailFlow/recipe.xml.ftl b/templates/activities/MasterDetailFlow/recipe.xml.ftl
index 0c68f35..25d12bd 100644
--- a/templates/activities/MasterDetailFlow/recipe.xml.ftl
+++ b/templates/activities/MasterDetailFlow/recipe.xml.ftl
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<recipe>
- <dependency mavenUrl="com.android.support:support-v4:18.0.0" />
-
+ <dependency mavenUrl="com.android.support:support-v4:19.+" />
+
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
diff --git a/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl b/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
index 34d0402..3fb177b 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="${packageName}.${CollectionName}Activity"
+ <activity android:name="${relativePackage}.${CollectionName}Activity"
<#if isNewProject>
android:label="@string/app_name"
<#else>
@@ -20,11 +20,11 @@
</#if>
</activity>
- <activity android:name="${packageName}.${DetailName}Activity"
+ <activity android:name="${relativePackage}.${DetailName}Activity"
android:label="@string/title_${detail_name}"
- <#if buildApi gte 16>android:parentActivityName=".${CollectionName}Activity"</#if>>
+ <#if buildApi gte 16>android:parentActivityName="${relativePackage}.${CollectionName}Activity"</#if>>
<meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${packageName}.${CollectionName}Activity" />
+ android:value="${relativePackage}.${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 02bf4f6..91f931a 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="${packageName}.${DetailName}Activity"
+ tools:context="${relativePackage}.${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 e51a98e..8777431 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="${packageName}.${CollectionName}Activity"
+ tools:context="${relativePackage}.${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 1f2bd19..4b922ee 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="${packageName}.${CollectionName}Activity">
+ tools:context="${relativePackage}.${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 f685145..f921d60 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="${packageName}.${DetailName}Fragment" />
+ tools:context="${relativePackage}.${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 79f0e90..6dc3409 100644
--- a/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
+++ b/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
@@ -2,9 +2,10 @@
import android.content.Intent;
import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.NavUtils;
+import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
+<#if minApiLevel lt 16>import android.support.v4.app.NavUtils;</#if>
import android.view.MenuItem;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* An activity representing a single ${objectKind} detail screen. This
@@ -15,7 +16,7 @@
* This activity is mostly just a 'shell' activity containing nothing
* more than a {@link ${DetailName}Fragment}.
*/
-public class ${DetailName}Activity extends FragmentActivity {
+public class ${DetailName}Activity extends ${appCompat?string('ActionBar','')}Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -23,7 +24,7 @@
setContentView(R.layout.activity_${detail_name});
// Show the Up button in the action bar.
- getActionBar().setDisplayHomeAsUpEnabled(true);
+ get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
@@ -42,7 +43,7 @@
getIntent().getStringExtra(${DetailName}Fragment.ARG_ITEM_ID));
${DetailName}Fragment fragment = new ${DetailName}Fragment();
fragment.setArguments(arguments);
- getSupportFragmentManager().beginTransaction()
+ get${Support}FragmentManager().beginTransaction()
.add(R.id.${detail_name}_container, fragment)
.commit();
}
@@ -52,6 +53,7 @@
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
+<#if minApiLevel lt 16>
// 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
@@ -60,6 +62,15 @@
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpTo(this, new Intent(this, ${CollectionName}Activity.class));
+<#else>
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ navigateUpTo(new Intent(this, ${CollectionName}Activity.class));
+</#if>
return true;
}
return super.onOptionsItemSelected(item);
diff --git a/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl b/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
index 2cecaec..27331cd 100644
--- a/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
+++ b/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
@@ -1,11 +1,12 @@
package ${packageName};
import android.os.Bundle;
-import android.support.v4.app.Fragment;
+import android.<#if appCompat>support.v4.</#if>app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
import ${packageName}.dummy.DummyContent;
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 fe02fe9..8e8ac58 100644
--- a/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
+++ b/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
@@ -2,9 +2,10 @@
import android.content.Intent;
import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-<#if parentActivityClass != "">import android.support.v4.app.NavUtils;
-import android.view.MenuItem;</#if>
+import <#if appCompat>android.support.v4.app.FragmentActivity<#else>android.app.Activity</#if>;
+<#if (parentActivityClass != "" && minApiLevel lt 16)>import android.support.v4.app.NavUtils;</#if>
+<#if parentActivityClass != "">import android.view.MenuItem;</#if>
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* An activity representing a list of ${objectKindPlural}. This activity
@@ -22,7 +23,7 @@
* {@link ${CollectionName}Fragment.Callbacks} interface
* to listen for item selections.
*/
-public class ${CollectionName}Activity extends FragmentActivity
+public class ${CollectionName}Activity extends ${(appCompat)?string('Fragment','')}Activity
implements ${CollectionName}Fragment.Callbacks {
/**
@@ -37,7 +38,7 @@
setContentView(R.layout.activity_${collection_name});
<#if parentActivityClass != "">
// Show the Up button in the action bar.
- getActionBar().setDisplayHomeAsUpEnabled(true);
+ get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
</#if>
if (findViewById(R.id.${detail_name}_container) != null) {
@@ -49,7 +50,7 @@
// In two-pane mode, list items should be given the
// 'activated' state when touched.
- ((${CollectionName}Fragment) getSupportFragmentManager()
+ ((${CollectionName}Fragment) get${Support}FragmentManager()
.findFragmentById(R.id.${collection_name}))
.setActivateOnItemClick(true);
}
@@ -69,7 +70,7 @@
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
- NavUtils.navigateUpFromSameTask(this);
+ ${(minApiLevel lt 16)?string('NavUtils.','')}navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
@@ -90,7 +91,7 @@
arguments.putString(${DetailName}Fragment.ARG_ITEM_ID, id);
${DetailName}Fragment fragment = new ${DetailName}Fragment();
fragment.setArguments(arguments);
- getSupportFragmentManager().beginTransaction()
+ get${Support}FragmentManager().beginTransaction()
.replace(R.id.${detail_name}_container, fragment)
.commit();
diff --git a/templates/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl b/templates/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
index e9a2e74..9e34f3e 100644
--- a/templates/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
+++ b/templates/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
@@ -2,10 +2,11 @@
import android.app.Activity;
import android.os.Bundle;
-import android.support.v4.app.ListFragment;
+import android.<#if Support?has_content>support.v4.</#if>app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
import ${packageName}.dummy.DummyContent;
diff --git a/templates/activities/MasterDetailFlow/template.xml b/templates/activities/MasterDetailFlow/template.xml
index c2e2b6e..b8b2587 100644
--- a/templates/activities/MasterDetailFlow/template.xml
+++ b/templates/activities/MasterDetailFlow/template.xml
@@ -1,19 +1,21 @@
<?xml version="1.0"?>
<template
format="4"
- revision="4"
+ revision="5"
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.">
+ minApi="4"
+ 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."
+ category="Activity">
<dependency name="android-support-v4" revision="8" />
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
<thumbs>
<thumb>template_master_detail.png</thumb>
</thumbs>
- <category value="Flows" />
-
<parameter
id="objectKind"
name="Object Kind"
diff --git a/templates/activities/NavigationDrawerActivity/globals.xml.ftl b/templates/activities/NavigationDrawerActivity/globals.xml.ftl
new file mode 100644
index 0000000..cd58b56
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/globals.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
+ <!-- e.g. getSupportActionBar vs. getActionBar -->
+ <global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
+ <global id="ActionNamespace" value="${(minApiLevel lt 14)?string('app','android')}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="menuName" value="${classToResource(activityClass)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/activities/NavigationDrawerActivity/recipe.xml.ftl b/templates/activities/NavigationDrawerActivity/recipe.xml.ftl
new file mode 100644
index 0000000..6646be7
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/recipe.xml.ftl
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
+ <#if !appCompat><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
+
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="res/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <!-- TODO: switch on Holo Dark v. Holo Light -->
+ <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" />
+
+
+ <instantiate from="res/layout/activity_drawer.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ <instantiate from="res/layout/fragment_navigation_drawer.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${navigationDrawerLayout}.xml" />
+
+ <instantiate from="res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
+ <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" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+</recipe>
diff --git a/templates/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl b/templates/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..af1d2d6
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,24 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/templates/activities/BlankActivity/root/build.gradle.ftl b/templates/activities/NavigationDrawerActivity/root/build.gradle.ftl
similarity index 100%
rename from templates/activities/BlankActivity/root/build.gradle.ftl
rename to templates/activities/NavigationDrawerActivity/root/build.gradle.ftl
diff --git a/templates/activities/BlankActivity/root/res/drawable-hdpi/drawer_shadow.9.png b/templates/activities/NavigationDrawerActivity/root/res/drawable-hdpi/drawer_shadow.9.png
similarity index 100%
rename from templates/activities/BlankActivity/root/res/drawable-hdpi/drawer_shadow.9.png
rename to templates/activities/NavigationDrawerActivity/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/NavigationDrawerActivity/root/res/drawable-hdpi/ic_drawer.png
similarity index 100%
rename from templates/activities/BlankActivity/root/res/drawable-hdpi/ic_drawer.png
rename to templates/activities/NavigationDrawerActivity/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/NavigationDrawerActivity/root/res/drawable-mdpi/drawer_shadow.9.png
similarity index 100%
rename from templates/activities/BlankActivity/root/res/drawable-mdpi/drawer_shadow.9.png
rename to templates/activities/NavigationDrawerActivity/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/NavigationDrawerActivity/root/res/drawable-mdpi/ic_drawer.png
similarity index 100%
rename from templates/activities/BlankActivity/root/res/drawable-mdpi/ic_drawer.png
rename to templates/activities/NavigationDrawerActivity/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/NavigationDrawerActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
similarity index 100%
rename from templates/activities/BlankActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
rename to templates/activities/NavigationDrawerActivity/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/NavigationDrawerActivity/root/res/drawable-xhdpi/ic_drawer.png
similarity index 100%
rename from templates/activities/BlankActivity/root/res/drawable-xhdpi/ic_drawer.png
rename to templates/activities/NavigationDrawerActivity/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/NavigationDrawerActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
similarity index 100%
rename from templates/activities/BlankActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
rename to templates/activities/NavigationDrawerActivity/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/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png
similarity index 100%
rename from templates/activities/BlankActivity/root/res/drawable-xxhdpi/ic_drawer.png
rename to templates/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png
Binary files differ
diff --git a/templates/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl
new file mode 100644
index 0000000..eabb6b1
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl
@@ -0,0 +1,31 @@
+<!-- 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="${relativePackage}.${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"
+ tools:layout="@layout/${navigationDrawerLayout}" />
+
+</android.support.v4.widget.DrawerLayout>
diff --git a/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
new file mode 100644
index 0000000..8958788
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/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="${relativePackage}.NavigationDrawerFragment" />
diff --git a/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl
new file mode 100644
index 0000000..1baa65a
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/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="${relativePackage}.${activityClass}$PlaceholderFragment">
+
+ <TextView
+ android:id="@+id/section_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/templates/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl
new file mode 100644
index 0000000..cfc1903
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl
@@ -0,0 +1,7 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
+ xmlns:app="http://schemas.android.com/apk/res-auto"</#if>>
+ <item android:id="@+id/action_settings"
+ android:title="@string/action_settings"
+ android:orderInCategory="100"
+ ${ActionNamespace}:showAsAction="never" />
+</menu>
diff --git a/templates/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl
new file mode 100644
index 0000000..2824974
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl
@@ -0,0 +1,12 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
+ xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="${relativePackage}.${activityClass}" >
+ <item android:id="@+id/action_example"
+ android:title="@string/action_example"
+ ${ActionNamespace}:showAsAction="withText|ifRoom" />
+ <item android:id="@+id/action_settings"
+ android:title="@string/action_settings"
+ android:orderInCategory="100"
+ ${ActionNamespace}:showAsAction="never" />
+</menu>
diff --git a/templates/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml b/templates/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/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/NavigationDrawerActivity/root/res/values/dimens.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl
new file mode 100644
index 0000000..074e7a0
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl
@@ -0,0 +1,9 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+
+ <!-- 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>
+</resources>
diff --git a/templates/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..68a6529
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,17 @@
+<resources>
+ <#if !isNewProject>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
+ </#if>
+
+ <string name="title_section1">Section 1</string>
+ <string name="title_section2">Section 2</string>
+ <string name="title_section3">Section 3</string>
+
+ <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>
+
+ <string name="action_settings">Settings</string>
+
+</resources>
diff --git a/templates/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl b/templates/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl
new file mode 100644
index 0000000..261d090
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl
@@ -0,0 +1,84 @@
+package ${packageName};
+
+import android.app.Activity;
+<#if appCompat>import android.support.v7.app.ActionBarActivity;</#if>
+import android.<#if appCompat>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat>support.v4.</#if>app.Fragment;
+import android.<#if appCompat>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;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
+
+public class ${activityClass} extends ${(appCompat)?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/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl b/templates/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
new file mode 100644
index 0000000..cb0f2c5
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
@@ -0,0 +1,282 @@
+package ${packageName};
+
+<#if appCompat>import android.support.v7.app.ActionBarActivity;</#if>
+import android.app.Activity;
+import android.<#if appCompat>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat>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.${navigationDrawerLayout}, 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?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?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>((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/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl b/templates/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl
new file mode 100644
index 0000000..57ae492
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl
@@ -0,0 +1,43 @@
+ /**
+ * A placeholder fragment containing a simple view.
+ */
+ public static class PlaceholderFragment extends Fragment {
+ /**
+ * 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;
+ }
+
+ 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;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ ((${activityClass}) activity).onSectionAttached(
+ getArguments().getInt(ARG_SECTION_NUMBER));
+ }
+ }
diff --git a/templates/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl b/templates/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl
new file mode 100644
index 0000000..8166367
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl
@@ -0,0 +1,25 @@
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ 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);
+ }
+
+ @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/NavigationDrawerActivity/template.xml b/templates/activities/NavigationDrawerActivity/template.xml
new file mode 100644
index 0000000..9329c7b
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/template.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<template
+ format="3"
+ revision="4"
+ name="Navigation Drawer Activity"
+ minApi="7"
+ minBuildApi="14"
+ description="Creates a new Activity with a Navigation Drawer.">
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ 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"
+ constraints="nonempty"
+ default="MainActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="navigationDrawerLayout"
+ name="Navigation Drawer Fragment Name"
+ type="string"
+ constraints="layout|unique"
+ default="fragment_navigation_drawer"/>
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_blank_activity_drawer.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/BlankActivity/template_blank_activity_drawer.png b/templates/activities/NavigationDrawerActivity/template_blank_activity_drawer.png
similarity index 100%
rename from templates/activities/BlankActivity/template_blank_activity_drawer.png
rename to templates/activities/NavigationDrawerActivity/template_blank_activity_drawer.png
Binary files differ
diff --git a/templates/activities/SettingsActivity/globals.xml.ftl b/templates/activities/SettingsActivity/globals.xml.ftl
index d566fee..81a41ba 100644
--- a/templates/activities/SettingsActivity/globals.xml.ftl
+++ b/templates/activities/SettingsActivity/globals.xml.ftl
@@ -5,4 +5,5 @@
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="simpleName" value="${activityToLayout(activityClass)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/activities/SettingsActivity/recipe.xml.ftl b/templates/activities/SettingsActivity/recipe.xml.ftl
index 20a6b2e..060d7b3 100644
--- a/templates/activities/SettingsActivity/recipe.xml.ftl
+++ b/templates/activities/SettingsActivity/recipe.xml.ftl
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<recipe>
- <dependency mavenUrl="com.android.support:support-v4:18.0.0" />
+ <dependency mavenUrl="com.android.support:support-v4:19.+" />
<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 a638dd6..b0048b2 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="${packageName}.${activityClass}"
+ <activity android:name="${relativePackage}.${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 bf4610f..70deb61 100644
--- a/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
+++ b/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
@@ -20,6 +20,7 @@
import android.view.MenuItem;
import android.support.v4.app.NavUtils;
</#if>
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
import java.util.List;
diff --git a/templates/activities/SettingsActivity/template.xml b/templates/activities/SettingsActivity/template.xml
index 33f9413..cedb287 100644
--- a/templates/activities/SettingsActivity/template.xml
+++ b/templates/activities/SettingsActivity/template.xml
@@ -5,11 +5,13 @@
name="Settings Activity"
description="Creates a new application settings activity that presents alternative layouts on handset and tablet-size screens."
minApi="4"
- minBuildApi="11">
+ minBuildApi="11"
+ category="Activity">
<dependency name="android-support-v4" revision="8" />
- <category value="Activities" />
+ <category value="Activity" />
+ <formfactor value="Mobile" />
<parameter
id="activityClass"
diff --git a/templates/activities/TabbedActivity/globals.xml.ftl b/templates/activities/TabbedActivity/globals.xml.ftl
new file mode 100644
index 0000000..e1b7485
--- /dev/null
+++ b/templates/activities/TabbedActivity/globals.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
+ <!-- e.g. getSupportActionBar vs. getActionBar -->
+ <global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
+ <global id="hasViewPager" type="boolean" value="${(features == 'pager' || features == 'tabs')?string}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="menuName" value="${classToResource(activityClass)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/activities/TabbedActivity/recipe.xml.ftl b/templates/activities/TabbedActivity/recipe.xml.ftl
new file mode 100644
index 0000000..72966fd
--- /dev/null
+++ b/templates/activities/TabbedActivity/recipe.xml.ftl
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
+ <#if !appCompat && hasViewPager><dependency mavenUrl="com.android.support:support-v13:19.+"/></#if>
+
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="res/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <!-- Decide what kind of layout(s) to add -->
+ <#if hasViewPager>
+ <instantiate from="res/layout/activity_pager.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <#else>
+ <instantiate from="res/layout/activity_fragment_container.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ </#if>
+
+ <instantiate from="res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
+ <!-- Decide which activity code to add -->
+ <#if features == "tabs" || features == "pager">
+ <instantiate from="src/app_package/TabsAndPagerActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <#elseif features == "spinner">
+ <instantiate from="src/app_package/DropdownActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ </#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+</recipe>
diff --git a/templates/activities/TabbedActivity/root/AndroidManifest.xml.ftl b/templates/activities/TabbedActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..af1d2d6
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,24 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/templates/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl b/templates/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl
new file mode 100644
index 0000000..92b2b62
--- /dev/null
+++ b/templates/activities/TabbedActivity/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="${relativePackage}.${activityClass}"
+ tools:ignore="MergeRootFrame" />
diff --git a/templates/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl b/templates/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl
new file mode 100644
index 0000000..e296a03
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl
@@ -0,0 +1,6 @@
+<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${relativePackage}.${activityClass}" />
diff --git a/templates/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl b/templates/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl
new file mode 100644
index 0000000..6fa8741
--- /dev/null
+++ b/templates/activities/TabbedActivity/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="${relativePackage}.${activityClass}$PlaceholderFragment">
+
+ <TextView
+ <#if hasViewPager>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/TabbedActivity/root/res/menu/main.xml.ftl b/templates/activities/TabbedActivity/root/res/menu/main.xml.ftl
new file mode 100644
index 0000000..27f6aaa
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/res/menu/main.xml.ftl
@@ -0,0 +1,9 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
+ xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="${relativePackage}.${activityClass}" >
+ <item android:id="@+id/action_settings"
+ android:title="@string/action_settings"
+ android:orderInCategory="100"
+ ${(appCompat)?string('app','android')}:showAsAction="never" />
+</menu>
diff --git a/templates/activities/TabbedActivity/root/res/values-w820dp/dimens.xml b/templates/activities/TabbedActivity/root/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/templates/activities/TabbedActivity/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/TabbedActivity/root/res/values/dimens.xml.ftl b/templates/activities/TabbedActivity/root/res/values/dimens.xml.ftl
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/res/values/dimens.xml.ftl
@@ -0,0 +1,5 @@
+<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/TabbedActivity/root/res/values/strings.xml.ftl b/templates/activities/TabbedActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..28603a3
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,15 @@
+<resources>
+ <#if !isNewProject>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
+ </#if>
+
+
+ <string name="title_section1">Section 1</string>
+ <string name="title_section2">Section 2</string>
+ <string name="title_section3">Section 3</string>
+
+ <string name="hello_world">Hello world!</string>
+
+ <string name="action_settings">Settings</string>
+
+</resources>
diff --git a/templates/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl b/templates/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl
new file mode 100644
index 0000000..8f579a4
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl
@@ -0,0 +1,86 @@
+package ${packageName};
+
+import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
+import android.<#if appCompat>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat>support.v4.</#if>app.Fragment;
+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.widget.ArrayAdapter;
+import android.widget.TextView;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
+
+public class ${activityClass} extends ${(appCompat)?string('ActionBar','')}Activity implements ActionBar.OnNavigationListener {
+
+ /**
+ * The serialization (saved instance state) Bundle key representing the
+ * current dropdown 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 a dropdown list.
+ final ActionBar actionBar = get${Support}ActionBar();
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ <#if parentActivityClass != "">
+ // Show the Up button in the action bar.
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ </#if>
+
+ // Set up the dropdown list navigation in the action bar.
+ actionBar.setListNavigationCallbacks(
+ // Specify a SpinnerAdapter to populate the dropdown list.
+ new ArrayAdapter<String>(
+ actionBar.getThemedContext(),
+ android.R.layout.simple_list_item_1,
+ android.R.id.text1,
+ new String[] {
+ getString(R.string.title_section1),
+ getString(R.string.title_section2),
+ getString(R.string.title_section3),
+ }),
+ this);
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ // Restore the previously serialized current dropdown position.
+ if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
+ get${Support}ActionBar().setSelectedNavigationItem(
+ savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ // Serialize the current dropdown position.
+ outState.putInt(STATE_SELECTED_NAVIGATION_ITEM,
+ get${Support}ActionBar().getSelectedNavigationIndex());
+ }
+
+ <#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.
+ get${Support}FragmentManager().beginTransaction()
+ .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
+ .commit();
+ return true;
+ }
+
+ <#include "include_fragment.java.ftl">
+
+}
diff --git a/templates/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl b/templates/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
new file mode 100644
index 0000000..dfa4786
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
@@ -0,0 +1,139 @@
+package ${packageName};
+
+import java.util.Locale;
+
+import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
+import android.<#if appCompat>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat>support.v4.</#if>app.Fragment;
+import android.<#if appCompat>support.v4.</#if>app.FragmentManager;
+import android.<#if appCompat>support.v4.</#if>app.FragmentTransaction;
+import android.support.${(appCompat)?string('v4','v13')}.app.FragmentPagerAdapter;
+import android.os.Bundle;
+import android.support.v4.view.ViewPager;
+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;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
+
+public class ${activityClass} extends ${(appCompat)?string('ActionBar','')}Activity<#if features == '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 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)?string('v4','v13')}.app.FragmentStatePagerAdapter}.
+ */
+ SectionsPagerAdapter mSectionsPagerAdapter;
+
+ /**
+ * The {@link ViewPager} that will host the section contents.
+ */
+ ViewPager mViewPager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+
+ <#if features == 'tabs'>
+ // Set up the action bar.
+ 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 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 features == '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.
+ mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ actionBar.setSelectedNavigationItem(position);
+ }
+ });
+
+ // For each of the sections in the app, add a tab to the action bar.
+ for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
+ // Create a tab with text corresponding to the page title defined by
+ // the adapter. Also specify this Activity object, which implements
+ // the TabListener interface, as the callback (listener) for when
+ // this tab is selected.
+ actionBar.addTab(
+ actionBar.newTab()
+ .setText(mSectionsPagerAdapter.getPageTitle(i))
+ .setTabListener(this));
+ }
+ </#if>
+ }
+
+ <#include "include_options_menu.java.ftl">
+
+ <#if features == 'tabs'>@Override
+ public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ // When the given tab is selected, switch to the corresponding page in
+ // the ViewPager.
+ mViewPager.setCurrentItem(tab.getPosition());
+ }
+
+ @Override
+ public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ }
+
+ @Override
+ public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ }</#if>
+
+ /**
+ * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
+ * one of the sections/tabs/pages.
+ */
+ public class SectionsPagerAdapter extends FragmentPagerAdapter {
+
+ public SectionsPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ // getItem is called to instantiate the fragment for the given page.
+ // Return a PlaceholderFragment (defined as a static inner class below).
+ return PlaceholderFragment.newInstance(position + 1);
+ }
+
+ @Override
+ public int getCount() {
+ // Show 3 total pages.
+ return 3;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ Locale l = Locale.getDefault();
+ switch (position) {
+ case 0:
+ return getString(R.string.title_section1).toUpperCase(l);
+ case 1:
+ return getString(R.string.title_section2).toUpperCase(l);
+ case 2:
+ return getString(R.string.title_section3).toUpperCase(l);
+ }
+ return null;
+ }
+ }
+
+ <#include "include_fragment.java.ftl">
+
+}
diff --git a/templates/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl b/templates/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl
new file mode 100644
index 0000000..f5c65b6
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl
@@ -0,0 +1,36 @@
+ /**
+ * A placeholder fragment containing a simple view.
+ */
+ public static class PlaceholderFragment extends Fragment {
+ /**
+ * 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;
+ }
+
+ 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;
+ }
+ }
diff --git a/templates/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl b/templates/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl
new file mode 100644
index 0000000..5c4bb95
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl
@@ -0,0 +1,19 @@
+
+ @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;
+ }
+
+ @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/TabbedActivity/template.xml b/templates/activities/TabbedActivity/template.xml
new file mode 100644
index 0000000..8b0f806
--- /dev/null
+++ b/templates/activities/TabbedActivity/template.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0"?>
+<template
+ format="3"
+ revision="4"
+ name="Tabbed Activity"
+ minApi="7"
+ minBuildApi="14"
+ description="Creates a new blank activity, with an action bar and navigational elements such as tabs or horizontal swipe.">
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ 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"
+ constraints="nonempty"
+ default="MainActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="features"
+ name="Navigation Style"
+ type="enum"
+ default="pager"
+ help="Additional features to include, such as a fragment, swipe views, or a navigation drawer" >
+ <option id="pager">Swipe Views (ViewPager)</option>
+ <option id="tabs">Action Bar Tabs (with ViewPager)</option>
+ <option id="spinner">Action Bar Spinner</option>
+ </parameter>
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_blank_activity_pager.png</thumb>
+ <!-- attributes act as selectors based on chosen parameters -->
+ <thumb features="tabs">template_blank_activity_tabs.png</thumb>
+ <thumb features="pager">template_blank_activity_pager.png</thumb>
+ <thumb features="spinner">template_blank_activity_dropdown.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/BlankActivity/template_blank_activity_dropdown.png b/templates/activities/TabbedActivity/template_blank_activity_dropdown.png
similarity index 100%
rename from templates/activities/BlankActivity/template_blank_activity_dropdown.png
rename to templates/activities/TabbedActivity/template_blank_activity_dropdown.png
Binary files differ
diff --git a/templates/activities/BlankActivity/template_blank_activity_pager.png b/templates/activities/TabbedActivity/template_blank_activity_pager.png
similarity index 100%
rename from templates/activities/BlankActivity/template_blank_activity_pager.png
rename to templates/activities/TabbedActivity/template_blank_activity_pager.png
Binary files differ
diff --git a/templates/activities/BlankActivity/template_blank_activity_tabs.png b/templates/activities/TabbedActivity/template_blank_activity_tabs.png
similarity index 100%
rename from templates/activities/BlankActivity/template_blank_activity_tabs.png
rename to templates/activities/TabbedActivity/template_blank_activity_tabs.png
Binary files differ
diff --git a/templates/build.gradle b/templates/build.gradle
new file mode 100644
index 0000000..30380f6
--- /dev/null
+++ b/templates/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'sdk-files'
+
+sdk {
+ common {
+ item('projects') { into 'templates/projects' }
+ item('activities') { into 'templates/activities' }
+ item('gradle') { into 'templates/gradle' }
+ item('other') { into 'templates/other' }
+ }
+}
\ No newline at end of file
diff --git a/templates/gradle-projects/ImportExistingProject/template.xml b/templates/gradle-projects/ImportExistingProject/template.xml
new file mode 100644
index 0000000..0ef0c72
--- /dev/null
+++ b/templates/gradle-projects/ImportExistingProject/template.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="1"
+ name="Import Existing Project"
+ description="Import existing Eclipse ADT or Gradle project as a module">
+
+ <category value="Application" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+</template>
diff --git a/templates/gradle-projects/ImportExistingProject/template_new_project.png b/templates/gradle-projects/ImportExistingProject/template_new_project.png
new file mode 100644
index 0000000..92e8556
--- /dev/null
+++ b/templates/gradle-projects/ImportExistingProject/template_new_project.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidModule/globals.xml.ftl b/templates/gradle-projects/NewAndroidModule/globals.xml.ftl
index 1843c0d..926ff9a 100644
--- a/templates/gradle-projects/NewAndroidModule/globals.xml.ftl
+++ b/templates/gradle-projects/NewAndroidModule/globals.xml.ftl
@@ -2,12 +2,12 @@
<globals>
<global id="topOut" value="." />
<global id="projectOut" value="." />
- <global id="appCompat" value="${(minApiLevel lt 14)?string('1','')}" />
+ <global id="appCompat" value="${(minApiLevel lt 14 && minApiLevel gte 7)?string('1','')}" />
<global id="manifestOut" value="${manifestDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="testOut" value="androidTest/${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
index ade222a..3e5aa76 100644
--- a/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl
+++ b/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl
@@ -2,12 +2,14 @@
<recipe>
- <#if appCompat?has_content><dependency mavenUrl="com.android.support:appcompat-v7:+"/></#if>
+ <#if appCompat?has_content><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
<#if !createActivity>
- <mkdir at="${srcOut}" />
+ <mkdir at="${escapeXmlAttribute(srcOut)}" />
</#if>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
+
<merge from="settings.gradle.ftl"
to="${escapeXmlAttribute(topOut)}/settings.gradle" />
<instantiate from="build.gradle.ftl"
@@ -31,7 +33,7 @@
</#if>
<#if enableProGuard>
<instantiate from="proguard-rules.txt.ftl"
- to="${escapeXmlAttribute(projectOut)}/proguard-rules.txt" />
+ to="${escapeXmlAttribute(projectOut)}/proguard-rules.pro" />
</#if>
<#if !(isLibraryProject??) || !isLibraryProject>
<instantiate from="res/values/styles.xml.ftl"
@@ -40,4 +42,7 @@
<instantiate from="res/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="test/app_package/ApplicationTest.java.ftl"
+ to="${testOut}/ApplicationTest.java" />
</recipe>
diff --git a/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl b/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl
index 137a9ea..e766971 100644
--- a/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl
+++ b/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl
@@ -2,8 +2,10 @@
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:label="@string/app_name"<#if copyIcons>
+ android:icon="@drawable/ic_launcher"<#else>
+ android:icon="@drawable/${assetName}"</#if>
+ <#if baseTheme != "none" && !isLibraryProject>
android:theme="@style/AppTheme"</#if>>
</application>
diff --git a/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl b/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl
index 2e35ec4..82d7391 100644
--- a/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl
+++ b/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl
@@ -1,36 +1,43 @@
+<#if !(perModuleRepositories??) || perModuleRepositories>
buildscript {
repositories {
-<#if mavenUrl == "mavenCentral">
mavenCentral()
-<#else>
- maven { url '${mavenUrl}' }
+<#if mavenUrl != "mavenCentral">
+ maven {
+ url '${mavenUrl}'
+ }
</#if>
}
dependencies {
classpath 'com.android.tools.build:gradle:${gradlePluginVersion}'
}
}
-<#if isLibraryProject?? && isLibraryProject>
-apply plugin: 'android-library'
-<#else>
-apply plugin: 'android'
</#if>
+<#if isLibraryProject?? && isLibraryProject>
+apply plugin: 'com.android.library'
+<#else>
+apply plugin: 'com.android.application'
+</#if>
+<#if !(perModuleRepositories??) || perModuleRepositories>
repositories {
-<#if mavenUrl == "mavenCentral">
- mavenCentral()
-<#else>
- maven { url '${mavenUrl}' }
+ mavenCentral()
+<#if mavenUrl != "mavenCentral">
+ maven {
+ url '${mavenUrl}'
+ }
</#if>
}
+</#if>
android {
- compileSdkVersion ${buildApi}
+ compileSdkVersion <#if buildApiString?matches("^\\d+$")>${buildApiString}<#else>'${buildApiString}'</#if>
buildToolsVersion "${buildToolsVersion}"
defaultConfig {
- minSdkVersion ${minApi}
- targetSdkVersion ${targetApi}
+ applicationId "${packageName}"
+ minSdkVersion <#if minApi?matches("^\\d+$")>${minApi}<#else>'${minApi}'</#if>
+ targetSdkVersion <#if targetApiString?matches("^\\d+$")>${targetApiString}<#else>'${targetApiString}'</#if>
versionCode 1
versionName "1.0"
}
@@ -42,25 +49,12 @@
}
</#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')
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
- productFlavors {
- defaultFlavor {
- proguardFile 'proguard-rules.txt'
- }
- }
- </#if>
</#if>
}
@@ -70,4 +64,5 @@
compile '${dependency}'
</#list>
</#if>
+ compile fileTree(dir: 'libs', include: ['*.jar'])
}
diff --git a/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl b/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl
index f766622..cc99b17 100644
--- a/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl
+++ b/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl
@@ -1,8 +1,8 @@
# 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.
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
@@ -14,4 +14,4 @@
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
-#}
\ No newline at end of file
+#}
diff --git a/templates/gradle-projects/NewAndroidModule/root/test/app_package/ApplicationTest.java.ftl b/templates/gradle-projects/NewAndroidModule/root/test/app_package/ApplicationTest.java.ftl
new file mode 100644
index 0000000..0140287
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/test/app_package/ApplicationTest.java.ftl
@@ -0,0 +1,13 @@
+package ${packageName};
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/templates/gradle-projects/NewAndroidModule/template.xml b/templates/gradle-projects/NewAndroidModule/template.xml
index dc0ecfa..ba6154f 100644
--- a/templates/gradle-projects/NewAndroidModule/template.xml
+++ b/templates/gradle-projects/NewAndroidModule/template.xml
@@ -5,12 +5,14 @@
name="Android Module"
description="Creates a new Android module.">
+ <category value="Application" />
+
+ <formfactor value="Mobile" />
+
<thumbs>
<thumb>template_new_project.png</thumb>
</thumbs>
- <category value="Applications" />
-
<parameter
id="packageName"
name="Package name"
@@ -34,7 +36,7 @@
<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>
+ <option id="holo_light_darkactionbar" minBuildApi="14">Holo Light with Dark Action Bar</option>
</parameter>
<parameter
@@ -42,7 +44,7 @@
name="Minimum API level"
type="string"
constraints="apilevel"
- default="7" />
+ default="8" />
<!--
Usually the same as minApi, but when minApi is a code name this will be the corresponding
@@ -53,21 +55,41 @@
name="Minimum API level"
type="string"
constraints="apilevel"
- default="7" />
+ default="8" />
<parameter
id="targetApi"
name="Target API level"
type="string"
constraints="apilevel"
- default="16" />
+ default="19" />
+
+ <!--
+ Usually the same as targetApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="targetApiString"
+ name="Target API"
+ type="string"
+ constraints="apilevel"
+ default="19" />
<parameter
id="buildApi"
name="Build API level"
type="string"
constraints="apilevel"
- default="18" />
+ default="19" />
+
+ <!--
+ Usually the same as buildApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="buildApiString"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"
+ default="19" />
<parameter
id="copyIcons"
diff --git a/templates/gradle-projects/NewAndroidProject/globals.xml.ftl b/templates/gradle-projects/NewAndroidProject/globals.xml.ftl
index 26e600e..da1c39a 100644
--- a/templates/gradle-projects/NewAndroidProject/globals.xml.ftl
+++ b/templates/gradle-projects/NewAndroidProject/globals.xml.ftl
@@ -1,4 +1,5 @@
<?xml version="1.0"?>
<globals>
<global id="topOut" value="." />
+ <global id="mavenUrl" value="mavenCentral" />
</globals>
diff --git a/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl b/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl
index 5321458..316065f 100644
--- a/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl
+++ b/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl
@@ -14,7 +14,7 @@
<instantiate from="gradle.properties.ftl"
to="${escapeXmlAttribute(topOut)}/gradle.properties" />
- <copy from="$TEMPLATEDIR/gradle/wrapper"
+ <copy from="${templateRoot}/gradle/wrapper"
to="${escapeXmlAttribute(topOut)}/" />
<#if sdkDir??>
diff --git a/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl b/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl
index f7a7ae7..83f9ae4 100644
--- a/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl
+++ b/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl
@@ -1 +1,29 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
\ No newline at end of file
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ mavenCentral()
+<#if mavenUrl != "mavenCentral">
+ maven {
+ url '${mavenUrl}'
+ }
+</#if>
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:${gradlePluginVersion}'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ mavenCentral()
+<#if mavenUrl != "mavenCentral">
+ maven {
+ url '${mavenUrl}'
+ }
+</#if>
+ }
+}
diff --git a/templates/gradle-projects/NewAndroidProject/root/project_ignore b/templates/gradle-projects/NewAndroidProject/root/project_ignore
index d6bfc95..0f885ac 100644
--- a/templates/gradle-projects/NewAndroidProject/root/project_ignore
+++ b/templates/gradle-projects/NewAndroidProject/root/project_ignore
@@ -2,3 +2,4 @@
/local.properties
/.idea/workspace.xml
.DS_Store
+/build
diff --git a/templates/gradle-projects/NewAndroidProject/template.xml b/templates/gradle-projects/NewAndroidProject/template.xml
index 6ae554a..d7c0430 100644
--- a/templates/gradle-projects/NewAndroidProject/template.xml
+++ b/templates/gradle-projects/NewAndroidProject/template.xml
@@ -5,12 +5,12 @@
name="Android Project"
description="Creates a new Android project.">
+ <category value="Application" />
+
<thumbs>
<thumb>template_new_project.png</thumb>
</thumbs>
- <category value="Projects" />
-
<parameter
id="makeIgnore"
name="Create .gitignore file"
diff --git a/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl b/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
index d750f76..0a92c5e 100644
--- a/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
+++ b/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
@@ -4,10 +4,12 @@
to="${escapeXmlAttribute(topOut)}/settings.gradle" />
<instantiate from="build.gradle.ftl"
to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <instantiate from="/src/library_package/Placeholder.java.ftl"
+ <instantiate from="src/library_package/Placeholder.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${className}.java" />
<#if makeIgnore>
<copy from="gitignore"
to="${escapeXmlAttribute(projectOut)}/.gitignore" />
</#if>
+
+ <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
</recipe>
diff --git a/templates/gradle-projects/NewJavaLibrary/root/build.gradle.ftl b/templates/gradle-projects/NewJavaLibrary/root/build.gradle.ftl
index bbfeb03..c152b19 100644
--- a/templates/gradle-projects/NewJavaLibrary/root/build.gradle.ftl
+++ b/templates/gradle-projects/NewJavaLibrary/root/build.gradle.ftl
@@ -1 +1,5 @@
apply plugin: 'java'
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+}
\ No newline at end of file
diff --git a/templates/gradle-projects/NewJavaLibrary/template.xml b/templates/gradle-projects/NewJavaLibrary/template.xml
index a9f35c1..9cda96e 100644
--- a/templates/gradle-projects/NewJavaLibrary/template.xml
+++ b/templates/gradle-projects/NewJavaLibrary/template.xml
@@ -5,31 +5,31 @@
name="Java Library"
description="Creates a new Java library.">
+ <category value="Application" />
+
<thumbs>
<thumb>template_new_project.png</thumb>
</thumbs>
- <category value="Applications" />
-
<parameter
id="projectName"
name="Library name"
type="string"
- constraints="nonempty"
- default="MyLibrary"/>
+ constraints="nonempty|module|unique"
+ default="lib"/>
<parameter
id="packageName"
name="Java package name"
type="string"
- constraints="nonempty"
+ constraints="nonempty|package"
default="com.example"/>
<parameter
id="className"
name="Java class name"
type="string"
- constraints="nonempty"
+ constraints="nonempty|class"
default="MyClass"/>
<parameter
diff --git a/templates/gradle/utils/dependencies.gradle.ftl b/templates/gradle/utils/dependencies.gradle.ftl
new file mode 100644
index 0000000..96bd373
--- /dev/null
+++ b/templates/gradle/utils/dependencies.gradle.ftl
@@ -0,0 +1,7 @@
+dependencies {
+<#if dependencyList?? >
+<#list dependencyList as dependency>
+compile '${dependency}'
+</#list>
+</#if>
+}
diff --git a/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties b/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
index d48578c..5de946b 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.9-bin.zip
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip
diff --git a/templates/other/AidlFile/recipe.xml.ftl b/templates/other/AidlFile/recipe.xml.ftl
new file mode 100644
index 0000000..ef6e31c
--- /dev/null
+++ b/templates/other/AidlFile/recipe.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <instantiate from="src/app_package/interface.aidl.ftl"
+ to="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
+ <open file="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
+</recipe>
diff --git a/templates/other/AidlFile/root/src/app_package/interface.aidl.ftl b/templates/other/AidlFile/root/src/app_package/interface.aidl.ftl
new file mode 100644
index 0000000..4404cf2
--- /dev/null
+++ b/templates/other/AidlFile/root/src/app_package/interface.aidl.ftl
@@ -0,0 +1,13 @@
+// ${interfaceName}.aidl
+package ${packageName};
+
+// Declare any non-default types here with import statements
+
+interface ${interfaceName} {
+ /**
+ * Demonstrates some basic types that you can use as parameters
+ * and return values in AIDL.
+ */
+ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
+ double aDouble, String aString);
+}
diff --git a/templates/other/AidlFile/template.xml b/templates/other/AidlFile/template.xml
new file mode 100644
index 0000000..bd42e84
--- /dev/null
+++ b/templates/other/AidlFile/template.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="1"
+ name="AIDL File"
+ description="Creates a new Android Interface Description Language file."
+ >
+
+ <category value="AIDL" />
+
+ <parameter
+ id="interfaceName"
+ name="Interface Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="IMyAidlInterface"
+ help="Name of the Interface." />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/AidlFolder/recipe.xml.ftl b/templates/other/AidlFolder/recipe.xml.ftl
new file mode 100644
index 0000000..93eef52
--- /dev/null
+++ b/templates/other/AidlFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/aidl/" />
+ </#if>
+
+</recipe>
diff --git a/templates/other/AidlFolder/root/build.gradle.ftl b/templates/other/AidlFolder/root/build.gradle.ftl
new file mode 100644
index 0000000..0cd590d
--- /dev/null
+++ b/templates/other/AidlFolder/root/build.gradle.ftl
@@ -0,0 +1 @@
+android {sourceSets {${sourceProviderName} {aidl.srcDirs=['src/${sourceProviderName}/aidl', '${newLocation}']}}}
\ No newline at end of file
diff --git a/templates/other/AidlFolder/template.xml b/templates/other/AidlFolder/template.xml
new file mode 100644
index 0000000..6155f0e
--- /dev/null
+++ b/templates/other/AidlFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="2"
+ name="AIDL Folder"
+ description="Creates a source root for Android Interface Description Language files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/aidl/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/AndroidManifest/recipe.xml.ftl b/templates/other/AndroidManifest/recipe.xml.ftl
new file mode 100644
index 0000000..747c6b3
--- /dev/null
+++ b/templates/other/AndroidManifest/recipe.xml.ftl
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFile>
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ </#if>
+
+</recipe>
diff --git a/templates/other/AndroidManifest/root/AndroidManifest.xml.ftl b/templates/other/AndroidManifest/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..14d5865
--- /dev/null
+++ b/templates/other/AndroidManifest/root/AndroidManifest.xml.ftl
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="${packageName}">
+
+ <application>
+
+ </application>
+
+</manifest>
diff --git a/templates/other/AndroidManifest/root/build.gradle.ftl b/templates/other/AndroidManifest/root/build.gradle.ftl
new file mode 100644
index 0000000..218a0dd
--- /dev/null
+++ b/templates/other/AndroidManifest/root/build.gradle.ftl
@@ -0,0 +1,3 @@
+android {sourceSets {${sourceProviderName} {
+manifest.srcFile '${newLocation}'
+}}}
\ No newline at end of file
diff --git a/templates/other/AndroidManifest/template.xml b/templates/other/AndroidManifest/template.xml
new file mode 100644
index 0000000..afc0aaa
--- /dev/null
+++ b/templates/other/AndroidManifest/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="2"
+ name="Android Manifest File"
+ description="Creates an Android Manifest XML File."
+ >
+
+ <category value="Other" />
+
+ <parameter
+ id="remapFile"
+ name="Change File Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the file location to another destination within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New File Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/AndroidManifest.xml"
+ help="The location for the new file"
+ visibility="remapFile" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/AppWidget/globals.xml.ftl b/templates/other/AppWidget/globals.xml.ftl
index 65e6905..73289bc 100644
--- a/templates/other/AppWidget/globals.xml.ftl
+++ b/templates/other/AppWidget/globals.xml.ftl
@@ -4,4 +4,5 @@
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="class_name" value="${camelCaseToUnderscore(className)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/other/AppWidget/root/AndroidManifest.xml.ftl b/templates/other/AppWidget/root/AndroidManifest.xml.ftl
index bf8f820..698f3ba 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="${packageName}.${className}" >
+ <receiver android:name="${relativePackage}.${className}" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
@@ -14,7 +14,7 @@
</receiver>
<#if configurable>
- <activity android:name="${packageName}.${className}ConfigureActivity" >
+ <activity android:name="${relativePackage}.${className}ConfigureActivity" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
@@ -22,4 +22,4 @@
</#if>
</application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl b/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl
index d86b0b5..a6c9a5f 100644
--- a/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl
+++ b/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl
@@ -4,6 +4,7 @@
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* Implementation of App Widget functionality.
diff --git a/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl b/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
index 3c2be67..f0d68a8 100644
--- a/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
+++ b/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
@@ -8,6 +8,7 @@
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* The configuration screen for the {@link ${className} ${className}} AppWidget.
diff --git a/templates/other/AppWidget/template.xml b/templates/other/AppWidget/template.xml
index d79ef0c..13659b7 100644
--- a/templates/other/AppWidget/template.xml
+++ b/templates/other/AppWidget/template.xml
@@ -2,12 +2,12 @@
<template
format="4"
revision="2"
- name="New App Widget"
+ name="App Widget"
description="Creates a new App Widget"
minApi="4"
minBuildApi="16">
- <category value="Other" />
+ <category value="Widget" />
<parameter
id="className"
@@ -24,7 +24,7 @@
default="homescreen"
help="Make the widget available on the Home-screen and/or on the Keyguard. Keyguard placement is only supported in Android 4.2 and above; this setting is ignored on earlier versions and defaults to Home-screen.">
<option id="both">Home-screen and Keyguard</option>
- <option id="homescreen" default="true" >Home-screen only</option>
+ <option id="homescreen">Home-screen only</option>
<option id="keyguard" >Keyguard only (API 17+)</option>
</parameter>
@@ -34,7 +34,7 @@
type="enum"
default="both"
help="Allow the user to resize the widget. Feature only available on Android 3.1 and above.">
- <option id="both" default="true">Horizontally and vertically</option>
+ <option id="both">Horizontally and vertically</option>
<option id="horizontal">Only horizontally</option>
<option id="vertical" >Only vertically</option>
<option id="none">Not resizable</option>
@@ -45,7 +45,7 @@
name="Minimum Width (cells)"
type="enum"
default="1">
- <option id="1" default="true">1</option>
+ <option id="1">1</option>
<option id="2" >2</option>
<option id="3" >3</option>
<option id="4" >4</option>
@@ -56,7 +56,7 @@
name="Minimum Height (cells)"
type="enum"
default="1">
- <option id="1" default="true">1</option>
+ <option id="1">1</option>
<option id="2" >2</option>
<option id="3" >3</option>
<option id="4" >4</option>
diff --git a/templates/other/AssetsFolder/recipe.xml.ftl b/templates/other/AssetsFolder/recipe.xml.ftl
new file mode 100644
index 0000000..f2042bf
--- /dev/null
+++ b/templates/other/AssetsFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/assets/" />
+ </#if>
+
+</recipe>
diff --git a/templates/other/AssetsFolder/root/build.gradle.ftl b/templates/other/AssetsFolder/root/build.gradle.ftl
new file mode 100644
index 0000000..fd527dc
--- /dev/null
+++ b/templates/other/AssetsFolder/root/build.gradle.ftl
@@ -0,0 +1 @@
+android {sourceSets {${sourceProviderName} {assets.srcDirs=['src/${sourceProviderName}/assets', '${newLocation}']}}}
\ No newline at end of file
diff --git a/templates/other/AssetsFolder/template.xml b/templates/other/AssetsFolder/template.xml
new file mode 100644
index 0000000..d659f88
--- /dev/null
+++ b/templates/other/AssetsFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="2"
+ name="Assets Folder"
+ description="Creates a source root for assets which will be included in the APK."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/assets/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/BlankFragment/globals.xml.ftl b/templates/other/BlankFragment/globals.xml.ftl
index 6e28120..e4cf72f 100644
--- a/templates/other/BlankFragment/globals.xml.ftl
+++ b/templates/other/BlankFragment/globals.xml.ftl
@@ -1,5 +1,7 @@
<?xml version="1.0"?>
<globals>
+ <global id="useSupport" type="boolean" value="${(minApiLevel lt 11)?string}" />
<global id="resOut" value="${resDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/other/BlankFragment/recipe.xml.ftl b/templates/other/BlankFragment/recipe.xml.ftl
index 0008b9b..61764c7 100644
--- a/templates/other/BlankFragment/recipe.xml.ftl
+++ b/templates/other/BlankFragment/recipe.xml.ftl
@@ -1,14 +1,14 @@
<?xml version="1.0"?>
<recipe>
- <dependency mavenUrl="com.android.support:support-v4:+"/>
+ <#if useSupport><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
<merge from="res/values/strings.xml" to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<#if includeLayout>
<instantiate from="res/layout/fragment_blank.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
+ to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
- <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
</#if>
<open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
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 5712aae..315171e 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="${packageName}.${className}">
+ tools:context="${relativePackage}.${className}">
<!-- TODO: Update blank fragment layout -->
<TextView
diff --git a/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl b/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
index 6b3fb1e..6e33197 100644
--- a/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
+++ b/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
@@ -3,14 +3,15 @@
<#if includeCallbacks>import android.app.Activity;</#if>
<#if includeCallbacks>import android.net.Uri;</#if>
import android.os.Bundle;
-import android.support.v4.app.Fragment;
+import android<#if useSupport>.support.v4</#if>.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
<#if !includeLayout>import android.widget.TextView;</#if>
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
- * A simple {@link android.support.v4.app.Fragment} subclass.
+ * A simple {@link Fragment} subclass.
<#if includeCallbacks>
* Activities that contain this fragment must implement the
* {@link ${className}.OnFragmentInteractionListener} interface
@@ -77,7 +78,7 @@
Bundle savedInstanceState) {
<#if includeLayout>
// Inflate the layout for this fragment
- return inflater.inflate(R.layout.fragment_${classToResource(className)}, container, false);
+ return inflater.inflate(R.layout.${fragmentName}, container, false);
<#else>
TextView textView = new TextView(getActivity());
textView.setText(R.string.hello_blank_fragment);
diff --git a/templates/other/BlankFragment/template.xml b/templates/other/BlankFragment/template.xml
index 5ed7c07..d9779ef 100644
--- a/templates/other/BlankFragment/template.xml
+++ b/templates/other/BlankFragment/template.xml
@@ -2,14 +2,14 @@
<template
format="4"
revision="2"
- name="New Blank Fragment"
+ name="Fragment (Blank)"
description="Creates a blank fragment that is compatible back to API level 4."
minApi="7"
minBuildApi="8">
- <dependency name="android-support-v4" revision="8" />
+ <category value="Fragment" />
- <category value="Other" />
+ <dependency name="android-support-v4" revision="8" />
<parameter
id="className"
@@ -27,6 +27,16 @@
help="Generate a layout XML for the fragment" />
<parameter
+ id="fragmentName"
+ name="Fragment Layout Name"
+ type="string"
+ constraints="layout|nonempty|unique"
+ default="fragment_blank"
+ visibility="includeLayout"
+ suggest="fragment_${classToResource(className)}"
+ help="The name of the layout to create" />
+
+ <parameter
id="includeFactory"
name="Include fragment factory methods?"
type="boolean"
diff --git a/templates/other/BlankFragment/template_blank_fragment.png b/templates/other/BlankFragment/template_blank_fragment.png
index e0e71ce..69a02bb 100644
--- a/templates/other/BlankFragment/template_blank_fragment.png
+++ b/templates/other/BlankFragment/template_blank_fragment.png
Binary files differ
diff --git a/templates/other/BroadcastReceiver/globals.xml.ftl b/templates/other/BroadcastReceiver/globals.xml.ftl
index 7beccc1..fca5ad8 100644
--- a/templates/other/BroadcastReceiver/globals.xml.ftl
+++ b/templates/other/BroadcastReceiver/globals.xml.ftl
@@ -2,4 +2,5 @@
<globals>
<global id="manifestOut" value="${manifestDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl b/templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
index 6849a3b..e767f55 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="${packageName}.${className}"
+ <receiver android:name="${relativePackage}.${className}"
android:exported="${isExported?string}"
android:enabled="${isEnabled?string}" >
</receiver>
diff --git a/templates/other/ContentProvider/globals.xml.ftl b/templates/other/ContentProvider/globals.xml.ftl
index 7beccc1..fca5ad8 100644
--- a/templates/other/ContentProvider/globals.xml.ftl
+++ b/templates/other/ContentProvider/globals.xml.ftl
@@ -2,4 +2,5 @@
<globals>
<global id="manifestOut" value="${manifestDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/other/ContentProvider/root/AndroidManifest.xml.ftl b/templates/other/ContentProvider/root/AndroidManifest.xml.ftl
index 819925d..354f6c9 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="${packageName}.${className}"
+ <provider android:name="${relativePackage}.${className}"
android:authorities="${authorities}"
android:exported="${isExported?string}"
android:enabled="${isEnabled?string}" >
diff --git a/templates/other/CustomView/root/src/app_package/CustomView.java.ftl b/templates/other/CustomView/root/src/app_package/CustomView.java.ftl
index e1c7e13..a989a30 100644
--- a/templates/other/CustomView/root/src/app_package/CustomView.java.ftl
+++ b/templates/other/CustomView/root/src/app_package/CustomView.java.ftl
@@ -9,6 +9,7 @@
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* TODO: document your custom view class.
diff --git a/templates/other/CustomView/template.xml b/templates/other/CustomView/template.xml
index 5373030..666d319 100644
--- a/templates/other/CustomView/template.xml
+++ b/templates/other/CustomView/template.xml
@@ -5,7 +5,7 @@
name="Custom View"
description="Creates a new custom view that extends android.view.View and exposes custom attributes.">
- <category value="UI Components" />
+ <category value="UI Component" />
<parameter
id="packageName"
diff --git a/templates/other/Daydream/globals.xml.ftl b/templates/other/Daydream/globals.xml.ftl
index 4d64b36..1a8b620 100644
--- a/templates/other/Daydream/globals.xml.ftl
+++ b/templates/other/Daydream/globals.xml.ftl
@@ -7,4 +7,5 @@
<global id="info_name" value="${classToResource(className)}_info" />
<global id="settingsClassName" value="${className}SettingsActivity" />
<global id="prefs_name" value="${classToResource(className)}_prefs" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/other/Daydream/root/AndroidManifest.xml.ftl b/templates/other/Daydream/root/AndroidManifest.xml.ftl
index 01beaec..9777461 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="${packageName}.${settingsClassName}" />
+ android:name="${relativePackage}.${settingsClassName}" />
</#if>
<!-- This service is only used on devices with API v17+ -->
<service
- android:name="${packageName}.${className}"
+ android:name="${relativePackage}.${className}"
android:exported="true" >
<intent-filter>
<action android:name="android.service.dreams.DreamService" />
diff --git a/templates/other/Daydream/root/src/app_package/DreamService.java.ftl b/templates/other/Daydream/root/src/app_package/DreamService.java.ftl
index 30f080f..0dc51ca 100644
--- a/templates/other/Daydream/root/src/app_package/DreamService.java.ftl
+++ b/templates/other/Daydream/root/src/app_package/DreamService.java.ftl
@@ -15,6 +15,7 @@
import android.view.ViewPropertyAnimator;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* This class is a sample implementation of a DreamService. When activated, a
diff --git a/templates/other/Daydream/root/src/app_package/SettingsActivity.java.ftl b/templates/other/Daydream/root/src/app_package/SettingsActivity.java.ftl
index 62d5887..cd1b4c8 100644
--- a/templates/other/Daydream/root/src/app_package/SettingsActivity.java.ftl
+++ b/templates/other/Daydream/root/src/app_package/SettingsActivity.java.ftl
@@ -5,6 +5,7 @@
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* A settings Activity for {@link ${className}}.
diff --git a/templates/other/Daydream/template.xml b/templates/other/Daydream/template.xml
index cd292a4..491d009 100644
--- a/templates/other/Daydream/template.xml
+++ b/templates/other/Daydream/template.xml
@@ -2,12 +2,10 @@
<template
format="4"
revision="2"
- name="New Daydream"
+ name="Daydream"
description="Creates a new Daydream service component, for use on devices running Android 4.2 and later."
minBuildApi="17">
- <category value="Other" />
-
<parameter
id="className"
name="Class Name"
diff --git a/templates/other/IntentService/globals.xml.ftl b/templates/other/IntentService/globals.xml.ftl
index b44b9f0..3b38cfd 100644
--- a/templates/other/IntentService/globals.xml.ftl
+++ b/templates/other/IntentService/globals.xml.ftl
@@ -1,4 +1,6 @@
<?xml version="1.0"?>
<globals>
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/other/IntentService/root/AndroidManifest.xml.ftl b/templates/other/IntentService/root/AndroidManifest.xml.ftl
index 61b66bb..f49f6f5 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="${packageName}.${className}"
+ <service android:name="${relativePackage}.${className}"
android:exported="false" >
</service>
</application>
diff --git a/templates/other/IntentService/template.xml b/templates/other/IntentService/template.xml
index 3d86c9c..7a0cc60 100644
--- a/templates/other/IntentService/template.xml
+++ b/templates/other/IntentService/template.xml
@@ -2,12 +2,12 @@
<template
format="4"
revision="2"
- name="New IntentService"
+ name="Service (IntentService)"
description="Creates a new intent service class."
minApi="3"
minBuildApi="3">
- <category value="Other" />
+ <category value="Service" />
<parameter
id="className"
diff --git a/templates/other/JavaFolder/recipe.xml.ftl b/templates/other/JavaFolder/recipe.xml.ftl
new file mode 100644
index 0000000..a470666
--- /dev/null
+++ b/templates/other/JavaFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/java/" />
+ </#if>
+
+</recipe>
diff --git a/templates/other/JavaFolder/root/build.gradle.ftl b/templates/other/JavaFolder/root/build.gradle.ftl
new file mode 100644
index 0000000..3692aaa
--- /dev/null
+++ b/templates/other/JavaFolder/root/build.gradle.ftl
@@ -0,0 +1 @@
+android {sourceSets {${sourceProviderName} {java.srcDirs=['src/${sourceProviderName}/java', '${newLocation}']}}}
\ No newline at end of file
diff --git a/templates/other/JavaFolder/template.xml b/templates/other/JavaFolder/template.xml
new file mode 100644
index 0000000..df0cd6f
--- /dev/null
+++ b/templates/other/JavaFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="2"
+ name="Java Folder"
+ description="Creates a source root for Java files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/java/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/JniFolder/recipe.xml.ftl b/templates/other/JniFolder/recipe.xml.ftl
new file mode 100644
index 0000000..29e7e98
--- /dev/null
+++ b/templates/other/JniFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/jni/" />
+ </#if>
+
+</recipe>
diff --git a/templates/other/JniFolder/root/build.gradle.ftl b/templates/other/JniFolder/root/build.gradle.ftl
new file mode 100644
index 0000000..d4ab6e3
--- /dev/null
+++ b/templates/other/JniFolder/root/build.gradle.ftl
@@ -0,0 +1 @@
+android {sourceSets {${sourceProviderName} {jni.srcDirs=['src/${sourceProviderName}/jni', '${newLocation}']}}}
\ No newline at end of file
diff --git a/templates/other/JniFolder/template.xml b/templates/other/JniFolder/template.xml
new file mode 100644
index 0000000..0067618
--- /dev/null
+++ b/templates/other/JniFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="2"
+ name="JNI Folder"
+ description="Creates a source root for Java Native Interface files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/jni/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/LayoutResourceFile/recipe.xml.ftl b/templates/other/LayoutResourceFile/recipe.xml.ftl
new file mode 100644
index 0000000..a73adc7
--- /dev/null
+++ b/templates/other/LayoutResourceFile/recipe.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <instantiate from="res/layout.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
+</recipe>
diff --git a/templates/other/LayoutResourceFile/root/res/layout.xml.ftl b/templates/other/LayoutResourceFile/root/res/layout.xml.ftl
new file mode 100644
index 0000000..18114ab
--- /dev/null
+++ b/templates/other/LayoutResourceFile/root/res/layout.xml.ftl
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<${rootTag} xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+</${rootTag}>
diff --git a/templates/other/LayoutResourceFile/template.xml b/templates/other/LayoutResourceFile/template.xml
new file mode 100644
index 0000000..abca0d2
--- /dev/null
+++ b/templates/other/LayoutResourceFile/template.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="1"
+ name="Layout XML File"
+ description="Creates a new XML layout file."
+ >
+
+ <category value="XML" />
+
+ <parameter
+ id="layoutName"
+ name="Layout File Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ default="layout"
+ help="Name of the layout XML file." />
+
+ <parameter
+ id="rootTag"
+ name="Root Tag"
+ type="string"
+ constraints="nonempty"
+ default="LinearLayout"
+ help="The root XML tag for the new file" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/ListFragment/globals.xml.ftl b/templates/other/ListFragment/globals.xml.ftl
index 29dbb98..9e56824 100644
--- a/templates/other/ListFragment/globals.xml.ftl
+++ b/templates/other/ListFragment/globals.xml.ftl
@@ -1,8 +1,11 @@
<?xml version="1.0"?>
<globals>
+ <global id="useSupport" type="boolean" value="${(minApiLevel lt 11)?string}" />
+ <global id="SupportPackage" value="${(minApiLevel lt 11)?string('.support.v4','')}" />
<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)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/other/ListFragment/recipe.xml.ftl b/templates/other/ListFragment/recipe.xml.ftl
index 7be60e9..5236d3c 100644
--- a/templates/other/ListFragment/recipe.xml.ftl
+++ b/templates/other/ListFragment/recipe.xml.ftl
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<recipe>
- <dependency mavenUrl="com.android.support:support-v4:+"/>
+ <#if useSupport><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
<#if switchGrid == true>
<merge from="res/values/refs.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/refs.xml" />
diff --git a/templates/other/ListFragment/root/res/layout/fragment_grid.xml b/templates/other/ListFragment/root/res/layout/fragment_grid.xml
index 55c0044..333ea8e 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="${packageName}.${className}">
+ tools:context="${relativePackage}.${className}">
<GridView
android:id="@android:id/list"
@@ -17,4 +17,4 @@
android:layout_height="match_parent"
android:gravity="center" />
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/templates/other/ListFragment/root/res/layout/fragment_list.xml b/templates/other/ListFragment/root/res/layout/fragment_list.xml
index e37a0a6..cc2aa02 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="${packageName}.${className}">
+ tools:context="${relativePackage}.${className}">
<ListView
android:id="@android:id/list"
@@ -16,4 +16,4 @@
android:layout_height="match_parent"
android:gravity="center" />
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl b/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl
index 6100448..f5171a1 100644
--- a/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl
+++ b/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl
@@ -3,10 +3,10 @@
import android.app.Activity;
import android.os.Bundle;
<#if switchGrid == true>
-import android.support.v4.app.Fragment;
+import android${SupportPackage}.app.Fragment;
import android.view.LayoutInflater;
<#else>
-import android.support.v4.app.ListFragment;
+import android${SupportPackage}.app.ListFragment;
</#if>
import android.view.View;
<#if switchGrid == true>
@@ -21,6 +21,7 @@
<#else>
import android.widget.ListView;
</#if>
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
import ${packageName}.dummy.DummyContent;
diff --git a/templates/other/ListFragment/template.xml b/templates/other/ListFragment/template.xml
index 81eabad..3d1f96d 100644
--- a/templates/other/ListFragment/template.xml
+++ b/templates/other/ListFragment/template.xml
@@ -2,14 +2,14 @@
<template
format="4"
revision="2"
- name="New List Fragment"
+ name="Fragment (List)"
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"
minBuildApi="8">
- <dependency name="android-support-v4" revision="8" />
+ <category value="Fragment" />
- <category value="UI Components" />
+ <dependency name="android-support-v4" revision="8" />
<parameter
id="packageName"
diff --git a/templates/other/Notification/recipe.xml.ftl b/templates/other/Notification/recipe.xml.ftl
index 1f1afec..c08a91f 100644
--- a/templates/other/Notification/recipe.xml.ftl
+++ b/templates/other/Notification/recipe.xml.ftl
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<recipe>
- <dependency mavenUrl="com.android.support:support-v4:+"/>
+ <dependency mavenUrl="com.android.support:support-v4:19.+"/>
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
diff --git a/templates/other/Notification/root/src/app_package/NotificationHelper.java.ftl b/templates/other/Notification/root/src/app_package/NotificationHelper.java.ftl
index af69d5a..6c39bfe 100644
--- a/templates/other/Notification/root/src/app_package/NotificationHelper.java.ftl
+++ b/templates/other/Notification/root/src/app_package/NotificationHelper.java.ftl
@@ -17,6 +17,7 @@
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
</#if>
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* Helper class for showing and canceling ${display_title?lower_case}
diff --git a/templates/other/Notification/template.xml b/templates/other/Notification/template.xml
index b9479b2..2a13999 100644
--- a/templates/other/Notification/template.xml
+++ b/templates/other/Notification/template.xml
@@ -2,13 +2,13 @@
<template
format="4"
revision="2"
- name="New Notification"
+ name="Notification"
description="Creates a new helper class that can show or cancel a status bar notification."
minApi="4">
- <dependency name="android-support-v4" revision="10" />
+ <category value="UI Component" />
- <category value="Other" />
+ <dependency name="android-support-v4" revision="10" />
<parameter
id="className"
@@ -25,7 +25,7 @@
default="text"
help="The expanded notification style to use for devices running Android 4.1 or later." >
<option id="none">None</option>
- <option id="text" default="true">More text</option>
+ <option id="text">More text</option>
<option id="picture">Picture</option>
<option id="list">List</option>
</parameter>
diff --git a/templates/other/PlusOneFragment/globals.xml.ftl b/templates/other/PlusOneFragment/globals.xml.ftl
new file mode 100644
index 0000000..6e28120
--- /dev/null
+++ b/templates/other/PlusOneFragment/globals.xml.ftl
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="resOut" value="${resDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+</globals>
diff --git a/templates/other/PlusOneFragment/recipe.xml.ftl b/templates/other/PlusOneFragment/recipe.xml.ftl
new file mode 100644
index 0000000..443404d
--- /dev/null
+++ b/templates/other/PlusOneFragment/recipe.xml.ftl
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <dependency mavenUrl="com.android.support:support-v4:19.+"/>
+ <dependency mavenUrl="com.google.android.gms:play-services:4.2.42"/>
+
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="res/layout/fragment_plus_one.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
+
+ <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+ <instantiate from="src/app_package/PlusOneFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+</recipe>
diff --git a/templates/other/PlusOneFragment/root/AndroidManifest.xml.ftl b/templates/other/PlusOneFragment/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..aa8f5a5
--- /dev/null
+++ b/templates/other/PlusOneFragment/root/AndroidManifest.xml.ftl
@@ -0,0 +1,6 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <!--To access Google+ APIs:-->
+ <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
diff --git a/templates/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl b/templates/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl
new file mode 100644
index 0000000..2f45612
--- /dev/null
+++ b/templates/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl
@@ -0,0 +1,15 @@
+<FrameLayout 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"
+ tools:context=".${className}">
+
+ <com.google.android.gms.plus.PlusOneButton
+ xmlns:plus="http://schemas.android.com/apk/lib/com.google.android.gms.plus"
+ android:id="@+id/plus_one_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ plus:size="standard"
+ plus:annotation="inline"/>
+
+</FrameLayout>
diff --git a/templates/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl b/templates/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl
new file mode 100644
index 0000000..c3d5ed4
--- /dev/null
+++ b/templates/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl
@@ -0,0 +1,147 @@
+package ${packageName};
+
+<#if includeCallbacks>import android.app.Activity;</#if>
+<#if includeCallbacks>import android.net.Uri;</#if>
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+<#if applicationPackage??>import ${applicationPackage}.R;</#if>
+
+import com.google.android.gms.plus.PlusOneButton;
+
+/**
+ * A fragment with a Google +1 button.
+<#if includeCallbacks>
+ * Activities that contain this fragment must implement the
+ * {@link ${className}.OnFragmentInteractionListener} interface
+ * to handle interaction events.
+</#if>
+<#if includeFactory>
+ * Use the {@link ${className}#newInstance} factory method to
+ * create an instance of this fragment.
+</#if>
+ *
+ */
+public class ${className} extends Fragment {
+<#if includeFactory>
+ // TODO: Rename parameter arguments, choose names that match
+ // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+ private static final String ARG_PARAM1 = "param1";
+ private static final String ARG_PARAM2 = "param2";
+
+ // TODO: Rename and change types of parameters
+ private String mParam1;
+ private String mParam2;
+</#if>
+
+ // The URL to +1. Must be a valid URL.
+ private final String PLUS_ONE_URL = "http://developer.android.com";
+
+ // The request code must be 0 or greater.
+ private static final int PLUS_ONE_REQUEST_CODE = 0;
+
+ private PlusOneButton mPlusOneButton;
+
+<#if includeCallbacks>
+ private OnFragmentInteractionListener mListener;
+</#if>
+
+<#if includeFactory>
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment ${className}.
+ */
+ // TODO: Rename and change types and number of parameters
+ public static ${className} newInstance(String param1, String param2) {
+ ${className} fragment = new ${className}();
+ Bundle args = new Bundle();
+ args.putString(ARG_PARAM1, param1);
+ args.putString(ARG_PARAM2, param2);
+ fragment.setArguments(args);
+ return fragment;
+ }
+</#if>
+ public ${className}() {
+ // Required empty public constructor
+ }
+
+<#if includeFactory>
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getArguments() != null) {
+ mParam1 = getArguments().getString(ARG_PARAM1);
+ mParam2 = getArguments().getString(ARG_PARAM2);
+ }
+ }
+</#if>
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View view = inflater.inflate(R.layout.fragment_${classToResource(className)}, container, false);
+
+ //Find the +1 button
+ mPlusOneButton = (PlusOneButton) view.findViewById(R.id.plus_one_button);
+
+ return view;
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Refresh the state of the +1 button each time the activity receives focus.
+ mPlusOneButton.initialize(PLUS_ONE_URL, PLUS_ONE_REQUEST_CODE);
+ }
+
+<#if includeCallbacks>
+ // TODO: Rename method, update argument and hook method into UI event
+ public void onButtonPressed(Uri uri) {
+ if (mListener != null) {
+ mListener.onFragmentInteraction(uri);
+ }
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ mListener = (OnFragmentInteractionListener) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString()
+ + " must implement OnFragmentInteractionListener");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+
+ /**
+ * This interface must be implemented by activities that contain this
+ * fragment to allow an interaction in this fragment to be communicated
+ * to the activity and potentially other fragments contained in that
+ * activity.
+ * <p>
+ * See the Android Training lesson <a href=
+ * "http://developer.android.com/training/basics/fragments/communicating.html"
+ * >Communicating with Other Fragments</a> for more information.
+ */
+ public interface OnFragmentInteractionListener {
+ // TODO: Update argument type and name
+ public void onFragmentInteraction(Uri uri);
+ }
+</#if>
+
+}
diff --git a/templates/other/PlusOneFragment/template.xml b/templates/other/PlusOneFragment/template.xml
new file mode 100644
index 0000000..618d92d
--- /dev/null
+++ b/templates/other/PlusOneFragment/template.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<template
+ format="3"
+ revision="1"
+ name="Fragment (with a +1 button)"
+ description="Creates a fragment with a Google Plus +1 button."
+ minApi="8"
+ minBuildApi="8">
+
+ <category value="Fragment" />
+
+ <dependency name="android-support-v4" revision="8" />
+
+ <category value="Other" />
+
+ <parameter
+ id="className"
+ name="Fragment Name"
+ type="string"
+ constraints="class|nonempty|unique"
+ default="PlusOneFragment"
+ help="The name of the fragment class to create" />
+
+ <parameter
+ id="includeFactory"
+ name="Include fragment factory methods?"
+ type="boolean"
+ default="true"
+ help="Generate static fragment factory methods for easy instantiation" />
+
+ <parameter
+ id="includeCallbacks"
+ name="Include interface callbacks?"
+ type="boolean"
+ default="true"
+ help="Generate event callbacks for communication with an Activity or other fragments" />
+
+ <thumbs>
+ <thumb>templates_plusone_fragment.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/PlusOneFragment/templates_plusone_fragment.png b/templates/other/PlusOneFragment/templates_plusone_fragment.png
new file mode 100644
index 0000000..47c1dcc
--- /dev/null
+++ b/templates/other/PlusOneFragment/templates_plusone_fragment.png
Binary files differ
diff --git a/templates/other/ResFolder/recipe.xml.ftl b/templates/other/ResFolder/recipe.xml.ftl
new file mode 100644
index 0000000..abfda2d
--- /dev/null
+++ b/templates/other/ResFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/res/" />
+ </#if>
+
+</recipe>
diff --git a/templates/other/ResFolder/root/build.gradle.ftl b/templates/other/ResFolder/root/build.gradle.ftl
new file mode 100644
index 0000000..beef1f8
--- /dev/null
+++ b/templates/other/ResFolder/root/build.gradle.ftl
@@ -0,0 +1 @@
+android {sourceSets {${sourceProviderName} {res.srcDirs=['src/${sourceProviderName}/res', '${newLocation}']}}}
\ No newline at end of file
diff --git a/templates/other/ResFolder/template.xml b/templates/other/ResFolder/template.xml
new file mode 100644
index 0000000..a813a95
--- /dev/null
+++ b/templates/other/ResFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="2"
+ name="Res Folder"
+ description="Creates a source root for Android Resource files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/res/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/ResourcesFolder/recipe.xml.ftl b/templates/other/ResourcesFolder/recipe.xml.ftl
new file mode 100644
index 0000000..2045012
--- /dev/null
+++ b/templates/other/ResourcesFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/resources/" />
+ </#if>
+
+</recipe>
diff --git a/templates/other/ResourcesFolder/root/build.gradle.ftl b/templates/other/ResourcesFolder/root/build.gradle.ftl
new file mode 100644
index 0000000..46cb70a
--- /dev/null
+++ b/templates/other/ResourcesFolder/root/build.gradle.ftl
@@ -0,0 +1 @@
+android {sourceSets {${sourceProviderName} {resources.srcDirs=['src/${sourceProviderName}/resources', '${newLocation}']}}}
\ No newline at end of file
diff --git a/templates/other/ResourcesFolder/template.xml b/templates/other/ResourcesFolder/template.xml
new file mode 100644
index 0000000..c5a9c1b
--- /dev/null
+++ b/templates/other/ResourcesFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="2"
+ name="Java Resources Folder"
+ description="Creates a source root for Java Resource (NOT Android resource) files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/resources/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/RsFolder/recipe.xml.ftl b/templates/other/RsFolder/recipe.xml.ftl
new file mode 100644
index 0000000..e2766e4
--- /dev/null
+++ b/templates/other/RsFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/rs/" />
+ </#if>
+
+</recipe>
diff --git a/templates/other/RsFolder/root/build.gradle.ftl b/templates/other/RsFolder/root/build.gradle.ftl
new file mode 100644
index 0000000..f393724
--- /dev/null
+++ b/templates/other/RsFolder/root/build.gradle.ftl
@@ -0,0 +1 @@
+android {sourceSets {${sourceProviderName} {renderscript.srcDirs=['src/${sourceProviderName}/rs', '${newLocation}']}}}
\ No newline at end of file
diff --git a/templates/other/RsFolder/template.xml b/templates/other/RsFolder/template.xml
new file mode 100644
index 0000000..0725931
--- /dev/null
+++ b/templates/other/RsFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="2"
+ name="RenderScript Folder"
+ description="Creates a source root for RenderScript files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/rs/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/Service/globals.xml.ftl b/templates/other/Service/globals.xml.ftl
index 7beccc1..fca5ad8 100644
--- a/templates/other/Service/globals.xml.ftl
+++ b/templates/other/Service/globals.xml.ftl
@@ -2,4 +2,5 @@
<globals>
<global id="manifestOut" value="${manifestDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>
diff --git a/templates/other/Service/root/AndroidManifest.xml.ftl b/templates/other/Service/root/AndroidManifest.xml.ftl
index 0f747ea..74a4a9c 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="${packageName}.${className}"
+ <service android:name="${relativePackage}.${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 de5b36c..96ad2a1 100644
--- a/templates/other/Service/template.xml
+++ b/templates/other/Service/template.xml
@@ -5,6 +5,8 @@
name="Service"
description="Creates a new service component and adds it to your Android manifest.">
+ <category value="Service" />
+
<parameter
id="className"
name="Class Name"
diff --git a/templates/other/ValueResourceFile/recipe.xml.ftl b/templates/other/ValueResourceFile/recipe.xml.ftl
new file mode 100644
index 0000000..8895ae1
--- /dev/null
+++ b/templates/other/ValueResourceFile/recipe.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <instantiate from="res/values.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
+ <open file="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
+</recipe>
diff --git a/templates/other/ValueResourceFile/root/res/values.xml.ftl b/templates/other/ValueResourceFile/root/res/values.xml.ftl
new file mode 100644
index 0000000..045e125
--- /dev/null
+++ b/templates/other/ValueResourceFile/root/res/values.xml.ftl
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+</resources>
diff --git a/templates/other/ValueResourceFile/template.xml b/templates/other/ValueResourceFile/template.xml
new file mode 100644
index 0000000..b270aae
--- /dev/null
+++ b/templates/other/ValueResourceFile/template.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="1"
+ name="Values XML File"
+ description="Creates a new XML values file."
+ >
+
+ <category value="XML" />
+
+ <parameter
+ id="fileName"
+ name="Values File Name"
+ type="string"
+ constraints="unique|nonempty"
+ default="values"
+ help="Name of the values XML file." />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/projects/NewAndroidApplication/recipe.xml.ftl b/templates/projects/NewAndroidApplication/recipe.xml.ftl
index cfa46df..73bf014 100644
--- a/templates/projects/NewAndroidApplication/recipe.xml.ftl
+++ b/templates/projects/NewAndroidApplication/recipe.xml.ftl
@@ -18,7 +18,7 @@
to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
</#if>
<#if buildApi gte 14 && baseTheme?contains("darkactionbar")>
- <copy from="res/values-v14/styles_ics.xml"
+ <instantiate from="res/values-v14/styles_ics.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values-v14/styles.xml" />
</#if>
diff --git a/templates/projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl b/templates/projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl
index f8993c3..7a1fd9d 100644
--- a/templates/projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl
+++ b/templates/projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl
@@ -4,7 +4,9 @@
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>">
+ <style name="AppBaseTheme" parent="<#if
+ appCompat?has_content>Theme.AppCompat<#else
+ >android:Theme.Holo</#if><#if baseTheme?contains("light")>.Light</#if>">
<!-- API 11 theme customizations can go here. -->
</style>
diff --git a/templates/projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml b/templates/projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml
deleted file mode 100644
index a91fd03..0000000
--- a/templates/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/projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml.ftl b/templates/projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml.ftl
new file mode 100644
index 0000000..21a96e8
--- /dev/null
+++ b/templates/projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml.ftl
@@ -0,0 +1,14 @@
+<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="<#if
+ appCompat?has_content>Theme.AppCompat<#else
+ >android:Theme.Holo</#if>.Light.DarkActionBar">
+ <!-- API 14 theme customizations can go here. -->
+ </style>
+
+</resources>
diff --git a/templates/projects/NewAndroidApplication/root/res/values/styles.xml.ftl b/templates/projects/NewAndroidApplication/root/res/values/styles.xml.ftl
index 30fe5b5..96464b8 100644
--- a/templates/projects/NewAndroidApplication/root/res/values/styles.xml.ftl
+++ b/templates/projects/NewAndroidApplication/root/res/values/styles.xml.ftl
@@ -4,7 +4,9 @@
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>">
+ <style name="AppBaseTheme" parent="<#if
+ appCompat?has_content>Theme.AppCompat<#else>android:Theme</#if><#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
diff --git a/templates/projects/NewAndroidLibrary/recipe.xml.ftl b/templates/projects/NewAndroidLibrary/recipe.xml.ftl
index cfa46df..73bf014 100644
--- a/templates/projects/NewAndroidLibrary/recipe.xml.ftl
+++ b/templates/projects/NewAndroidLibrary/recipe.xml.ftl
@@ -18,7 +18,7 @@
to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
</#if>
<#if buildApi gte 14 && baseTheme?contains("darkactionbar")>
- <copy from="res/values-v14/styles_ics.xml"
+ <instantiate from="res/values-v14/styles_ics.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values-v14/styles.xml" />
</#if>
diff --git a/templates/projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl b/templates/projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl
index f8993c3..7a1fd9d 100644
--- a/templates/projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl
+++ b/templates/projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl
@@ -4,7 +4,9 @@
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>">
+ <style name="AppBaseTheme" parent="<#if
+ appCompat?has_content>Theme.AppCompat<#else
+ >android:Theme.Holo</#if><#if baseTheme?contains("light")>.Light</#if>">
<!-- API 11 theme customizations can go here. -->
</style>
diff --git a/templates/projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml b/templates/projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml
deleted file mode 100644
index a91fd03..0000000
--- a/templates/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/projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml.ftl b/templates/projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml.ftl
new file mode 100644
index 0000000..21a96e8
--- /dev/null
+++ b/templates/projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml.ftl
@@ -0,0 +1,14 @@
+<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="<#if
+ appCompat?has_content>Theme.AppCompat<#else
+ >android:Theme.Holo</#if>.Light.DarkActionBar">
+ <!-- API 14 theme customizations can go here. -->
+ </style>
+
+</resources>
diff --git a/templates/projects/NewAndroidLibrary/root/res/values/styles.xml.ftl b/templates/projects/NewAndroidLibrary/root/res/values/styles.xml.ftl
index 30fe5b5..96464b8 100644
--- a/templates/projects/NewAndroidLibrary/root/res/values/styles.xml.ftl
+++ b/templates/projects/NewAndroidLibrary/root/res/values/styles.xml.ftl
@@ -4,7 +4,9 @@
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>">
+ <style name="AppBaseTheme" parent="<#if
+ appCompat?has_content>Theme.AppCompat<#else>android:Theme</#if><#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
diff --git a/testutils/build.gradle b/testutils/build.gradle
index 26118af..ea03301 100644
--- a/testutils/build.gradle
+++ b/testutils/build.gradle
@@ -1,11 +1,11 @@
apply plugin: 'java'
-apply plugin: 'distrib'
group = 'com.android.tools'
archivesBaseName = 'testutils'
+version = rootProject.ext.baseVersion
dependencies {
- compile project(':common')
+ compile project(':base:common')
compile 'junit:junit:3.8.1'
}
@@ -13,9 +13,3 @@
main.resources.srcDir 'src/main/java'
test.resources.srcDir 'src/test/java'
}
-
-// Needed for ADT
-// 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/SdkTestCase.java b/testutils/src/main/java/com/android/testutils/SdkTestCase.java
index 402d7a9..4a65178 100644
--- a/testutils/src/main/java/com/android/testutils/SdkTestCase.java
+++ b/testutils/src/main/java/com/android/testutils/SdkTestCase.java
@@ -175,7 +175,12 @@
}
String xml = new String(ByteStreams.toByteArray(stream), Charsets.UTF_8);
- Closeables.closeQuietly(stream);
+ try {
+ Closeables.close(stream, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+
assertTrue(xml.length() > 0);
// Remove any references to the project name such that we are isolated from
diff --git a/testutils/src/test/.classpath b/testutils/src/test/.classpath
index 271183e..1bae051 100644
--- a/testutils/src/test/.classpath
+++ b/testutils/src/test/.classpath
@@ -3,7 +3,7 @@
<classpathentry kind="src" path="java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/guava-tools/guava-13.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/guava-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/testutils"/>
<classpathentry kind="output" path="bin"/>
</classpath>