summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/AaptAssets.cpp25
-rw-r--r--tools/aapt/Android.mk60
-rw-r--r--tools/aapt/Bundle.h18
-rw-r--r--tools/aapt/CacheUpdater.h4
-rw-r--r--tools/aapt/Command.cpp534
-rw-r--r--tools/aapt/CrunchCache.cpp1
-rw-r--r--tools/aapt/Images.cpp187
-rw-r--r--tools/aapt/Main.cpp34
-rw-r--r--tools/aapt/Main.h1
-rw-r--r--tools/aapt/Package.cpp2
-rw-r--r--tools/aapt/Resource.cpp134
-rw-r--r--tools/aapt/ResourceTable.cpp333
-rw-r--r--tools/aapt/ResourceTable.h36
-rw-r--r--tools/aapt/StringPool.h2
-rw-r--r--tools/aapt/XMLNode.cpp20
-rw-r--r--tools/aapt/XMLNode.h6
-rw-r--r--tools/aapt/ZipFile.cpp3
-rw-r--r--tools/aapt/pseudolocalize.h2
-rw-r--r--tools/aapt2/Android.mk210
-rw-r--r--tools/aapt2/BinaryResourceParser.cpp897
-rw-r--r--tools/aapt2/BindingXmlPullParser.cpp268
-rw-r--r--tools/aapt2/BindingXmlPullParser.h90
-rw-r--r--tools/aapt2/BindingXmlPullParser_test.cpp110
-rw-r--r--tools/aapt2/ConfigDescription.cpp40
-rw-r--r--tools/aapt2/ConfigDescription.h7
-rw-r--r--tools/aapt2/ConfigDescription_test.cpp19
-rw-r--r--tools/aapt2/Debug.cpp199
-rw-r--r--tools/aapt2/Debug.h12
-rw-r--r--tools/aapt2/Diagnostics.h147
-rw-r--r--tools/aapt2/Flag.cpp132
-rw-r--r--tools/aapt2/Flag.h34
-rw-r--r--tools/aapt2/Flags.cpp168
-rw-r--r--tools/aapt2/Flags.h67
-rw-r--r--tools/aapt2/Format.proto210
-rw-r--r--tools/aapt2/JavaClassGenerator.cpp208
-rw-r--r--tools/aapt2/JavaClassGenerator.h77
-rw-r--r--tools/aapt2/JavaClassGenerator_test.cpp146
-rw-r--r--tools/aapt2/Linker.cpp290
-rw-r--r--tools/aapt2/Linker.h124
-rw-r--r--tools/aapt2/Linker_test.cpp153
-rw-r--r--tools/aapt2/Locale.cpp24
-rw-r--r--tools/aapt2/Locale.h4
-rw-r--r--tools/aapt2/Locale_test.cpp2
-rw-r--r--tools/aapt2/Logger.cpp97
-rw-r--r--tools/aapt2/Logger.h81
-rw-r--r--tools/aapt2/Main.cpp1268
-rw-r--r--tools/aapt2/ManifestMerger.cpp376
-rw-r--r--tools/aapt2/ManifestMerger.h45
-rw-r--r--tools/aapt2/ManifestMerger_test.cpp121
-rw-r--r--tools/aapt2/ManifestParser.cpp84
-rw-r--r--tools/aapt2/ManifestParser_test.cpp42
-rw-r--r--tools/aapt2/ManifestValidator.cpp217
-rw-r--r--tools/aapt2/ManifestValidator.h55
-rw-r--r--tools/aapt2/MockResolver.h93
-rw-r--r--tools/aapt2/NameMangler.h58
-rw-r--r--tools/aapt2/Resolver.h75
-rw-r--r--tools/aapt2/Resource.cpp14
-rw-r--r--tools/aapt2/Resource.h150
-rw-r--r--tools/aapt2/ResourceParser.cpp1901
-rw-r--r--tools/aapt2/ResourceParser.h196
-rw-r--r--tools/aapt2/ResourceParser_test.cpp575
-rw-r--r--tools/aapt2/ResourceTable.cpp623
-rw-r--r--tools/aapt2/ResourceTable.h351
-rw-r--r--tools/aapt2/ResourceTableResolver.cpp202
-rw-r--r--tools/aapt2/ResourceTableResolver.h70
-rw-r--r--tools/aapt2/ResourceTable_test.cpp257
-rw-r--r--tools/aapt2/ResourceTypeExtensions.h147
-rw-r--r--tools/aapt2/ResourceUtils.cpp580
-rw-r--r--tools/aapt2/ResourceUtils.h172
-rw-r--r--tools/aapt2/ResourceUtils_test.cpp207
-rw-r--r--tools/aapt2/ResourceValues.cpp373
-rw-r--r--tools/aapt2/ResourceValues.h319
-rw-r--r--tools/aapt2/Resource_test.cpp4
-rw-r--r--tools/aapt2/ScopedXmlPullParser.cpp104
-rw-r--r--tools/aapt2/ScopedXmlPullParser.h85
-rw-r--r--tools/aapt2/ScopedXmlPullParser_test.cpp106
-rw-r--r--tools/aapt2/SdkConstants.cpp5
-rw-r--r--tools/aapt2/SdkConstants.h1
-rw-r--r--tools/aapt2/SdkConstants_test.cpp38
-rw-r--r--tools/aapt2/Source.h70
-rw-r--r--tools/aapt2/SourceXmlPullParser.h91
-rw-r--r--tools/aapt2/StringPool.cpp23
-rw-r--r--tools/aapt2/StringPool.h4
-rw-r--r--tools/aapt2/StringPool_test.cpp48
-rw-r--r--tools/aapt2/TableFlattener.cpp570
-rw-r--r--tools/aapt2/TableFlattener.h61
-rw-r--r--tools/aapt2/Util_test.cpp136
-rw-r--r--tools/aapt2/ValueVisitor.h163
-rw-r--r--tools/aapt2/ValueVisitor_test.cpp87
-rw-r--r--tools/aapt2/XliffXmlPullParser.cpp113
-rw-r--r--tools/aapt2/XliffXmlPullParser.h64
-rw-r--r--tools/aapt2/XliffXmlPullParser_test.cpp75
-rw-r--r--tools/aapt2/XmlDom.h154
-rw-r--r--tools/aapt2/XmlFlattener.cpp574
-rw-r--r--tools/aapt2/XmlFlattener.h69
-rw-r--r--tools/aapt2/XmlFlattener_test.cpp232
-rw-r--r--tools/aapt2/ZipEntry.cpp745
-rw-r--r--tools/aapt2/ZipEntry.h350
-rw-r--r--tools/aapt2/ZipFile.cpp1306
-rw-r--r--tools/aapt2/ZipFile.h278
-rw-r--r--tools/aapt2/compile/Compile.cpp578
-rw-r--r--tools/aapt2/compile/IdAssigner.cpp110
-rw-r--r--tools/aapt2/compile/IdAssigner.h34
-rw-r--r--tools/aapt2/compile/IdAssigner_test.cpp123
-rw-r--r--tools/aapt2/compile/Png.cpp (renamed from tools/aapt2/Png.cpp)96
-rw-r--r--tools/aapt2/compile/Png.h (renamed from tools/aapt2/Png.h)22
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.cpp261
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.h36
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator_test.cpp123
-rw-r--r--tools/aapt2/compile/Pseudolocalizer.cpp394
-rw-r--r--tools/aapt2/compile/Pseudolocalizer.h58
-rw-r--r--tools/aapt2/compile/Pseudolocalizer_test.cpp227
-rw-r--r--tools/aapt2/compile/XmlIdCollector.cpp70
-rw-r--r--tools/aapt2/compile/XmlIdCollector.h31
-rw-r--r--tools/aapt2/compile/XmlIdCollector_test.cpp61
-rw-r--r--tools/aapt2/data/AndroidManifest.xml7
-rw-r--r--tools/aapt2/data/Makefile83
-rw-r--r--tools/aapt2/data/lib/AndroidManifest.xml6
-rw-r--r--tools/aapt2/data/lib/Makefile81
-rw-r--r--tools/aapt2/data/lib/res/layout/main.xml4
-rw-r--r--tools/aapt2/data/lib/res/raw/hello.txt1
-rw-r--r--tools/aapt2/data/lib/res/values/styles.xml8
-rw-r--r--tools/aapt2/data/res/drawable/image.xml2
-rw-r--r--tools/aapt2/data/res/values-v4/styles.xml7
-rw-r--r--tools/aapt2/data/res/values/colors.xml6
-rw-r--r--tools/aapt2/data/res/values/test.xml13
-rw-r--r--tools/aapt2/data/resources.arscbin9059884 -> 0 bytes
-rw-r--r--tools/aapt2/data/resources_base.arscbin4784 -> 0 bytes
-rw-r--r--tools/aapt2/data/resources_hdpi.arscbin936 -> 0 bytes
-rw-r--r--tools/aapt2/dump/Dump.cpp170
-rw-r--r--tools/aapt2/filter/ConfigFilter.cpp77
-rw-r--r--tools/aapt2/filter/ConfigFilter.h61
-rw-r--r--tools/aapt2/filter/ConfigFilter_test.cpp112
-rw-r--r--tools/aapt2/flatten/Archive.cpp184
-rw-r--r--tools/aapt2/flatten/Archive.h66
-rw-r--r--tools/aapt2/flatten/ChunkWriter.h87
-rw-r--r--tools/aapt2/flatten/ResourceTypeExtensions.h36
-rw-r--r--tools/aapt2/flatten/TableFlattener.cpp504
-rw-r--r--tools/aapt2/flatten/TableFlattener.h (renamed from tools/aapt2/ManifestParser.h)29
-rw-r--r--tools/aapt2/flatten/TableFlattener_test.cpp231
-rw-r--r--tools/aapt2/flatten/XmlFlattener.cpp315
-rw-r--r--tools/aapt2/flatten/XmlFlattener.h55
-rw-r--r--tools/aapt2/flatten/XmlFlattener_test.cpp210
-rw-r--r--tools/aapt2/integration-tests/Android.mk2
-rw-r--r--tools/aapt2/integration-tests/AppOne/Android.mk28
-rw-r--r--tools/aapt2/integration-tests/AppOne/AndroidManifest.xml17
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/icon.png (renamed from tools/aapt2/data/res/drawable/icon.png)bin2341 -> 2341 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/image.xml17
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/test.9.png (renamed from tools/aapt2/data/res/drawable/test.9.png)bin124 -> 124 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml22
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/layout/main.xml (renamed from tools/aapt2/data/res/layout/main.xml)17
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/raw/test.txt1
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml22
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/values/colors.xml21
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/values/styles.xml (renamed from tools/aapt2/data/res/values/styles.xml)21
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/values/test.xml32
-rw-r--r--tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java26
-rw-r--r--tools/aapt2/integration-tests/StaticLibOne/Android.mk28
-rw-r--r--tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml17
-rw-r--r--tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml18
-rw-r--r--tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml27
-rw-r--r--tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java22
-rw-r--r--tools/aapt2/integration-tests/StaticLibTwo/Android.mk27
-rw-r--r--tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml17
-rw-r--r--tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml3
-rw-r--r--tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml18
-rw-r--r--tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml19
-rw-r--r--tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java25
-rw-r--r--tools/aapt2/io/Data.h100
-rw-r--r--tools/aapt2/io/File.h78
-rw-r--r--tools/aapt2/io/FileSystem.cpp78
-rw-r--r--tools/aapt2/io/FileSystem.h74
-rw-r--r--tools/aapt2/io/ZipArchive.cpp142
-rw-r--r--tools/aapt2/io/ZipArchive.h83
-rw-r--r--tools/aapt2/java/AnnotationProcessor.cpp93
-rw-r--r--tools/aapt2/java/AnnotationProcessor.h86
-rw-r--r--tools/aapt2/java/AnnotationProcessor_test.cpp52
-rw-r--r--tools/aapt2/java/ClassDefinition.cpp75
-rw-r--r--tools/aapt2/java/ClassDefinition.h188
-rw-r--r--tools/aapt2/java/JavaClassGenerator.cpp557
-rw-r--r--tools/aapt2/java/JavaClassGenerator.h102
-rw-r--r--tools/aapt2/java/JavaClassGenerator_test.cpp325
-rw-r--r--tools/aapt2/java/ManifestClassGenerator.cpp124
-rw-r--r--tools/aapt2/java/ManifestClassGenerator.h33
-rw-r--r--tools/aapt2/java/ManifestClassGenerator_test.cpp136
-rw-r--r--tools/aapt2/java/ProguardRules.cpp (renamed from tools/aapt2/ProguardRules.cpp)63
-rw-r--r--tools/aapt2/java/ProguardRules.h (renamed from tools/aapt2/ProguardRules.h)15
-rw-r--r--tools/aapt2/link/AutoVersioner.cpp139
-rw-r--r--tools/aapt2/link/AutoVersioner_test.cpp125
-rw-r--r--tools/aapt2/link/Link.cpp1589
-rw-r--r--tools/aapt2/link/Linkers.h103
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp292
-rw-r--r--tools/aapt2/link/ManifestFixer.h57
-rw-r--r--tools/aapt2/link/ManifestFixer_test.cpp256
-rw-r--r--tools/aapt2/link/PrivateAttributeMover.cpp80
-rw-r--r--tools/aapt2/link/PrivateAttributeMover_test.cpp78
-rw-r--r--tools/aapt2/link/ProductFilter.cpp118
-rw-r--r--tools/aapt2/link/ProductFilter.h49
-rw-r--r--tools/aapt2/link/ProductFilter_test.cpp136
-rw-r--r--tools/aapt2/link/ReferenceLinker.cpp334
-rw-r--r--tools/aapt2/link/ReferenceLinker.h106
-rw-r--r--tools/aapt2/link/ReferenceLinker_test.cpp227
-rw-r--r--tools/aapt2/link/TableMerger.cpp321
-rw-r--r--tools/aapt2/link/TableMerger.h126
-rw-r--r--tools/aapt2/link/TableMerger_test.cpp220
-rw-r--r--tools/aapt2/link/XmlReferenceLinker.cpp173
-rw-r--r--tools/aapt2/link/XmlReferenceLinker_test.cpp260
-rw-r--r--tools/aapt2/process.dot108
-rw-r--r--tools/aapt2/process/IResourceTableConsumer.h64
-rw-r--r--tools/aapt2/process/SymbolTable.cpp310
-rw-r--r--tools/aapt2/process/SymbolTable.h172
-rw-r--r--tools/aapt2/process/SymbolTable_test.cpp53
-rw-r--r--tools/aapt2/proto/ProtoHelpers.cpp137
-rw-r--r--tools/aapt2/proto/ProtoHelpers.h52
-rw-r--r--tools/aapt2/proto/ProtoSerialize.h77
-rw-r--r--tools/aapt2/proto/TableProtoDeserializer.cpp520
-rw-r--r--tools/aapt2/proto/TableProtoSerializer.cpp322
-rw-r--r--tools/aapt2/proto/TableProtoSerializer_test.cpp174
-rw-r--r--tools/aapt2/split/TableSplitter.cpp265
-rw-r--r--tools/aapt2/split/TableSplitter.h78
-rw-r--r--tools/aapt2/split/TableSplitter_test.cpp107
-rw-r--r--tools/aapt2/test/Builders.h256
-rw-r--r--tools/aapt2/test/Common.h121
-rw-r--r--tools/aapt2/test/Context.h175
-rw-r--r--tools/aapt2/test/Test.h (renamed from tools/aapt2/Compat_test.cpp)25
-rw-r--r--tools/aapt2/todo.txt29
-rw-r--r--tools/aapt2/unflatten/BinaryResourceParser.cpp573
-rw-r--r--tools/aapt2/unflatten/BinaryResourceParser.h (renamed from tools/aapt2/BinaryResourceParser.h)83
-rw-r--r--tools/aapt2/unflatten/ResChunkPullParser.cpp (renamed from tools/aapt2/ResChunkPullParser.cpp)17
-rw-r--r--tools/aapt2/unflatten/ResChunkPullParser.h (renamed from tools/aapt2/ResChunkPullParser.h)12
-rw-r--r--tools/aapt2/util/BigBuffer.cpp (renamed from tools/aapt2/BigBuffer.cpp)2
-rw-r--r--tools/aapt2/util/BigBuffer.h (renamed from tools/aapt2/BigBuffer.h)2
-rw-r--r--tools/aapt2/util/BigBuffer_test.cpp (renamed from tools/aapt2/BigBuffer_test.cpp)2
-rw-r--r--tools/aapt2/util/Files.cpp (renamed from tools/aapt2/Files.cpp)134
-rw-r--r--tools/aapt2/util/Files.h (renamed from tools/aapt2/Files.h)66
-rw-r--r--tools/aapt2/util/Files_test.cpp58
-rw-r--r--tools/aapt2/util/ImmutableMap.h84
-rw-r--r--tools/aapt2/util/Maybe.h (renamed from tools/aapt2/Maybe.h)33
-rw-r--r--tools/aapt2/util/Maybe_test.cpp (renamed from tools/aapt2/Maybe_test.cpp)28
-rw-r--r--tools/aapt2/util/StringPiece.h (renamed from tools/aapt2/StringPiece.h)28
-rw-r--r--tools/aapt2/util/StringPiece_test.cpp (renamed from tools/aapt2/StringPiece_test.cpp)34
-rw-r--r--tools/aapt2/util/TypeTraits.h51
-rw-r--r--tools/aapt2/util/Util.cpp (renamed from tools/aapt2/Util.cpp)284
-rw-r--r--tools/aapt2/util/Util.h (renamed from tools/aapt2/Util.h)95
-rw-r--r--tools/aapt2/util/Util_test.cpp204
-rw-r--r--tools/aapt2/xml/XmlActionExecutor.cpp112
-rw-r--r--tools/aapt2/xml/XmlActionExecutor.h108
-rw-r--r--tools/aapt2/xml/XmlActionExecutor_test.cpp62
-rw-r--r--tools/aapt2/xml/XmlDom.cpp (renamed from tools/aapt2/XmlDom.cpp)165
-rw-r--r--tools/aapt2/xml/XmlDom.h220
-rw-r--r--tools/aapt2/xml/XmlDom_test.cpp (renamed from tools/aapt2/XmlDom_test.cpp)13
-rw-r--r--tools/aapt2/xml/XmlPullParser.cpp (renamed from tools/aapt2/SourceXmlPullParser.cpp)115
-rw-r--r--tools/aapt2/xml/XmlPullParser.h (renamed from tools/aapt2/XmlPullParser.h)140
-rw-r--r--tools/aapt2/xml/XmlPullParser_test.cpp55
-rw-r--r--tools/aapt2/xml/XmlUtil.cpp67
-rw-r--r--tools/aapt2/xml/XmlUtil.h85
-rw-r--r--tools/aapt2/xml/XmlUtil_test.cpp54
-rw-r--r--tools/aidl4
-rw-r--r--tools/aidl/AST.cpp912
-rw-r--r--tools/aidl/AST.h371
-rw-r--r--tools/aidl/Android.mk29
-rw-r--r--tools/aidl/NOTICE190
-rw-r--r--tools/aidl/Type.cpp1442
-rw-r--r--tools/aidl/Type.h542
-rw-r--r--tools/aidl/aidl.cpp1158
-rw-r--r--tools/aidl/aidl_language.cpp20
-rw-r--r--tools/aidl/aidl_language.h172
-rw-r--r--tools/aidl/aidl_language_l.l214
-rw-r--r--tools/aidl/aidl_language_y.y373
-rw-r--r--tools/aidl/generate_java.cpp99
-rw-r--r--tools/aidl/generate_java.h33
-rw-r--r--tools/aidl/generate_java_binder.cpp560
-rw-r--r--tools/aidl/generate_java_rpc.cpp1001
-rw-r--r--tools/aidl/options.cpp154
-rw-r--r--tools/aidl/options.h36
-rw-r--r--tools/aidl/options_test.cpp291
-rw-r--r--tools/aidl/search_path.cpp57
-rw-r--r--tools/aidl/search_path.h23
-rw-r--r--tools/apilint/apilint.py47
-rwxr-xr-xtools/fonts/fontchain_lint.py559
-rw-r--r--tools/layoutlib/.idea/codeStyleSettings.xml2
-rw-r--r--tools/layoutlib/.idea/compiler.xml4
-rw-r--r--tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml10
-rw-r--r--tools/layoutlib/.idea/libraries/junit.xml11
-rw-r--r--tools/layoutlib/.idea/misc.xml2
-rw-r--r--tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml2
-rw-r--r--tools/layoutlib/.idea/runConfigurations/Create.xml4
-rw-r--r--tools/layoutlib/Android.mk19
-rw-r--r--tools/layoutlib/bridge/Android.mk2
-rw-r--r--tools/layoutlib/bridge/bridge.iml2
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java73
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java46
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java90
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java70
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java48
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java181
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java60
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java69
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java329
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java55
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java70
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java13
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java284
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java (renamed from tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java)16
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java1213
-rw-r--r--tools/layoutlib/bridge/src/android/os/ServiceManager.java4
-rw-r--r--tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java5
-rw-r--r--tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java843
-rw-r--r--tools/layoutlib/bridge/src/android/view/BridgeInflater.java15
-rw-r--r--tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java17
-rw-r--r--tools/layoutlib/bridge/src/android/view/HandlerActionQueue_Delegate.java (renamed from tools/layoutlib/bridge/src/android/view/ViewRootImpl_RunQueue_Delegate.java)10
-rw-r--r--tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java98
-rw-r--r--tools/layoutlib/bridge/src/android/view/RectShadowPainter.java8
-rw-r--r--tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java2
-rw-r--r--tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java2
-rw-r--r--tools/layoutlib/bridge/src/android/view/WindowCallback.java3
-rw-r--r--tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java44
-rw-r--r--tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java53
-rw-r--r--tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java128
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java14
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java21
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java310
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java21
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java156
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java12
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java20
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java34
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java6
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java303
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java5
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java12
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java52
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java5
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java10
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java288
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java149
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java6
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java4
-rw-r--r--tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java64
-rw-r--r--tools/layoutlib/bridge/tests/Android.mk1
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.pngbin10663 -> 10819 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.pngbin7214 -> 7305 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/scrolled.pngbin0 -> 714 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.pngbin4939 -> 5966 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml24
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml29
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml8
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml57
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java8
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java262
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java63
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java10
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java4
-rw-r--r--tools/layoutlib/create/Android.mk2
-rw-r--r--tools/layoutlib/create/create.iml10
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java23
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java33
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java26
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java2
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java38
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java2
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java11
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java26
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java4
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java3
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java6
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java13
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java2
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java3
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java55
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java13
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java25
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java2
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java3
-rw-r--r--tools/layoutlib/create/tests/Android.mk2
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java55
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java114
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java2
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java24
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java132
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java30
-rwxr-xr-xtools/localedata/extract_icu_data.py286
-rw-r--r--tools/preload2/Android.mk32
-rw-r--r--tools/preload2/preload-tool37
-rw-r--r--tools/preload2/src/com/android/preload/ClientUtils.java224
-rw-r--r--tools/preload2/src/com/android/preload/DeviceUtils.java390
-rw-r--r--tools/preload2/src/com/android/preload/DumpData.java91
-rw-r--r--tools/preload2/src/com/android/preload/DumpDataIO.java141
-rw-r--r--tools/preload2/src/com/android/preload/DumpTableModel.java93
-rw-r--r--tools/preload2/src/com/android/preload/Main.java242
-rw-r--r--tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java34
-rw-r--r--tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java45
-rw-r--r--tools/preload2/src/com/android/preload/actions/ClearTableAction.java37
-rw-r--r--tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java151
-rw-r--r--tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java41
-rw-r--r--tools/preload2/src/com/android/preload/actions/DeviceSpecific.java38
-rw-r--r--tools/preload2/src/com/android/preload/actions/ExportAction.java65
-rw-r--r--tools/preload2/src/com/android/preload/actions/ImportAction.java68
-rw-r--r--tools/preload2/src/com/android/preload/actions/ReloadListAction.java68
-rw-r--r--tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java119
-rw-r--r--tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java63
-rw-r--r--tools/preload2/src/com/android/preload/actions/ScanPackageAction.java97
-rw-r--r--tools/preload2/src/com/android/preload/actions/ShowDataAction.java94
-rw-r--r--tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java29
-rw-r--r--tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java70
-rw-r--r--tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java220
-rw-r--r--tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java221
-rw-r--r--tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java40
-rw-r--r--tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java39
-rw-r--r--tools/preload2/src/com/android/preload/ui/UI.java267
-rw-r--r--tools/split-select/Android.mk41
411 files changed, 32372 insertions, 23419 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index d346731e63e2..c1cfd0b5f1e7 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -235,7 +235,7 @@ bool AaptLocaleValue::initFromFilterString(const String8& str) {
setRegion(part2.string());
} else if (part2.length() == 4 && isAlpha(part2)) {
setScript(part2.string());
- } else if (part2.length() >= 5 && part2.length() <= 8) {
+ } else if (part2.length() >= 4 && part2.length() <= 8) {
setVariant(part2.string());
} else {
valid = false;
@@ -250,7 +250,7 @@ bool AaptLocaleValue::initFromFilterString(const String8& str) {
if (((part3.length() == 2 && isAlpha(part3)) ||
(part3.length() == 3 && isNumber(part3))) && script[0]) {
setRegion(part3.string());
- } else if (part3.length() >= 5 && part3.length() <= 8) {
+ } else if (part3.length() >= 4 && part3.length() <= 8) {
setVariant(part3.string());
} else {
valid = false;
@@ -261,7 +261,7 @@ bool AaptLocaleValue::initFromFilterString(const String8& str) {
}
const String8& part4 = parts[3];
- if (part4.length() >= 5 && part4.length() <= 8) {
+ if (part4.length() >= 4 && part4.length() <= 8) {
setVariant(part4.string());
} else {
valid = false;
@@ -280,7 +280,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta
String8 part = parts[currentIndex];
if (part[0] == 'b' && part[1] == '+') {
- // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
+ // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
// except that the separator is "+" and not "-".
Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+');
subtags.removeItemsAt(0);
@@ -296,8 +296,11 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta
setRegion(subtags[1]);
break;
case 4:
- setScript(subtags[1]);
- break;
+ if (isAlpha(subtags[1])) {
+ setScript(subtags[1]);
+ break;
+ }
+ // This is not alphabetical, so we fall through to variant
case 5:
case 6:
case 7:
@@ -305,7 +308,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta
setVariant(subtags[1]);
break;
default:
- fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n",
+ fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n",
part.string());
return -1;
}
@@ -322,13 +325,13 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta
setRegion(subtags[1]);
hasRegion = true;
} else {
- fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n", part.string());
+ fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n", part.string());
return -1;
}
// The third tag can either be a region code (if the second tag was
// a script), else a variant code.
- if (subtags[2].size() > 4) {
+ if (subtags[2].size() >= 4) {
setVariant(subtags[2]);
} else {
setRegion(subtags[2]);
@@ -339,7 +342,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta
setRegion(subtags[2]);
setVariant(subtags[3]);
} else {
- fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name: %s\n", part.string());
+ fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name: %s\n", part.string());
return -1;
}
@@ -370,7 +373,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta
void AaptLocaleValue::initFromResTable(const ResTable_config& config) {
config.unpackLanguage(language);
config.unpackRegion(region);
- if (config.localeScript[0]) {
+ if (config.localeScript[0] && !config.localeScriptWasComputed) {
memcpy(script, config.localeScript, sizeof(config.localeScript));
}
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index b991d55dc343..2a490d1097ef 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -54,36 +54,26 @@ aaptTests := \
tests/ResourceFilter_test.cpp \
tests/ResourceTable_test.cpp
-aaptCIncludes := \
- system/core/base/include \
- external/libpng \
- external/zlib
-
-aaptHostLdLibs :=
aaptHostStaticLibs := \
libandroidfw \
libpng \
- liblog \
libutils \
+ liblog \
libcutils \
libexpat \
libziparchive-host \
libbase
-aaptCFlags := -DAAPT_VERSION=\"$(BUILD_NUMBER)\"
+aaptCFlags := -DAAPT_VERSION=\"$(BUILD_NUMBER_FROM_FILE)\"
aaptCFlags += -Wall -Werror
-ifeq ($(HOST_OS),linux)
- aaptHostLdLibs += -lrt -ldl -lpthread
-endif
+aaptHostLdLibs_linux := -lrt -ldl -lpthread
# Statically link libz for MinGW (Win SDK under Linux),
# and dynamically link for all others.
-ifneq ($(strip $(USE_MINGW)),)
- aaptHostStaticLibs += libz
-else
- aaptHostLdLibs += -lz
-endif
+aaptHostStaticLibs_windows := libz
+aaptHostLdLibs_linux += -lz
+aaptHostLdLibs_darwin := -lz
# ==========================================================
@@ -92,13 +82,13 @@ endif
include $(CLEAR_VARS)
LOCAL_MODULE := libaapt
-LOCAL_CFLAGS += -Wno-format-y2k -DSTATIC_ANDROIDFW_FOR_TOOLS $(aaptCFlags)
-LOCAL_CPPFLAGS += $(aaptCppFlags)
-ifeq (darwin,$(HOST_OS))
-LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS
-endif
-LOCAL_C_INCLUDES += $(aaptCIncludes)
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CFLAGS := -Wno-format-y2k -DSTATIC_ANDROIDFW_FOR_TOOLS $(aaptCFlags)
+LOCAL_CPPFLAGS := $(aaptCppFlags)
+LOCAL_CFLAGS_darwin := -D_DARWIN_UNLIMITED_STREAMS
LOCAL_SRC_FILES := $(aaptSources)
+LOCAL_STATIC_LIBRARIES := $(aaptHostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(aaptHostStaticLibs_windows)
include $(BUILD_HOST_STATIC_LIBRARY)
@@ -108,11 +98,14 @@ include $(BUILD_HOST_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := aapt
-LOCAL_CFLAGS += $(aaptCFlags)
-LOCAL_CPPFLAGS += $(aaptCppFlags)
-LOCAL_LDLIBS += $(aaptHostLdLibs)
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CFLAGS := $(aaptCFlags)
+LOCAL_CPPFLAGS := $(aaptCppFlags)
+LOCAL_LDLIBS_darwin := $(aaptHostLdLibs_darwin)
+LOCAL_LDLIBS_linux := $(aaptHostLdLibs_linux)
LOCAL_SRC_FILES := $(aaptMain)
-LOCAL_STATIC_LIBRARIES += libaapt $(aaptHostStaticLibs)
+LOCAL_STATIC_LIBRARIES := libaapt $(aaptHostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(aaptHostStaticLibs_windows)
include $(BUILD_HOST_EXECUTABLE)
@@ -121,15 +114,16 @@ include $(BUILD_HOST_EXECUTABLE)
# Build the host tests: libaapt_tests
# ==========================================================
include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_MODULE := libaapt_tests
-LOCAL_CFLAGS += $(aaptCFlags)
-LOCAL_CPPFLAGS += $(aaptCppFlags)
-LOCAL_LDLIBS += $(aaptHostLdLibs)
-LOCAL_SRC_FILES += $(aaptTests)
-LOCAL_C_INCLUDES += $(LOCAL_PATH)
-LOCAL_STATIC_LIBRARIES += libaapt $(aaptHostStaticLibs)
+LOCAL_CFLAGS := $(aaptCFlags)
+LOCAL_CPPFLAGS := $(aaptCppFlags)
+LOCAL_LDLIBS_darwin := $(aaptHostLdLibs_darwin)
+LOCAL_LDLIBS_linux := $(aaptHostLdLibs_linux)
+LOCAL_SRC_FILES := $(aaptTests)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)
+LOCAL_STATIC_LIBRARIES := libaapt $(aaptHostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(aaptHostStaticLibs_windows)
include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index cbe7c5dacc1e..653c1b4d6f97 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -55,7 +55,7 @@ public:
mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL),
mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL),
mAutoAddOverlay(false), mGenDependencies(false), mNoVersionVectors(false),
- mCrunchedOutputDir(NULL), mProguardFile(NULL),
+ mCrunchedOutputDir(NULL), mProguardFile(NULL), mMainDexProguardFile(NULL),
mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL),
mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL),
@@ -66,6 +66,7 @@ public:
mErrorOnMissingConfigEntry(false), mOutputTextSymbols(NULL),
mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL),
mBuildSharedLibrary(false),
+ mBuildAppAsSharedLibrary(false),
mArgc(0), mArgv(NULL)
{}
~Bundle(void) {}
@@ -126,6 +127,12 @@ public:
const android::String8& getPlatformBuildVersionName() { return mPlatformVersionName; }
void setPlatformBuildVersionName(const android::String8& name) { mPlatformVersionName = name; }
+ const android::String8& getPrivateSymbolsPackage() const { return mPrivateSymbolsPackage; }
+
+ void setPrivateSymbolsPackage(const android::String8& package) {
+ mPrivateSymbolsPackage = package;
+ }
+
bool getUTF16StringsOption() {
return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO);
}
@@ -139,6 +146,8 @@ public:
void setCrunchedOutputDir(const char* dir) { mCrunchedOutputDir = dir; }
const char* getProguardFile() const { return mProguardFile; }
void setProguardFile(const char* file) { mProguardFile = file; }
+ const char* getMainDexProguardFile() const { return mMainDexProguardFile; }
+ void setMainDexProguardFile(const char* file) { mMainDexProguardFile = file; }
const android::Vector<const char*>& getResourceSourceDirs() const { return mResourceSourceDirs; }
void addResourceSourceDir(const char* dir) { mResourceSourceDirs.insertAt(dir,0); }
const char* getAndroidManifestFile() const { return mAndroidManifestFile; }
@@ -206,6 +215,8 @@ public:
void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; }
bool getBuildSharedLibrary() const { return mBuildSharedLibrary; }
void setBuildSharedLibrary(bool val) { mBuildSharedLibrary = val; }
+ bool getBuildAppAsSharedLibrary() const { return mBuildAppAsSharedLibrary; }
+ void setBuildAppAsSharedLibrary(bool val) { mBuildAppAsSharedLibrary = val; }
void setNoVersionVectors(bool val) { mNoVersionVectors = val; }
bool getNoVersionVectors() const { return mNoVersionVectors; }
@@ -240,7 +251,7 @@ public:
* above. SDK levels that have a non-numeric identifier are assumed
* to be newer than any SDK level that has a number designated.
*/
- bool isMinSdkAtLeast(int desired) {
+ bool isMinSdkAtLeast(int desired) const {
/* If the application specifies a minSdkVersion in the manifest
* then use that. Otherwise, check what the user specified on
* the command line. If neither, it's not available since
@@ -290,6 +301,7 @@ private:
bool mNoVersionVectors;
const char* mCrunchedOutputDir;
const char* mProguardFile;
+ const char* mMainDexProguardFile;
const char* mAndroidManifestFile;
const char* mPublicOutputFile;
const char* mRClassDir;
@@ -327,8 +339,10 @@ private:
const char* mSingleCrunchInputFile;
const char* mSingleCrunchOutputFile;
bool mBuildSharedLibrary;
+ bool mBuildAppAsSharedLibrary;
android::String8 mPlatformVersionCode;
android::String8 mPlatformVersionName;
+ android::String8 mPrivateSymbolsPackage;
/* file specification */
int mArgc;
diff --git a/tools/aapt/CacheUpdater.h b/tools/aapt/CacheUpdater.h
index fade53ac2629..10a1bbc2f4aa 100644
--- a/tools/aapt/CacheUpdater.h
+++ b/tools/aapt/CacheUpdater.h
@@ -12,7 +12,7 @@
#include <sys/stat.h>
#include <stdio.h>
#include "Images.h"
-#ifdef HAVE_MS_C_RUNTIME
+#ifdef _WIN32
#include <direct.h>
#endif
@@ -81,7 +81,7 @@ public:
// Advance to the next segment of the path
existsPath.appendPath(toCreate.walkPath(&remains));
toCreate = remains;
-#ifdef HAVE_MS_C_RUNTIME
+#ifdef _WIN32
_mkdir(existsPath.string());
#else
mkdir(existsPath.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 8a0a39cfb445..9976d00fa872 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -215,7 +215,7 @@ int doList(Bundle* bundle)
goto bail;
}
-#ifdef HAVE_ANDROID_OS
+#ifdef __ANDROID__
static const bool kHaveAndroidOs = true;
#else
static const bool kHaveAndroidOs = false;
@@ -383,6 +383,16 @@ static void printUsesPermission(const String8& name, bool optional=false, int ma
}
}
+static void printUsesPermissionSdk23(const String8& name, int maxSdkVersion=-1) {
+ printf("uses-permission-sdk-23: ");
+
+ printf("name='%s'", ResTable::normalizeForOutput(name.string()).string());
+ if (maxSdkVersion != -1) {
+ printf(" maxSdkVersion='%d'", maxSdkVersion);
+ }
+ printf("\n");
+}
+
static void printUsesImpliedPermission(const String8& name, const String8& reason) {
printf("uses-implied-permission: name='%s' reason='%s'\n",
ResTable::normalizeForOutput(name.string()).string(),
@@ -463,17 +473,40 @@ static void printComponentPresence(const char* componentName) {
* a pre-requisite or some other reason.
*/
struct ImpliedFeature {
+ ImpliedFeature() : impliedBySdk23(false) {}
+ ImpliedFeature(const String8& n, bool sdk23) : name(n), impliedBySdk23(sdk23) {}
+
/**
* Name of the implied feature.
*/
String8 name;
/**
+ * Was this implied by a permission from SDK 23 (<uses-permission-sdk-23 />)?
+ */
+ bool impliedBySdk23;
+
+ /**
* List of human-readable reasons for why this feature was implied.
*/
SortedVector<String8> reasons;
};
+struct Feature {
+ Feature() : required(false), version(-1) {}
+ Feature(bool required, int32_t version = -1) : required(required), version(version) {}
+
+ /**
+ * Whether the feature is required.
+ */
+ bool required;
+
+ /**
+ * What version of the feature is requested.
+ */
+ int32_t version;
+};
+
/**
* Represents a <feature-group> tag in the AndroidManifest.xml
*/
@@ -488,7 +521,7 @@ struct FeatureGroup {
/**
* Explicit features defined in the group
*/
- KeyedVector<String8, bool> features;
+ KeyedVector<String8, Feature> features;
/**
* OpenGL ES version required
@@ -497,18 +530,24 @@ struct FeatureGroup {
};
static void addImpliedFeature(KeyedVector<String8, ImpliedFeature>* impliedFeatures,
- const char* name, const char* reason) {
+ const char* name, const char* reason, bool sdk23) {
String8 name8(name);
ssize_t idx = impliedFeatures->indexOfKey(name8);
if (idx < 0) {
- idx = impliedFeatures->add(name8, ImpliedFeature());
- impliedFeatures->editValueAt(idx).name = name8;
+ idx = impliedFeatures->add(name8, ImpliedFeature(name8, sdk23));
}
- impliedFeatures->editValueAt(idx).reasons.add(String8(reason));
+
+ ImpliedFeature* feature = &impliedFeatures->editValueAt(idx);
+
+ // A non-sdk 23 implied feature takes precedence.
+ if (feature->impliedBySdk23 && !sdk23) {
+ feature->impliedBySdk23 = false;
+ }
+ feature->reasons.add(String8(reason));
}
-static void printFeatureGroup(const FeatureGroup& grp,
- const KeyedVector<String8, ImpliedFeature>* impliedFeatures = NULL) {
+static void printFeatureGroupImpl(const FeatureGroup& grp,
+ const KeyedVector<String8, ImpliedFeature>* impliedFeatures) {
printf("feature-group: label='%s'\n", grp.label.string());
if (grp.openGLESVersion > 0) {
@@ -517,11 +556,18 @@ static void printFeatureGroup(const FeatureGroup& grp,
const size_t numFeatures = grp.features.size();
for (size_t i = 0; i < numFeatures; i++) {
- const bool required = grp.features[i];
+ const Feature& feature = grp.features[i];
+ const bool required = feature.required;
+ const int32_t version = feature.version;
const String8& featureName = grp.features.keyAt(i);
- printf(" uses-feature%s: name='%s'\n", (required ? "" : "-not-required"),
+ printf(" uses-feature%s: name='%s'", (required ? "" : "-not-required"),
ResTable::normalizeForOutput(featureName.string()).string());
+
+ if (version > 0) {
+ printf(" version='%d'", version);
+ }
+ printf("\n");
}
const size_t numImpliedFeatures =
@@ -536,9 +582,11 @@ static void printFeatureGroup(const FeatureGroup& grp,
String8 printableFeatureName(ResTable::normalizeForOutput(
impliedFeature.name.string()));
- printf(" uses-feature: name='%s'\n", printableFeatureName.string());
- printf(" uses-implied-feature: name='%s' reason='",
- printableFeatureName.string());
+ const char* sdk23Suffix = impliedFeature.impliedBySdk23 ? "-sdk-23" : "";
+
+ printf(" uses-feature%s: name='%s'\n", sdk23Suffix, printableFeatureName.string());
+ printf(" uses-implied-feature%s: name='%s' reason='", sdk23Suffix,
+ printableFeatureName.string());
const size_t numReasons = impliedFeature.reasons.size();
for (size_t j = 0; j < numReasons; j++) {
printf("%s", impliedFeature.reasons[j].string());
@@ -552,18 +600,27 @@ static void printFeatureGroup(const FeatureGroup& grp,
}
}
+static void printFeatureGroup(const FeatureGroup& grp) {
+ printFeatureGroupImpl(grp, NULL);
+}
+
+static void printDefaultFeatureGroup(const FeatureGroup& grp,
+ const KeyedVector<String8, ImpliedFeature>& impliedFeatures) {
+ printFeatureGroupImpl(grp, &impliedFeatures);
+}
+
static void addParentFeatures(FeatureGroup* grp, const String8& name) {
if (name == "android.hardware.camera.autofocus" ||
name == "android.hardware.camera.flash") {
- grp->features.add(String8("android.hardware.camera"), true);
+ grp->features.add(String8("android.hardware.camera"), Feature(true));
} else if (name == "android.hardware.location.gps" ||
name == "android.hardware.location.network") {
- grp->features.add(String8("android.hardware.location"), true);
+ grp->features.add(String8("android.hardware.location"), Feature(true));
} else if (name == "android.hardware.touchscreen.multitouch") {
- grp->features.add(String8("android.hardware.touchscreen"), true);
+ grp->features.add(String8("android.hardware.touchscreen"), Feature(true));
} else if (name == "android.hardware.touchscreen.multitouch.distinct") {
- grp->features.add(String8("android.hardware.touchscreen.multitouch"), true);
- grp->features.add(String8("android.hardware.touchscreen"), true);
+ grp->features.add(String8("android.hardware.touchscreen.multitouch"), Feature(true));
+ grp->features.add(String8("android.hardware.touchscreen"), Feature(true));
} else if (name == "android.hardware.opengles.aep") {
const int openGLESVersion31 = 0x00030001;
if (openGLESVersion31 > grp->openGLESVersion) {
@@ -572,6 +629,72 @@ static void addParentFeatures(FeatureGroup* grp, const String8& name) {
}
}
+static void addImpliedFeaturesForPermission(const int targetSdk, const String8& name,
+ KeyedVector<String8, ImpliedFeature>* impliedFeatures,
+ bool impliedBySdk23Permission) {
+ if (name == "android.permission.CAMERA") {
+ addImpliedFeature(impliedFeatures, "android.hardware.camera",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
+ addImpliedFeature(impliedFeatures, "android.hardware.location.gps",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ addImpliedFeature(impliedFeatures, "android.hardware.location",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
+ addImpliedFeature(impliedFeatures, "android.hardware.location",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
+ addImpliedFeature(impliedFeatures, "android.hardware.location.network",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ addImpliedFeature(impliedFeatures, "android.hardware.location",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
+ name == "android.permission.INSTALL_LOCATION_PROVIDER") {
+ addImpliedFeature(impliedFeatures, "android.hardware.location",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.BLUETOOTH" ||
+ name == "android.permission.BLUETOOTH_ADMIN") {
+ if (targetSdk > 4) {
+ addImpliedFeature(impliedFeatures, "android.hardware.bluetooth",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ addImpliedFeature(impliedFeatures, "android.hardware.bluetooth",
+ "targetSdkVersion > 4", impliedBySdk23Permission);
+ }
+ } else if (name == "android.permission.RECORD_AUDIO") {
+ addImpliedFeature(impliedFeatures, "android.hardware.microphone",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
+ name == "android.permission.CHANGE_WIFI_STATE" ||
+ name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
+ addImpliedFeature(impliedFeatures, "android.hardware.wifi",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.CALL_PHONE" ||
+ name == "android.permission.CALL_PRIVILEGED" ||
+ name == "android.permission.MODIFY_PHONE_STATE" ||
+ name == "android.permission.PROCESS_OUTGOING_CALLS" ||
+ name == "android.permission.READ_SMS" ||
+ name == "android.permission.RECEIVE_SMS" ||
+ name == "android.permission.RECEIVE_MMS" ||
+ name == "android.permission.RECEIVE_WAP_PUSH" ||
+ name == "android.permission.SEND_SMS" ||
+ name == "android.permission.WRITE_APN_SETTINGS" ||
+ name == "android.permission.WRITE_SMS") {
+ addImpliedFeature(impliedFeatures, "android.hardware.telephony",
+ String8("requested a telephony permission").string(),
+ impliedBySdk23Permission);
+ }
+}
+
/*
* Handle the "dump" command, to extract select data from an archive.
*/
@@ -626,6 +749,9 @@ int doDump(Bundle* bundle)
return 1;
}
+ // Source for AndroidManifest.xml
+ const String8 manifestFile = String8::format("%s@AndroidManifest.xml", filename);
+
// The dynamicRefTable can be null if there are no resources for this asset cookie.
// This fine.
const DynamicRefTable* dynamicRefTable = res.getDynamicRefTableForCookie(assetsCookie);
@@ -633,7 +759,7 @@ int doDump(Bundle* bundle)
Asset* asset = NULL;
if (strcmp("resources", option) == 0) {
-#ifndef HAVE_ANDROID_OS
+#ifndef __ANDROID__
res.print(bundle->getValues());
#endif
@@ -712,7 +838,8 @@ int doDump(Bundle* bundle)
size_t len;
ResXMLTree::event_code_t code;
int depth = 0;
- while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT &&
+ code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::END_TAG) {
depth--;
continue;
@@ -735,25 +862,53 @@ int doDump(Bundle* bundle)
}
String8 pkg = AaptXml::getAttribute(tree, NULL, "package", NULL);
printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
- } else if (depth == 2 && tag == "permission") {
- String8 error;
- String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
- if (error != "") {
- fprintf(stderr, "ERROR: %s\n", error.string());
- goto bail;
- }
- printf("permission: %s\n",
- ResTable::normalizeForOutput(name.string()).string());
- } else if (depth == 2 && tag == "uses-permission") {
- String8 error;
- String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
- if (error != "") {
- fprintf(stderr, "ERROR: %s\n", error.string());
- goto bail;
+ } else if (depth == 2) {
+ if (tag == "permission") {
+ String8 error;
+ String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for permission\n");
+ goto bail;
+ }
+ printf("permission: %s\n",
+ ResTable::normalizeForOutput(name.string()).string());
+ } else if (tag == "uses-permission") {
+ String8 error;
+ String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n");
+ goto bail;
+ }
+ printUsesPermission(name,
+ AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
+ AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
+ } else if (tag == "uses-permission-sdk-23" || tag == "uses-permission-sdk-m") {
+ String8 error;
+ String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for "
+ "uses-permission-sdk-23\n");
+ goto bail;
+ }
+ printUsesPermissionSdk23(
+ name,
+ AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
}
- printUsesPermission(name,
- AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
- AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
}
}
} else if (strcmp("badging", option) == 0) {
@@ -893,7 +1048,8 @@ int doDump(Bundle* bundle)
Vector<FeatureGroup> featureGroups;
KeyedVector<String8, ImpliedFeature> impliedFeatures;
- while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT &&
+ code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::END_TAG) {
depth--;
if (depth < 2) {
@@ -924,8 +1080,10 @@ int doDump(Bundle* bundle)
ResTable::normalizeForOutput(aName.string()).string());
}
printf(" label='%s' icon='%s'\n",
- ResTable::normalizeForOutput(activityLabel.string()).string(),
- ResTable::normalizeForOutput(activityIcon.string()).string());
+ ResTable::normalizeForOutput(activityLabel.string())
+ .string(),
+ ResTable::normalizeForOutput(activityIcon.string())
+ .string());
}
if (isLeanbackLauncherActivity) {
printf("leanback-launchable-activity:");
@@ -934,9 +1092,12 @@ int doDump(Bundle* bundle)
ResTable::normalizeForOutput(aName.string()).string());
}
printf(" label='%s' icon='%s' banner='%s'\n",
- ResTable::normalizeForOutput(activityLabel.string()).string(),
- ResTable::normalizeForOutput(activityIcon.string()).string(),
- ResTable::normalizeForOutput(activityBanner.string()).string());
+ ResTable::normalizeForOutput(activityLabel.string())
+ .string(),
+ ResTable::normalizeForOutput(activityIcon.string())
+ .string(),
+ ResTable::normalizeForOutput(activityBanner.string())
+ .string());
}
}
if (!hasIntentFilter) {
@@ -964,18 +1125,21 @@ int doDump(Bundle* bundle)
hasLauncher |= catLauncher;
hasCameraActivity |= actCamera;
hasCameraSecureActivity |= actCameraSecure;
- hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure;
+ hasOtherActivities |=
+ !actMainActivity && !actCamera && !actCameraSecure;
} else if (withinReceiver) {
hasWidgetReceivers |= actWidgetReceivers;
hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
hasBindDeviceAdminPermission);
- hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
+ hasOtherReceivers |=
+ (!actWidgetReceivers && !actDeviceAdminEnabled);
} else if (withinService) {
hasImeService |= actImeService;
hasWallpaperService |= actWallpaperService;
hasAccessibilityService |= (actAccessibilityService &&
hasBindAccessibilityServicePermission);
- hasPrintService |= (actPrintService && hasBindPrintServicePermission);
+ hasPrintService |=
+ (actPrintService && hasBindPrintServicePermission);
hasNotificationListenerService |= actNotificationListenerService &&
hasBindNotificationListenerServicePermission;
hasDreamService |= actDreamService && hasBindDreamServicePermission;
@@ -984,7 +1148,8 @@ int doDump(Bundle* bundle)
!actHostApduService && !actOffHostApduService &&
!actNotificationListenerService);
} else if (withinProvider) {
- hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes;
+ hasDocumentsProvider |=
+ actDocumentsProvider && hasRequiredSafAttributes;
}
}
withinIntentFilter = false;
@@ -1125,7 +1290,8 @@ int doDump(Bundle* bundle)
goto bail;
}
- String8 banner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, &error);
+ String8 banner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR,
+ &error);
if (error != "") {
fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
error.string());
@@ -1135,7 +1301,8 @@ int doDump(Bundle* bundle)
ResTable::normalizeForOutput(label.string()).string());
printf("icon='%s'", ResTable::normalizeForOutput(icon.string()).string());
if (banner != "") {
- printf(" banner='%s'", ResTable::normalizeForOutput(banner.string()).string());
+ printf(" banner='%s'",
+ ResTable::normalizeForOutput(banner.string()).string());
}
printf("\n");
if (testOnly != 0) {
@@ -1178,13 +1345,15 @@ int doDump(Bundle* bundle)
}
}
} else if (tag == "uses-sdk") {
- int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
+ int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR,
+ &error);
if (error != "") {
error = "";
String8 name = AaptXml::getResolvedAttribute(res, tree,
MIN_SDK_VERSION_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
+ fprintf(stderr,
+ "ERROR getting 'android:minSdkVersion' attribute: %s\n",
error.string());
goto bail;
}
@@ -1205,7 +1374,8 @@ int doDump(Bundle* bundle)
String8 name = AaptXml::getResolvedAttribute(res, tree,
TARGET_SDK_VERSION_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
+ fprintf(stderr,
+ "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
error.string());
goto bail;
}
@@ -1279,10 +1449,28 @@ int doDump(Bundle* bundle)
} else if (tag == "uses-feature") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (name != "" && error == "") {
- int req = AaptXml::getIntegerAttribute(tree,
- REQUIRED_ATTR, 1);
+ const char* androidSchema =
+ "http://schemas.android.com/apk/res/android";
- commonFeatures.features.add(name, req);
+ int32_t req = AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1,
+ &error);
+ if (error != "") {
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "failed to read attribute 'android:required': %s",
+ error.string());
+ goto bail;
+ }
+
+ int32_t version = AaptXml::getIntegerAttribute(tree, androidSchema,
+ "version", 0, &error);
+ if (error != "") {
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "failed to read attribute 'android:version': %s",
+ error.string());
+ goto bail;
+ }
+
+ commonFeatures.features.add(name, Feature(req != 0, version));
if (req) {
addParentFeatures(&commonFeatures, name);
}
@@ -1297,90 +1485,58 @@ int doDump(Bundle* bundle)
}
} else if (tag == "uses-permission") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
- if (name != "" && error == "") {
- if (name == "android.permission.CAMERA") {
- addImpliedFeature(&impliedFeatures, "android.hardware.camera",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
- addImpliedFeature(&impliedFeatures, "android.hardware.location.gps",
- String8::format("requested %s permission", name.string())
- .string());
- addImpliedFeature(&impliedFeatures, "android.hardware.location",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
- addImpliedFeature(&impliedFeatures, "android.hardware.location",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
- addImpliedFeature(&impliedFeatures, "android.hardware.location.network",
- String8::format("requested %s permission", name.string())
- .string());
- addImpliedFeature(&impliedFeatures, "android.hardware.location",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
- name == "android.permission.INSTALL_LOCATION_PROVIDER") {
- addImpliedFeature(&impliedFeatures, "android.hardware.location",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.BLUETOOTH" ||
- name == "android.permission.BLUETOOTH_ADMIN") {
- if (targetSdk > 4) {
- addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
- String8::format("requested %s permission", name.string())
- .string());
- addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
- "targetSdkVersion > 4");
- }
- } else if (name == "android.permission.RECORD_AUDIO") {
- addImpliedFeature(&impliedFeatures, "android.hardware.microphone",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
- name == "android.permission.CHANGE_WIFI_STATE" ||
- name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
- addImpliedFeature(&impliedFeatures, "android.hardware.wifi",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.CALL_PHONE" ||
- name == "android.permission.CALL_PRIVILEGED" ||
- name == "android.permission.MODIFY_PHONE_STATE" ||
- name == "android.permission.PROCESS_OUTGOING_CALLS" ||
- name == "android.permission.READ_SMS" ||
- name == "android.permission.RECEIVE_SMS" ||
- name == "android.permission.RECEIVE_MMS" ||
- name == "android.permission.RECEIVE_WAP_PUSH" ||
- name == "android.permission.SEND_SMS" ||
- name == "android.permission.WRITE_APN_SETTINGS" ||
- name == "android.permission.WRITE_SMS") {
- addImpliedFeature(&impliedFeatures, "android.hardware.telephony",
- String8("requested a telephony permission").string());
- } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
- hasWriteExternalStoragePermission = true;
- } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
- hasReadExternalStoragePermission = true;
- } else if (name == "android.permission.READ_PHONE_STATE") {
- hasReadPhoneStatePermission = true;
- } else if (name == "android.permission.READ_CONTACTS") {
- hasReadContactsPermission = true;
- } else if (name == "android.permission.WRITE_CONTACTS") {
- hasWriteContactsPermission = true;
- } else if (name == "android.permission.READ_CALL_LOG") {
- hasReadCallLogPermission = true;
- } else if (name == "android.permission.WRITE_CALL_LOG") {
- hasWriteCallLogPermission = true;
- }
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n");
+ goto bail;
+ }
- printUsesPermission(name,
- AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
- AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
- } else {
+ addImpliedFeaturesForPermission(targetSdk, name, &impliedFeatures, false);
+
+ if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
+ hasWriteExternalStoragePermission = true;
+ } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
+ hasReadExternalStoragePermission = true;
+ } else if (name == "android.permission.READ_PHONE_STATE") {
+ hasReadPhoneStatePermission = true;
+ } else if (name == "android.permission.READ_CONTACTS") {
+ hasReadContactsPermission = true;
+ } else if (name == "android.permission.WRITE_CONTACTS") {
+ hasWriteContactsPermission = true;
+ } else if (name == "android.permission.READ_CALL_LOG") {
+ hasReadCallLogPermission = true;
+ } else if (name == "android.permission.WRITE_CALL_LOG") {
+ hasWriteCallLogPermission = true;
+ }
+
+ printUsesPermission(name,
+ AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
+ AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
+
+ } else if (tag == "uses-permission-sdk-23" || tag == "uses-permission-sdk-m") {
+ String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
error.string());
goto bail;
}
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for "
+ "uses-permission-sdk-23\n");
+ goto bail;
+ }
+
+ addImpliedFeaturesForPermission(targetSdk, name, &impliedFeatures, true);
+
+ printUsesPermissionSdk23(
+ name, AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
+
} else if (tag == "uses-package") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (name != "" && error == "") {
@@ -1422,7 +1578,8 @@ int doDump(Bundle* bundle)
} else if (tag == "package-verifier") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (name != "" && error == "") {
- String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR, &error);
+ String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR,
+ &error);
if (publicKey != "" && error == "") {
printf("package-verifier: name='%s' publicKey='%s'\n",
ResTable::normalizeForOutput(name.string()).string(),
@@ -1485,12 +1642,18 @@ int doDump(Bundle* bundle)
if (error == "") {
if (orien == 0 || orien == 6 || orien == 8) {
// Requests landscape, sensorLandscape, or reverseLandscape.
- addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape",
- "one or more activities have specified a landscape orientation");
+ addImpliedFeature(&impliedFeatures,
+ "android.hardware.screen.landscape",
+ "one or more activities have specified a "
+ "landscape orientation",
+ false);
} else if (orien == 1 || orien == 7 || orien == 9) {
// Requests portrait, sensorPortrait, or reversePortrait.
- addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait",
- "one or more activities have specified a portrait orientation");
+ addImpliedFeature(&impliedFeatures,
+ "android.hardware.screen.portrait",
+ "one or more activities have specified a "
+ "portrait orientation",
+ false);
}
}
} else if (tag == "uses-library") {
@@ -1524,8 +1687,10 @@ int doDump(Bundle* bundle)
hasBindDeviceAdminPermission = true;
}
} else {
- fprintf(stderr, "ERROR getting 'android:permission' attribute for"
- " receiver '%s': %s\n", receiverName.string(), error.string());
+ fprintf(stderr,
+ "ERROR getting 'android:permission' attribute for"
+ " receiver '%s': %s\n",
+ receiverName.string(), error.string());
}
} else if (tag == "service") {
withinService = true;
@@ -1542,20 +1707,24 @@ int doDump(Bundle* bundle)
if (error == "") {
if (permission == "android.permission.BIND_INPUT_METHOD") {
hasBindInputMethodPermission = true;
- } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
+ } else if (permission ==
+ "android.permission.BIND_ACCESSIBILITY_SERVICE") {
hasBindAccessibilityServicePermission = true;
- } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
+ } else if (permission ==
+ "android.permission.BIND_PRINT_SERVICE") {
hasBindPrintServicePermission = true;
- } else if (permission == "android.permission.BIND_NFC_SERVICE") {
+ } else if (permission ==
+ "android.permission.BIND_NFC_SERVICE") {
hasBindNfcServicePermission = true;
- } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
+ } else if (permission ==
+ "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
hasBindNotificationListenerServicePermission = true;
} else if (permission == "android.permission.BIND_DREAM_SERVICE") {
hasBindDreamServicePermission = true;
}
} else {
- fprintf(stderr, "ERROR getting 'android:permission' attribute for"
- " service '%s': %s\n", serviceName.string(), error.string());
+ fprintf(stderr, "ERROR getting 'android:permission' attribute for "
+ "service '%s': %s\n", serviceName.string(), error.string());
}
} else if (tag == "provider") {
withinProvider = true;
@@ -1563,7 +1732,8 @@ int doDump(Bundle* bundle)
bool exported = AaptXml::getResolvedIntegerAttribute(res, tree,
EXPORTED_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
+ fprintf(stderr,
+ "ERROR getting 'android:exported' attribute for provider:"
" %s\n", error.string());
goto bail;
}
@@ -1571,16 +1741,17 @@ int doDump(Bundle* bundle)
bool grantUriPermissions = AaptXml::getResolvedIntegerAttribute(
res, tree, GRANT_URI_PERMISSIONS_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
- " %s\n", error.string());
+ fprintf(stderr,
+ "ERROR getting 'android:grantUriPermissions' attribute for "
+ "provider: %s\n", error.string());
goto bail;
}
String8 permission = AaptXml::getResolvedAttribute(res, tree,
PERMISSION_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
- " %s\n", error.string());
+ fprintf(stderr, "ERROR getting 'android:permission' attribute for "
+ "provider: %s\n", error.string());
goto bail;
}
@@ -1623,12 +1794,27 @@ int doDump(Bundle* bundle)
}
}
} else if (withinFeatureGroup && tag == "uses-feature") {
+ const String8 androidSchema("http://schemas.android.com/apk/res/android");
FeatureGroup& top = featureGroups.editTop();
String8 name = AaptXml::getResolvedAttribute(res, tree, NAME_ATTR, &error);
if (name != "" && error == "") {
- top.features.add(name, true);
+ Feature feature(true);
+
+ int32_t featureVers = AaptXml::getIntegerAttribute(
+ tree, androidSchema.string(), "version", 0, &error);
+ if (error == "") {
+ feature.version = featureVers;
+ } else {
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "failed to read attribute 'android:version': %s",
+ error.string());
+ goto bail;
+ }
+
+ top.features.add(name, feature);
addParentFeatures(&top, name);
+
} else {
int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR,
&error);
@@ -1661,8 +1847,9 @@ int doDump(Bundle* bundle)
} else if (withinService && tag == "meta-data") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:name' attribute for"
- " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+ fprintf(stderr, "ERROR getting 'android:name' attribute for "
+ "meta-data tag in service '%s': %s\n", serviceName.string(),
+ error.string());
goto bail;
}
@@ -1676,8 +1863,9 @@ int doDump(Bundle* bundle)
String8 xmlPath = AaptXml::getResolvedAttribute(res, tree,
RESOURCE_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:resource' attribute for"
- " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+ fprintf(stderr, "ERROR getting 'android:resource' attribute for "
+ "meta-data tag in service '%s': %s\n",
+ serviceName.string(), error.string());
goto bail;
}
@@ -1731,15 +1919,19 @@ int doDump(Bundle* bundle)
actImeService = true;
} else if (action == "android.service.wallpaper.WallpaperService") {
actWallpaperService = true;
- } else if (action == "android.accessibilityservice.AccessibilityService") {
+ } else if (action ==
+ "android.accessibilityservice.AccessibilityService") {
actAccessibilityService = true;
- } else if (action == "android.printservice.PrintService") {
+ } else if (action =="android.printservice.PrintService") {
actPrintService = true;
- } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
+ } else if (action ==
+ "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
actHostApduService = true;
- } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
+ } else if (action ==
+ "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
actOffHostApduService = true;
- } else if (action == "android.service.notification.NotificationListenerService") {
+ } else if (action ==
+ "android.service.notification.NotificationListenerService") {
actNotificationListenerService = true;
} else if (action == "android.service.dreams.DreamService") {
actDreamService = true;
@@ -1814,12 +2006,12 @@ int doDump(Bundle* bundle)
}
addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen",
- "default feature for all apps");
+ "default feature for all apps", false);
const size_t numFeatureGroups = featureGroups.size();
if (numFeatureGroups == 0) {
// If no <feature-group> tags were defined, apply auto-implied features.
- printFeatureGroup(commonFeatures, &impliedFeatures);
+ printDefaultFeatureGroup(commonFeatures, impliedFeatures);
} else {
// <feature-group> tags are defined, so we ignore implied features and
@@ -2395,11 +2587,11 @@ int doPackage(Bundle* bundle)
// Write the R.java file into the appropriate class directory
// e.g. gen/com/foo/app/R.java
err = writeResourceSymbols(bundle, assets, assets->getPackage(), true,
- bundle->getBuildSharedLibrary());
+ bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary());
} else {
const String8 customPkg(bundle->getCustomPackage());
err = writeResourceSymbols(bundle, assets, customPkg, true,
- bundle->getBuildSharedLibrary());
+ bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary());
}
if (err < 0) {
goto bail;
@@ -2414,7 +2606,7 @@ int doPackage(Bundle* bundle)
while (packageString != NULL) {
// Write the R.java file out with the correct package name
err = writeResourceSymbols(bundle, assets, String8(packageString), true,
- bundle->getBuildSharedLibrary());
+ bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary());
if (err < 0) {
goto bail;
}
@@ -2439,6 +2631,12 @@ int doPackage(Bundle* bundle)
goto bail;
}
+ // Write out the Main Dex ProGuard file
+ err = writeMainDexProguardFile(bundle, assets);
+ if (err < 0) {
+ goto bail;
+ }
+
// Write the apk
if (outputAPKFile) {
// Gather all resources and add them to the APK Builder. The builder will then
diff --git a/tools/aapt/CrunchCache.cpp b/tools/aapt/CrunchCache.cpp
index 6c39d1d7753c..0d574cf127dd 100644
--- a/tools/aapt/CrunchCache.cpp
+++ b/tools/aapt/CrunchCache.cpp
@@ -5,6 +5,7 @@
// This file defines functions laid out and documented in
// CrunchCache.h
+#include <utils/Compat.h>
#include <utils/Vector.h>
#include <utils/String8.h>
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index e4738f5eda7d..9939c188cdd9 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -873,14 +873,15 @@ static void dump_image(int w, int h, png_bytepp rows, int color_type)
static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance,
png_colorp rgbPalette, png_bytep alphaPalette,
- int *paletteEntries, bool *hasTransparency, int *colorType,
- png_bytepp outRows)
+ int *paletteEntries, int *alphaPaletteEntries, bool *hasTransparency,
+ int *colorType, png_bytepp outRows)
{
int w = imageInfo.width;
int h = imageInfo.height;
- int i, j, rr, gg, bb, aa, idx;
- uint32_t colors[256], col;
- int num_colors = 0;
+ int i, j, rr, gg, bb, aa, idx;;
+ uint32_t opaqueColors[256], alphaColors[256];
+ uint32_t col;
+ int numOpaqueColors = 0, numAlphaColors = 0;
int maxGrayDeviation = 0;
bool isOpaque = true;
@@ -891,6 +892,10 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray
// 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
+ // We will track opaque colors separately from colors with
+ // alpha. This allows us to reencode the color table more
+ // efficiently (color tables entries without a corresponding
+ // alpha value are assumed to be opaque).
if (kIsDebug) {
printf("Initial image data:\n");
@@ -901,10 +906,34 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray
png_bytep row = imageInfo.rows[j];
png_bytep out = outRows[j];
for (i = 0; i < w; i++) {
- rr = *row++;
- gg = *row++;
- bb = *row++;
- aa = *row++;
+
+ // Make sure any zero alpha pixels are fully zeroed. On average,
+ // each of our PNG assets seem to have about four distinct pixels
+ // with zero alpha.
+ // There are several advantages to setting these to zero:
+ // (1) Images are more likely able to be encodable with a palette.
+ // (2) Image palettes will be smaller.
+ // (3) Premultiplied and unpremultiplied PNG decodes can skip
+ // writing zeros to memory, often saving significant numbers
+ // of memory pages.
+ aa = *(row + 3);
+ if (aa == 0) {
+ rr = 0;
+ gg = 0;
+ bb = 0;
+
+ // Also set red, green, and blue to zero in "row". If we later
+ // decide to encode the PNG as RGB or RGBA, we will use the
+ // values stored there.
+ *(row) = 0;
+ *(row + 1) = 0;
+ *(row + 2) = 0;
+ } else {
+ rr = *(row);
+ gg = *(row + 1);
+ bb = *(row + 2);
+ }
+ row += 4;
int odev = maxGrayDeviation;
maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
@@ -943,36 +972,68 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray
if (isPalette) {
col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
bool match = false;
- for (idx = 0; idx < num_colors; idx++) {
- if (colors[idx] == col) {
- match = true;
- break;
+
+ if (aa == 0xff) {
+ for (idx = 0; idx < numOpaqueColors; idx++) {
+ if (opaqueColors[idx] == col) {
+ 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
- *out++ = idx;
- if (!match) {
- if (num_colors == 256) {
- if (kIsDebug) {
- printf("Found 257th color at %d, %d\n", i, j);
+ if (!match) {
+ if (numOpaqueColors < 256) {
+ opaqueColors[numOpaqueColors] = col;
+ }
+ numOpaqueColors++;
+ }
+
+ // 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. We may also need to overwrite it when we combine
+ // into a single palette.
+ *out++ = idx;
+ } else {
+ for (idx = 0; idx < numAlphaColors; idx++) {
+ if (alphaColors[idx] == col) {
+ match = true;
+ break;
}
- isPalette = false;
- } else {
- colors[num_colors++] = col;
}
+
+ if (!match) {
+ if (numAlphaColors < 256) {
+ alphaColors[numAlphaColors] = col;
+ }
+ numAlphaColors++;
+ }
+
+ // 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.
+ *out++ = idx;
+ }
+
+ if (numOpaqueColors + numAlphaColors > 256) {
+ if (kIsDebug) {
+ printf("Found 257th color at %d, %d\n", i, j);
+ }
+ isPalette = false;
}
}
}
}
+ // If we decide to encode the image using a palette, we will reset these counts
+ // to the appropriate values later. Initializing them here avoids compiler
+ // complaints about uses of possibly uninitialized variables.
*paletteEntries = 0;
+ *alphaPaletteEntries = 0;
+
*hasTransparency = !isOpaque;
- int bpp = isOpaque ? 3 : 4;
- int paletteSize = w * h + bpp * num_colors;
+ int paletteSize = w * h + 3 * numOpaqueColors + 4 * numAlphaColors;
+ int bpp = isOpaque ? 3 : 4;
if (kIsDebug) {
printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
printf("isOpaque = %s\n", isOpaque ? "true" : "false");
@@ -1017,16 +1078,37 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray
// color type chosen
if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Combine the alphaColors and the opaqueColors into a single palette.
+ // The alphaColors must be at the start of the palette.
+ uint32_t* colors = alphaColors;
+ memcpy(colors + numAlphaColors, opaqueColors, 4 * numOpaqueColors);
+
+ // Fix the indices of the opaque colors in the image.
+ for (j = 0; j < h; j++) {
+ png_bytep row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ uint32_t pixel = ((uint32_t*) row)[i];
+ if (pixel >> 24 == 0xFF) {
+ out[i] += numAlphaColors;
+ }
+ }
+ }
+
// Create separate RGB and Alpha palettes and set the number of colors
- *paletteEntries = num_colors;
+ int numColors = numOpaqueColors + numAlphaColors;
+ *paletteEntries = numColors;
+ *alphaPaletteEntries = numAlphaColors;
// Create the RGB and alpha palettes
- for (int idx = 0; idx < num_colors; idx++) {
+ for (int idx = 0; idx < numColors; idx++) {
col = colors[idx];
rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
- alphaPalette[idx] = (png_byte) (col & 0xff);
+ if (idx < numAlphaColors) {
+ alphaPalette[idx] = (png_byte) (col & 0xff);
+ }
}
} else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
// If the image is gray or gray + alpha, compact the pixels into outRows
@@ -1052,10 +1134,9 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray
}
}
-
static void write_png(const char* imageName,
png_structp write_ptr, png_infop write_info,
- image_info& imageInfo, int grayscaleTolerance)
+ image_info& imageInfo, const Bundle* bundle)
{
png_uint_32 width, height;
int color_type;
@@ -1090,16 +1171,26 @@ static void write_png(const char* imageName,
png_color rgbPalette[256];
png_byte alphaPalette[256];
bool hasTransparency;
- int paletteEntries;
+ int paletteEntries, alphaPaletteEntries;
+ int grayscaleTolerance = bundle->getGrayscaleTolerance();
analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette,
- &paletteEntries, &hasTransparency, &color_type, outRows);
+ &paletteEntries, &alphaPaletteEntries, &hasTransparency, &color_type, outRows);
- // 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 (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB ||
- color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
- color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ // Legacy versions of aapt would always encode 9patch PNGs as RGBA. This had the unintended
+ // benefit of working around a bug decoding paletted images in Android 4.1.
+ // https://code.google.com/p/android/issues/detail?id=34619
+ //
+ // If SDK_JELLY_BEAN is supported, we need to avoid a paletted encoding in order to not expose
+ // this bug.
+ if (!bundle->isMinSdkAtLeast(SDK_JELLY_BEAN_MR1)) {
+ if (imageInfo.is9Patch && PNG_COLOR_TYPE_PALETTE == color_type) {
+ if (hasTransparency) {
+ color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ } else {
+ color_type = PNG_COLOR_TYPE_RGB;
+ }
+ }
}
if (kIsDebug) {
@@ -1131,7 +1222,8 @@ static void write_png(const char* imageName,
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries);
if (hasTransparency) {
- png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0);
+ png_set_tRNS(write_ptr, write_info, alphaPalette, alphaPaletteEntries,
+ (png_color_16p) 0);
}
png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
} else {
@@ -1180,18 +1272,11 @@ static void write_png(const char* imageName,
}
for (int i = 0; i < chunk_count; i++) {
- unknowns[i].location = PNG_HAVE_PLTE;
+ unknowns[i].location = PNG_HAVE_IHDR;
}
png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
chunk_names, chunk_count);
png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count);
-#if PNG_LIBPNG_VER < 10600
- /* Deal with unknown chunk location bug in 1.5.x and earlier */
- png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
- if (imageInfo.haveLayoutBounds) {
- png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE);
- }
-#endif
}
@@ -1263,8 +1348,7 @@ static bool write_png_protected(png_structp write_ptr, String8& printableName, p
return false;
}
- write_png(printableName.string(), write_ptr, write_info, *imageInfo,
- bundle->getGrayscaleTolerance());
+ write_png(printableName.string(), write_ptr, write_info, *imageInfo, bundle);
return true;
}
@@ -1474,8 +1558,7 @@ status_t preProcessImageToCache(const Bundle* bundle, const String8& source, con
}
// Actually write out to the new png
- write_png(dest.string(), write_ptr, write_info, imageInfo,
- bundle->getGrayscaleTolerance());
+ write_png(dest.string(), write_ptr, write_info, imageInfo, bundle);
if (bundle->getVerbose()) {
// Find the size of our new file
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index f832c605376c..984d98e30f29 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -6,6 +6,7 @@
#include "Main.h"
#include "Bundle.h"
+#include <utils/Compat.h>
#include <utils/Log.h>
#include <utils/threads.h>
#include <utils/List.h>
@@ -66,6 +67,7 @@ void usage(void)
" [--max-res-version VAL] \\\n"
" [-I base-package [-I base-package ...]] \\\n"
" [-A asset-source-dir] [-G class-list-file] [-P public-definitions-file] \\\n"
+ " [-D main-dex-class-list-file] \\\n"
" [-S resource-sources [-S resource-sources ...]] \\\n"
" [-F apk-file] [-J R-file-dir] \\\n"
" [--product product1,product2,...] \\\n"
@@ -119,6 +121,7 @@ void usage(void)
" localization=\"suggested\"\n"
" -A additional directory in which to find raw asset files\n"
" -G A file to output proguard options into.\n"
+ " -D A file to output proguard options for the main dex into.\n"
" -F specify the apk file to output\n"
" -I add an existing package to base include set\n"
" -J specify where to output R.java resource constant definitions\n"
@@ -199,6 +202,9 @@ void usage(void)
" --shared-lib\n"
" Make a shared library resource package that can be loaded by an application\n"
" at runtime to access the libraries resources. Implies --non-constant-id.\n"
+ " --app-as-shared-lib\n"
+ " Make an app resource package that also can be loaded as shared library at runtime.\n"
+ " Implies --non-constant-id.\n"
" --error-on-failed-insert\n"
" Forces aapt to return an error if it fails to insert values into the manifest\n"
" with --debug-mode, --min-sdk-version, --target-sdk-version --version-code\n"
@@ -216,7 +222,9 @@ void usage(void)
" Prevents symbols from being generated for strings that do not have a default\n"
" localization\n"
" --no-version-vectors\n"
- " Do not automatically generate versioned copies of vector XML resources.\n",
+ " Do not automatically generate versioned copies of vector XML resources.\n"
+ " --private-symbols\n"
+ " Java package name to use when generating R.java for private resources.\n",
gDefaultIgnoreAssets);
}
@@ -384,6 +392,17 @@ int main(int argc, char* const argv[])
convertPath(argv[0]);
bundle.setProguardFile(argv[0]);
break;
+ case 'D':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-D' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setMainDexProguardFile(argv[0]);
+ break;
case 'I':
argc--;
argv++;
@@ -667,6 +686,9 @@ int main(int argc, char* const argv[])
} else if (strcmp(cp, "-shared-lib") == 0) {
bundle.setNonConstantId(true);
bundle.setBuildSharedLibrary(true);
+ } else if (strcmp(cp, "-app-as-shared-lib") == 0) {
+ bundle.setNonConstantId(true);
+ bundle.setBuildAppAsSharedLibrary(true);
} else if (strcmp(cp, "-no-crunch") == 0) {
bundle.setUseCrunchCache(true);
} else if (strcmp(cp, "-ignore-assets") == 0) {
@@ -682,6 +704,16 @@ int main(int argc, char* const argv[])
bundle.setPseudolocalize(PSEUDO_ACCENTED | PSEUDO_BIDI);
} else if (strcmp(cp, "-no-version-vectors") == 0) {
bundle.setNoVersionVectors(true);
+ } else if (strcmp(cp, "-private-symbols") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for "
+ "'--private-symbols' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setPrivateSymbolsPackage(String8(argv[0]));
} else {
fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp);
wantUsage = true;
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
index e84c4c503cd2..a493842b8d10 100644
--- a/tools/aapt/Main.h
+++ b/tools/aapt/Main.h
@@ -54,6 +54,7 @@ extern android::status_t writeResourceSymbols(Bundle* bundle,
bool includePrivate, bool emitCallback);
extern android::status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets);
+extern android::status_t writeMainDexProguardFile(Bundle* bundle, const sp<AaptAssets>& assets);
extern bool isValidResourceType(const String8& type);
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index cb244eccfe21..d631f3531127 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -33,7 +33,7 @@ static const char* kNoCompressExt[] = {
".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
- ".amr", ".awb", ".wma", ".wmv"
+ ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"
};
/* fwd decls, so I can write this downward */
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 5d208152e084..e6407332bb90 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1161,6 +1161,12 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
printf("Creating resources for package %s\n", assets->getPackage().string());
}
+ // Set the private symbols package if it was declared.
+ // This can also be declared in XML as <private-symbols name="package" />
+ if (bundle->getPrivateSymbolsPackage().size() != 0) {
+ assets->setSymbolsPrivatePackage(bundle->getPrivateSymbolsPackage());
+ }
+
ResourceTable::PackageType packageType = ResourceTable::App;
if (bundle->getBuildSharedLibrary()) {
packageType = ResourceTable::SharedLibrary;
@@ -1537,12 +1543,20 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
while (!workQueue.empty()) {
CompileResourceWorkItem& workItem = workQueue.front();
- err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags);
+ int xmlCompilationFlags = xmlFlags | XML_COMPILE_PARSE_VALUES
+ | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+ if (!workItem.needsCompiling) {
+ xmlCompilationFlags &= ~XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+ xmlCompilationFlags &= ~XML_COMPILE_PARSE_VALUES;
+ }
+ err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
+ workItem.file, &table, xmlCompilationFlags);
+
if (err == NO_ERROR) {
assets->addResource(workItem.resPath.getPathLeaf(),
- workItem.resPath,
- workItem.file,
- workItem.file->getResourceType());
+ workItem.resPath,
+ workItem.file,
+ workItem.file->getResourceType());
} else {
hasErrors = true;
}
@@ -1737,9 +1751,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
manifestFile->getGroupEntry(),
manifestFile->getResourceType());
err = compileXmlFile(bundle, assets, String16(), manifestFile,
- outManifestFile, &table,
- XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
- | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
+ outManifestFile, &table, XML_COMPILE_STANDARD_RESOURCE & ~XML_COMPILE_STRIP_COMMENTS);
if (err < NO_ERROR) {
return err;
}
@@ -1871,8 +1883,6 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
//printf("Comment of %s: %s\n", String8(e).string(),
// String8(cmt).string());
syms->appendComment(String8(e), String16(cmt), srcPos);
- } else {
- //printf("No comment for %s\n", String8(e).string());
}
syms->makeSymbolPublic(String8(e), srcPos);
} else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) {
@@ -2107,7 +2117,7 @@ static status_t writeResourceLoadedCallbackForLayoutClasses(
indentStr);
}
- return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+ return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
}
static status_t writeResourceLoadedCallback(
@@ -2120,7 +2130,7 @@ static status_t writeResourceLoadedCallback(
size_t N = symbols->getSymbols().size();
for (i=0; i<N; i++) {
const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
- if (sym.typeCode == AaptSymbolEntry::TYPE_UNKNOWN) {
+ if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) {
continue;
}
if (!assets->isJavaSymbol(sym, includePrivate)) {
@@ -2245,6 +2255,9 @@ static status_t writeLayoutClasses(
if (comment.size() <= 0) {
comment = getAttributeComment(assets, name8);
}
+ if (comment.contains(u"@removed")) {
+ continue;
+ }
if (comment.size() > 0) {
const char16_t* p = comment.string();
while (*p != 0 && *p != '.') {
@@ -2523,10 +2536,6 @@ static status_t writeSymbolClass(
fprintf(fp,
"%s/** %s\n",
getIndentSpace(indent), cmt.string());
- } else if (sym.isPublic && !includePrivate) {
- sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
- assets->getPackage().string(), className.string(),
- String8(sym.name).string());
}
String16 typeComment(sym.typeComment);
if (typeComment.size() > 0) {
@@ -2569,10 +2578,6 @@ static status_t writeSymbolClass(
"%s */\n",
getIndentSpace(indent), cmt.string(),
getIndentSpace(indent));
- } else if (sym.isPublic && !includePrivate) {
- sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
- assets->getPackage().string(), className.string(),
- String8(sym.name).string());
}
ann.printAnnotations(fp, getIndentSpace(indent));
fprintf(fp, "%spublic static final String %s=\"%s\";\n",
@@ -2682,7 +2687,7 @@ status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets,
if (s > last && (*s == '.' || *s == 0)) {
String8 part(last, s-last);
dest.appendPath(part);
-#ifdef HAVE_MS_C_RUNTIME
+#ifdef _WIN32
_mkdir(dest.string());
#else
mkdir(dest.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
@@ -2830,7 +2835,7 @@ addProguardKeepMethodRule(ProguardKeepSet* keep, const String8& memberName,
}
status_t
-writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
+writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets, bool mainDex)
{
status_t err;
ResXMLTree tree;
@@ -2842,6 +2847,7 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass
sp<AaptGroup> assGroup;
sp<AaptFile> assFile;
String8 pkg;
+ String8 defaultProcess;
// First, look for a package file to parse. This is required to
// be able to generate the resource information.
@@ -2898,6 +2904,15 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass
addProguardKeepRule(keep, agent, pkg.string(),
assFile->getPrintableSource(), tree.getLineNumber());
}
+
+ if (mainDex) {
+ defaultProcess = AaptXml::getAttribute(tree,
+ "http://schemas.android.com/apk/res/android", "process", &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ return -1;
+ }
+ }
} else if (tag == "instrumentation") {
keepTag = true;
}
@@ -2914,7 +2929,23 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass
fprintf(stderr, "ERROR: %s\n", error.string());
return -1;
}
- if (name.length() > 0) {
+
+ keepTag = name.length() > 0;
+
+ if (keepTag && mainDex) {
+ String8 componentProcess = AaptXml::getAttribute(tree,
+ "http://schemas.android.com/apk/res/android", "process", &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ return -1;
+ }
+
+ const String8& process =
+ componentProcess.length() > 0 ? componentProcess : defaultProcess;
+ keepTag = process.length() > 0 && process.find(":") != 0;
+ }
+
+ if (keepTag) {
addProguardKeepRule(keep, name, pkg.string(),
assFile->getPrintableSource(), tree.getLineNumber());
}
@@ -3097,6 +3128,31 @@ writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
}
status_t
+writeProguardSpec(const char* filename, const ProguardKeepSet& keep, status_t err)
+{
+ FILE* fp = fopen(filename, "w+");
+ if (fp == NULL) {
+ fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
+ filename, strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+
+ const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules;
+ const size_t N = rules.size();
+ for (size_t i=0; i<N; i++) {
+ const SortedVector<String8>& locations = rules.valueAt(i);
+ const size_t M = locations.size();
+ for (size_t j=0; j<M; j++) {
+ fprintf(fp, "# %s\n", locations.itemAt(j).string());
+ }
+ fprintf(fp, "%s\n\n", rules.keyAt(i).string());
+ }
+ fclose(fp);
+
+ return err;
+}
+
+status_t
writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
{
status_t err = -1;
@@ -3107,7 +3163,7 @@ writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
ProguardKeepSet keep;
- err = writeProguardForAndroidManifest(&keep, assets);
+ err = writeProguardForAndroidManifest(&keep, assets, false);
if (err < 0) {
return err;
}
@@ -3117,26 +3173,26 @@ writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
return err;
}
- FILE* fp = fopen(bundle->getProguardFile(), "w+");
- if (fp == NULL) {
- fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
- bundle->getProguardFile(), strerror(errno));
- return UNKNOWN_ERROR;
+ return writeProguardSpec(bundle->getProguardFile(), keep, err);
+}
+
+status_t
+writeMainDexProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+ status_t err = -1;
+
+ if (!bundle->getMainDexProguardFile()) {
+ return NO_ERROR;
}
- const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules;
- const size_t N = rules.size();
- for (size_t i=0; i<N; i++) {
- const SortedVector<String8>& locations = rules.valueAt(i);
- const size_t M = locations.size();
- for (size_t j=0; j<M; j++) {
- fprintf(fp, "# %s\n", locations.itemAt(j).string());
- }
- fprintf(fp, "%s\n\n", rules.keyAt(i).string());
+ ProguardKeepSet keep;
+
+ err = writeProguardForAndroidManifest(&keep, assets, true);
+ if (err < 0) {
+ return err;
}
- fclose(fp);
- return err;
+ return writeProguardSpec(bundle->getMainDexProguardFile(), keep, err);
}
// Loops through the string paths and writes them to the file pointer
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index d5a09d817b1e..6a4b63789815 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -88,8 +88,11 @@ status_t compileXmlFile(const Bundle* bundle,
root->setUTF8(true);
}
- bool hasErrors = false;
+ if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) {
+ return UNKNOWN_ERROR;
+ }
+ bool hasErrors = false;
if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
status_t err = root->assignResourceIds(assets, table);
if (err != NO_ERROR) {
@@ -97,9 +100,11 @@ status_t compileXmlFile(const Bundle* bundle,
}
}
- status_t err = root->parseValues(assets, table);
- if (err != NO_ERROR) {
- hasErrors = true;
+ if ((options&XML_COMPILE_PARSE_VALUES) != 0) {
+ status_t err = root->parseValues(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
}
if (hasErrors) {
@@ -114,7 +119,7 @@ status_t compileXmlFile(const Bundle* bundle,
printf("Input XML Resource:\n");
root->print();
}
- err = root->flatten(target,
+ status_t err = root->flatten(target,
(options&XML_COMPILE_STRIP_COMMENTS) != 0,
(options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
if (err != NO_ERROR) {
@@ -303,29 +308,11 @@ struct PendingAttribute
}
added = true;
- String16 attr16("attr");
-
- if (outTable->hasBagOrEntry(myPackage, attr16, ident)) {
- sourcePos.error("Attribute \"%s\" has already been defined\n",
- String8(ident).string());
+ if (!outTable->makeAttribute(myPackage, ident, sourcePos, type, comment, appendComment)) {
hasErrors = true;
return UNKNOWN_ERROR;
}
-
- char numberStr[16];
- sprintf(numberStr, "%d", type);
- status_t err = outTable->addBag(sourcePos, myPackage,
- attr16, ident, String16(""),
- String16("^type"),
- String16(numberStr), NULL, NULL);
- if (err != NO_ERROR) {
- hasErrors = true;
- return err;
- }
- outTable->appendComment(myPackage, attr16, ident, comment, appendComment);
- //printf("Attribute %s comment: %s\n", String8(ident).string(),
- // String8(comment).string());
- return err;
+ return NO_ERROR;
}
};
@@ -1136,7 +1123,15 @@ status_t compileResourceFile(Bundle* bundle,
}
pkg = String16(block.getAttributeStringValue(pkgIdx, &len));
if (!localHasErrors) {
- assets->setSymbolsPrivatePackage(String8(pkg));
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).warning(
+ "<private-symbols> is deprecated. Use the command line flag "
+ "--private-symbols instead.\n");
+ if (assets->havePrivateSymbols()) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).warning(
+ "private symbol package already specified. Ignoring...\n");
+ } else {
+ assets->setSymbolsPrivatePackage(String8(pkg));
+ }
}
while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
@@ -2102,6 +2097,61 @@ bool ResourceTable::appendTypeComment(const String16& package,
return false;
}
+bool ResourceTable::makeAttribute(const String16& package,
+ const String16& name,
+ const SourcePos& source,
+ int32_t format,
+ const String16& comment,
+ bool shouldAppendComment) {
+ const String16 attr16("attr");
+
+ // First look for this in the included resources...
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ attr16.string(), attr16.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ source.error("Attribute \"%s\" has already been defined", String8(name).string());
+ return false;
+ }
+
+ sp<ResourceTable::Entry> entry = getEntry(package, attr16, name, source, false);
+ if (entry == NULL) {
+ source.error("Failed to create entry attr/%s", String8(name).string());
+ return false;
+ }
+
+ if (entry->makeItABag(source) != NO_ERROR) {
+ return false;
+ }
+
+ const String16 formatKey16("^type");
+ const String16 formatValue16(String8::format("%d", format));
+
+ ssize_t idx = entry->getBag().indexOfKey(formatKey16);
+ if (idx >= 0) {
+ // We have already set a format for this attribute, check if they are different.
+ // We allow duplicate attribute definitions so long as they are identical.
+ // This is to ensure inter-operation with libraries that define the same generic attribute.
+ const Item& formatItem = entry->getBag().valueAt(idx);
+ if ((format & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) ||
+ formatItem.value != formatValue16) {
+ source.error("Attribute \"%s\" already defined with incompatible format.\n"
+ "%s:%d: Original attribute defined here.",
+ String8(name).string(), formatItem.sourcePos.file.string(),
+ formatItem.sourcePos.line);
+ return false;
+ }
+ } else {
+ entry->addToBag(source, formatKey16, formatValue16);
+ // Increment the number of resources we have. This is used to determine if we should
+ // even generate a resource table.
+ mNumLocal++;
+ }
+ appendComment(package, attr16, name, comment, shouldAppendComment);
+ return true;
+}
+
void ResourceTable::canAddEntry(const SourcePos& pos,
const String16& package, const String16& type, const String16& name)
{
@@ -4755,9 +4805,9 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle,
newConfig.sdkVersion = sdkVersionToGenerate;
sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
AaptGroupEntry(newConfig), target->getResourceType());
- String8 resPath = String8::format("res/%s/%s",
+ String8 resPath = String8::format("res/%s/%s.xml",
newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
- target->getSourceFile().getPathLeaf().string());
+ String8(resourceName).string());
resPath.convertToResPath();
// Add a resource table entry.
@@ -4784,9 +4834,11 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle,
item.resourceName = resourceName;
item.resPath = resPath;
item.file = newFile;
+ item.xmlRoot = newRoot;
+ item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need
+ // to do it again.
mWorkQueue.push(item);
}
-
return NO_ERROR;
}
@@ -4825,3 +4877,226 @@ void ResourceTable::getDensityVaryingResources(
}
}
}
+
+static String16 buildNamespace(const String16& package) {
+ return String16("http://schemas.android.com/apk/res/") + package;
+}
+
+static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) {
+ const Vector<sp<XMLNode> >& children = parent->getChildren();
+ sp<XMLNode> onlyChild;
+ for (size_t i = 0; i < children.size(); i++) {
+ if (children[i]->getType() != XMLNode::TYPE_CDATA) {
+ if (onlyChild != NULL) {
+ return NULL;
+ }
+ onlyChild = children[i];
+ }
+ }
+ return onlyChild;
+}
+
+/**
+ * Detects use of the `bundle' format and extracts nested resources into their own top level
+ * resources. The bundle format looks like this:
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector xmlns:aapt="http://schemas.android.com/aapt">
+ * <aapt:attr name="android:drawable">
+ * <vector android:width="60dp"
+ * android:height="60dp">
+ * <path android:name="v"
+ * android:fillColor="#000000"
+ * android:pathData="M300,70 l 0,-70 70,..." />
+ * </vector>
+ * </aapt:attr>
+ * </animated-vector>
+ *
+ * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children
+ * into a new high-level resource, assigning it a name and ID. Then value of the `name`
+ * attribute must be a resource attribute. That resource attribute is inserted into the parent
+ * with the reference to the extracted resource as the value.
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector android:drawable="@drawable/bundle_1.xml">
+ * </animated-vector>
+ *
+ * <!-- res/drawable/bundle_1.xml -->
+ * <vector android:width="60dp"
+ * android:height="60dp">
+ * <path android:name="v"
+ * android:fillColor="#000000"
+ * android:pathData="M300,70 l 0,-70 70,..." />
+ * </vector>
+ */
+status_t ResourceTable::processBundleFormat(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& target,
+ const sp<XMLNode>& root) {
+ Vector<sp<XMLNode> > namespaces;
+ if (root->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces.push(root);
+ }
+ return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces);
+}
+
+status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& target,
+ const sp<XMLNode>& parent,
+ Vector<sp<XMLNode> >* namespaces) {
+ const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt");
+ const String16 kName16("name");
+ const String16 kAttr16("attr");
+ const String16 kAssetPackage16(mAssets->getPackage());
+
+ Vector<sp<XMLNode> >& children = parent->getChildren();
+ for (size_t i = 0; i < children.size(); i++) {
+ const sp<XMLNode>& child = children[i];
+
+ if (child->getType() == XMLNode::TYPE_CDATA) {
+ continue;
+ } else if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces->push(child);
+ }
+
+ if (child->getElementNamespace() != kAaptNamespaceUri16 ||
+ child->getElementName() != kAttr16) {
+ status_t result = processBundleFormatImpl(bundle, resourceName, target, child,
+ namespaces);
+ if (result != NO_ERROR) {
+ return result;
+ }
+
+ if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces->pop();
+ }
+ continue;
+ }
+
+ // This is the <aapt:attr> tag. Look for the 'name' attribute.
+ SourcePos source(child->getFilename(), child->getStartLineNumber());
+
+ sp<XMLNode> nestedRoot = findOnlyChildElement(child);
+ if (nestedRoot == NULL) {
+ source.error("<%s:%s> must have exactly one child element",
+ String8(child->getElementNamespace()).string(),
+ String8(child->getElementName()).string());
+ return UNKNOWN_ERROR;
+ }
+
+ // Find the special attribute 'parent-attr'. This attribute's value contains
+ // the resource attribute for which this element should be assigned in the parent.
+ const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16);
+ if (attr == NULL) {
+ source.error("inline resource definition must specify an attribute via 'name'");
+ return UNKNOWN_ERROR;
+ }
+
+ // Parse the attribute name.
+ const char* errorMsg = NULL;
+ String16 attrPackage, attrType, attrName;
+ bool result = ResTable::expandResourceRef(attr->string.string(),
+ attr->string.size(),
+ &attrPackage, &attrType, &attrName,
+ &kAttr16, &kAssetPackage16,
+ &errorMsg, NULL);
+ if (!result) {
+ source.error("invalid attribute name for 'name': %s", errorMsg);
+ return UNKNOWN_ERROR;
+ }
+
+ if (attrType != kAttr16) {
+ // The value of the 'name' attribute must be an attribute reference.
+ source.error("value of 'name' must be an attribute reference.");
+ return UNKNOWN_ERROR;
+ }
+
+ // Generate a name for this nested resource and try to add it to the table.
+ // We do this in a loop because the name may be taken, in which case we will
+ // increment a suffix until we succeed.
+ String8 nestedResourceName;
+ String8 nestedResourcePath;
+ int suffix = 1;
+ while (true) {
+ // This child element will be extracted into its own resource file.
+ // Generate a name and path for it from its parent.
+ nestedResourceName = String8::format("%s_%d",
+ String8(resourceName).string(), suffix++);
+ nestedResourcePath = String8::format("res/%s/%s.xml",
+ target->getGroupEntry().toDirName(target->getResourceType())
+ .string(),
+ nestedResourceName.string());
+
+ // Lookup or create the entry for this name.
+ sp<Entry> entry = getEntry(kAssetPackage16,
+ String16(target->getResourceType()),
+ String16(nestedResourceName),
+ source,
+ false,
+ &target->getGroupEntry().toParams(),
+ true);
+ if (entry == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (entry->getType() == Entry::TYPE_UNKNOWN) {
+ // The value for this resource has never been set,
+ // meaning we're good!
+ entry->setItem(source, String16(nestedResourcePath));
+ break;
+ }
+
+ // We failed (name already exists), so try with a different name
+ // (increment the suffix).
+ }
+
+ if (bundle->getVerbose()) {
+ source.printf("generating nested resource %s:%s/%s",
+ mAssets->getPackage().string(), target->getResourceType().string(),
+ nestedResourceName.string());
+ }
+
+ // Build the attribute reference and assign it to the parent.
+ String16 nestedResourceRef = String16(String8::format("@%s:%s/%s",
+ mAssets->getPackage().string(), target->getResourceType().string(),
+ nestedResourceName.string()));
+
+ String16 attrNs = buildNamespace(attrPackage);
+ if (parent->getAttribute(attrNs, attrName) != NULL) {
+ SourcePos(parent->getFilename(), parent->getStartLineNumber())
+ .error("parent of nested resource already defines attribute '%s:%s'",
+ String8(attrPackage).string(), String8(attrName).string());
+ return UNKNOWN_ERROR;
+ }
+
+ // Add the reference to the inline resource.
+ parent->addAttribute(attrNs, attrName, nestedResourceRef);
+
+ // Remove the <aapt:attr> child element from here.
+ children.removeAt(i);
+ i--;
+
+ // Append all namespace declarations that we've seen on this branch in the XML tree
+ // to this resource.
+ // We do this because the order of namespace declarations and prefix usage is determined
+ // by the developer and we do not want to override any decisions. Be conservative.
+ for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) {
+ const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1);
+ sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(),
+ ns->getNamespaceUri());
+ newNs->addChild(nestedRoot);
+ nestedRoot = newNs;
+ }
+
+ // Schedule compilation of the nested resource.
+ CompileResourceWorkItem workItem;
+ workItem.resPath = nestedResourcePath;
+ workItem.resourceName = String16(nestedResourceName);
+ workItem.xmlRoot = nestedRoot;
+ workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(),
+ target->getResourceType());
+ mWorkQueue.push(workItem);
+ }
+ return NO_ERROR;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index c4bdf09d8b19..cf1e992ec330 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -23,13 +23,14 @@ class ResourceTable;
enum {
XML_COMPILE_STRIP_COMMENTS = 1<<0,
XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1,
- XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
- XML_COMPILE_STRIP_WHITESPACE = 1<<3,
- XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
- XML_COMPILE_UTF8 = 1<<5,
+ XML_COMPILE_PARSE_VALUES = 1 << 2,
+ XML_COMPILE_COMPACT_WHITESPACE = 1<<3,
+ XML_COMPILE_STRIP_WHITESPACE = 1<<4,
+ XML_COMPILE_STRIP_RAW_VALUES = 1<<5,
+ XML_COMPILE_UTF8 = 1<<6,
XML_COMPILE_STANDARD_RESOURCE =
- XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+ XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_PARSE_VALUES
| XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
};
@@ -83,6 +84,8 @@ struct CompileResourceWorkItem {
String16 resourceName;
String8 resPath;
sp<AaptFile> file;
+ sp<XMLNode> xmlRoot;
+ bool needsCompiling = true;
};
class ResourceTable : public ResTable::Accessor
@@ -206,6 +209,12 @@ public:
const sp<AaptFile>& file,
const sp<XMLNode>& root);
+ status_t processBundleFormat(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& file,
+ const sp<XMLNode>& parent);
+
+
sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
const bool isBase);
@@ -562,6 +571,18 @@ public:
void getDensityVaryingResources(KeyedVector<Symbol, Vector<SymbolDefinition> >& resources);
+ /**
+ * Make an attribute with the specified format. If another attribute with the same name but
+ * different format exists, this method returns false. If the name is not taken, or if the
+ * format is identical, this returns true.
+ */
+ bool makeAttribute(const String16& package,
+ const String16& name,
+ const SourcePos& source,
+ int32_t format,
+ const String16& comment,
+ bool appendComment);
+
private:
void writePublicDefinitions(const String16& package, FILE* fp, bool pub);
sp<Package> getPackage(const String16& package);
@@ -586,6 +607,11 @@ private:
Res_value* outValue);
int getPublicAttributeSdkLevel(uint32_t attrId) const;
+ status_t processBundleFormatImpl(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& file,
+ const sp<XMLNode>& parent,
+ Vector<sp<XMLNode> >* namespaces);
String16 mAssetsPackage;
PackageType mPackageType;
diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h
index dbe8c8542185..4b0d920c3274 100644
--- a/tools/aapt/StringPool.h
+++ b/tools/aapt/StringPool.h
@@ -20,8 +20,6 @@
#include <ctype.h>
#include <errno.h>
-#include <libexpat/expat.h>
-
using namespace android;
#define PRINT_STRING_METRICS 0
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index ca3f68748ef9..5b215daeb494 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -12,7 +12,7 @@
#include <errno.h>
#include <string.h>
-#ifndef HAVE_MS_C_RUNTIME
+#ifndef _WIN32
#define O_BINARY 0
#endif
@@ -693,6 +693,12 @@ const Vector<sp<XMLNode> >& XMLNode::getChildren() const
return mChildren;
}
+
+Vector<sp<XMLNode> >& XMLNode::getChildren()
+{
+ return mChildren;
+}
+
const String8& XMLNode::getFilename() const
{
return mFilename;
@@ -717,6 +723,18 @@ const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns,
return NULL;
}
+bool XMLNode::removeAttribute(const String16& ns, const String16& name)
+{
+ for (size_t i = 0; i < mAttributes.size(); i++) {
+ const attribute_entry& ae(mAttributes.itemAt(i));
+ if (ae.ns == ns && ae.name == name) {
+ removeAttribute(i);
+ return true;
+ }
+ }
+ return false;
+}
+
XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
const String16& name)
{
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index 3161f6500291..749bf9f59bf7 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -10,6 +10,8 @@
#include "StringPool.h"
#include "ResourceTable.h"
+#include <expat.h>
+
class XMLNode;
extern const char* const RESOURCES_ROOT_NAMESPACE;
@@ -53,7 +55,7 @@ public:
sp<XMLNode> newCData(const String8& filename) {
return new XMLNode(filename);
}
-
+
enum type {
TYPE_NAMESPACE,
TYPE_ELEMENT,
@@ -68,6 +70,7 @@ public:
const String16& getElementNamespace() const;
const String16& getElementName() const;
const Vector<sp<XMLNode> >& getChildren() const;
+ Vector<sp<XMLNode> >& getChildren();
const String8& getFilename() const;
@@ -95,6 +98,7 @@ public:
const Vector<attribute_entry>& getAttributes() const;
const attribute_entry* getAttribute(const String16& ns, const String16& name) const;
+ bool removeAttribute(const String16& ns, const String16& name);
attribute_entry* editAttribute(const String16& ns, const String16& name);
diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp
index 36f4e73b3ac2..2840826c32a6 100644
--- a/tools/aapt/ZipFile.cpp
+++ b/tools/aapt/ZipFile.cpp
@@ -364,7 +364,7 @@ status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
long lfhPosn, startPosn, endPosn, uncompressedLen;
FILE* inputFp = NULL;
unsigned long crc;
- time_t modWhen;
+ time_t modWhen = 0;
if (mReadOnly)
return INVALID_OPERATION;
@@ -497,7 +497,6 @@ status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
*/
pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
compressionMethod);
- modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
pEntry->setModWhen(modWhen);
pEntry->setLFHOffset(lfhPosn);
mEOCD.mNumEntries++;
diff --git a/tools/aapt/pseudolocalize.h b/tools/aapt/pseudolocalize.h
index 71b974b0c269..1faecd14172d 100644
--- a/tools/aapt/pseudolocalize.h
+++ b/tools/aapt/pseudolocalize.h
@@ -1,7 +1,7 @@
#ifndef HOST_PSEUDOLOCALIZE_H
#define HOST_PSEUDOLOCALIZE_H
-#include <base/macros.h>
+#include <android-base/macros.h>
#include "StringPool.h"
class PseudoMethodImpl {
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 10f81502f268..3a1e2bb3bf08 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -13,78 +13,110 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-
-# This tool is prebuilt if we're doing an app-only build.
-ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),)
+LOCAL_PATH:= $(call my-dir)
# ==========================================================
# Setup some common variables for the different build
# targets here.
# ==========================================================
-LOCAL_PATH:= $(call my-dir)
main := Main.cpp
sources := \
- BigBuffer.cpp \
- BinaryResourceParser.cpp \
- BindingXmlPullParser.cpp \
+ compile/IdAssigner.cpp \
+ compile/Png.cpp \
+ compile/PseudolocaleGenerator.cpp \
+ compile/Pseudolocalizer.cpp \
+ compile/XmlIdCollector.cpp \
+ filter/ConfigFilter.cpp \
+ flatten/Archive.cpp \
+ flatten/TableFlattener.cpp \
+ flatten/XmlFlattener.cpp \
+ io/FileSystem.cpp \
+ io/ZipArchive.cpp \
+ link/AutoVersioner.cpp \
+ link/ManifestFixer.cpp \
+ link/ProductFilter.cpp \
+ link/PrivateAttributeMover.cpp \
+ link/ReferenceLinker.cpp \
+ link/TableMerger.cpp \
+ link/XmlReferenceLinker.cpp \
+ process/SymbolTable.cpp \
+ proto/ProtoHelpers.cpp \
+ proto/TableProtoDeserializer.cpp \
+ proto/TableProtoSerializer.cpp \
+ split/TableSplitter.cpp \
+ unflatten/BinaryResourceParser.cpp \
+ unflatten/ResChunkPullParser.cpp \
+ util/BigBuffer.cpp \
+ util/Files.cpp \
+ util/Util.cpp \
ConfigDescription.cpp \
Debug.cpp \
- Files.cpp \
- Flag.cpp \
- JavaClassGenerator.cpp \
- Linker.cpp \
+ Flags.cpp \
+ java/AnnotationProcessor.cpp \
+ java/ClassDefinition.cpp \
+ java/JavaClassGenerator.cpp \
+ java/ManifestClassGenerator.cpp \
+ java/ProguardRules.cpp \
Locale.cpp \
- Logger.cpp \
- ManifestMerger.cpp \
- ManifestParser.cpp \
- ManifestValidator.cpp \
- Png.cpp \
- ProguardRules.cpp \
- ResChunkPullParser.cpp \
Resource.cpp \
ResourceParser.cpp \
ResourceTable.cpp \
- ResourceTableResolver.cpp \
+ ResourceUtils.cpp \
ResourceValues.cpp \
SdkConstants.cpp \
StringPool.cpp \
- TableFlattener.cpp \
- Util.cpp \
- ScopedXmlPullParser.cpp \
- SourceXmlPullParser.cpp \
- XliffXmlPullParser.cpp \
- XmlDom.cpp \
- XmlFlattener.cpp \
- ZipEntry.cpp \
- ZipFile.cpp
+ xml/XmlActionExecutor.cpp \
+ xml/XmlDom.cpp \
+ xml/XmlPullParser.cpp \
+ xml/XmlUtil.cpp
+
+sources += Format.proto
testSources := \
- BigBuffer_test.cpp \
- BindingXmlPullParser_test.cpp \
- Compat_test.cpp \
+ compile/IdAssigner_test.cpp \
+ compile/PseudolocaleGenerator_test.cpp \
+ compile/Pseudolocalizer_test.cpp \
+ compile/XmlIdCollector_test.cpp \
+ filter/ConfigFilter_test.cpp \
+ flatten/TableFlattener_test.cpp \
+ flatten/XmlFlattener_test.cpp \
+ link/AutoVersioner_test.cpp \
+ link/ManifestFixer_test.cpp \
+ link/PrivateAttributeMover_test.cpp \
+ link/ProductFilter_test.cpp \
+ link/ReferenceLinker_test.cpp \
+ link/TableMerger_test.cpp \
+ link/XmlReferenceLinker_test.cpp \
+ process/SymbolTable_test.cpp \
+ proto/TableProtoSerializer_test.cpp \
+ split/TableSplitter_test.cpp \
+ util/BigBuffer_test.cpp \
+ util/Files_test.cpp \
+ util/Maybe_test.cpp \
+ util/StringPiece_test.cpp \
+ util/Util_test.cpp \
ConfigDescription_test.cpp \
- JavaClassGenerator_test.cpp \
- Linker_test.cpp \
+ java/AnnotationProcessor_test.cpp \
+ java/JavaClassGenerator_test.cpp \
+ java/ManifestClassGenerator_test.cpp \
Locale_test.cpp \
- ManifestMerger_test.cpp \
- ManifestParser_test.cpp \
- Maybe_test.cpp \
- NameMangler_test.cpp \
- ResourceParser_test.cpp \
Resource_test.cpp \
+ ResourceParser_test.cpp \
ResourceTable_test.cpp \
- ScopedXmlPullParser_test.cpp \
- StringPiece_test.cpp \
+ ResourceUtils_test.cpp \
+ SdkConstants_test.cpp \
StringPool_test.cpp \
- Util_test.cpp \
- XliffXmlPullParser_test.cpp \
- XmlDom_test.cpp \
- XmlFlattener_test.cpp
+ ValueVisitor_test.cpp \
+ xml/XmlActionExecutor_test.cpp \
+ xml/XmlDom_test.cpp \
+ xml/XmlPullParser_test.cpp \
+ xml/XmlUtil_test.cpp
-cIncludes := \
- external/libpng \
- external/libz
+toolSources := \
+ compile/Compile.cpp \
+ dump/Dump.cpp \
+ link/Link.cpp
hostLdLibs :=
@@ -96,46 +128,63 @@ hostStaticLibs := \
libexpat \
libziparchive-host \
libpng \
- libbase
+ libbase \
+ libprotobuf-cpp-lite_static
-ifneq ($(strip $(USE_MINGW)),)
- hostStaticLibs += libz
-else
- hostLdLibs += -lz
-endif
+
+# Statically link libz for MinGW (Win SDK under Linux),
+# and dynamically link for all others.
+hostStaticLibs_windows := libz
+hostLdLibs_linux := -lz
+hostLdLibs_darwin := -lz
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
-cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field
+cFlags_darwin := -D_DARWIN_UNLIMITED_STREAMS
+cFlags_windows := -Wno-maybe-uninitialized # Incorrectly marking use of Maybe.value() as error.
+cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti
+protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST)
+
+# ==========================================================
+# NOTE: Do not add any shared libraries.
+# AAPT2 is built to run on many environments
+# that may not have the required dependencies.
+# ==========================================================
# ==========================================================
# Build the host static library: libaapt2
# ==========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := libaapt2
-
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CFLAGS := $(cFlags)
+LOCAL_CFLAGS_darwin := $(cFlags_darwin)
+LOCAL_CFLAGS_windows := $(cFlags_windows)
+LOCAL_CPPFLAGS := $(cppFlags)
+LOCAL_C_INCLUDES := $(protoIncludes)
LOCAL_SRC_FILES := $(sources)
-LOCAL_C_INCLUDES += $(cIncludes)
-LOCAL_CFLAGS += $(cFlags)
-LOCAL_CPPFLAGS += $(cppFlags)
-
+LOCAL_STATIC_LIBRARIES := $(hostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
include $(BUILD_HOST_STATIC_LIBRARY)
-
# ==========================================================
# Build the host tests: libaapt2_tests
# ==========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := libaapt2_tests
LOCAL_MODULE_TAGS := tests
-
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CFLAGS := $(cFlags)
+LOCAL_CFLAGS_darwin := $(cFlags_darwin)
+LOCAL_CFLAGS_windows := $(cFlags_windows)
+LOCAL_CPPFLAGS := $(cppFlags)
+LOCAL_C_INCLUDES := $(protoIncludes)
LOCAL_SRC_FILES := $(testSources)
-
-LOCAL_C_INCLUDES += $(cIncludes)
-LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
-LOCAL_LDLIBS += $(hostLdLibs)
-LOCAL_CFLAGS += $(cFlags)
-LOCAL_CPPFLAGS += $(cppFlags)
-
+LOCAL_STATIC_LIBRARIES := libaapt2 $(hostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
+LOCAL_LDLIBS := $(hostLdLibs)
+LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin)
+LOCAL_LDLIBS_linux := $(hostLdLibs_linux)
include $(BUILD_HOST_NATIVE_TEST)
# ==========================================================
@@ -143,15 +192,20 @@ include $(BUILD_HOST_NATIVE_TEST)
# ==========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := aapt2
-
-LOCAL_SRC_FILES := $(main)
-
-LOCAL_C_INCLUDES += $(cIncludes)
-LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
-LOCAL_LDLIBS += $(hostLdLibs)
-LOCAL_CFLAGS += $(cFlags)
-LOCAL_CPPFLAGS += $(cppFlags)
-
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CFLAGS := $(cFlags)
+LOCAL_CFLAGS_darwin := $(cFlags_darwin)
+LOCAL_CFLAGS_windows := $(cFlags_windows)
+LOCAL_CPPFLAGS := $(cppFlags)
+LOCAL_C_INCLUDES := $(protoIncludes)
+LOCAL_SRC_FILES := $(main) $(toolSources)
+LOCAL_STATIC_LIBRARIES := libaapt2 $(hostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
+LOCAL_LDLIBS := $(hostLdLibs)
+LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin)
+LOCAL_LDLIBS_linux := $(hostLdLibs_linux)
include $(BUILD_HOST_EXECUTABLE)
-endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK
+ifeq ($(ONE_SHOT_MAKEFILE),)
+include $(call all-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp
deleted file mode 100644
index 4f1947ab8364..000000000000
--- a/tools/aapt2/BinaryResourceParser.cpp
+++ /dev/null
@@ -1,897 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BinaryResourceParser.h"
-#include "Logger.h"
-#include "ResChunkPullParser.h"
-#include "Resolver.h"
-#include "ResourceParser.h"
-#include "ResourceTable.h"
-#include "ResourceTypeExtensions.h"
-#include "ResourceValues.h"
-#include "Source.h"
-#include "Util.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <androidfw/TypeWrappers.h>
-#include <map>
-#include <string>
-
-namespace aapt {
-
-using namespace android;
-
-/*
- * Visitor that converts a reference's resource ID to a resource name,
- * given a mapping from resource ID to resource name.
- */
-struct ReferenceIdToNameVisitor : ValueVisitor {
- ReferenceIdToNameVisitor(const std::shared_ptr<IResolver>& resolver,
- std::map<ResourceId, ResourceName>* cache) :
- mResolver(resolver), mCache(cache) {
- }
-
- void visit(Reference& reference, ValueVisitorArgs&) override {
- idToName(reference);
- }
-
- void visit(Attribute& attr, ValueVisitorArgs&) override {
- for (auto& entry : attr.symbols) {
- idToName(entry.symbol);
- }
- }
-
- void visit(Style& style, ValueVisitorArgs&) override {
- if (style.parent.id.isValid()) {
- idToName(style.parent);
- }
-
- for (auto& entry : style.entries) {
- idToName(entry.key);
- entry.value->accept(*this, {});
- }
- }
-
- void visit(Styleable& styleable, ValueVisitorArgs&) override {
- for (auto& attr : styleable.entries) {
- idToName(attr);
- }
- }
-
- void visit(Array& array, ValueVisitorArgs&) override {
- for (auto& item : array.items) {
- item->accept(*this, {});
- }
- }
-
- void visit(Plural& plural, ValueVisitorArgs&) override {
- for (auto& item : plural.values) {
- if (item) {
- item->accept(*this, {});
- }
- }
- }
-
-private:
- void idToName(Reference& reference) {
- if (!reference.id.isValid()) {
- return;
- }
-
- auto cacheIter = mCache->find(reference.id);
- if (cacheIter != mCache->end()) {
- reference.name = cacheIter->second;
- reference.id = 0;
- } else {
- Maybe<ResourceName> result = mResolver->findName(reference.id);
- if (result) {
- reference.name = result.value();
-
- // Add to cache.
- mCache->insert({reference.id, reference.name});
-
- reference.id = 0;
- }
- }
- }
-
- std::shared_ptr<IResolver> mResolver;
- std::map<ResourceId, ResourceName>* mCache;
-};
-
-
-BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
- const std::shared_ptr<IResolver>& resolver,
- const Source& source,
- const std::u16string& defaultPackage,
- const void* data,
- size_t len) :
- mTable(table), mResolver(resolver), mSource(source), mDefaultPackage(defaultPackage),
- mData(data), mDataLen(len) {
-}
-
-bool BinaryResourceParser::parse() {
- ResChunkPullParser parser(mData, mDataLen);
-
- bool error = false;
- while(ResChunkPullParser::isGoodEvent(parser.next())) {
- if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
- Logger::warn(mSource)
- << "unknown chunk of type '"
- << parser.getChunk()->type
- << "'."
- << std::endl;
- continue;
- }
-
- error |= !parseTable(parser.getChunk());
- }
-
- if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
- Logger::error(mSource)
- << "bad document: "
- << parser.getLastError()
- << "."
- << std::endl;
- return false;
- }
- return !error;
-}
-
-bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
- if (!mSymbolEntries || mSymbolEntryCount == 0) {
- return false;
- }
-
- if (reinterpret_cast<uintptr_t>(data) < reinterpret_cast<uintptr_t>(mData)) {
- return false;
- }
-
- // We only support 32 bit offsets right now.
- const uintptr_t offset = reinterpret_cast<uintptr_t>(data) -
- reinterpret_cast<uintptr_t>(mData);
- if (offset > std::numeric_limits<uint32_t>::max()) {
- return false;
- }
-
- for (size_t i = 0; i < mSymbolEntryCount; i++) {
- if (mSymbolEntries[i].offset == offset) {
- // This offset is a symbol!
- const StringPiece16 str = util::getString(mSymbolPool,
- mSymbolEntries[i].stringIndex);
- StringPiece16 typeStr;
- ResourceParser::extractResourceName(str, &outSymbol->package, &typeStr,
- &outSymbol->entry);
- const ResourceType* type = parseResourceType(typeStr);
- if (!type) {
- return false;
- }
- if (outSymbol->package.empty()) {
- outSymbol->package = mTable->getPackage();
- }
- outSymbol->type = *type;
-
- // Since we scan the symbol table in order, we can start looking for the
- // next symbol from this point.
- mSymbolEntryCount -= i + 1;
- mSymbolEntries += i + 1;
- return true;
- }
- }
- return false;
-}
-
-bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) {
- const SymbolTable_header* symbolTableHeader = convertTo<SymbolTable_header>(chunk);
- if (!symbolTableHeader) {
- Logger::error(mSource)
- << "could not parse chunk as SymbolTable_header."
- << std::endl;
- return false;
- }
-
- const size_t entrySizeBytes = symbolTableHeader->count * sizeof(SymbolTable_entry);
- if (entrySizeBytes > getChunkDataLen(symbolTableHeader->header)) {
- Logger::error(mSource)
- << "entries extend beyond chunk."
- << std::endl;
- return false;
- }
-
- mSymbolEntries = reinterpret_cast<const SymbolTable_entry*>(
- getChunkData(symbolTableHeader->header));
- mSymbolEntryCount = symbolTableHeader->count;
-
- ResChunkPullParser parser(getChunkData(symbolTableHeader->header) + entrySizeBytes,
- getChunkDataLen(symbolTableHeader->header) - entrySizeBytes);
- if (!ResChunkPullParser::isGoodEvent(parser.next())) {
- Logger::error(mSource)
- << "failed to parse chunk: "
- << parser.getLastError()
- << "."
- << std::endl;
- return false;
- }
-
- if (parser.getChunk()->type != android::RES_STRING_POOL_TYPE) {
- Logger::error(mSource)
- << "expected Symbol string pool."
- << std::endl;
- return false;
- }
-
- if (mSymbolPool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) {
- Logger::error(mSource)
- << "failed to parse symbol string pool with code: "
- << mSymbolPool.getError()
- << "."
- << std::endl;
- return false;
- }
- return true;
-}
-
-bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
- const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
- if (!tableHeader) {
- Logger::error(mSource)
- << "could not parse chunk as ResTable_header."
- << std::endl;
- return false;
- }
-
- ResChunkPullParser parser(getChunkData(tableHeader->header),
- getChunkDataLen(tableHeader->header));
- while (ResChunkPullParser::isGoodEvent(parser.next())) {
- switch (parser.getChunk()->type) {
- case android::RES_STRING_POOL_TYPE:
- if (mValuePool.getError() == NO_INIT) {
- if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
- NO_ERROR) {
- Logger::error(mSource)
- << "failed to parse value string pool with code: "
- << mValuePool.getError()
- << "."
- << std::endl;
- return false;
- }
-
- // Reserve some space for the strings we are going to add.
- mTable->getValueStringPool().hintWillAdd(
- mValuePool.size(), mValuePool.styleCount());
- } else {
- Logger::warn(mSource)
- << "unexpected string pool."
- << std::endl;
- }
- break;
-
- case RES_TABLE_SYMBOL_TABLE_TYPE:
- if (!parseSymbolTable(parser.getChunk())) {
- return false;
- }
- break;
-
- case RES_TABLE_SOURCE_POOL_TYPE: {
- if (mSourcePool.setTo(getChunkData(*parser.getChunk()),
- getChunkDataLen(*parser.getChunk())) != NO_ERROR) {
- Logger::error(mSource)
- << "failed to parse source pool with code: "
- << mSourcePool.getError()
- << "."
- << std::endl;
- return false;
- }
- break;
- }
-
- case android::RES_TABLE_PACKAGE_TYPE:
- if (!parsePackage(parser.getChunk())) {
- return false;
- }
- break;
-
- default:
- Logger::warn(mSource)
- << "unexpected chunk of type "
- << parser.getChunk()->type
- << "."
- << std::endl;
- break;
- }
- }
-
- if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
- Logger::error(mSource)
- << "bad resource table: " << parser.getLastError()
- << "."
- << std::endl;
- return false;
- }
- return true;
-}
-
-bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
- if (mValuePool.getError() != NO_ERROR) {
- Logger::error(mSource)
- << "no value string pool for ResTable."
- << std::endl;
- return false;
- }
-
- const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
- if (!packageHeader) {
- Logger::error(mSource)
- << "could not parse chunk as ResTable_header."
- << std::endl;
- return false;
- }
-
- if (mTable->getPackageId() == ResourceTable::kUnsetPackageId) {
- // This is the first time the table has it's package ID set.
- mTable->setPackageId(packageHeader->id);
- } else if (mTable->getPackageId() != packageHeader->id) {
- Logger::error(mSource)
- << "ResTable_package has package ID "
- << std::hex << packageHeader->id << std::dec
- << " but ResourceTable has package ID "
- << std::hex << mTable->getPackageId() << std::dec
- << std::endl;
- return false;
- }
-
- size_t len = strnlen16(reinterpret_cast<const char16_t*>(packageHeader->name),
- sizeof(packageHeader->name) / sizeof(packageHeader->name[0]));
- if (mTable->getPackage().empty() && len == 0) {
- mTable->setPackage(mDefaultPackage);
- } else if (len > 0) {
- StringPiece16 thisPackage(reinterpret_cast<const char16_t*>(packageHeader->name), len);
- if (mTable->getPackage().empty()) {
- mTable->setPackage(thisPackage);
- } else if (thisPackage != mTable->getPackage()) {
- Logger::error(mSource)
- << "incompatible packages: "
- << mTable->getPackage()
- << " vs. "
- << thisPackage
- << std::endl;
- return false;
- }
- }
-
- ResChunkPullParser parser(getChunkData(packageHeader->header),
- getChunkDataLen(packageHeader->header));
- while (ResChunkPullParser::isGoodEvent(parser.next())) {
- switch (parser.getChunk()->type) {
- case android::RES_STRING_POOL_TYPE:
- if (mTypePool.getError() == NO_INIT) {
- if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
- NO_ERROR) {
- Logger::error(mSource)
- << "failed to parse type string pool with code "
- << mTypePool.getError()
- << "."
- << std::endl;
- return false;
- }
- } else if (mKeyPool.getError() == NO_INIT) {
- if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) !=
- NO_ERROR) {
- Logger::error(mSource)
- << "failed to parse key string pool with code "
- << mKeyPool.getError()
- << "."
- << std::endl;
- return false;
- }
- } else {
- Logger::warn(mSource)
- << "unexpected string pool."
- << std::endl;
- }
- break;
-
- case android::RES_TABLE_TYPE_SPEC_TYPE:
- if (!parseTypeSpec(parser.getChunk())) {
- return false;
- }
- break;
-
- case android::RES_TABLE_TYPE_TYPE:
- if (!parseType(parser.getChunk())) {
- return false;
- }
- break;
-
- case RES_TABLE_PUBLIC_TYPE:
- if (!parsePublic(parser.getChunk())) {
- return false;
- }
- break;
-
- default:
- Logger::warn(mSource)
- << "unexpected chunk of type "
- << parser.getChunk()->type
- << "."
- << std::endl;
- break;
- }
- }
-
- if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
- Logger::error(mSource)
- << "bad package: "
- << parser.getLastError()
- << "."
- << std::endl;
- return false;
- }
-
- // Now go through the table and change resource ID references to
- // symbolic references.
-
- ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex);
- for (auto& type : *mTable) {
- for (auto& entry : type->entries) {
- for (auto& configValue : entry->values) {
- configValue.value->accept(visitor, {});
- }
- }
- }
- return true;
-}
-
-bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) {
- const Public_header* header = convertTo<Public_header>(chunk);
-
- if (header->typeId == 0) {
- Logger::error(mSource)
- << "invalid type ID " << header->typeId << std::endl;
- return false;
- }
-
- const ResourceType* parsedType = parseResourceType(util::getString(mTypePool,
- header->typeId - 1));
- if (!parsedType) {
- Logger::error(mSource)
- << "invalid type " << util::getString(mTypePool, header->typeId - 1) << std::endl;
- return false;
- }
-
- const uintptr_t chunkEnd = reinterpret_cast<uintptr_t>(chunk) + chunk->size;
- const Public_entry* entry = reinterpret_cast<const Public_entry*>(
- getChunkData(header->header));
- for (uint32_t i = 0; i < header->count; i++) {
- if (reinterpret_cast<uintptr_t>(entry) + sizeof(*entry) > chunkEnd) {
- Logger::error(mSource)
- << "Public_entry extends beyond chunk."
- << std::endl;
- return false;
- }
-
- const ResourceId resId = { mTable->getPackageId(), header->typeId, entry->entryId };
- const ResourceName name = {
- mTable->getPackage(),
- *parsedType,
- util::getString(mKeyPool, entry->key.index).toString() };
-
- SourceLine source;
- if (mSourcePool.getError() == NO_ERROR) {
- source.path = util::utf16ToUtf8(util::getString(mSourcePool, entry->source.index));
- source.line = entry->sourceLine;
- }
-
- if (!mTable->markPublicAllowMangled(name, resId, source)) {
- return false;
- }
-
- // Add this resource name->id mapping to the index so
- // that we can resolve all ID references to name references.
- auto cacheIter = mIdIndex.find(resId);
- if (cacheIter == mIdIndex.end()) {
- mIdIndex.insert({ resId, name });
- }
-
- entry++;
- }
- return true;
-}
-
-bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
- if (mTypePool.getError() != NO_ERROR) {
- Logger::error(mSource)
- << "no type string pool available for ResTable_typeSpec."
- << std::endl;
- return false;
- }
-
- const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
- if (!typeSpec) {
- Logger::error(mSource)
- << "could not parse chunk as ResTable_typeSpec."
- << std::endl;
- return false;
- }
-
- if (typeSpec->id == 0) {
- Logger::error(mSource)
- << "ResTable_typeSpec has invalid id: "
- << typeSpec->id
- << "."
- << std::endl;
- return false;
- }
- return true;
-}
-
-bool BinaryResourceParser::parseType(const ResChunk_header* chunk) {
- if (mTypePool.getError() != NO_ERROR) {
- Logger::error(mSource)
- << "no type string pool available for ResTable_typeSpec."
- << std::endl;
- return false;
- }
-
- if (mKeyPool.getError() != NO_ERROR) {
- Logger::error(mSource)
- << "no key string pool available for ResTable_type."
- << std::endl;
- return false;
- }
-
- const ResTable_type* type = convertTo<ResTable_type>(chunk);
- if (!type) {
- Logger::error(mSource)
- << "could not parse chunk as ResTable_type."
- << std::endl;
- return false;
- }
-
- if (type->id == 0) {
- Logger::error(mSource)
- << "ResTable_type has invalid id: "
- << type->id
- << "."
- << std::endl;
- return false;
- }
-
- const ConfigDescription config(type->config);
- const StringPiece16 typeName = util::getString(mTypePool, type->id - 1);
-
- const ResourceType* parsedType = parseResourceType(typeName);
- if (!parsedType) {
- Logger::error(mSource)
- << "invalid type name '"
- << typeName
- << "' for type with ID "
- << uint32_t(type->id)
- << "." << std::endl;
- return false;
- }
-
- android::TypeVariant tv(type);
- for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
- if (!*it) {
- continue;
- }
-
- const ResTable_entry* entry = *it;
- const ResourceName name = {
- mTable->getPackage(),
- *parsedType,
- util::getString(mKeyPool, entry->key.index).toString()
- };
-
- const ResourceId resId = { mTable->getPackageId(), type->id, it.index() };
-
- std::unique_ptr<Value> resourceValue;
- const ResTable_entry_source* sourceBlock = nullptr;
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
- if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
- const uint8_t* data = reinterpret_cast<const uint8_t*>(mapEntry);
- data += mapEntry->size - sizeof(*sourceBlock);
- sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
- }
-
- // TODO(adamlesinski): Check that the entry count is valid.
- resourceValue = parseMapEntry(name, config, mapEntry);
- } else {
- if (entry->size - sizeof(*entry) == sizeof(*sourceBlock)) {
- const uint8_t* data = reinterpret_cast<const uint8_t*>(entry);
- data += entry->size - sizeof(*sourceBlock);
- sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
- }
-
- const Res_value* value = reinterpret_cast<const Res_value*>(
- reinterpret_cast<const uint8_t*>(entry) + entry->size);
- resourceValue = parseValue(name, config, value, entry->flags);
- }
-
- if (!resourceValue) {
- // TODO(adamlesinski): For now this is ok, but it really shouldn't be.
- continue;
- }
-
- SourceLine source = mSource.line(0);
- if (sourceBlock) {
- size_t len;
- const char* str = mSourcePool.string8At(sourceBlock->pathIndex, &len);
- if (str) {
- source.path.assign(str, len);
- }
- source.line = sourceBlock->line;
- }
-
- if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) {
- return false;
- }
-
- if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
- if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) {
- return false;
- }
- }
-
- // Add this resource name->id mapping to the index so
- // that we can resolve all ID references to name references.
- auto cacheIter = mIdIndex.find(resId);
- if (cacheIter == mIdIndex.end()) {
- mIdIndex.insert({ resId, name });
- }
- }
- return true;
-}
-
-std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Res_value* value,
- uint16_t flags) {
- if (name.type == ResourceType::kId) {
- return util::make_unique<Id>();
- }
-
- if (value->dataType == Res_value::TYPE_STRING) {
- StringPiece16 str = util::getString(mValuePool, value->data);
-
- const ResStringPool_span* spans = mValuePool.styleAt(value->data);
- if (spans != nullptr) {
- StyleString styleStr = { str.toString() };
- while (spans->name.index != ResStringPool_span::END) {
- styleStr.spans.push_back(Span{
- util::getString(mValuePool, spans->name.index).toString(),
- spans->firstChar,
- spans->lastChar
- });
- spans++;
- }
- return util::make_unique<StyledString>(
- mTable->getValueStringPool().makeRef(
- styleStr, StringPool::Context{1, config}));
- } else {
- if (name.type != ResourceType::kString &&
- util::stringStartsWith<char16_t>(str, u"res/")) {
- // This must be a FileReference.
- return util::make_unique<FileReference>(mTable->getValueStringPool().makeRef(
- str, StringPool::Context{ 0, config }));
- }
-
- // There are no styles associated with this string, so treat it as
- // a simple string.
- return util::make_unique<String>(
- mTable->getValueStringPool().makeRef(
- str, StringPool::Context{1, config}));
- }
- }
-
- if (value->dataType == Res_value::TYPE_REFERENCE ||
- value->dataType == Res_value::TYPE_ATTRIBUTE) {
- const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
- Reference::Type::kResource : Reference::Type::kAttribute;
-
- if (value->data != 0) {
- // This is a normal reference.
- return util::make_unique<Reference>(value->data, type);
- }
-
- // This reference has an invalid ID. Check if it is an unresolved symbol.
- ResourceNameRef symbol;
- if (getSymbol(&value->data, &symbol)) {
- return util::make_unique<Reference>(symbol, type);
- }
-
- // This is not an unresolved symbol, so it must be the magic @null reference.
- Res_value nullType = {};
- nullType.dataType = Res_value::TYPE_REFERENCE;
- return util::make_unique<BinaryPrimitive>(nullType);
- }
-
- if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
- return util::make_unique<RawString>(
- mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data),
- StringPool::Context{ 1, config }));
- }
-
- // Treat this as a raw binary primitive.
- return util::make_unique<BinaryPrimitive>(*value);
-}
-
-std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- switch (name.type) {
- case ResourceType::kStyle:
- return parseStyle(name, config, map);
- case ResourceType::kAttr:
- return parseAttr(name, config, map);
- case ResourceType::kArray:
- return parseArray(name, config, map);
- case ResourceType::kStyleable:
- return parseStyleable(name, config, map);
- case ResourceType::kPlurals:
- return parsePlural(name, config, map);
- default:
- break;
- }
- return {};
-}
-
-std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
- if (map->parent.ident == 0) {
- // The parent is either not set or it is an unresolved symbol.
- // Check to see if it is a symbol.
- ResourceNameRef symbol;
- if (getSymbol(&map->parent.ident, &symbol)) {
- style->parent.name = symbol.toResourceName();
- }
- } else {
- // The parent is a regular reference to a resource.
- style->parent.id = map->parent.ident;
- }
-
- for (const ResTable_map& mapEntry : map) {
- style->entries.emplace_back();
- Style::Entry& styleEntry = style->entries.back();
-
- if (mapEntry.name.ident == 0) {
- // The map entry's key (attribute) is not set. This must be
- // a symbol reference, so resolve it.
- ResourceNameRef symbol;
- bool result = getSymbol(&mapEntry.name.ident, &symbol);
- assert(result);
- styleEntry.key.name = symbol.toResourceName();
- } else {
- // The map entry's key (attribute) is a regular reference.
- styleEntry.key.id = mapEntry.name.ident;
- }
-
- // Parse the attribute's value.
- styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
- assert(styleEntry.value);
- }
- return style;
-}
-
-std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0;
- std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
-
- // First we must discover what type of attribute this is. Find the type mask.
- auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
- return entry.name.ident == ResTable_map::ATTR_TYPE;
- });
-
- if (typeMaskIter != end(map)) {
- attr->typeMask = typeMaskIter->value.data;
- }
-
- if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
- for (const ResTable_map& mapEntry : map) {
- if (Res_INTERNALID(mapEntry.name.ident)) {
- continue;
- }
-
- Attribute::Symbol symbol;
- symbol.value = mapEntry.value.data;
- if (mapEntry.name.ident == 0) {
- // The map entry's key (id) is not set. This must be
- // a symbol reference, so resolve it.
- ResourceNameRef symbolName;
- bool result = getSymbol(&mapEntry.name.ident, &symbolName);
- assert(result);
- symbol.symbol.name = symbolName.toResourceName();
- } else {
- // The map entry's key (id) is a regular reference.
- symbol.symbol.id = mapEntry.name.ident;
- }
-
- attr->symbols.push_back(std::move(symbol));
- }
- }
-
- // TODO(adamlesinski): Find min, max, i80n, etc attributes.
- return attr;
-}
-
-std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- std::unique_ptr<Array> array = util::make_unique<Array>();
- for (const ResTable_map& mapEntry : map) {
- array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
- }
- return array;
-}
-
-std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
- for (const ResTable_map& mapEntry : map) {
- if (mapEntry.name.ident == 0) {
- // The map entry's key (attribute) is not set. This must be
- // a symbol reference, so resolve it.
- ResourceNameRef symbol;
- bool result = getSymbol(&mapEntry.name.ident, &symbol);
- assert(result);
- styleable->entries.emplace_back(symbol);
- } else {
- // The map entry's key (attribute) is a regular reference.
- styleable->entries.emplace_back(mapEntry.name.ident);
- }
- }
- return styleable;
-}
-
-std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- std::unique_ptr<Plural> plural = util::make_unique<Plural>();
- for (const ResTable_map& mapEntry : map) {
- std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
-
- switch (mapEntry.name.ident) {
- case android::ResTable_map::ATTR_ZERO:
- plural->values[Plural::Zero] = std::move(item);
- break;
- case android::ResTable_map::ATTR_ONE:
- plural->values[Plural::One] = std::move(item);
- break;
- case android::ResTable_map::ATTR_TWO:
- plural->values[Plural::Two] = std::move(item);
- break;
- case android::ResTable_map::ATTR_FEW:
- plural->values[Plural::Few] = std::move(item);
- break;
- case android::ResTable_map::ATTR_MANY:
- plural->values[Plural::Many] = std::move(item);
- break;
- case android::ResTable_map::ATTR_OTHER:
- plural->values[Plural::Other] = std::move(item);
- break;
- }
- }
- return plural;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp
deleted file mode 100644
index 4b7a656deac6..000000000000
--- a/tools/aapt2/BindingXmlPullParser.cpp
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BindingXmlPullParser.h"
-#include "Util.h"
-
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <vector>
-
-namespace aapt {
-
-constexpr const char16_t* kBindingNamespaceUri = u"http://schemas.android.com/apk/binding";
-constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
-constexpr const char16_t* kVariableTagName = u"variable";
-constexpr const char* kBindingTagPrefix = "android:binding_";
-
-BindingXmlPullParser::BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
- mParser(parser), mOverride(false), mNextTagId(0) {
-}
-
-bool BindingXmlPullParser::readVariableDeclaration() {
- VarDecl var;
-
- const auto endAttrIter = mParser->endAttributes();
- for (auto attrIter = mParser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
- if (!attrIter->namespaceUri.empty()) {
- continue;
- }
-
- if (attrIter->name == u"name") {
- var.name = util::utf16ToUtf8(attrIter->value);
- } else if (attrIter->name == u"type") {
- var.type = util::utf16ToUtf8(attrIter->value);
- }
- }
-
- XmlPullParser::skipCurrentElement(mParser.get());
-
- if (var.name.empty()) {
- mLastError = "variable declaration missing name";
- return false;
- }
-
- if (var.type.empty()) {
- mLastError = "variable declaration missing type";
- return false;
- }
-
- mVarDecls.push_back(std::move(var));
- return true;
-}
-
-bool BindingXmlPullParser::readExpressions() {
- mOverride = true;
- std::vector<XmlPullParser::Attribute> expressions;
- std::string idValue;
-
- const auto endAttrIter = mParser->endAttributes();
- for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
- if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") {
- idValue = util::utf16ToUtf8(attr->value);
- } else {
- StringPiece16 value = util::trimWhitespace(attr->value);
- if (util::stringStartsWith<char16_t>(value, u"@{") &&
- util::stringEndsWith<char16_t>(value, u"}")) {
- // This is attribute's value is an expression of the form
- // @{expression}. We need to capture the expression inside.
- expressions.push_back(XmlPullParser::Attribute{
- attr->namespaceUri,
- attr->name,
- value.substr(2, value.size() - 3).toString()
- });
- } else {
- // This is a normal attribute, use as is.
- mAttributes.emplace_back(*attr);
- }
- }
- }
-
- // Check if we have any expressions.
- if (!expressions.empty()) {
- // We have expressions, so let's assign the target a tag number
- // and add it to our targets list.
- int32_t targetId = mNextTagId++;
- mTargets.push_back(Target{
- util::utf16ToUtf8(mParser->getElementName()),
- idValue,
- targetId,
- std::move(expressions)
- });
-
- std::stringstream numGen;
- numGen << kBindingTagPrefix << targetId;
- mAttributes.push_back(XmlPullParser::Attribute{
- std::u16string(kAndroidNamespaceUri),
- std::u16string(u"tag"),
- util::utf8ToUtf16(numGen.str())
- });
- }
- return true;
-}
-
-XmlPullParser::Event BindingXmlPullParser::next() {
- // Clear old state in preparation for the next event.
- mOverride = false;
- mAttributes.clear();
-
- while (true) {
- Event event = mParser->next();
- if (event == Event::kStartElement) {
- if (mParser->getElementNamespace().empty() &&
- mParser->getElementName() == kVariableTagName) {
- // This is a variable tag. Record data from it, and
- // then discard the entire element.
- if (!readVariableDeclaration()) {
- // mLastError is set, so getEvent will return kBadDocument.
- return getEvent();
- }
- continue;
- } else {
- // Check for expressions of the form @{} in attribute text.
- const auto endAttrIter = mParser->endAttributes();
- for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
- StringPiece16 value = util::trimWhitespace(attr->value);
- if (util::stringStartsWith<char16_t>(value, u"@{") &&
- util::stringEndsWith<char16_t>(value, u"}")) {
- if (!readExpressions()) {
- return getEvent();
- }
- break;
- }
- }
- }
- } else if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
- if (mParser->getNamespaceUri() == kBindingNamespaceUri) {
- // Skip binding namespace tags.
- continue;
- }
- }
- return event;
- }
- return Event::kBadDocument;
-}
-
-bool BindingXmlPullParser::writeToFile(std::ostream& out) const {
- out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
- out << "<Layout directory=\"\" layout=\"\" layoutId=\"\">\n";
-
- // Write the variables.
- out << " <Variables>\n";
- for (const VarDecl& v : mVarDecls) {
- out << " <entries name=\"" << v.name << "\" type=\"" << v.type << "\"/>\n";
- }
- out << " </Variables>\n";
-
- // Write the imports.
-
- std::stringstream tagGen;
-
- // Write the targets.
- out << " <Targets>\n";
- for (const Target& t : mTargets) {
- tagGen.str({});
- tagGen << kBindingTagPrefix << t.tagId;
- out << " <Target boundClass=\"" << t.className << "\" id=\"" << t.id
- << "\" tag=\"" << tagGen.str() << "\">\n";
- out << " <Expressions>\n";
- for (const XmlPullParser::Attribute& a : t.expressions) {
- out << " <Expression attribute=\"" << a.namespaceUri << ":" << a.name
- << "\" text=\"" << a.value << "\"/>\n";
- }
- out << " </Expressions>\n";
- out << " </Target>\n";
- }
- out << " </Targets>\n";
-
- out << "</Layout>\n";
- return bool(out);
-}
-
-XmlPullParser::const_iterator BindingXmlPullParser::beginAttributes() const {
- if (mOverride) {
- return mAttributes.begin();
- }
- return mParser->beginAttributes();
-}
-
-XmlPullParser::const_iterator BindingXmlPullParser::endAttributes() const {
- if (mOverride) {
- return mAttributes.end();
- }
- return mParser->endAttributes();
-}
-
-size_t BindingXmlPullParser::getAttributeCount() const {
- if (mOverride) {
- return mAttributes.size();
- }
- return mParser->getAttributeCount();
-}
-
-XmlPullParser::Event BindingXmlPullParser::getEvent() const {
- if (!mLastError.empty()) {
- return Event::kBadDocument;
- }
- return mParser->getEvent();
-}
-
-const std::string& BindingXmlPullParser::getLastError() const {
- if (!mLastError.empty()) {
- return mLastError;
- }
- return mParser->getLastError();
-}
-
-const std::u16string& BindingXmlPullParser::getComment() const {
- return mParser->getComment();
-}
-
-size_t BindingXmlPullParser::getLineNumber() const {
- return mParser->getLineNumber();
-}
-
-size_t BindingXmlPullParser::getDepth() const {
- return mParser->getDepth();
-}
-
-const std::u16string& BindingXmlPullParser::getText() const {
- return mParser->getText();
-}
-
-const std::u16string& BindingXmlPullParser::getNamespacePrefix() const {
- return mParser->getNamespacePrefix();
-}
-
-const std::u16string& BindingXmlPullParser::getNamespaceUri() const {
- return mParser->getNamespaceUri();
-}
-
-bool BindingXmlPullParser::applyPackageAlias(std::u16string* package,
- const std::u16string& defaultPackage) const {
- return mParser->applyPackageAlias(package, defaultPackage);
-}
-
-const std::u16string& BindingXmlPullParser::getElementNamespace() const {
- return mParser->getElementNamespace();
-}
-
-const std::u16string& BindingXmlPullParser::getElementName() const {
- return mParser->getElementName();
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h
deleted file mode 100644
index cfb16ef477c9..000000000000
--- a/tools/aapt2/BindingXmlPullParser.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_BINDING_XML_PULL_PARSER_H
-#define AAPT_BINDING_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <iostream>
-#include <memory>
-#include <string>
-
-namespace aapt {
-
-class BindingXmlPullParser : public XmlPullParser {
-public:
- BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
- BindingXmlPullParser(const BindingXmlPullParser& rhs) = delete;
-
- Event getEvent() const override;
- const std::string& getLastError() const override;
- Event next() override;
-
- const std::u16string& getComment() const override;
- size_t getLineNumber() const override;
- size_t getDepth() const override;
-
- const std::u16string& getText() const override;
-
- const std::u16string& getNamespacePrefix() const override;
- const std::u16string& getNamespaceUri() const override;
- bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
- const override;
-
- const std::u16string& getElementNamespace() const override;
- const std::u16string& getElementName() const override;
-
- const_iterator beginAttributes() const override;
- const_iterator endAttributes() const override;
- size_t getAttributeCount() const override;
-
- bool writeToFile(std::ostream& out) const;
-
-private:
- struct VarDecl {
- std::string name;
- std::string type;
- };
-
- struct Import {
- std::string name;
- std::string type;
- };
-
- struct Target {
- std::string className;
- std::string id;
- int32_t tagId;
-
- std::vector<XmlPullParser::Attribute> expressions;
- };
-
- bool readVariableDeclaration();
- bool readExpressions();
-
- std::shared_ptr<XmlPullParser> mParser;
- std::string mLastError;
- bool mOverride;
- std::vector<XmlPullParser::Attribute> mAttributes;
- std::vector<VarDecl> mVarDecls;
- std::vector<Target> mTargets;
- int32_t mNextTagId;
-};
-
-} // namespace aapt
-
-#endif // AAPT_BINDING_XML_PULL_PARSER_H
diff --git a/tools/aapt2/BindingXmlPullParser_test.cpp b/tools/aapt2/BindingXmlPullParser_test.cpp
deleted file mode 100644
index 28edcb672840..000000000000
--- a/tools/aapt2/BindingXmlPullParser_test.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "SourceXmlPullParser.h"
-#include "BindingXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
-
-TEST(BindingXmlPullParserTest, SubstituteBindingExpressionsWithTag) {
- std::stringstream input;
- input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
- << " android:id=\"@+id/content\">\n"
- << " <variable name=\"user\" type=\"com.android.test.User\"/>\n"
- << " <TextView android:text=\"@{user.name}\" android:layout_width=\"wrap_content\"\n"
- << " android:layout_height=\"wrap_content\"/>\n"
- << "</LinearLayout>\n";
- std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
- BindingXmlPullParser parser(sourceParser);
-
- ASSERT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
- EXPECT_EQ(std::u16string(u"http://schemas.android.com/apk/res/android"),
- parser.getNamespaceUri());
-
- ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.next());
- EXPECT_EQ(std::u16string(u"LinearLayout"), parser.getElementName());
-
- while (parser.next() == XmlPullParser::Event::kText) {}
-
- ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
- EXPECT_EQ(std::u16string(u"TextView"), parser.getElementName());
-
- ASSERT_EQ(3u, parser.getAttributeCount());
- const auto endAttr = parser.endAttributes();
- EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_width"));
- EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_height"));
- EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"tag"));
-
- while (parser.next() == XmlPullParser::Event::kText) {}
-
- ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
-
- while (parser.next() == XmlPullParser::Event::kText) {}
-
- ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
- ASSERT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
-}
-
-TEST(BindingXmlPullParserTest, GenerateVariableDeclarations) {
- std::stringstream input;
- input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
- << " android:id=\"@+id/content\">\n"
- << " <variable name=\"user\" type=\"com.android.test.User\"/>\n"
- << "</LinearLayout>\n";
- std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
- BindingXmlPullParser parser(sourceParser);
-
- while (XmlPullParser::isGoodEvent(parser.next())) {
- ASSERT_NE(XmlPullParser::Event::kBadDocument, parser.getEvent());
- }
-
- std::stringstream output;
- ASSERT_TRUE(parser.writeToFile(output));
-
- std::string result = output.str();
- EXPECT_NE(std::string::npos,
- result.find("<entries name=\"user\" type=\"com.android.test.User\"/>"));
-}
-
-TEST(BindingXmlPullParserTest, FailOnMissingNameOrTypeInVariableDeclaration) {
- std::stringstream input;
- input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
- << " android:id=\"@+id/content\">\n"
- << " <variable name=\"user\"/>\n"
- << "</LinearLayout>\n";
- std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
- BindingXmlPullParser parser(sourceParser);
-
- while (XmlPullParser::isGoodEvent(parser.next())) {}
-
- EXPECT_EQ(XmlPullParser::Event::kBadDocument, parser.getEvent());
- EXPECT_FALSE(parser.getLastError().empty());
-}
-
-
-} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 6ddf94a681b8..13f8b3b54f68 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -17,8 +17,8 @@
#include "ConfigDescription.h"
#include "Locale.h"
#include "SdkConstants.h"
-#include "StringPiece.h"
-#include "Util.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
#include <string>
@@ -30,6 +30,11 @@ using android::ResTable_config;
static const char* kWildcardName = "any";
+const ConfigDescription& ConfigDescription::defaultConfig() {
+ static ConfigDescription config = {};
+ return config;
+}
+
static bool parseMcc(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out) out->mcc = 0;
@@ -164,6 +169,26 @@ static bool parseScreenLayoutLong(const char* name, ResTable_config* out) {
return false;
}
+static bool parseScreenRound(const char* name, ResTable_config* out) {
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->screenLayout2 =
+ (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+ | ResTable_config::SCREENROUND_ANY;
+ return true;
+ } else if (strcmp(name, "round") == 0) {
+ if (out) out->screenLayout2 =
+ (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+ | ResTable_config::SCREENROUND_YES;
+ return true;
+ } else if (strcmp(name, "notround") == 0) {
+ if (out) out->screenLayout2 =
+ (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+ | ResTable_config::SCREENROUND_NO;
+ return true;
+ }
+ return false;
+}
+
static bool parseOrientation(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out) out->orientation = out->ORIENTATION_ANY;
@@ -635,6 +660,13 @@ bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) {
}
}
+ if (parseScreenRound(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
if (parseOrientation(partIter->c_str(), &config)) {
++partIter;
if (partIter == partsEnd) {
@@ -725,7 +757,9 @@ success:
void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) {
uint16_t minSdk = 0;
- if (config->density == ResTable_config::DENSITY_ANY) {
+ if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
+ minSdk = SDK_MARSHMALLOW;
+ } else if (config->density == ResTable_config::DENSITY_ANY) {
minSdk = SDK_LOLLIPOP;
} else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
|| config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index 67b4b75cce0b..5749816f5124 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -17,7 +17,7 @@
#ifndef AAPT_CONFIG_DESCRIPTION_H
#define AAPT_CONFIG_DESCRIPTION_H
-#include "StringPiece.h"
+#include "util/StringPiece.h"
#include <androidfw/ResourceTypes.h>
#include <ostream>
@@ -29,6 +29,11 @@ namespace aapt {
* initialization and comparison methods.
*/
struct ConfigDescription : public android::ResTable_config {
+ /**
+ * Returns an immutable default config.
+ */
+ static const ConfigDescription& defaultConfig();
+
/*
* Parse a string of the form 'fr-sw600dp-land' and fill in the
* given ResTable_config with resulting configuration parameters.
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index c57e35191a76..e68d6be536df 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -15,7 +15,9 @@
*/
#include "ConfigDescription.h"
-#include "StringPiece.h"
+#include "SdkConstants.h"
+
+#include "util/StringPiece.h"
#include <gtest/gtest.h>
#include <string>
@@ -79,4 +81,19 @@ TEST(ConfigDescriptionTest, ParseCarAttribute) {
EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
}
+TEST(ConfigDescriptionTest, TestParsingRoundQualifier) {
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("round", &config));
+ EXPECT_EQ(android::ResTable_config::SCREENROUND_YES,
+ config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+ EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+ EXPECT_EQ(std::string("round-v23"), config.toString().string());
+
+ EXPECT_TRUE(TestParse("notround", &config));
+ EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
+ config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+ EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+ EXPECT_EQ(std::string("notround-v23"), config.toString().string());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index cf222c68de55..19bd5210c840 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -17,7 +17,8 @@
#include "Debug.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
#include <algorithm>
#include <iostream>
@@ -29,102 +30,147 @@
namespace aapt {
-struct PrintVisitor : ConstValueVisitor {
- void visit(const Attribute& attr, ValueVisitorArgs&) override {
+class PrintVisitor : public ValueVisitor {
+public:
+ using ValueVisitor::visit;
+
+ void visit(Attribute* attr) override {
std::cout << "(attr) type=";
- attr.printMask(std::cout);
+ attr->printMask(&std::cout);
static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
android::ResTable_map::TYPE_FLAGS;
- if (attr.typeMask & kMask) {
- for (const auto& symbol : attr.symbols) {
- std::cout << "\n "
- << symbol.symbol.name.entry << " (" << symbol.symbol.id << ") = "
- << symbol.value;
+ if (attr->typeMask & kMask) {
+ for (const auto& symbol : attr->symbols) {
+ std::cout << "\n " << symbol.symbol.name.value().entry;
+ if (symbol.symbol.id) {
+ std::cout << " (" << symbol.symbol.id.value() << ")";
+ }
+ std::cout << " = " << symbol.value;
}
}
}
- void visit(const Style& style, ValueVisitorArgs&) override {
+ void visit(Style* style) override {
std::cout << "(style)";
- if (style.parent.name.isValid() || style.parent.id.isValid()) {
+ if (style->parent) {
+ const Reference& parentRef = style->parent.value();
std::cout << " parent=";
- if (style.parent.name.isValid()) {
- std::cout << style.parent.name << " ";
+ if (parentRef.name) {
+ if (parentRef.privateReference) {
+ std::cout << "*";
+ }
+ std::cout << parentRef.name.value() << " ";
}
- if (style.parent.id.isValid()) {
- std::cout << style.parent.id;
+ if (parentRef.id) {
+ std::cout << parentRef.id.value();
}
}
- for (const auto& entry : style.entries) {
+ for (const auto& entry : style->entries) {
std::cout << "\n ";
- if (entry.key.name.isValid()) {
- std::cout << entry.key.name.package << ":" << entry.key.name.entry;
+ if (entry.key.name) {
+ const ResourceName& name = entry.key.name.value();
+ if (!name.package.empty()) {
+ std::cout << name.package << ":";
+ }
+ std::cout << name.entry;
}
- if (entry.key.id.isValid()) {
- std::cout << "(" << entry.key.id << ")";
+ if (entry.key.id) {
+ std::cout << "(" << entry.key.id.value() << ")";
}
std::cout << "=" << *entry.value;
}
}
- void visit(const Array& array, ValueVisitorArgs&) override {
- array.print(std::cout);
+ void visit(Array* array) override {
+ array->print(&std::cout);
}
- void visit(const Plural& plural, ValueVisitorArgs&) override {
- plural.print(std::cout);
+ void visit(Plural* plural) override {
+ plural->print(&std::cout);
}
- void visit(const Styleable& styleable, ValueVisitorArgs&) override {
- styleable.print(std::cout);
+ void visit(Styleable* styleable) override {
+ std::cout << "(styleable)";
+ for (const auto& attr : styleable->entries) {
+ std::cout << "\n ";
+ if (attr.name) {
+ const ResourceName& name = attr.name.value();
+ if (!name.package.empty()) {
+ std::cout << name.package << ":";
+ }
+ std::cout << name.entry;
+ }
+
+ if (attr.id) {
+ std::cout << "(" << attr.id.value() << ")";
+ }
+ }
}
- void visitItem(const Item& item, ValueVisitorArgs& args) override {
- item.print(std::cout);
+ void visitItem(Item* item) override {
+ item->print(&std::cout);
}
};
-void Debug::printTable(const std::shared_ptr<ResourceTable>& table) {
- std::cout << "Package name=" << table->getPackage();
- if (table->getPackageId() != ResourceTable::kUnsetPackageId) {
- std::cout << " id=" << std::hex << table->getPackageId() << std::dec;
- }
- std::cout << std::endl;
+void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) {
+ PrintVisitor visitor;
- for (const auto& type : *table) {
- std::cout << " type " << type->type;
- if (type->typeId != ResourceTableType::kUnsetTypeId) {
- std::cout << " id=" << std::hex << type->typeId << std::dec;
- }
- std::cout << " entryCount=" << type->entries.size() << std::endl;
-
- std::vector<const ResourceEntry*> sortedEntries;
- for (const auto& entry : type->entries) {
- auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
- [](const ResourceEntry* a, const ResourceEntry* b) -> bool {
- return a->entryId < b->entryId;
- });
- sortedEntries.insert(iter, entry.get());
+ for (auto& package : table->packages) {
+ std::cout << "Package name=" << package->name;
+ if (package->id) {
+ std::cout << " id=" << std::hex << (int) package->id.value() << std::dec;
}
+ std::cout << std::endl;
- for (const ResourceEntry* entry : sortedEntries) {
- ResourceId id = { table->getPackageId(), type->typeId, entry->entryId };
- ResourceName name = { table->getPackage(), type->type, entry->name };
- std::cout << " spec resource " << id << " " << name;
- if (entry->publicStatus.isPublic) {
- std::cout << " PUBLIC";
+ for (const auto& type : package->types) {
+ std::cout << "\n type " << type->type;
+ if (type->id) {
+ std::cout << " id=" << std::hex << (int) type->id.value() << std::dec;
+ }
+ std::cout << " entryCount=" << type->entries.size() << std::endl;
+
+ std::vector<const ResourceEntry*> sortedEntries;
+ for (const auto& entry : type->entries) {
+ auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
+ [](const ResourceEntry* a, const ResourceEntry* b) -> bool {
+ if (a->id && b->id) {
+ return a->id.value() < b->id.value();
+ } else if (a->id) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+ sortedEntries.insert(iter, entry.get());
}
- std::cout << std::endl;
- PrintVisitor visitor;
- for (const auto& value : entry->values) {
- std::cout << " (" << value.config << ") ";
- value.value->accept(visitor, {});
+ for (const ResourceEntry* entry : sortedEntries) {
+ ResourceId id(package->id ? package->id.value() : uint8_t(0),
+ type->id ? type->id.value() : uint8_t(0),
+ entry->id ? entry->id.value() : uint16_t(0));
+ ResourceName name(package->name, type->type, entry->name);
+
+ std::cout << " spec resource " << id << " " << name;
+ switch (entry->symbolStatus.state) {
+ case SymbolState::kPublic: std::cout << " PUBLIC"; break;
+ case SymbolState::kPrivate: std::cout << " _PRIVATE_"; break;
+ default: break;
+ }
+
std::cout << std::endl;
+
+ for (const auto& value : entry->values) {
+ std::cout << " (" << value->config << ") ";
+ value->value->accept(&visitor);
+ if (options.showSources && !value->value->getSource().path.empty()) {
+ std::cout << " src=" << value->value->getSource();
+ }
+ std::cout << std::endl;
+ }
}
}
}
@@ -136,8 +182,7 @@ static size_t getNodeIndex(const std::vector<ResourceName>& names, const Resourc
return std::distance(names.begin(), iter);
}
-void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table,
- const ResourceName& targetStyle) {
+void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) {
std::map<ResourceName, std::set<ResourceName>> graph;
std::queue<ResourceName> stylesToVisit;
@@ -150,17 +195,16 @@ void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table,
continue;
}
- const ResourceTableType* type;
- const ResourceEntry* entry;
- std::tie(type, entry) = table->findResource(styleName);
- if (entry) {
+ Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
+ if (result) {
+ ResourceEntry* entry = result.value().entry;
for (const auto& value : entry->values) {
- visitFunc<Style>(*value.value, [&](const Style& style) {
- if (style.parent.name.isValid()) {
- parents.insert(style.parent.name);
- stylesToVisit.push(style.parent.name);
+ if (Style* style = valueCast<Style>(value->value.get())) {
+ if (style->parent && style->parent.value().name) {
+ parents.insert(style->parent.value().name.value());
+ stylesToVisit.push(style->parent.value().name.value());
}
- });
+ }
}
}
}
@@ -189,4 +233,19 @@ void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table,
std::cout << "}" << std::endl;
}
+void Debug::dumpHex(const void* data, size_t len) {
+ const uint8_t* d = (const uint8_t*) data;
+ for (size_t i = 0; i < len; i++) {
+ std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t) d[i] << " ";
+ if (i % 8 == 7) {
+ std::cerr << "\n";
+ }
+ }
+
+ if (len - 1 % 8 != 7) {
+ std::cerr << std::endl;
+ }
+}
+
+
} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index cdb3dcb6a5d8..fbe64773d4ed 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -20,14 +20,20 @@
#include "Resource.h"
#include "ResourceTable.h"
-#include <memory>
+// Include for printf-like debugging.
+#include <iostream>
namespace aapt {
+struct DebugPrintTableOptions {
+ bool showSources = false;
+};
+
struct Debug {
- static void printTable(const std::shared_ptr<ResourceTable>& table);
- static void printStyleGraph(const std::shared_ptr<ResourceTable>& table,
+ static void printTable(ResourceTable* table, const DebugPrintTableOptions& options = {});
+ static void printStyleGraph(ResourceTable* table,
const ResourceName& targetStyle);
+ static void dumpHex(const void* data, size_t len);
};
} // namespace aapt
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
new file mode 100644
index 000000000000..e86f2a8830e8
--- /dev/null
+++ b/tools/aapt2/Diagnostics.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_DIAGNOSTICS_H
+#define AAPT_DIAGNOSTICS_H
+
+#include "Source.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <android-base/macros.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+struct DiagMessageActual {
+ Source source;
+ std::string message;
+};
+
+struct DiagMessage {
+private:
+ Source mSource;
+ std::stringstream mMessage;
+
+public:
+ DiagMessage() = default;
+
+ DiagMessage(const StringPiece& src) : mSource(src) {
+ }
+
+ DiagMessage(const Source& src) : mSource(src) {
+ }
+
+ DiagMessage(size_t line) : mSource(Source().withLine(line)) {
+ }
+
+ template <typename T>
+ DiagMessage& operator<<(const T& value) {
+ mMessage << value;
+ return *this;
+ }
+
+ DiagMessageActual build() const {
+ return DiagMessageActual{ mSource, mMessage.str() };
+ }
+};
+
+struct IDiagnostics {
+ virtual ~IDiagnostics() = default;
+
+ enum class Level {
+ Note,
+ Warn,
+ Error
+ };
+
+ virtual void log(Level level, DiagMessageActual& actualMsg) = 0;
+
+ virtual void error(const DiagMessage& message) {
+ DiagMessageActual actual = message.build();
+ log(Level::Error, actual);
+ }
+
+ virtual void warn(const DiagMessage& message) {
+ DiagMessageActual actual = message.build();
+ log(Level::Warn, actual);
+ }
+
+ virtual void note(const DiagMessage& message) {
+ DiagMessageActual actual = message.build();
+ log(Level::Note, actual);
+ }
+};
+
+class StdErrDiagnostics : public IDiagnostics {
+public:
+ StdErrDiagnostics() = default;
+
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ const char* tag;
+
+ switch (level) {
+ case Level::Error:
+ mNumErrors++;
+ if (mNumErrors > 20) {
+ return;
+ }
+ tag = "error";
+ break;
+
+ case Level::Warn:
+ tag = "warn";
+ break;
+
+ case Level::Note:
+ tag = "note";
+ break;
+ }
+
+ if (!actualMsg.source.path.empty()) {
+ std::cerr << actualMsg.source << ": ";
+ }
+ std::cerr << tag << ": " << actualMsg.message << "." << std::endl;
+ }
+
+private:
+ size_t mNumErrors = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
+};
+
+class SourcePathDiagnostics : public IDiagnostics {
+public:
+ SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) {
+ }
+
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ actualMsg.source.path = mSource.path;
+ mDiag->log(level, actualMsg);
+ }
+
+private:
+ Source mSource;
+ IDiagnostics* mDiag;
+
+ DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_DIAGNOSTICS_H */
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
deleted file mode 100644
index 76985da99912..000000000000
--- a/tools/aapt2/Flag.cpp
+++ /dev/null
@@ -1,132 +0,0 @@
-#include "Flag.h"
-#include "StringPiece.h"
-
-#include <functional>
-#include <iomanip>
-#include <iostream>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace flag {
-
-struct Flag {
- std::string name;
- std::string description;
- std::function<bool(const StringPiece&, std::string*)> action;
- bool required;
- bool* flagResult;
- bool flagValueWhenSet;
- bool parsed;
-};
-
-static std::vector<Flag> sFlags;
-static std::vector<std::string> sArgs;
-
-static std::function<bool(const StringPiece&, std::string*)> wrap(
- const std::function<void(const StringPiece&)>& action) {
- return [action](const StringPiece& arg, std::string*) -> bool {
- action(arg);
- return true;
- };
-}
-
-void optionalFlag(const StringPiece& name, const StringPiece& description,
- std::function<void(const StringPiece&)> action) {
- sFlags.push_back(Flag{
- name.toString(), description.toString(), wrap(action),
- false, nullptr, false, false });
-}
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
- std::function<void(const StringPiece&)> action) {
- sFlags.push_back(Flag{ name.toString(), description.toString(), wrap(action),
- true, nullptr, false, false });
-}
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
- std::function<bool(const StringPiece&, std::string*)> action) {
- sFlags.push_back(Flag{ name.toString(), description.toString(), action,
- true, nullptr, false, false });
-}
-
-void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
- bool* result) {
- sFlags.push_back(Flag{
- name.toString(), description.toString(), {},
- false, result, resultWhenSet, false });
-}
-
-void usageAndDie(const StringPiece& command) {
- std::cerr << command << " [options]";
- for (const Flag& flag : sFlags) {
- if (flag.required) {
- std::cerr << " " << flag.name << " arg";
- }
- }
- std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl;
-
- for (const Flag& flag : sFlags) {
- std::string command = flag.name;
- if (!flag.flagResult) {
- command += " arg ";
- }
- std::cerr << " " << std::setw(30) << std::left << command
- << flag.description << std::endl;
- }
- exit(1);
-}
-
-void parse(int argc, char** argv, const StringPiece& command) {
- std::string errorStr;
- for (int i = 0; i < argc; i++) {
- const StringPiece arg(argv[i]);
- if (*arg.data() != '-') {
- sArgs.push_back(arg.toString());
- continue;
- }
-
- bool match = false;
- for (Flag& flag : sFlags) {
- if (arg == flag.name) {
- match = true;
- flag.parsed = true;
- if (flag.flagResult) {
- *flag.flagResult = flag.flagValueWhenSet;
- } else {
- i++;
- if (i >= argc) {
- std::cerr << flag.name << " missing argument." << std::endl
- << std::endl;
- usageAndDie(command);
- }
-
- if (!flag.action(argv[i], &errorStr)) {
- std::cerr << errorStr << "." << std::endl << std::endl;
- usageAndDie(command);
- }
- }
- break;
- }
- }
-
- if (!match) {
- std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl;
- usageAndDie(command);
- }
- }
-
- for (const Flag& flag : sFlags) {
- if (flag.required && !flag.parsed) {
- std::cerr << "missing required flag " << flag.name << std::endl << std::endl;
- usageAndDie(command);
- }
- }
-}
-
-const std::vector<std::string>& getArgs() {
- return sArgs;
-}
-
-} // namespace flag
-} // namespace aapt
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
deleted file mode 100644
index e86374283986..000000000000
--- a/tools/aapt2/Flag.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef AAPT_FLAG_H
-#define AAPT_FLAG_H
-
-#include "StringPiece.h"
-
-#include <functional>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace flag {
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
- std::function<void(const StringPiece&)> action);
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
- std::function<bool(const StringPiece&, std::string*)> action);
-
-void optionalFlag(const StringPiece& name, const StringPiece& description,
- std::function<void(const StringPiece&)> action);
-
-void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
- bool* result);
-
-void usageAndDie(const StringPiece& command);
-
-void parse(int argc, char** argv, const StringPiece& command);
-
-const std::vector<std::string>& getArgs();
-
-} // namespace flag
-} // namespace aapt
-
-#endif // AAPT_FLAG_H
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
new file mode 100644
index 000000000000..666e8a8efff1
--- /dev/null
+++ b/tools/aapt2/Flags.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Flags.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+Flags& Flags::requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::string* value) {
+ auto func = [value](const StringPiece& arg) -> bool {
+ *value = arg.toString();
+ return true;
+ };
+
+ mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false});
+ return *this;
+}
+
+Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description,
+ std::vector<std::string>* value) {
+ auto func = [value](const StringPiece& arg) -> bool {
+ value->push_back(arg.toString());
+ return true;
+ };
+
+ mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false });
+ return *this;
+}
+
+Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description,
+ Maybe<std::string>* value) {
+ auto func = [value](const StringPiece& arg) -> bool {
+ *value = arg.toString();
+ return true;
+ };
+
+ mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
+ return *this;
+}
+
+Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description,
+ std::vector<std::string>* value) {
+ auto func = [value](const StringPiece& arg) -> bool {
+ value->push_back(arg.toString());
+ return true;
+ };
+
+ mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
+ return *this;
+}
+
+Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& description,
+ bool* value) {
+ auto func = [value](const StringPiece& arg) -> bool {
+ *value = true;
+ return true;
+ };
+
+ mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false });
+ return *this;
+}
+
+void Flags::usage(const StringPiece& command, std::ostream* out) {
+ constexpr size_t kWidth = 50;
+
+ *out << command << " [options]";
+ for (const Flag& flag : mFlags) {
+ if (flag.required) {
+ *out << " " << flag.name << " arg";
+ }
+ }
+
+ *out << " files...\n\nOptions:\n";
+
+ for (const Flag& flag : mFlags) {
+ std::string argLine = flag.name;
+ if (flag.numArgs > 0) {
+ argLine += " arg";
+ }
+
+ // Split the description by newlines and write out the argument (which is empty after
+ // the first line) followed by the description line. This will make sure that multiline
+ // descriptions are still right justified and aligned.
+ for (StringPiece line : util::tokenize<char>(flag.description, '\n')) {
+ *out << " " << std::setw(kWidth) << std::left << argLine << line << "\n";
+ argLine = " ";
+ }
+ }
+ *out << " " << std::setw(kWidth) << std::left << "-h" << "Displays this help menu\n";
+ out->flush();
+}
+
+bool Flags::parse(const StringPiece& command, const std::vector<StringPiece>& args,
+ std::ostream* outError) {
+ for (size_t i = 0; i < args.size(); i++) {
+ StringPiece arg = args[i];
+ if (*(arg.data()) != '-') {
+ mArgs.push_back(arg.toString());
+ continue;
+ }
+
+ if (arg == "-h" || arg == "--help") {
+ usage(command, outError);
+ return false;
+ }
+
+ bool match = false;
+ for (Flag& flag : mFlags) {
+ if (arg == flag.name) {
+ if (flag.numArgs > 0) {
+ i++;
+ if (i >= args.size()) {
+ *outError << flag.name << " missing argument.\n\n";
+ usage(command, outError);
+ return false;
+ }
+ flag.action(args[i]);
+ } else {
+ flag.action({});
+ }
+ flag.parsed = true;
+ match = true;
+ break;
+ }
+ }
+
+ if (!match) {
+ *outError << "unknown option '" << arg << "'.\n\n";
+ usage(command, outError);
+ return false;
+ }
+ }
+
+ for (const Flag& flag : mFlags) {
+ if (flag.required && !flag.parsed) {
+ *outError << "missing required flag " << flag.name << "\n\n";
+ usage(command, outError);
+ return false;
+ }
+ }
+ return true;
+}
+
+const std::vector<std::string>& Flags::getArgs() {
+ return mArgs;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h
new file mode 100644
index 000000000000..ce7a4857eb6e
--- /dev/null
+++ b/tools/aapt2/Flags.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLAGS_H
+#define AAPT_FLAGS_H
+
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
+#include <functional>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+class Flags {
+public:
+ Flags& requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::string* value);
+ Flags& requiredFlagList(const StringPiece& name, const StringPiece& description,
+ std::vector<std::string>* value);
+ Flags& optionalFlag(const StringPiece& name, const StringPiece& description,
+ Maybe<std::string>* value);
+ Flags& optionalFlagList(const StringPiece& name, const StringPiece& description,
+ std::vector<std::string>* value);
+ Flags& optionalSwitch(const StringPiece& name, const StringPiece& description,
+ bool* value);
+
+ void usage(const StringPiece& command, std::ostream* out);
+
+ bool parse(const StringPiece& command, const std::vector<StringPiece>& args,
+ std::ostream* outError);
+
+ const std::vector<std::string>& getArgs();
+
+private:
+ struct Flag {
+ std::string name;
+ std::string description;
+ std::function<bool(const StringPiece& value)> action;
+ bool required;
+ size_t numArgs;
+
+ bool parsed;
+ };
+
+ std::vector<Flag> mFlags;
+ std::vector<std::string> mArgs;
+};
+
+} // namespace aapt
+
+#endif // AAPT_FLAGS_H
diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto
new file mode 100644
index 000000000000..d05425c5c64d
--- /dev/null
+++ b/tools/aapt2/Format.proto
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package aapt.pb;
+
+message ConfigDescription {
+ optional bytes data = 1;
+ optional string product = 2;
+}
+
+message StringPool {
+ optional bytes data = 1;
+}
+
+message CompiledFile {
+ message Symbol {
+ optional string resource_name = 1;
+ optional uint32 line_no = 2;
+ }
+
+ optional string resource_name = 1;
+ optional ConfigDescription config = 2;
+ optional string source_path = 3;
+ repeated Symbol exported_symbols = 4;
+}
+
+message ResourceTable {
+ optional StringPool string_pool = 1;
+ optional StringPool source_pool = 2;
+ optional StringPool symbol_pool = 3;
+ repeated Package packages = 4;
+}
+
+message Package {
+ optional uint32 package_id = 1;
+ optional string package_name = 2;
+ repeated Type types = 3;
+}
+
+message Type {
+ optional uint32 id = 1;
+ optional string name = 2;
+ repeated Entry entries = 3;
+}
+
+message SymbolStatus {
+ enum Visibility {
+ Unknown = 0;
+ Private = 1;
+ Public = 2;
+ }
+ optional Visibility visibility = 1;
+ optional Source source = 2;
+ optional string comment = 3;
+}
+
+message Entry {
+ optional uint32 id = 1;
+ optional string name = 2;
+ optional SymbolStatus symbol_status = 3;
+ repeated ConfigValue config_values = 4;
+}
+
+message ConfigValue {
+ optional ConfigDescription config = 1;
+ optional Value value = 2;
+}
+
+message Source {
+ optional uint32 path_idx = 1;
+ optional uint32 line_no = 2;
+ optional uint32 col_no = 3;
+}
+
+message Reference {
+ enum Type {
+ Ref = 0;
+ Attr = 1;
+ }
+ optional Type type = 1;
+ optional uint32 id = 2;
+ optional uint32 symbol_idx = 3;
+ optional bool private = 4;
+}
+
+message Id {
+}
+
+message String {
+ optional uint32 idx = 1;
+}
+
+message RawString {
+ optional uint32 idx = 1;
+}
+
+message FileReference {
+ optional uint32 path_idx = 1;
+}
+
+message Primitive {
+ optional uint32 type = 1;
+ optional uint32 data = 2;
+}
+
+message Attribute {
+ message Symbol {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Reference name = 3;
+ optional uint32 value = 4;
+ }
+ optional uint32 format_flags = 1;
+ optional int32 min_int = 2;
+ optional int32 max_int = 3;
+ repeated Symbol symbols = 4;
+}
+
+message Style {
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Reference key = 3;
+ optional Item item = 4;
+ }
+
+ optional Reference parent = 1;
+ optional Source parent_source = 2;
+ repeated Entry entries = 3;
+}
+
+message Styleable {
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Reference attr = 3;
+ }
+ repeated Entry entries = 1;
+}
+
+message Array {
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Item item = 3;
+ }
+ repeated Entry entries = 1;
+}
+
+message Plural {
+ enum Arity {
+ Zero = 0;
+ One = 1;
+ Two = 2;
+ Few = 3;
+ Many = 4;
+ Other = 5;
+ }
+
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Arity arity = 3;
+ optional Item item = 4;
+ }
+ repeated Entry entries = 1;
+}
+
+message Item {
+ optional Reference ref = 1;
+ optional String str = 2;
+ optional RawString raw_str = 3;
+ optional FileReference file = 4;
+ optional Id id = 5;
+ optional Primitive prim = 6;
+}
+
+message CompoundValue {
+ optional Attribute attr = 1;
+ optional Style style = 2;
+ optional Styleable styleable = 3;
+ optional Array array = 4;
+ optional Plural plural = 5;
+}
+
+message Value {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional bool weak = 3;
+
+ optional Item item = 4;
+ optional CompoundValue compound_value = 5;
+}
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
deleted file mode 100644
index e2ffe79c764d..000000000000
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "JavaClassGenerator.h"
-#include "NameMangler.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "StringPiece.h"
-
-#include <algorithm>
-#include <ostream>
-#include <set>
-#include <sstream>
-#include <tuple>
-
-namespace aapt {
-
-// The number of attributes to emit per line in a Styleable array.
-constexpr size_t kAttribsPerLine = 4;
-
-JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table,
- Options options) :
- mTable(table), mOptions(options) {
-}
-
-static void generateHeader(std::ostream& out, const StringPiece16& package) {
- out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
- " *\n"
- " * This class was automatically generated by the\n"
- " * aapt tool from the resource data it found. It\n"
- " * should not be modified by hand.\n"
- " */\n\n";
- out << "package " << package << ";"
- << std::endl
- << std::endl;
-}
-
-static const std::set<StringPiece16> sJavaIdentifiers = {
- u"abstract", u"assert", u"boolean", u"break", u"byte",
- u"case", u"catch", u"char", u"class", u"const", u"continue",
- u"default", u"do", u"double", u"else", u"enum", u"extends",
- u"final", u"finally", u"float", u"for", u"goto", u"if",
- u"implements", u"import", u"instanceof", u"int", u"interface",
- u"long", u"native", u"new", u"package", u"private", u"protected",
- u"public", u"return", u"short", u"static", u"strictfp", u"super",
- u"switch", u"synchronized", u"this", u"throw", u"throws",
- u"transient", u"try", u"void", u"volatile", u"while", u"true",
- u"false", u"null"
-};
-
-static bool isValidSymbol(const StringPiece16& symbol) {
- return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
-}
-
-/*
- * Java symbols can not contain . or -, but those are valid in a resource name.
- * Replace those with '_'.
- */
-static std::u16string transform(const StringPiece16& symbol) {
- std::u16string output = symbol.toString();
- for (char16_t& c : output) {
- if (c == u'.' || c == u'-') {
- c = u'_';
- }
- }
- return output;
-}
-
-struct GenArgs : ValueVisitorArgs {
- GenArgs(std::ostream* o, const std::u16string* p, std::u16string* e) :
- out(o), package(p), entryName(e) {
- }
-
- std::ostream* out;
- const std::u16string* package;
- std::u16string* entryName;
-};
-
-void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) {
- const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
- std::ostream* out = static_cast<GenArgs&>(a).out;
- const std::u16string* package = static_cast<GenArgs&>(a).package;
- std::u16string* entryName = static_cast<GenArgs&>(a).entryName;
-
- // This must be sorted by resource ID.
- std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes;
- sortedAttributes.reserve(styleable.entries.size());
- for (const auto& attr : styleable.entries) {
- // If we are not encoding final attributes, the styleable entry may have no ID
- // if we are building a static library.
- assert((!mOptions.useFinal || attr.id.isValid()) && "no ID set for Styleable entry");
- assert(attr.name.isValid() && "no name set for Styleable entry");
- sortedAttributes.emplace_back(attr.id, attr.name);
- }
- std::sort(sortedAttributes.begin(), sortedAttributes.end());
-
- // First we emit the array containing the IDs of each attribute.
- *out << " "
- << "public static final int[] " << transform(*entryName) << " = {";
-
- const size_t attrCount = sortedAttributes.size();
- for (size_t i = 0; i < attrCount; i++) {
- if (i % kAttribsPerLine == 0) {
- *out << std::endl << " ";
- }
-
- *out << sortedAttributes[i].first;
- if (i != attrCount - 1) {
- *out << ", ";
- }
- }
- *out << std::endl << " };" << std::endl;
-
- // Now we emit the indices into the array.
- for (size_t i = 0; i < attrCount; i++) {
- *out << " "
- << "public static" << finalModifier
- << " int " << transform(*entryName);
-
- // We may reference IDs from other packages, so prefix the entry name with
- // the package.
- const ResourceNameRef& itemName = sortedAttributes[i].second;
- if (itemName.package != *package) {
- *out << "_" << transform(itemName.package);
- }
- *out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl;
- }
-}
-
-bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId,
- const ResourceTableType& type, std::ostream& out) {
- const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
-
- std::u16string unmangledPackage;
- std::u16string unmangledName;
- for (const auto& entry : type.entries) {
- ResourceId id = { packageId, type.typeId, entry->entryId };
- assert(id.isValid());
-
- unmangledName = entry->name;
- if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
- // The entry name was mangled, and we successfully unmangled it.
- // Check that we want to emit this symbol.
- if (package != unmangledPackage) {
- // Skip the entry if it doesn't belong to the package we're writing.
- continue;
- }
- } else {
- if (package != mTable->getPackage()) {
- // We are processing a mangled package name,
- // but this is a non-mangled resource.
- continue;
- }
- }
-
- if (!isValidSymbol(unmangledName)) {
- ResourceNameRef resourceName = { package, type.type, unmangledName };
- std::stringstream err;
- err << "invalid symbol name '" << resourceName << "'";
- mError = err.str();
- return false;
- }
-
- if (type.type == ResourceType::kStyleable) {
- assert(!entry->values.empty());
- entry->values.front().value->accept(*this, GenArgs{ &out, &package, &unmangledName });
- } else {
- out << " " << "public static" << finalModifier
- << " int " << transform(unmangledName) << " = " << id << ";" << std::endl;
- }
- }
- return true;
-}
-
-bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) {
- const size_t packageId = mTable->getPackageId();
-
- generateHeader(out, package);
-
- out << "public final class R {" << std::endl;
-
- for (const auto& type : *mTable) {
- out << " public static final class " << type->type << " {" << std::endl;
- if (!generateType(package, packageId, *type, out)) {
- return false;
- }
- out << " }" << std::endl;
- }
-
- out << "}" << std::endl;
- return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h
deleted file mode 100644
index f8b9ee3f1fc8..000000000000
--- a/tools/aapt2/JavaClassGenerator.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_JAVA_CLASS_GENERATOR_H
-#define AAPT_JAVA_CLASS_GENERATOR_H
-
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-
-#include <ostream>
-#include <string>
-
-namespace aapt {
-
-/*
- * Generates the R.java file for a resource table.
- */
-class JavaClassGenerator : ConstValueVisitor {
-public:
- /*
- * A set of options for this JavaClassGenerator.
- */
- struct Options {
- /*
- * Specifies whether to use the 'final' modifier
- * on resource entries. Default is true.
- */
- bool useFinal = true;
- };
-
- JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options);
-
- /*
- * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
- * All symbols technically belong to a single package, but linked libraries will
- * have their names mangled, denoting that they came from a different package.
- * We need to generate these symbols in a separate file.
- * Returns true on success.
- */
- bool generate(const std::u16string& package, std::ostream& out);
-
- /*
- * ConstValueVisitor implementation.
- */
- void visit(const Styleable& styleable, ValueVisitorArgs& args);
-
- const std::string& getError() const;
-
-private:
- bool generateType(const std::u16string& package, size_t packageId,
- const ResourceTableType& type, std::ostream& out);
-
- std::shared_ptr<const ResourceTable> mTable;
- Options mOptions;
- std::string mError;
-};
-
-inline const std::string& JavaClassGenerator::getError() const {
- return mError;
-}
-
-} // namespace aapt
-
-#endif // AAPT_JAVA_CLASS_GENERATOR_H
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
deleted file mode 100644
index b385ff4828e1..000000000000
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "JavaClassGenerator.h"
-#include "Linker.h"
-#include "MockResolver.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "Util.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-struct JavaClassGeneratorTest : public ::testing::Test {
- virtual void SetUp() override {
- mTable = std::make_shared<ResourceTable>();
- mTable->setPackage(u"android");
- mTable->setPackageId(0x01);
- }
-
- bool addResource(const ResourceNameRef& name, ResourceId id) {
- return mTable->addResource(name, id, {}, SourceLine{ "test.xml", 21 },
- util::make_unique<Id>());
- }
-
- std::shared_ptr<ResourceTable> mTable;
-};
-
-TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
- ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"class" },
- ResourceId{ 0x01, 0x02, 0x0000 }));
-
- JavaClassGenerator generator(mTable, {});
-
- std::stringstream out;
- EXPECT_FALSE(generator.generate(mTable->getPackage(), out));
-}
-
-TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
- ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"hey-man" },
- ResourceId{ 0x01, 0x02, 0x0000 }));
-
- ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kAttr, u"cool.attr" },
- ResourceId{ 0x01, 0x01, 0x0000 }));
-
- std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
- Reference ref(ResourceName{ u"android", ResourceType::kAttr, u"cool.attr"});
- ref.id = ResourceId{ 0x01, 0x01, 0x0000 };
- styleable->entries.emplace_back(ref);
-
- ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"hey.dude" },
- ResourceId{ 0x01, 0x03, 0x0000 }, {},
- SourceLine{ "test.xml", 21 }, std::move(styleable)));
-
- JavaClassGenerator generator(mTable, {});
-
- std::stringstream out;
- EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
- std::string output = out.str();
-
- EXPECT_NE(std::string::npos,
- output.find("public static final int hey_man = 0x01020000;"));
-
- EXPECT_NE(std::string::npos,
- output.find("public static final int[] hey_dude = {"));
-
- EXPECT_NE(std::string::npos,
- output.find("public static final int hey_dude_cool_attr = 0;"));
-}
-
-
-TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
- ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" },
- ResourceId{ 0x01, 0x02, 0x0000 }));
- ResourceTable table;
- table.setPackage(u"com.lib");
- ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
- SourceLine{ "lib.xml", 33 }, util::make_unique<Id>()));
- ASSERT_TRUE(mTable->merge(std::move(table)));
-
- Linker linker(mTable,
- std::make_shared<MockResolver>(mTable, std::map<ResourceName, ResourceId>()),
- {});
- ASSERT_TRUE(linker.linkAndValidate());
-
- JavaClassGenerator generator(mTable, {});
-
- std::stringstream out;
- EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
- std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("int foo ="));
- EXPECT_EQ(std::string::npos, output.find("int test ="));
-
- out.str("");
- EXPECT_TRUE(generator.generate(u"com.lib", out));
- output = out.str();
- EXPECT_NE(std::string::npos, output.find("int test ="));
- EXPECT_EQ(std::string::npos, output.find("int foo ="));
-}
-
-TEST_F(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
- std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
- styleable->entries.emplace_back(ResourceNameRef{ mTable->getPackage(),
- ResourceType::kAttr,
- u"bar" });
- styleable->entries.emplace_back(ResourceNameRef{ u"com.lib", ResourceType::kAttr, u"bar" });
- ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"Foo" }, {}, {},
- std::move(styleable)));
-
- std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(mTable,
- std::map<ResourceName, ResourceId>({
- { ResourceName{ u"android", ResourceType::kAttr, u"bar" },
- ResourceId{ 0x01, 0x01, 0x0000 } },
- { ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" },
- ResourceId{ 0x02, 0x01, 0x0000 } }}));
-
- Linker linker(mTable, resolver, {});
- ASSERT_TRUE(linker.linkAndValidate());
-
- JavaClassGenerator generator(mTable, {});
-
- std::stringstream out;
- EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
- std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("int Foo_bar ="));
- EXPECT_NE(std::string::npos, output.find("int Foo_com_lib_bar ="));
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp
deleted file mode 100644
index c37cc932cd3b..000000000000
--- a/tools/aapt2/Linker.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Linker.h"
-#include "Logger.h"
-#include "NameMangler.h"
-#include "Resolver.h"
-#include "ResourceParser.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "StringPiece.h"
-#include "Util.h"
-
-#include <androidfw/AssetManager.h>
-#include <array>
-#include <bitset>
-#include <iostream>
-#include <map>
-#include <ostream>
-#include <set>
-#include <sstream>
-#include <tuple>
-#include <vector>
-
-namespace aapt {
-
-Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) {
-}
-
-Linker::Linker(const std::shared_ptr<ResourceTable>& table,
- const std::shared_ptr<IResolver>& resolver, const Options& options) :
- mResolver(resolver), mTable(table), mOptions(options), mError(false) {
-}
-
-bool Linker::linkAndValidate() {
- std::bitset<256> usedTypeIds;
- std::array<std::set<uint16_t>, 256> usedIds;
- usedTypeIds.set(0);
-
- // Collect which resource IDs are already taken.
- for (auto& type : *mTable) {
- if (type->typeId != ResourceTableType::kUnsetTypeId) {
- // The ID for this type has already been set. We
- // mark this ID as taken so we don't re-assign it
- // later.
- usedTypeIds.set(type->typeId);
- }
-
- for (auto& entry : type->entries) {
- if (type->typeId != ResourceTableType::kUnsetTypeId &&
- entry->entryId != ResourceEntry::kUnsetEntryId) {
- // The ID for this entry has already been set. We
- // mark this ID as taken so we don't re-assign it
- // later.
- usedIds[type->typeId].insert(entry->entryId);
- }
- }
- }
-
- // Assign resource IDs that are available.
- size_t nextTypeIndex = 0;
- for (auto& type : *mTable) {
- if (type->typeId == ResourceTableType::kUnsetTypeId) {
- while (nextTypeIndex < usedTypeIds.size() && usedTypeIds[nextTypeIndex]) {
- nextTypeIndex++;
- }
- type->typeId = nextTypeIndex++;
- }
-
- const auto endEntryIter = std::end(usedIds[type->typeId]);
- auto nextEntryIter = std::begin(usedIds[type->typeId]);
- size_t nextIndex = 0;
- for (auto& entry : type->entries) {
- if (entry->entryId == ResourceTableType::kUnsetTypeId) {
- while (nextEntryIter != endEntryIter &&
- nextIndex == *nextEntryIter) {
- nextIndex++;
- ++nextEntryIter;
- }
- entry->entryId = nextIndex++;
- }
- }
- }
-
- // Now do reference linking.
- for (auto& type : *mTable) {
- for (auto& entry : type->entries) {
- if (entry->publicStatus.isPublic && entry->values.empty()) {
- // A public resource has no values. It will not be encoded
- // properly without a symbol table. This is a unresolved symbol.
- addUnresolvedSymbol(ResourceNameRef{
- mTable->getPackage(), type->type, entry->name },
- entry->publicStatus.source);
- continue;
- }
-
- for (auto& valueConfig : entry->values) {
- // Dispatch to the right method of this linker
- // based on the value's type.
- valueConfig.value->accept(*this, Args{
- ResourceNameRef{ mTable->getPackage(), type->type, entry->name },
- valueConfig.source
- });
- }
- }
- }
- return !mError;
-}
-
-const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const {
- return mUnresolvedSymbols;
-}
-
-void Linker::doResolveReference(Reference& reference, const SourceLine& source) {
- Maybe<ResourceId> result = mResolver->findId(reference.name);
- if (!result) {
- addUnresolvedSymbol(reference.name, source);
- return;
- }
- assert(result.value().isValid());
-
- if (mOptions.linkResourceIds) {
- reference.id = result.value();
- } else {
- reference.id = 0;
- }
-}
-
-const Attribute* Linker::doResolveAttribute(Reference& attribute, const SourceLine& source) {
- Maybe<IResolver::Entry> result = mResolver->findAttribute(attribute.name);
- if (!result || !result.value().attr) {
- addUnresolvedSymbol(attribute.name, source);
- return nullptr;
- }
-
- const IResolver::Entry& entry = result.value();
- assert(entry.id.isValid());
-
- if (mOptions.linkResourceIds) {
- attribute.id = entry.id;
- } else {
- attribute.id = 0;
- }
- return entry.attr;
-}
-
-void Linker::visit(Reference& reference, ValueVisitorArgs& a) {
- Args& args = static_cast<Args&>(a);
-
- if (reference.name.entry.empty()) {
- // We can't have a completely bad reference.
- if (!reference.id.isValid()) {
- Logger::error() << "srsly? " << args.referrer << std::endl;
- assert(reference.id.isValid());
- }
-
- // This reference has no name but has an ID.
- // It is a really bad error to have no name and have the same
- // package ID.
- assert(reference.id.packageId() != mTable->getPackageId());
-
- // The reference goes outside this package, let it stay as a
- // resource ID because it will not change.
- return;
- }
-
- doResolveReference(reference, args.source);
-
- // TODO(adamlesinski): Verify the referencedType is another reference
- // or a compatible primitive.
-}
-
-void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
- const Attribute& attr, std::unique_ptr<Item>& value) {
- std::unique_ptr<Item> convertedValue;
- visitFunc<RawString>(*value, [&](RawString& str) {
- // This is a raw string, so check if it can be converted to anything.
- // We can NOT swap value with the converted value in here, since
- // we called through the original value.
-
- auto onCreateReference = [&](const ResourceName& name) {
- // We should never get here. All references would have been
- // parsed in the parser phase.
- assert(false);
- };
-
- convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr,
- onCreateReference);
- if (!convertedValue && attr.typeMask & android::ResTable_map::TYPE_STRING) {
- // Last effort is to parse as a string.
- util::StringBuilder builder;
- builder.append(*str.value);
- if (builder) {
- convertedValue = util::make_unique<String>(
- mTable->getValueStringPool().makeRef(builder.str()));
- }
- }
- });
-
- if (convertedValue) {
- value = std::move(convertedValue);
- }
-
- // Process this new or old value (it can be a reference!).
- value->accept(*this, Args{ name, source });
-
- // Flatten the value to see what resource type it is.
- android::Res_value resValue;
- value->flatten(resValue);
-
- // Always allow references.
- const uint32_t typeMask = attr.typeMask | android::ResTable_map::TYPE_REFERENCE;
- if (!(typeMask & ResourceParser::androidTypeToAttributeTypeMask(resValue.dataType))) {
- Logger::error(source)
- << *value
- << " is not compatible with attribute "
- << attr
- << "."
- << std::endl;
- mError = true;
- }
-}
-
-void Linker::visit(Style& style, ValueVisitorArgs& a) {
- Args& args = static_cast<Args&>(a);
-
- if (style.parent.name.isValid() || style.parent.id.isValid()) {
- visit(style.parent, a);
- }
-
- for (Style::Entry& styleEntry : style.entries) {
- const Attribute* attr = doResolveAttribute(styleEntry.key, args.source);
- if (attr) {
- processAttributeValue(args.referrer, args.source, *attr, styleEntry.value);
- }
- }
-}
-
-void Linker::visit(Attribute& attr, ValueVisitorArgs& a) {
- static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
- android::ResTable_map::TYPE_FLAGS;
- if (attr.typeMask & kMask) {
- for (auto& symbol : attr.symbols) {
- visit(symbol.symbol, a);
- }
- }
-}
-
-void Linker::visit(Styleable& styleable, ValueVisitorArgs& a) {
- for (auto& attrRef : styleable.entries) {
- visit(attrRef, a);
- }
-}
-
-void Linker::visit(Array& array, ValueVisitorArgs& a) {
- Args& args = static_cast<Args&>(a);
-
- for (auto& item : array.items) {
- item->accept(*this, Args{ args.referrer, args.source });
- }
-}
-
-void Linker::visit(Plural& plural, ValueVisitorArgs& a) {
- Args& args = static_cast<Args&>(a);
-
- for (auto& item : plural.values) {
- if (item) {
- item->accept(*this, Args{ args.referrer, args.source });
- }
- }
-}
-
-void Linker::addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source) {
- mUnresolvedSymbols[name.toResourceName()].push_back(source);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h
deleted file mode 100644
index 6f0351592fcd..000000000000
--- a/tools/aapt2/Linker.h
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_LINKER_H
-#define AAPT_LINKER_H
-
-#include "Resolver.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "Source.h"
-#include "StringPiece.h"
-
-#include <androidfw/AssetManager.h>
-#include <map>
-#include <memory>
-#include <ostream>
-#include <set>
-#include <vector>
-
-namespace aapt {
-
-/**
- * The Linker has two jobs. It follows resource references
- * and verifies that their targert exists and that their
- * types are compatible. The Linker will also assign resource
- * IDs and fill in all the dependent references with the newly
- * assigned resource IDs.
- *
- * To do this, the Linker builds a graph of references. This
- * can be useful to do other analysis, like building a
- * dependency graph of source files. The hope is to be able to
- * add functionality that operates on the graph without
- * overcomplicating the Linker.
- *
- * TODO(adamlesinski): Build the graph first then run the separate
- * steps over the graph.
- */
-class Linker : ValueVisitor {
-public:
- struct Options {
- /**
- * Assign resource Ids to references when linking.
- * When building a static library, set this to false.
- */
- bool linkResourceIds = true;
- };
-
- /**
- * Create a Linker for the given resource table with the sources available in
- * IResolver. IResolver should contain the ResourceTable as a source too.
- */
- Linker(const std::shared_ptr<ResourceTable>& table,
- const std::shared_ptr<IResolver>& resolver, const Options& options);
-
- Linker(const Linker&) = delete;
-
- virtual ~Linker() = default;
-
- /**
- * Entry point to the linker. Assigns resource IDs, follows references,
- * and validates types. Returns true if all references to defined values
- * are type-compatible. Missing resource references are recorded but do
- * not cause this method to fail.
- */
- bool linkAndValidate();
-
- /**
- * Returns any references to resources that were not defined in any of the
- * sources.
- */
- using ResourceNameToSourceMap = std::map<ResourceName, std::vector<SourceLine>>;
- const ResourceNameToSourceMap& getUnresolvedReferences() const;
-
-protected:
- virtual void doResolveReference(Reference& reference, const SourceLine& source);
- virtual const Attribute* doResolveAttribute(Reference& attribute, const SourceLine& source);
-
- std::shared_ptr<IResolver> mResolver;
-
-private:
- struct Args : public ValueVisitorArgs {
- Args(const ResourceNameRef& r, const SourceLine& s);
-
- const ResourceNameRef& referrer;
- const SourceLine& source;
- };
-
- //
- // Overrides of ValueVisitor
- //
- void visit(Reference& reference, ValueVisitorArgs& args) override;
- void visit(Attribute& attribute, ValueVisitorArgs& args) override;
- void visit(Styleable& styleable, ValueVisitorArgs& args) override;
- void visit(Style& style, ValueVisitorArgs& args) override;
- void visit(Array& array, ValueVisitorArgs& args) override;
- void visit(Plural& plural, ValueVisitorArgs& args) override;
-
- void processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
- const Attribute& attr, std::unique_ptr<Item>& value);
-
- void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source);
-
- std::shared_ptr<ResourceTable> mTable;
- std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols;
- Options mOptions;
- bool mError;
-};
-
-} // namespace aapt
-
-#endif // AAPT_LINKER_H
diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp
deleted file mode 100644
index d897f9824a95..000000000000
--- a/tools/aapt2/Linker_test.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Linker.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "Util.h"
-
-#include <androidfw/AssetManager.h>
-#include <gtest/gtest.h>
-#include <string>
-
-namespace aapt {
-
-struct LinkerTest : public ::testing::Test {
- virtual void SetUp() override {
- mTable = std::make_shared<ResourceTable>();
- mTable->setPackage(u"android");
- mTable->setPackageId(0x01);
- mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>(
- mTable, std::vector<std::shared_ptr<const android::AssetManager>>()),
- Linker::Options{});
-
- // Create a few attributes for use in the tests.
-
- addResource(ResourceName{ {}, ResourceType::kAttr, u"integer" },
- util::make_unique<Attribute>(false, android::ResTable_map::TYPE_INTEGER));
-
- addResource(ResourceName{ {}, ResourceType::kAttr, u"string" },
- util::make_unique<Attribute>(false, android::ResTable_map::TYPE_STRING));
-
- addResource(ResourceName{ {}, ResourceType::kId, u"apple" }, util::make_unique<Id>());
-
- addResource(ResourceName{ {}, ResourceType::kId, u"banana" }, util::make_unique<Id>());
-
- std::unique_ptr<Attribute> flagAttr = util::make_unique<Attribute>(
- false, android::ResTable_map::TYPE_FLAGS);
- flagAttr->symbols.push_back(Attribute::Symbol{
- ResourceNameRef{ u"android", ResourceType::kId, u"apple" }, 1 });
- flagAttr->symbols.push_back(Attribute::Symbol{
- ResourceNameRef{ u"android", ResourceType::kId, u"banana" }, 2 });
- addResource(ResourceName{ {}, ResourceType::kAttr, u"flags" }, std::move(flagAttr));
- }
-
- /*
- * Convenience method for adding resources with the default configuration and some
- * bogus source line.
- */
- bool addResource(const ResourceNameRef& name, std::unique_ptr<Value> value) {
- return mTable->addResource(name, {}, SourceLine{ "test.xml", 21 }, std::move(value));
- }
-
- std::shared_ptr<ResourceTable> mTable;
- std::shared_ptr<Linker> mLinker;
-};
-
-TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) {
- ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kString, u"foo" },
- util::make_unique<String>(mTable->getValueStringPool().makeRef(u"?123"))));
-
- ASSERT_TRUE(mLinker->linkAndValidate());
- EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-}
-
-TEST_F(LinkerTest, EscapeAndConvertRawString) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
- style->entries.push_back(Style::Entry{
- ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
- util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123"))
- });
- const Style* result = style.get();
- ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
- std::move(style)));
-
- ASSERT_TRUE(mLinker->linkAndValidate());
- EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-
- EXPECT_NE(nullptr, dynamic_cast<BinaryPrimitive*>(result->entries.front().value.get()));
-}
-
-TEST_F(LinkerTest, FailToConvertRawString) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
- style->entries.push_back(Style::Entry{
- ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
- util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?"))
- });
- ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
- std::move(style)));
-
- ASSERT_FALSE(mLinker->linkAndValidate());
-}
-
-TEST_F(LinkerTest, ConvertRawStringToString) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
- style->entries.push_back(Style::Entry{
- ResourceNameRef{ u"android", ResourceType::kAttr, u"string" },
- util::make_unique<RawString>(
- mTable->getValueStringPool().makeRef(u" \"this is \\u00fa\"."))
- });
- const Style* result = style.get();
- ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
- std::move(style)));
-
- ASSERT_TRUE(mLinker->linkAndValidate());
- EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-
- const String* str = dynamic_cast<const String*>(result->entries.front().value.get());
- ASSERT_NE(nullptr, str);
- EXPECT_EQ(*str->value, u"this is \u00fa.");
-}
-
-TEST_F(LinkerTest, ConvertRawStringToFlags) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
- style->entries.push_back(Style::Entry{
- ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" },
- util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple"))
- });
- const Style* result = style.get();
- ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
- std::move(style)));
-
- ASSERT_TRUE(mLinker->linkAndValidate());
- EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-
- const BinaryPrimitive* bin = dynamic_cast<const BinaryPrimitive*>(
- result->entries.front().value.get());
- ASSERT_NE(nullptr, bin);
- EXPECT_EQ(bin->value.data, 1u | 2u);
-}
-
-TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) {
- ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" },
- util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 })));
-
- ASSERT_TRUE(mLinker->linkAndValidate());
- EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index eed0ea71f6c0..be576613b9b2 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -15,7 +15,7 @@
*/
#include "Locale.h"
-#include "Util.h"
+#include "util/Util.h"
#include <algorithm>
#include <ctype.h>
@@ -70,7 +70,7 @@ static inline bool isNumber(const std::string& str) {
return std::all_of(std::begin(str), std::end(str), ::isdigit);
}
-bool LocaleValue::initFromFilterString(const std::string& str) {
+bool LocaleValue::initFromFilterString(const StringPiece& str) {
// A locale (as specified in the filter) is an underscore separated name such
// as "en_US", "en_Latn_US", or "en_US_POSIX".
std::vector<std::string> parts = util::splitAndLowercase(str, '_');
@@ -96,7 +96,7 @@ bool LocaleValue::initFromFilterString(const std::string& str) {
setRegion(part2.c_str());
} else if (part2.length() == 4 && isAlpha(part2)) {
setScript(part2.c_str());
- } else if (part2.length() >= 5 && part2.length() <= 8) {
+ } else if (part2.length() >= 4 && part2.length() <= 8) {
setVariant(part2.c_str());
} else {
valid = false;
@@ -111,7 +111,7 @@ bool LocaleValue::initFromFilterString(const std::string& str) {
if (((part3.length() == 2 && isAlpha(part3)) ||
(part3.length() == 3 && isNumber(part3))) && script[0]) {
setRegion(part3.c_str());
- } else if (part3.length() >= 5 && part3.length() <= 8) {
+ } else if (part3.length() >= 4 && part3.length() <= 8) {
setVariant(part3.c_str());
} else {
valid = false;
@@ -122,7 +122,7 @@ bool LocaleValue::initFromFilterString(const std::string& str) {
}
const std::string& part4 = parts[3];
- if (part4.length() >= 5 && part4.length() <= 8) {
+ if (part4.length() >= 4 && part4.length() <= 8) {
setVariant(part4.c_str());
} else {
valid = false;
@@ -141,7 +141,7 @@ ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
std::string& part = *iter;
if (part[0] == 'b' && part[1] == '+') {
- // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
+ // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
// except that the separator is "+" and not "-".
std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
subtags.erase(subtags.begin());
@@ -157,8 +157,12 @@ ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
setRegion(subtags[1].c_str());
break;
case 4:
- setScript(subtags[1].c_str());
- break;
+ if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
+ // This is a variant: fall through
+ } else {
+ setScript(subtags[1].c_str());
+ break;
+ }
case 5:
case 6:
case 7:
@@ -184,7 +188,7 @@ ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
// The third tag can either be a region code (if the second tag was
// a script), else a variant code.
- if (subtags[2].size() > 4) {
+ if (subtags[2].size() >= 4) {
setVariant(subtags[2].c_str());
} else {
setRegion(subtags[2].c_str());
@@ -249,7 +253,7 @@ std::string LocaleValue::toDirName() const {
void LocaleValue::initFromResTable(const ResTable_config& config) {
config.unpackLanguage(language);
config.unpackRegion(region);
- if (config.localeScript[0]) {
+ if (config.localeScript[0] && !config.localeScriptWasComputed) {
memcpy(script, config.localeScript, sizeof(config.localeScript));
}
diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h
index ceec764ba4fd..b1c80ab27641 100644
--- a/tools/aapt2/Locale.h
+++ b/tools/aapt2/Locale.h
@@ -17,6 +17,8 @@
#ifndef AAPT_LOCALE_VALUE_H
#define AAPT_LOCALE_VALUE_H
+#include "util/StringPiece.h"
+
#include <androidfw/ResourceTypes.h>
#include <string>
#include <vector>
@@ -37,7 +39,7 @@ struct LocaleValue {
/**
* Initialize this LocaleValue from a config string.
*/
- bool initFromFilterString(const std::string& config);
+ bool initFromFilterString(const StringPiece& config);
/**
* Initialize this LocaleValue from parts of a vector.
diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp
index 4e154d6720d6..758e1e31c0e7 100644
--- a/tools/aapt2/Locale_test.cpp
+++ b/tools/aapt2/Locale_test.cpp
@@ -15,7 +15,7 @@
*/
#include "Locale.h"
-#include "Util.h"
+#include "util/Util.h"
#include <gtest/gtest.h>
#include <string>
diff --git a/tools/aapt2/Logger.cpp b/tools/aapt2/Logger.cpp
deleted file mode 100644
index 384718567984..000000000000
--- a/tools/aapt2/Logger.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "Logger.h"
-#include "Source.h"
-
-#include <memory>
-#include <iostream>
-
-namespace aapt {
-
-Log::Log(std::ostream& _out, std::ostream& _err) : out(_out), err(_err) {
-}
-
-std::shared_ptr<Log> Logger::sLog(std::make_shared<Log>(std::cerr, std::cerr));
-
-void Logger::setLog(const std::shared_ptr<Log>& log) {
- sLog = log;
-}
-
-std::ostream& Logger::error() {
- return sLog->err << "error: ";
-}
-
-std::ostream& Logger::error(const Source& source) {
- return sLog->err << source << ": error: ";
-}
-
-std::ostream& Logger::error(const SourceLine& source) {
- return sLog->err << source << ": error: ";
-}
-
-std::ostream& Logger::warn() {
- return sLog->err << "warning: ";
-}
-
-std::ostream& Logger::warn(const Source& source) {
- return sLog->err << source << ": warning: ";
-}
-
-std::ostream& Logger::warn(const SourceLine& source) {
- return sLog->err << source << ": warning: ";
-}
-
-std::ostream& Logger::note() {
- return sLog->out << "note: ";
-}
-
-std::ostream& Logger::note(const Source& source) {
- return sLog->err << source << ": note: ";
-}
-
-std::ostream& Logger::note(const SourceLine& source) {
- return sLog->err << source << ": note: ";
-}
-
-SourceLogger::SourceLogger(const Source& source)
-: mSource(source) {
-}
-
-std::ostream& SourceLogger::error() {
- return Logger::error(mSource);
-}
-
-std::ostream& SourceLogger::error(size_t line) {
- return Logger::error(SourceLine{ mSource.path, line });
-}
-
-std::ostream& SourceLogger::warn() {
- return Logger::warn(mSource);
-}
-
-std::ostream& SourceLogger::warn(size_t line) {
- return Logger::warn(SourceLine{ mSource.path, line });
-}
-
-std::ostream& SourceLogger::note() {
- return Logger::note(mSource);
-}
-
-std::ostream& SourceLogger::note(size_t line) {
- return Logger::note(SourceLine{ mSource.path, line });
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Logger.h b/tools/aapt2/Logger.h
deleted file mode 100644
index 1d437ebe6492..000000000000
--- a/tools/aapt2/Logger.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_LOGGER_H
-#define AAPT_LOGGER_H
-
-#include "Source.h"
-
-#include <memory>
-#include <ostream>
-#include <string>
-#include <utils/String8.h>
-
-namespace aapt {
-
-struct Log {
- Log(std::ostream& out, std::ostream& err);
- Log(const Log& rhs) = delete;
-
- std::ostream& out;
- std::ostream& err;
-};
-
-class Logger {
-public:
- static void setLog(const std::shared_ptr<Log>& log);
-
- static std::ostream& error();
- static std::ostream& error(const Source& source);
- static std::ostream& error(const SourceLine& sourceLine);
-
- static std::ostream& warn();
- static std::ostream& warn(const Source& source);
- static std::ostream& warn(const SourceLine& sourceLine);
-
- static std::ostream& note();
- static std::ostream& note(const Source& source);
- static std::ostream& note(const SourceLine& sourceLine);
-
-private:
- static std::shared_ptr<Log> sLog;
-};
-
-class SourceLogger {
-public:
- SourceLogger(const Source& source);
-
- std::ostream& error();
- std::ostream& error(size_t line);
-
- std::ostream& warn();
- std::ostream& warn(size_t line);
-
- std::ostream& note();
- std::ostream& note(size_t line);
-
-private:
- Source mSource;
-};
-
-inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
- android::String8 utf8(str.data(), str.size());
- return out.write(utf8.string(), utf8.size());
-}
-
-} // namespace aapt
-
-#endif // AAPT_LOGGER_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 54a7329359f1..a2fadd95db3f 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -14,1262 +14,42 @@
* limitations under the License.
*/
-#include "AppInfo.h"
-#include "BigBuffer.h"
-#include "BinaryResourceParser.h"
-#include "BindingXmlPullParser.h"
-#include "Debug.h"
-#include "Files.h"
-#include "Flag.h"
-#include "JavaClassGenerator.h"
-#include "Linker.h"
-#include "ManifestMerger.h"
-#include "ManifestParser.h"
-#include "ManifestValidator.h"
-#include "NameMangler.h"
-#include "Png.h"
-#include "ProguardRules.h"
-#include "ResourceParser.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "SdkConstants.h"
-#include "SourceXmlPullParser.h"
-#include "StringPiece.h"
-#include "TableFlattener.h"
-#include "Util.h"
-#include "XmlFlattener.h"
-#include "ZipFile.h"
+#include "util/StringPiece.h"
-#include <algorithm>
-#include <androidfw/AssetManager.h>
-#include <cstdlib>
-#include <dirent.h>
-#include <errno.h>
-#include <fstream>
#include <iostream>
-#include <sstream>
-#include <sys/stat.h>
-#include <unordered_set>
-#include <utils/Errors.h>
+#include <vector>
-constexpr const char* kAaptVersionStr = "2.0-alpha";
+namespace aapt {
-using namespace aapt;
+extern int compile(const std::vector<StringPiece>& args);
+extern int link(const std::vector<StringPiece>& args);
+extern int dump(const std::vector<StringPiece>& args);
-/**
- * Used with smart pointers to free malloc'ed memory.
- */
-struct DeleteMalloc {
- void operator()(void* ptr) {
- free(ptr);
- }
-};
-
-struct StaticLibraryData {
- Source source;
- std::unique_ptr<ZipFile> apk;
-};
-
-/**
- * Collect files from 'root', filtering out any files that do not
- * match the FileFilter 'filter'.
- */
-bool walkTree(const Source& root, const FileFilter& filter,
- std::vector<Source>* outEntries) {
- bool error = false;
-
- for (const std::string& dirName : listFiles(root.path)) {
- std::string dir = root.path;
- appendPath(&dir, dirName);
-
- FileType ft = getFileType(dir);
- if (!filter(dirName, ft)) {
- continue;
- }
-
- if (ft != FileType::kDirectory) {
- continue;
- }
-
- for (const std::string& fileName : listFiles(dir)) {
- std::string file(dir);
- appendPath(&file, fileName);
-
- FileType ft = getFileType(file);
- if (!filter(fileName, ft)) {
- continue;
- }
-
- if (ft != FileType::kRegular) {
- Logger::error(Source{ file }) << "not a regular file." << std::endl;
- error = true;
- continue;
- }
- outEntries->push_back(Source{ file });
- }
- }
- return !error;
-}
-
-void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
- for (auto& type : *table) {
- if (type->type != ResourceType::kStyle) {
- continue;
- }
-
- for (auto& entry : type->entries) {
- // Add the versioned styles we want to create
- // here. They are added to the table after
- // iterating over the original set of styles.
- //
- // A stack is used since auto-generated styles
- // from later versions should override
- // auto-generated styles from earlier versions.
- // Iterating over the styles is done in order,
- // so we will always visit sdkVersions from smallest
- // to largest.
- std::stack<ResourceConfigValue> addStack;
-
- for (ResourceConfigValue& configValue : entry->values) {
- visitFunc<Style>(*configValue.value, [&](Style& style) {
- // Collect which entries we've stripped and the smallest
- // SDK level which was stripped.
- size_t minSdkStripped = std::numeric_limits<size_t>::max();
- std::vector<Style::Entry> stripped;
-
- // Iterate over the style's entries and erase/record the
- // attributes whose SDK level exceeds the config's sdkVersion.
- auto iter = style.entries.begin();
- while (iter != style.entries.end()) {
- if (iter->key.name.package == u"android") {
- size_t sdkLevel = findAttributeSdkLevel(iter->key.name);
- if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
- // Record that we are about to strip this.
- stripped.emplace_back(std::move(*iter));
- minSdkStripped = std::min(minSdkStripped, sdkLevel);
-
- // Erase this from this style.
- iter = style.entries.erase(iter);
- continue;
- }
- }
- ++iter;
- }
-
- if (!stripped.empty()) {
- // We have stripped attributes, so let's create a new style to hold them.
- ConfigDescription versionConfig(configValue.config);
- versionConfig.sdkVersion = minSdkStripped;
-
- ResourceConfigValue value = {
- versionConfig,
- configValue.source,
- {},
-
- // Create a copy of the original style.
- std::unique_ptr<Value>(configValue.value->clone(
- &table->getValueStringPool()))
- };
-
- Style& newStyle = static_cast<Style&>(*value.value);
-
- // Move the recorded stripped attributes into this new style.
- std::move(stripped.begin(), stripped.end(),
- std::back_inserter(newStyle.entries));
-
- // We will add this style to the table later. If we do it now, we will
- // mess up iteration.
- addStack.push(std::move(value));
- }
- });
- }
-
- auto comparator =
- [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
- return lhs.config < rhs;
- };
-
- while (!addStack.empty()) {
- ResourceConfigValue& value = addStack.top();
- auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
- value.config, comparator);
- if (iter == entry->values.end() || iter->config != value.config) {
- entry->values.insert(iter, std::move(value));
- }
- addStack.pop();
- }
- }
- }
-}
-
-struct CompileItem {
- ResourceName name;
- ConfigDescription config;
- Source source;
- std::string extension;
-};
-
-struct LinkItem {
- ResourceName name;
- ConfigDescription config;
- Source source;
- std::string originalPath;
- ZipFile* apk;
- std::u16string originalPackage;
-};
-
-template <typename TChar>
-static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
- auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
- if (iter == str.end()) {
- return BasicStringPiece<TChar>();
- }
- size_t offset = (iter - str.begin()) + 1;
- return str.substr(offset, str.size() - offset);
-}
-
-std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
- const StringPiece& extension) {
- std::stringstream path;
- path << "res/" << name.type;
- if (config != ConfigDescription{}) {
- path << "-" << config;
- }
- path << "/" << util::utf16ToUtf8(name.entry);
- if (!extension.empty()) {
- path << "." << extension;
- }
- return path.str();
-}
-
-std::string buildFileReference(const CompileItem& item) {
- return buildFileReference(item.name, item.config, item.extension);
-}
-
-std::string buildFileReference(const LinkItem& item) {
- return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
-}
-
-template <typename T>
-bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
- StringPool& pool = table->getValueStringPool();
- StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
- StringPool::Context{ 0, item.config });
- return table->addResource(item.name, item.config, item.source.line(0),
- util::make_unique<FileReference>(ref));
-}
-
-struct AaptOptions {
- enum class Phase {
- Link,
- Compile,
- Dump,
- DumpStyleGraph,
- };
-
- enum class PackageType {
- StandardApp,
- StaticLibrary,
- };
-
- // The phase to process.
- Phase phase;
-
- // The type of package to produce.
- PackageType packageType = PackageType::StandardApp;
-
- // Details about the app.
- AppInfo appInfo;
-
- // The location of the manifest file.
- Source manifest;
-
- // The APK files to link.
- std::vector<Source> input;
-
- // The libraries these files may reference.
- std::vector<Source> libraries;
-
- // Output path. This can be a directory or file
- // depending on the phase.
- Source output;
-
- // Directory in which to write binding xml files.
- Source bindingOutput;
-
- // Directory to in which to generate R.java.
- Maybe<Source> generateJavaClass;
-
- // File in which to produce proguard rules.
- Maybe<Source> generateProguardRules;
-
- // Whether to output verbose details about
- // compilation.
- bool verbose = false;
-
- // Whether or not to auto-version styles or layouts
- // referencing attributes defined in a newer SDK
- // level than the style or layout is defined for.
- bool versionStylesAndLayouts = true;
-
- // The target style that will have it's style hierarchy dumped
- // when the phase is DumpStyleGraph.
- ResourceName dumpStyleTarget;
-};
-
-struct IdCollector : public xml::Visitor {
- IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) :
- mSource(source), mTable(table) {
- }
-
- virtual void visit(xml::Text* node) override {}
-
- virtual void visit(xml::Namespace* node) override {
- for (const auto& child : node->children) {
- child->accept(this);
- }
- }
-
- virtual void visit(xml::Element* node) override {
- for (const xml::Attribute& attr : node->attributes) {
- bool create = false;
- bool priv = false;
- ResourceNameRef nameRef;
- if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) {
- if (create) {
- mTable->addResource(nameRef, {}, mSource.line(node->lineNumber),
- util::make_unique<Id>());
- }
- }
- }
-
- for (const auto& child : node->children) {
- child->accept(this);
- }
- }
-
-private:
- Source mSource;
- std::shared_ptr<ResourceTable> mTable;
-};
-
-bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
- const CompileItem& item, ZipFile* outApk) {
- std::ifstream in(item.source.path, std::ifstream::binary);
- if (!in) {
- Logger::error(item.source) << strerror(errno) << std::endl;
- return false;
- }
-
- SourceLogger logger(item.source);
- std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
- if (!root) {
- return false;
- }
-
- // Collect any resource ID's declared here.
- IdCollector idCollector(item.source, table);
- root->accept(&idCollector);
-
- BigBuffer outBuffer(1024);
- if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) {
- logger.error() << "failed to encode XML." << std::endl;
- return false;
- }
-
- // Write the resulting compiled XML file to the output APK.
- if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
- nullptr) != android::NO_ERROR) {
- Logger::error(options.output) << "failed to write compiled '" << item.source
- << "' to apk." << std::endl;
- return false;
- }
- return true;
-}
-
-/**
- * Determines if a layout should be auto generated based on SDK level. We do not
- * generate a layout if there is already a layout defined whose SDK version is greater than
- * the one we want to generate.
- */
-bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
- const ResourceName& name, const ConfigDescription& config,
- int sdkVersionToGenerate) {
- assert(sdkVersionToGenerate > config.sdkVersion);
- const ResourceTableType* type;
- const ResourceEntry* entry;
- std::tie(type, entry) = table->findResource(name);
- assert(type && entry);
-
- auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
- [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
- return lhs.config < config;
- });
-
- assert(iter != entry->values.end());
- ++iter;
-
- if (iter == entry->values.end()) {
- return true;
- }
-
- ConfigDescription newConfig = config;
- newConfig.sdkVersion = sdkVersionToGenerate;
- return newConfig < iter->config;
-}
-
-bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
- const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
- const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue,
- proguard::KeepSet* keepSet) {
- SourceLogger logger(item.source);
- std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
- if (!root) {
- return false;
- }
-
- xml::FlattenOptions xmlOptions;
- if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
- xmlOptions.keepRawValues = true;
- }
-
- if (options.versionStylesAndLayouts) {
- // We strip attributes that do not belong in this version of the resource.
- // Non-version qualified resources have an implicit version 1 requirement.
- xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
- }
-
- if (options.generateProguardRules) {
- proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet);
- }
-
- BigBuffer outBuffer(1024);
- Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
- item.originalPackage, resolver,
- xmlOptions, &outBuffer);
- if (!minStrippedSdk) {
- logger.error() << "failed to encode XML." << std::endl;
- return false;
- }
-
- if (minStrippedSdk.value() > 0) {
- // Something was stripped, so let's generate a new file
- // with the version of the smallest SDK version stripped.
- // We can only generate a versioned layout if there doesn't exist a layout
- // with sdk version greater than the current one but less than the one we
- // want to generate.
- if (shouldGenerateVersionedResource(table, item.name, item.config,
- minStrippedSdk.value())) {
- LinkItem newWork = item;
- newWork.config.sdkVersion = minStrippedSdk.value();
- outQueue->push(newWork);
-
- if (!addFileReference(table, newWork)) {
- Logger::error(options.output) << "failed to add auto-versioned resource '"
- << newWork.name << "'." << std::endl;
- return false;
- }
- }
- }
-
- if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
- nullptr) != android::NO_ERROR) {
- Logger::error(options.output) << "failed to write linked file '"
- << buildFileReference(item) << "' to apk." << std::endl;
- return false;
- }
- return true;
-}
-
-bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
- std::ifstream in(item.source.path, std::ifstream::binary);
- if (!in) {
- Logger::error(item.source) << strerror(errno) << std::endl;
- return false;
- }
-
- BigBuffer outBuffer(4096);
- std::string err;
- Png png;
- if (!png.process(item.source, in, &outBuffer, {}, &err)) {
- Logger::error(item.source) << err << std::endl;
- return false;
- }
-
- if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
- nullptr) != android::NO_ERROR) {
- Logger::error(options.output) << "failed to write compiled '" << item.source
- << "' to apk." << std::endl;
- return false;
- }
- return true;
-}
-
-bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
- if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
- ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
- Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
- << std::endl;
- return false;
- }
- return true;
-}
-
-bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
- const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
- const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) {
- if (options.verbose) {
- Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
- }
-
- std::ifstream in(options.manifest.path, std::ifstream::binary);
- if (!in) {
- Logger::error(options.manifest) << strerror(errno) << std::endl;
- return false;
- }
-
- SourceLogger logger(options.manifest);
- std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
- if (!root) {
- return false;
- }
-
- ManifestMerger merger({});
- if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) {
- return false;
- }
-
- for (const auto& entry : libApks) {
- ZipFile* libApk = entry.second.apk.get();
- const std::u16string& libPackage = entry.first->getPackage();
- const Source& libSource = entry.second.source;
-
- ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml");
- if (!zipEntry) {
- continue;
- }
-
- std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
- libApk->uncompress(zipEntry));
- assert(uncompressedData);
-
- SourceLogger logger(libSource);
- std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(),
- zipEntry->getUncompressedLen(), &logger);
- if (!libRoot) {
- return false;
- }
-
- if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) {
- return false;
- }
- }
-
- if (options.generateProguardRules) {
- proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(),
- keepSet);
- }
-
- BigBuffer outBuffer(1024);
- if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
- resolver, {}, &outBuffer)) {
- return false;
- }
-
- std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
-
- android::ResXMLTree tree;
- if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
- return false;
- }
-
- ManifestValidator validator(table);
- if (!validator.validate(options.manifest, &tree)) {
- return false;
- }
-
- if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
- ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
- Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
- << std::endl;
- return false;
- }
- return true;
-}
-
-static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
- const ConfigDescription& config) {
- std::ifstream in(source.path, std::ifstream::binary);
- if (!in) {
- Logger::error(source) << strerror(errno) << std::endl;
- return false;
- }
-
- std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
- ResourceParser parser(table, source, config, xmlParser);
- return parser.parse();
-}
-
-struct ResourcePathData {
- std::u16string resourceDir;
- std::u16string name;
- std::string extension;
- ConfigDescription config;
-};
-
-/**
- * Resource file paths are expected to look like:
- * [--/res/]type[-config]/name
- */
-static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
- // TODO(adamlesinski): Use Windows path separator on windows.
- std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
- if (parts.size() < 2) {
- Logger::error(source) << "bad resource path." << std::endl;
- return {};
- }
-
- std::string& dir = parts[parts.size() - 2];
- StringPiece dirStr = dir;
-
- ConfigDescription config;
- size_t dashPos = dir.find('-');
- if (dashPos != std::string::npos) {
- StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
- if (!ConfigDescription::parse(configStr, &config)) {
- Logger::error(source)
- << "invalid configuration '"
- << configStr
- << "'."
- << std::endl;
- return {};
- }
- dirStr = dirStr.substr(0, dashPos);
- }
-
- std::string& filename = parts[parts.size() - 1];
- StringPiece name = filename;
- StringPiece extension;
- size_t dotPos = filename.find('.');
- if (dotPos != std::string::npos) {
- extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
- name = name.substr(0, dotPos);
- }
-
- return ResourcePathData{
- util::utf8ToUtf16(dirStr),
- util::utf8ToUtf16(name),
- extension.toString(),
- config
- };
-}
-
-bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
- const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
- if (table->begin() != table->end()) {
- BigBuffer buffer(1024);
- TableFlattener flattener(flattenerOptions);
- if (!flattener.flatten(&buffer, *table)) {
- Logger::error() << "failed to flatten resource table." << std::endl;
- return false;
- }
-
- if (options.verbose) {
- Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
- << std::endl;
- }
-
- if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
- android::NO_ERROR) {
- Logger::note(options.output) << "failed to store resource table." << std::endl;
- return false;
- }
- }
- return true;
-}
-
-/**
- * For each FileReference in the table, adds a LinkItem to the link queue for processing.
- */
-static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
- const std::shared_ptr<ResourceTable>& table,
- const std::unique_ptr<ZipFile>& apk,
- std::queue<LinkItem>* outLinkQueue) {
- bool mangle = package != table->getPackage();
- for (auto& type : *table) {
- for (auto& entry : type->entries) {
- ResourceName name = { package, type->type, entry->name };
- if (mangle) {
- NameMangler::mangle(table->getPackage(), &name.entry);
- }
-
- for (auto& value : entry->values) {
- visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
- std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
- Source newSource = source;
- newSource.path += "/";
- newSource.path += pathUtf8;
- outLinkQueue->push(LinkItem{
- name, value.config, newSource, pathUtf8, apk.get(),
- table->getPackage() });
- // Now rewrite the file path.
- if (mangle) {
- ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
- buildFileReference(name, value.config,
- getExtension<char>(pathUtf8))));
- }
- });
- }
- }
- }
-}
-
-static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
- ZipFile::kOpenReadWrite;
-
-bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
- const std::shared_ptr<IResolver>& resolver) {
- std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
- std::unordered_set<std::u16string> linkedPackages;
-
- // Populate the linkedPackages with our own.
- linkedPackages.insert(options.appInfo.package);
-
- // Load all APK files.
- for (const Source& source : options.input) {
- std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
- if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
- Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
- return false;
- }
-
- std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
-
- ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
- if (!entry) {
- Logger::error(source) << "missing 'resources.arsc'." << std::endl;
- return false;
- }
-
- std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
- zipFile->uncompress(entry));
- assert(uncompressedData);
-
- BinaryResourceParser parser(table, resolver, source, options.appInfo.package,
- uncompressedData.get(), entry->getUncompressedLen());
- if (!parser.parse()) {
- return false;
- }
-
- // Keep track of where this table came from.
- apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
-
- // Add the package to the set of linked packages.
- linkedPackages.insert(table->getPackage());
- }
-
- std::queue<LinkItem> linkQueue;
- for (auto& p : apkFiles) {
- const std::shared_ptr<ResourceTable>& inTable = p.first;
-
- // Collect all FileReferences and add them to the queue for processing.
- addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
- &linkQueue);
-
- // Merge the tables.
- if (!outTable->merge(std::move(*inTable))) {
- return false;
- }
- }
-
- // Version all styles referencing attributes outside of their specified SDK version.
- if (options.versionStylesAndLayouts) {
- versionStylesForCompat(outTable);
- }
-
- {
- // Now that everything is merged, let's link it.
- Linker::Options linkerOptions;
- if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
- linkerOptions.linkResourceIds = false;
- }
- Linker linker(outTable, resolver, linkerOptions);
- if (!linker.linkAndValidate()) {
- return false;
- }
-
- // Verify that all symbols exist.
- const auto& unresolvedRefs = linker.getUnresolvedReferences();
- if (!unresolvedRefs.empty()) {
- for (const auto& entry : unresolvedRefs) {
- for (const auto& source : entry.second) {
- Logger::error(source) << "unresolved symbol '" << entry.first << "'."
- << std::endl;
- }
- }
- return false;
- }
- }
-
- // Open the output APK file for writing.
- ZipFile outApk;
- if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
- Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
- return false;
- }
-
- proguard::KeepSet keepSet;
-
- android::ResTable binTable;
- if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) {
- return false;
- }
-
- for (; !linkQueue.empty(); linkQueue.pop()) {
- const LinkItem& item = linkQueue.front();
-
- assert(!item.originalPackage.empty());
- ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
- if (!entry) {
- Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
- << std::endl;
- return false;
- }
-
- if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
- void* uncompressedData = item.apk->uncompress(entry);
- assert(uncompressedData);
-
- if (!linkXml(options, outTable, resolver, item, uncompressedData,
- entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) {
- Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
- << std::endl;
- return false;
- }
- } else {
- if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
- android::NO_ERROR) {
- Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
- << std::endl;
- return false;
- }
- }
- }
-
- // Generate the Java class file.
- if (options.generateJavaClass) {
- JavaClassGenerator::Options javaOptions;
- if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
- javaOptions.useFinal = false;
- }
- JavaClassGenerator generator(outTable, javaOptions);
-
- for (const std::u16string& package : linkedPackages) {
- Source outPath = options.generateJavaClass.value();
-
- // Build the output directory from the package name.
- // Eg. com.android.app -> com/android/app
- const std::string packageUtf8 = util::utf16ToUtf8(package);
- for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
- appendPath(&outPath.path, part);
- }
-
- if (!mkdirs(outPath.path)) {
- Logger::error(outPath) << strerror(errno) << std::endl;
- return false;
- }
-
- appendPath(&outPath.path, "R.java");
-
- if (options.verbose) {
- Logger::note(outPath) << "writing Java symbols." << std::endl;
- }
-
- std::ofstream fout(outPath.path);
- if (!fout) {
- Logger::error(outPath) << strerror(errno) << std::endl;
- return false;
- }
-
- if (!generator.generate(package, fout)) {
- Logger::error(outPath) << generator.getError() << "." << std::endl;
- return false;
- }
- }
- }
-
- // Generate the Proguard rules file.
- if (options.generateProguardRules) {
- const Source& outPath = options.generateProguardRules.value();
-
- if (options.verbose) {
- Logger::note(outPath) << "writing proguard rules." << std::endl;
- }
-
- std::ofstream fout(outPath.path);
- if (!fout) {
- Logger::error(outPath) << strerror(errno) << std::endl;
- return false;
- }
-
- if (!proguard::writeKeepSet(&fout, keepSet)) {
- Logger::error(outPath) << "failed to write proguard rules." << std::endl;
- return false;
- }
- }
-
- outTable->getValueStringPool().prune();
- outTable->getValueStringPool().sort(
- [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- if (a.context.priority < b.context.priority) {
- return true;
- }
-
- if (a.context.priority > b.context.priority) {
- return false;
- }
- return a.value < b.value;
- });
-
-
- // Flatten the resource table.
- TableFlattener::Options flattenerOptions;
- if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
- flattenerOptions.useExtendedChunks = false;
- }
-
- if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
- return false;
- }
-
- outApk.flush();
- return true;
-}
-
-bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
- const std::shared_ptr<IResolver>& resolver) {
- std::queue<CompileItem> compileQueue;
- bool error = false;
-
- // Compile all the resource files passed in on the command line.
- for (const Source& source : options.input) {
- // Need to parse the resource type/config/filename.
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
- return false;
- }
-
- const ResourcePathData& pathData = maybePathData.value();
- if (pathData.resourceDir == u"values") {
- // The file is in the values directory, which means its contents will
- // go into the resource table.
- if (options.verbose) {
- Logger::note(source) << "compiling values." << std::endl;
- }
-
- error |= !compileValues(table, source, pathData.config);
- } else {
- // The file is in a directory like 'layout' or 'drawable'. Find out
- // the type.
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
- << std::endl;
- return false;
- }
-
- compileQueue.push(CompileItem{
- ResourceName{ table->getPackage(), *type, pathData.name },
- pathData.config,
- source,
- pathData.extension
- });
- }
- }
-
- if (error) {
- return false;
- }
- // Open the output APK file for writing.
- ZipFile outApk;
- if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
- Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
- return false;
- }
-
- // Compile each file.
- for (; !compileQueue.empty(); compileQueue.pop()) {
- const CompileItem& item = compileQueue.front();
-
- // Add the file name to the resource table.
- error |= !addFileReference(table, item);
-
- if (item.extension == "xml") {
- error |= !compileXml(options, table, item, &outApk);
- } else if (item.extension == "png" || item.extension == "9.png") {
- error |= !compilePng(options, item, &outApk);
- } else {
- error |= !copyFile(options, item, &outApk);
- }
- }
-
- if (error) {
- return false;
- }
-
- // Link and assign resource IDs.
- Linker linker(table, resolver, {});
- if (!linker.linkAndValidate()) {
- return false;
- }
-
- // Flatten the resource table.
- if (!writeResourceTable(options, table, {}, &outApk)) {
- return false;
- }
-
- outApk.flush();
- return true;
-}
-
-bool loadAppInfo(const Source& source, AppInfo* outInfo) {
- std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
- if (!ifs) {
- Logger::error(source) << strerror(errno) << std::endl;
- return false;
- }
-
- ManifestParser parser;
- std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
- return parser.parse(source, pullParser, outInfo);
-}
-
-static void printCommandsAndDie() {
- std::cerr << "The following commands are supported:" << std::endl << std::endl;
- std::cerr << "compile compiles a subset of resources" << std::endl;
- std::cerr << "link links together compiled resources and libraries" << std::endl;
- std::cerr << "dump dumps resource contents to to standard out" << std::endl;
- std::cerr << std::endl;
- std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
- << std::endl;
- exit(1);
-}
-
-static AaptOptions prepareArgs(int argc, char** argv) {
- if (argc < 2) {
- std::cerr << "no command specified." << std::endl << std::endl;
- printCommandsAndDie();
- }
-
- const StringPiece command(argv[1]);
- argc -= 2;
- argv += 2;
-
- AaptOptions options;
-
- if (command == "--version" || command == "version") {
- std::cout << kAaptVersionStr << std::endl;
- exit(0);
- } else if (command == "link") {
- options.phase = AaptOptions::Phase::Link;
- } else if (command == "compile") {
- options.phase = AaptOptions::Phase::Compile;
- } else if (command == "dump") {
- options.phase = AaptOptions::Phase::Dump;
- } else if (command == "dump-style-graph") {
- options.phase = AaptOptions::Phase::DumpStyleGraph;
- } else {
- std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
- printCommandsAndDie();
- }
-
- bool isStaticLib = false;
- if (options.phase == AaptOptions::Phase::Link) {
- flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
- [&options](const StringPiece& arg) {
- options.manifest = Source{ arg.toString() };
- });
-
- flag::optionalFlag("-I", "add an Android APK to link against",
- [&options](const StringPiece& arg) {
- options.libraries.push_back(Source{ arg.toString() });
- });
-
- flag::optionalFlag("--java", "directory in which to generate R.java",
- [&options](const StringPiece& arg) {
- options.generateJavaClass = Source{ arg.toString() };
- });
-
- flag::optionalFlag("--proguard", "file in which to output proguard rules",
- [&options](const StringPiece& arg) {
- options.generateProguardRules = Source{ arg.toString() };
- });
-
- flag::optionalSwitch("--static-lib", "generate a static Android library", true,
- &isStaticLib);
-
- flag::optionalFlag("--binding", "Output directory for binding XML files",
- [&options](const StringPiece& arg) {
- options.bindingOutput = Source{ arg.toString() };
- });
- flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
- false, &options.versionStylesAndLayouts);
- }
-
- if (options.phase == AaptOptions::Phase::Compile ||
- options.phase == AaptOptions::Phase::Link) {
- // Common flags for all steps.
- flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
- options.output = Source{ arg.toString() };
- });
- }
-
- if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
- flag::requiredFlag("--style", "Name of the style to dump",
- [&options](const StringPiece& arg, std::string* outError) -> bool {
- Reference styleReference;
- if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
- &styleReference, outError)) {
- return false;
- }
- options.dumpStyleTarget = styleReference.name;
- return true;
- });
- }
-
- bool help = false;
- flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
- flag::optionalSwitch("-h", "displays this help menu", true, &help);
-
- // Build the command string for output (eg. "aapt2 compile").
- std::string fullCommand = "aapt2";
- fullCommand += " ";
- fullCommand += command.toString();
-
- // Actually read the command line flags.
- flag::parse(argc, argv, fullCommand);
-
- if (help) {
- flag::usageAndDie(fullCommand);
- }
-
- if (isStaticLib) {
- options.packageType = AaptOptions::PackageType::StaticLibrary;
- }
-
- // Copy all the remaining arguments.
- for (const std::string& arg : flag::getArgs()) {
- options.input.push_back(Source{ arg });
- }
- return options;
-}
-
-static bool doDump(const AaptOptions& options) {
- for (const Source& source : options.input) {
- std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
- if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
- Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
- return false;
- }
-
- std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
- std::shared_ptr<ResourceTableResolver> resolver =
- std::make_shared<ResourceTableResolver>(
- table, std::vector<std::shared_ptr<const android::AssetManager>>());
-
- ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
- if (!entry) {
- Logger::error(source) << "missing 'resources.arsc'." << std::endl;
- return false;
- }
-
- std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
- zipFile->uncompress(entry));
- assert(uncompressedData);
-
- BinaryResourceParser parser(table, resolver, source, {}, uncompressedData.get(),
- entry->getUncompressedLen());
- if (!parser.parse()) {
- return false;
- }
-
- if (options.phase == AaptOptions::Phase::Dump) {
- Debug::printTable(table);
- } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
- Debug::printStyleGraph(table, options.dumpStyleTarget);
- }
- }
- return true;
-}
+} // namespace aapt
int main(int argc, char** argv) {
- Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
- AaptOptions options = prepareArgs(argc, argv);
+ if (argc >= 2) {
+ argv += 1;
+ argc -= 1;
- if (options.phase == AaptOptions::Phase::Dump ||
- options.phase == AaptOptions::Phase::DumpStyleGraph) {
- if (!doDump(options)) {
- return 1;
- }
- return 0;
- }
-
- // If we specified a manifest, go ahead and load the package name from the manifest.
- if (!options.manifest.path.empty()) {
- if (!loadAppInfo(options.manifest, &options.appInfo)) {
- return false;
+ std::vector<aapt::StringPiece> args;
+ for (int i = 1; i < argc; i++) {
+ args.push_back(argv[i]);
}
- if (options.appInfo.package.empty()) {
- Logger::error() << "no package name specified." << std::endl;
- return false;
+ aapt::StringPiece command(argv[0]);
+ if (command == "compile" || command == "c") {
+ return aapt::compile(args);
+ } else if (command == "link" || command == "l") {
+ return aapt::link(args);
+ } else if (command == "dump" || command == "d") {
+ return aapt::dump(args);
}
- }
-
- // Every phase needs a resource table.
- std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
-
- // The package name is empty when in the compile phase.
- table->setPackage(options.appInfo.package);
- if (options.appInfo.package == u"android") {
- table->setPackageId(0x01);
+ std::cerr << "unknown command '" << command << "'\n";
} else {
- table->setPackageId(0x7f);
- }
-
- // Load the included libraries.
- std::vector<std::shared_ptr<const android::AssetManager>> sources;
- for (const Source& source : options.libraries) {
- std::shared_ptr<android::AssetManager> assetManager =
- std::make_shared<android::AssetManager>();
- int32_t cookie;
- if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
- Logger::error(source) << "failed to load library." << std::endl;
- return false;
- }
-
- if (cookie == 0) {
- Logger::error(source) << "failed to load library." << std::endl;
- return false;
- }
- sources.push_back(assetManager);
+ std::cerr << "no command specified\n";
}
- // Make the resolver that will cache IDs for us.
- std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
- table, sources);
-
- if (options.phase == AaptOptions::Phase::Compile) {
- if (!compile(options, table, resolver)) {
- Logger::error() << "aapt exiting with failures." << std::endl;
- return 1;
- }
- } else if (options.phase == AaptOptions::Phase::Link) {
- if (!link(options, table, resolver)) {
- Logger::error() << "aapt exiting with failures." << std::endl;
- return 1;
- }
- }
- return 0;
+ std::cerr << "\nusage: aapt2 [compile|link|dump] ..." << std::endl;
+ return 1;
}
diff --git a/tools/aapt2/ManifestMerger.cpp b/tools/aapt2/ManifestMerger.cpp
deleted file mode 100644
index 71d3424c6ad8..000000000000
--- a/tools/aapt2/ManifestMerger.cpp
+++ /dev/null
@@ -1,376 +0,0 @@
-#include "ManifestMerger.h"
-#include "Maybe.h"
-#include "ResourceParser.h"
-#include "Source.h"
-#include "Util.h"
-#include "XmlPullParser.h"
-
-#include <iostream>
-#include <memory>
-#include <set>
-#include <string>
-
-namespace aapt {
-
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
-static xml::Element* findManifest(xml::Node* root) {
- if (!root) {
- return nullptr;
- }
-
- while (root->type == xml::NodeType::kNamespace) {
- if (root->children.empty()) {
- break;
- }
- root = root->children[0].get();
- }
-
- if (root && root->type == xml::NodeType::kElement) {
- xml::Element* el = static_cast<xml::Element*>(root);
- if (el->namespaceUri.empty() && el->name == u"manifest") {
- return el;
- }
- }
- return nullptr;
-}
-
-static xml::Element* findChildWithSameName(xml::Element* parent, xml::Element* src) {
- xml::Attribute* attrKey = src->findAttribute(kSchemaAndroid, u"name");
- if (!attrKey) {
- return nullptr;
- }
- return parent->findChildWithAttribute(src->namespaceUri, src->name, attrKey);
-}
-
-static bool attrLess(const xml::Attribute& lhs, const xml::Attribute& rhs) {
- return std::tie(lhs.namespaceUri, lhs.name, lhs.value)
- < std::tie(rhs.namespaceUri, rhs.name, rhs.value);
-}
-
-static int compare(xml::Element* lhs, xml::Element* rhs) {
- int diff = lhs->attributes.size() - rhs->attributes.size();
- if (diff != 0) {
- return diff;
- }
-
- std::set<xml::Attribute, decltype(&attrLess)> lhsAttrs(&attrLess);
- lhsAttrs.insert(lhs->attributes.begin(), lhs->attributes.end());
- for (auto& attr : rhs->attributes) {
- if (lhsAttrs.erase(attr) == 0) {
- // The rhs attribute is not in the left.
- return -1;
- }
- }
-
- if (!lhsAttrs.empty()) {
- // The lhs has attributes not in the rhs.
- return 1;
- }
- return 0;
-}
-
-ManifestMerger::ManifestMerger(const Options& options) :
- mOptions(options), mAppLogger({}), mLogger({}) {
-}
-
-bool ManifestMerger::setAppManifest(const Source& source, const std::u16string& package,
- std::unique_ptr<xml::Node> root) {
-
- mAppLogger = SourceLogger{ source };
- mRoot = std::move(root);
- return true;
-}
-
-bool ManifestMerger::checkEqual(xml::Element* elA, xml::Element* elB) {
- if (compare(elA, elB) != 0) {
- mLogger.error(elB->lineNumber)
- << "library tag '" << elB->name << "' conflicts with app tag."
- << std::endl;
- mAppLogger.note(elA->lineNumber)
- << "app tag '" << elA->name << "' defined here."
- << std::endl;
- return false;
- }
-
- std::vector<xml::Element*> childrenA = elA->getChildElements();
- std::vector<xml::Element*> childrenB = elB->getChildElements();
-
- if (childrenA.size() != childrenB.size()) {
- mLogger.error(elB->lineNumber)
- << "library tag '" << elB->name << "' children conflict with app tag."
- << std::endl;
- mAppLogger.note(elA->lineNumber)
- << "app tag '" << elA->name << "' defined here."
- << std::endl;
- return false;
- }
-
- auto cmp = [](xml::Element* lhs, xml::Element* rhs) -> bool {
- return compare(lhs, rhs) < 0;
- };
-
- std::sort(childrenA.begin(), childrenA.end(), cmp);
- std::sort(childrenB.begin(), childrenB.end(), cmp);
-
- for (size_t i = 0; i < childrenA.size(); i++) {
- if (!checkEqual(childrenA[i], childrenB[i])) {
- return false;
- }
- }
- return true;
-}
-
-bool ManifestMerger::mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB) {
- if (!elA) {
- parentA->addChild(elB->clone());
- return true;
- }
- return checkEqual(elA, elB);
-}
-
-bool ManifestMerger::mergePreferRequired(xml::Element* parentA, xml::Element* elA,
- xml::Element* elB) {
- if (!elA) {
- parentA->addChild(elB->clone());
- return true;
- }
-
- xml::Attribute* reqA = elA->findAttribute(kSchemaAndroid, u"required");
- xml::Attribute* reqB = elB->findAttribute(kSchemaAndroid, u"required");
- bool requiredA = !reqA || (reqA->value != u"false" && reqA->value != u"FALSE");
- bool requiredB = !reqB || (reqB->value != u"false" && reqB->value != u"FALSE");
- if (!requiredA && requiredB) {
- if (reqA) {
- *reqA = xml::Attribute{ kSchemaAndroid, u"required", u"true" };
- } else {
- elA->attributes.push_back(xml::Attribute{ kSchemaAndroid, u"required", u"true" });
- }
- }
- return true;
-}
-
-static int findIntegerValue(xml::Attribute* attr, int defaultValue) {
- if (attr) {
- std::unique_ptr<BinaryPrimitive> integer = ResourceParser::tryParseInt(attr->value);
- if (integer) {
- return integer->value.data;
- }
- }
- return defaultValue;
-}
-
-bool ManifestMerger::mergeUsesSdk(xml::Element* elA, xml::Element* elB) {
- bool error = false;
- xml::Attribute* minAttrA = nullptr;
- xml::Attribute* minAttrB = nullptr;
- if (elA) {
- minAttrA = elA->findAttribute(kSchemaAndroid, u"minSdkVersion");
- }
-
- if (elB) {
- minAttrB = elB->findAttribute(kSchemaAndroid, u"minSdkVersion");
- }
-
- int minSdkA = findIntegerValue(minAttrA, 1);
- int minSdkB = findIntegerValue(minAttrB, 1);
-
- if (minSdkA < minSdkB) {
- std::ostream* out;
- if (minAttrA) {
- out = &(mAppLogger.error(elA->lineNumber) << "app declares ");
- } else if (elA) {
- out = &(mAppLogger.error(elA->lineNumber) << "app has implied ");
- } else {
- out = &(mAppLogger.error() << "app has implied ");
- }
-
- *out << "minSdkVersion=" << minSdkA << " but library expects a higher SDK version."
- << std::endl;
-
- // elB is valid because minSdkB wouldn't be greater than minSdkA if it wasn't.
- mLogger.note(elB->lineNumber)
- << "library declares minSdkVersion=" << minSdkB << "."
- << std::endl;
- error = true;
- }
-
- xml::Attribute* targetAttrA = nullptr;
- xml::Attribute* targetAttrB = nullptr;
-
- if (elA) {
- targetAttrA = elA->findAttribute(kSchemaAndroid, u"targetSdkVersion");
- }
-
- if (elB) {
- targetAttrB = elB->findAttribute(kSchemaAndroid, u"targetSdkVersion");
- }
-
- int targetSdkA = findIntegerValue(targetAttrA, minSdkA);
- int targetSdkB = findIntegerValue(targetAttrB, minSdkB);
-
- if (targetSdkA < targetSdkB) {
- std::ostream* out;
- if (targetAttrA) {
- out = &(mAppLogger.warn(elA->lineNumber) << "app declares ");
- } else if (elA) {
- out = &(mAppLogger.warn(elA->lineNumber) << "app has implied ");
- } else {
- out = &(mAppLogger.warn() << "app has implied ");
- }
-
- *out << "targetSdkVerion=" << targetSdkA << " but library expects target SDK "
- << targetSdkB << "." << std::endl;
-
- mLogger.note(elB->lineNumber)
- << "library declares targetSdkVersion=" << targetSdkB << "."
- << std::endl;
- error = true;
- }
- return !error;
-}
-
-bool ManifestMerger::mergeApplication(xml::Element* applicationA, xml::Element* applicationB) {
- if (!applicationA || !applicationB) {
- return true;
- }
-
- bool error = false;
-
- // First make sure that the names are identical.
- xml::Attribute* nameA = applicationA->findAttribute(kSchemaAndroid, u"name");
- xml::Attribute* nameB = applicationB->findAttribute(kSchemaAndroid, u"name");
- if (nameB) {
- if (!nameA) {
- applicationA->attributes.push_back(*nameB);
- } else if (nameA->value != nameB->value) {
- mLogger.error(applicationB->lineNumber)
- << "conflicting application name '"
- << nameB->value
- << "'." << std::endl;
- mAppLogger.note(applicationA->lineNumber)
- << "application defines application name '"
- << nameA->value
- << "'." << std::endl;
- error = true;
- }
- }
-
- // Now we descend into the activity/receiver/service/provider tags
- for (xml::Element* elB : applicationB->getChildElements()) {
- if (!elB->namespaceUri.empty()) {
- continue;
- }
-
- if (elB->name == u"activity" || elB->name == u"activity-alias"
- || elB->name == u"service" || elB->name == u"receiver"
- || elB->name == u"provider" || elB->name == u"meta-data") {
- xml::Element* elA = findChildWithSameName(applicationA, elB);
- error |= !mergeNewOrEqual(applicationA, elA, elB);
- } else if (elB->name == u"uses-library") {
- xml::Element* elA = findChildWithSameName(applicationA, elB);
- error |= !mergePreferRequired(applicationA, elA, elB);
- }
- }
- return !error;
-}
-
-bool ManifestMerger::mergeLibraryManifest(const Source& source, const std::u16string& package,
- std::unique_ptr<xml::Node> libRoot) {
- mLogger = SourceLogger{ source };
- xml::Element* manifestA = findManifest(mRoot.get());
- xml::Element* manifestB = findManifest(libRoot.get());
- if (!manifestA) {
- mAppLogger.error() << "missing manifest tag." << std::endl;
- return false;
- }
-
- if (!manifestB) {
- mLogger.error() << "library missing manifest tag." << std::endl;
- return false;
- }
-
- bool error = false;
-
- // Do <application> first.
- xml::Element* applicationA = manifestA->findChild({}, u"application");
- xml::Element* applicationB = manifestB->findChild({}, u"application");
- error |= !mergeApplication(applicationA, applicationB);
-
- // Do <uses-sdk> next.
- xml::Element* usesSdkA = manifestA->findChild({}, u"uses-sdk");
- xml::Element* usesSdkB = manifestB->findChild({}, u"uses-sdk");
- error |= !mergeUsesSdk(usesSdkA, usesSdkB);
-
- for (xml::Element* elB : manifestB->getChildElements()) {
- if (!elB->namespaceUri.empty()) {
- continue;
- }
-
- if (elB->name == u"uses-permission" || elB->name == u"permission"
- || elB->name == u"permission-group" || elB->name == u"permission-tree") {
- xml::Element* elA = findChildWithSameName(manifestA, elB);
- error |= !mergeNewOrEqual(manifestA, elA, elB);
- } else if (elB->name == u"uses-feature") {
- xml::Element* elA = findChildWithSameName(manifestA, elB);
- error |= !mergePreferRequired(manifestA, elA, elB);
- } else if (elB->name == u"uses-configuration" || elB->name == u"supports-screen"
- || elB->name == u"compatible-screens" || elB->name == u"supports-gl-texture") {
- xml::Element* elA = findChildWithSameName(manifestA, elB);
- error |= !checkEqual(elA, elB);
- }
- }
- return !error;
-}
-
-static void printMerged(xml::Node* node, int depth) {
- std::string indent;
- for (int i = 0; i < depth; i++) {
- indent += " ";
- }
-
- switch (node->type) {
- case xml::NodeType::kNamespace:
- std::cerr << indent << "N: "
- << "xmlns:" << static_cast<xml::Namespace*>(node)->namespacePrefix
- << "=\"" << static_cast<xml::Namespace*>(node)->namespaceUri
- << "\"\n";
- break;
-
- case xml::NodeType::kElement:
- std::cerr << indent << "E: "
- << static_cast<xml::Element*>(node)->namespaceUri
- << ":" << static_cast<xml::Element*>(node)->name
- << "\n";
- for (const auto& attr : static_cast<xml::Element*>(node)->attributes) {
- std::cerr << indent << " A: "
- << attr.namespaceUri
- << ":" << attr.name
- << "=\"" << attr.value << "\"\n";
- }
- break;
-
- case xml::NodeType::kText:
- std::cerr << indent << "T: \"" << static_cast<xml::Text*>(node)->text << "\"\n";
- break;
- }
-
- for (auto& child : node->children) {
- printMerged(child.get(), depth + 1);
- }
-}
-
-xml::Node* ManifestMerger::getMergedXml() {
- return mRoot.get();
-}
-
-bool ManifestMerger::printMerged() {
- if (!mRoot) {
- return false;
- }
-
- ::aapt::printMerged(mRoot.get(), 0);
- return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestMerger.h b/tools/aapt2/ManifestMerger.h
deleted file mode 100644
index c6219dbba65e..000000000000
--- a/tools/aapt2/ManifestMerger.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#ifndef AAPT_MANIFEST_MERGER_H
-#define AAPT_MANIFEST_MERGER_H
-
-#include "Logger.h"
-#include "Source.h"
-#include "XmlDom.h"
-
-#include <memory>
-#include <string>
-
-namespace aapt {
-
-class ManifestMerger {
-public:
- struct Options {
- };
-
- ManifestMerger(const Options& options);
-
- bool setAppManifest(const Source& source, const std::u16string& package,
- std::unique_ptr<xml::Node> root);
-
- bool mergeLibraryManifest(const Source& source, const std::u16string& package,
- std::unique_ptr<xml::Node> libRoot);
-
- xml::Node* getMergedXml();
-
- bool printMerged();
-
-private:
- bool mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
- bool mergePreferRequired(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
- bool checkEqual(xml::Element* elA, xml::Element* elB);
- bool mergeApplication(xml::Element* applicationA, xml::Element* applicationB);
- bool mergeUsesSdk(xml::Element* elA, xml::Element* elB);
-
- Options mOptions;
- std::unique_ptr<xml::Node> mRoot;
- SourceLogger mAppLogger;
- SourceLogger mLogger;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MANIFEST_MERGER_H
diff --git a/tools/aapt2/ManifestMerger_test.cpp b/tools/aapt2/ManifestMerger_test.cpp
deleted file mode 100644
index 6838253dad20..000000000000
--- a/tools/aapt2/ManifestMerger_test.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ManifestMerger.h"
-#include "SourceXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-constexpr const char* kAppManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
- <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-feature android:name="android.hardware.GPS" android:required="false" />
- <application android:name="com.android.library.Application">
- <activity android:name="com.android.example.MainActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
- <service android:name="com.android.library.Service">
- <intent-filter>
- <action android:name="com.android.library.intent.action.SYNC" />
- </intent-filter>
- </service>
- </application>
-</manifest>
-)EOF";
-
-constexpr const char* kLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
- <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" />
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-feature android:name="android.hardware.GPS" />
- <uses-permission android:name="android.permission.GPS" />
- <application android:name="com.android.library.Application">
- <service android:name="com.android.library.Service">
- <intent-filter>
- <action android:name="com.android.library.intent.action.SYNC" />
- </intent-filter>
- </service>
- <provider android:name="com.android.library.DocumentProvider"
- android:authorities="com.android.library.documents"
- android:grantUriPermission="true"
- android:exported="true"
- android:permission="android.permission.MANAGE_DOCUMENTS"
- android:enabled="@bool/atLeastKitKat">
- <intent-filter>
- <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
- </intent-filter>
- </provider>
- </application>
-</manifest>
-)EOF";
-
-constexpr const char* kBadLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
- <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="22" />
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-feature android:name="android.hardware.GPS" />
- <uses-permission android:name="android.permission.GPS" />
- <application android:name="com.android.library.Application2">
- <service android:name="com.android.library.Service">
- <intent-filter>
- <action android:name="com.android.library.intent.action.SYNC_ACTION" />
- </intent-filter>
- </service>
- </application>
-</manifest>
-)EOF";
-
-TEST(ManifestMergerTest, MergeManifestsSuccess) {
- std::stringstream inA(kAppManifest);
- std::stringstream inB(kLibManifest);
-
- const Source sourceA = { "AndroidManifest.xml" };
- const Source sourceB = { "lib.apk/AndroidManifest.xml" };
- SourceLogger loggerA(sourceA);
- SourceLogger loggerB(sourceB);
-
- ManifestMerger merger({});
- EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
- xml::inflate(&inA, &loggerA)));
- EXPECT_TRUE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
- xml::inflate(&inB, &loggerB)));
-}
-
-TEST(ManifestMergerTest, MergeManifestFail) {
- std::stringstream inA(kAppManifest);
- std::stringstream inB(kBadLibManifest);
-
- const Source sourceA = { "AndroidManifest.xml" };
- const Source sourceB = { "lib.apk/AndroidManifest.xml" };
- SourceLogger loggerA(sourceA);
- SourceLogger loggerB(sourceB);
-
- ManifestMerger merger({});
- EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
- xml::inflate(&inA, &loggerA)));
- EXPECT_FALSE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
- xml::inflate(&inB, &loggerB)));
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestParser.cpp b/tools/aapt2/ManifestParser.cpp
deleted file mode 100644
index b8f0a430bcee..000000000000
--- a/tools/aapt2/ManifestParser.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "AppInfo.h"
-#include "Logger.h"
-#include "ManifestParser.h"
-#include "Source.h"
-#include "XmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-bool ManifestParser::parse(const Source& source, std::shared_ptr<XmlPullParser> parser,
- AppInfo* outInfo) {
- SourceLogger logger = { source };
-
- int depth = 0;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- XmlPullParser::Event event = parser->getEvent();
- if (event == XmlPullParser::Event::kEndElement) {
- depth--;
- continue;
- } else if (event != XmlPullParser::Event::kStartElement) {
- continue;
- }
-
- depth++;
-
- const std::u16string& element = parser->getElementName();
- if (depth == 1) {
- if (element == u"manifest") {
- if (!parseManifest(logger, parser, outInfo)) {
- return false;
- }
- } else {
- logger.error()
- << "unexpected top-level element '"
- << element
- << "'."
- << std::endl;
- return false;
- }
- } else {
- XmlPullParser::skipCurrentElement(parser.get());
- }
- }
-
- if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
- logger.error(parser->getLineNumber())
- << "failed to parse manifest: "
- << parser->getLastError()
- << "."
- << std::endl;
- return false;
- }
- return true;
-}
-
-bool ManifestParser::parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
- AppInfo* outInfo) {
- auto attrIter = parser->findAttribute(u"", u"package");
- if (attrIter == parser->endAttributes() || attrIter->value.empty()) {
- logger.error() << "no 'package' attribute found for element <manifest>." << std::endl;
- return false;
- }
- outInfo->package = attrIter->value;
- return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestParser_test.cpp b/tools/aapt2/ManifestParser_test.cpp
deleted file mode 100644
index be3a6fbe614a..000000000000
--- a/tools/aapt2/ManifestParser_test.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "AppInfo.h"
-#include "ManifestParser.h"
-#include "SourceXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-TEST(ManifestParserTest, FindPackage) {
- std::stringstream input;
- input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- "package=\"android\">\n"
- "</manifest>\n";
-
- ManifestParser parser;
- AppInfo info;
- std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
- ASSERT_TRUE(parser.parse(Source{ "AndroidManifest.xml" }, xmlParser, &info));
-
- EXPECT_EQ(std::u16string(u"android"), info.package);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp
deleted file mode 100644
index 123b9fae2fba..000000000000
--- a/tools/aapt2/ManifestValidator.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Logger.h"
-#include "ManifestValidator.h"
-#include "Maybe.h"
-#include "Source.h"
-#include "Util.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-ManifestValidator::ManifestValidator(const android::ResTable& table)
-: mTable(table) {
-}
-
-bool ManifestValidator::validate(const Source& source, android::ResXMLParser* parser) {
- SourceLogger logger(source);
-
- android::ResXMLParser::event_code_t code;
- while ((code = parser->next()) != android::ResXMLParser::END_DOCUMENT &&
- code != android::ResXMLParser::BAD_DOCUMENT) {
- if (code != android::ResXMLParser::START_TAG) {
- continue;
- }
-
- size_t len = 0;
- const StringPiece16 namespaceUri(parser->getElementNamespace(&len), len);
- if (!namespaceUri.empty()) {
- continue;
- }
-
- const StringPiece16 name(parser->getElementName(&len), len);
- if (name.empty()) {
- logger.error(parser->getLineNumber())
- << "failed to get the element name."
- << std::endl;
- return false;
- }
-
- if (name == u"manifest") {
- if (!validateManifest(source, parser)) {
- return false;
- }
- }
- }
- return true;
-}
-
-Maybe<StringPiece16> ManifestValidator::getAttributeValue(android::ResXMLParser* parser,
- size_t idx) {
- android::Res_value value;
- if (parser->getAttributeValue(idx, &value) < 0) {
- return StringPiece16();
- }
-
- const android::ResStringPool* pool = &parser->getStrings();
- if (value.dataType == android::Res_value::TYPE_REFERENCE) {
- ssize_t strIdx = mTable.resolveReference(&value, 0x10000000u);
- if (strIdx < 0) {
- return {};
- }
- pool = mTable.getTableStringBlock(strIdx);
- }
-
- if (value.dataType != android::Res_value::TYPE_STRING || !pool) {
- return {};
- }
- return util::getString(*pool, value.data);
-}
-
-Maybe<StringPiece16> ManifestValidator::getAttributeInlineValue(android::ResXMLParser* parser,
- size_t idx) {
- android::Res_value value;
- if (parser->getAttributeValue(idx, &value) < 0) {
- return StringPiece16();
- }
-
- if (value.dataType != android::Res_value::TYPE_STRING) {
- return {};
- }
- return util::getString(parser->getStrings(), value.data);
-}
-
-bool ManifestValidator::validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
- SourceLogger& logger,
- const StringPiece16& charSet) {
- size_t len = 0;
- StringPiece16 element(parser->getElementName(&len), len);
- StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
- Maybe<StringPiece16> result = getAttributeInlineValue(parser, idx);
- if (!result) {
- logger.error(parser->getLineNumber())
- << "<"
- << element
- << "> must have a '"
- << attributeName
- << "' attribute with a string literal value."
- << std::endl;
- return false;
- }
- return validateAttributeImpl(element, attributeName, result.value(), charSet,
- parser->getLineNumber(), logger);
-}
-
-bool ManifestValidator::validateAttribute(android::ResXMLParser* parser, size_t idx,
- SourceLogger& logger, const StringPiece16& charSet) {
- size_t len = 0;
- StringPiece16 element(parser->getElementName(&len), len);
- StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
- Maybe<StringPiece16> result = getAttributeValue(parser, idx);
- if (!result) {
- logger.error(parser->getLineNumber())
- << "<"
- << element
- << "> must have a '"
- << attributeName
- << "' attribute that points to a string."
- << std::endl;
- return false;
- }
- return validateAttributeImpl(element, attributeName, result.value(), charSet,
- parser->getLineNumber(), logger);
-}
-
-bool ManifestValidator::validateAttributeImpl(const StringPiece16& element,
- const StringPiece16& attributeName,
- const StringPiece16& attributeValue,
- const StringPiece16& charSet, size_t lineNumber,
- SourceLogger& logger) {
- StringPiece16::const_iterator badIter =
- util::findNonAlphaNumericAndNotInSet(attributeValue, charSet);
- if (badIter != attributeValue.end()) {
- logger.error(lineNumber)
- << "tag <"
- << element
- << "> attribute '"
- << attributeName
- << "' has invalid character '"
- << StringPiece16(badIter, 1)
- << "'."
- << std::endl;
- return false;
- }
-
- if (!attributeValue.empty()) {
- StringPiece16 trimmed = util::trimWhitespace(attributeValue);
- if (attributeValue.begin() != trimmed.begin()) {
- logger.error(lineNumber)
- << "tag <"
- << element
- << "> attribute '"
- << attributeName
- << "' can not start with whitespace."
- << std::endl;
- return false;
- }
-
- if (attributeValue.end() != trimmed.end()) {
- logger.error(lineNumber)
- << "tag <"
- << element
- << "> attribute '"
- << attributeName
- << "' can not end with whitespace."
- << std::endl;
- return false;
- }
- }
- return true;
-}
-
-constexpr const char16_t* kPackageIdentSet = u"._";
-
-bool ManifestValidator::validateManifest(const Source& source, android::ResXMLParser* parser) {
- bool error = false;
- SourceLogger logger(source);
-
- const StringPiece16 kAndroid = u"android";
- const StringPiece16 kPackage = u"package";
- const StringPiece16 kSharedUserId = u"sharedUserId";
-
- ssize_t idx;
-
- idx = parser->indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
- if (idx < 0) {
- logger.error(parser->getLineNumber())
- << "missing package attribute."
- << std::endl;
- error = true;
- } else {
- error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
- }
-
- idx = parser->indexOfAttribute(kAndroid.data(), kAndroid.size(),
- kSharedUserId.data(), kSharedUserId.size());
- if (idx >= 0) {
- error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
- }
- return !error;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h
deleted file mode 100644
index 318878499cfa..000000000000
--- a/tools/aapt2/ManifestValidator.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_MANIFEST_VALIDATOR_H
-#define AAPT_MANIFEST_VALIDATOR_H
-
-#include "Logger.h"
-#include "Maybe.h"
-#include "Source.h"
-#include "StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-class ManifestValidator {
-public:
- ManifestValidator(const android::ResTable& table);
- ManifestValidator(const ManifestValidator&) = delete;
-
- bool validate(const Source& source, android::ResXMLParser* parser);
-
-private:
- bool validateManifest(const Source& source, android::ResXMLParser* parser);
-
- Maybe<StringPiece16> getAttributeInlineValue(android::ResXMLParser* parser, size_t idx);
- Maybe<StringPiece16> getAttributeValue(android::ResXMLParser* parser, size_t idx);
-
- bool validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
- SourceLogger& logger, const StringPiece16& charSet);
- bool validateAttribute(android::ResXMLParser* parser, size_t idx, SourceLogger& logger,
- const StringPiece16& charSet);
- bool validateAttributeImpl(const StringPiece16& element, const StringPiece16& attributeName,
- const StringPiece16& attributeValue, const StringPiece16& charSet,
- size_t lineNumber, SourceLogger& logger);
-
- const android::ResTable& mTable;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MANIFEST_VALIDATOR_H
diff --git a/tools/aapt2/MockResolver.h b/tools/aapt2/MockResolver.h
deleted file mode 100644
index 0c9b95464186..000000000000
--- a/tools/aapt2/MockResolver.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_MOCK_RESOLVER_H
-#define AAPT_MOCK_RESOLVER_H
-
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "StringPiece.h"
-
-#include <map>
-#include <string>
-
-namespace aapt {
-
-struct MockResolver : public IResolver {
- MockResolver(const std::shared_ptr<ResourceTable>& table,
- const std::map<ResourceName, ResourceId>& items) :
- mResolver(std::make_shared<ResourceTableResolver>(
- table, std::vector<std::shared_ptr<const android::AssetManager>>())),
- mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) {
- }
-
- virtual Maybe<ResourceId> findId(const ResourceName& name) override {
- Maybe<ResourceId> result = mResolver->findId(name);
- if (result) {
- return result;
- }
-
- const auto iter = mItems.find(name);
- if (iter != mItems.end()) {
- return iter->second;
- }
- return {};
- }
-
- virtual Maybe<Entry> findAttribute(const ResourceName& name) override {
- Maybe<Entry> tableResult = mResolver->findAttribute(name);
- if (tableResult) {
- return tableResult;
- }
-
- Maybe<ResourceId> result = findId(name);
- if (result) {
- if (name.type == ResourceType::kAttr) {
- return Entry{ result.value(), &mAttr };
- } else {
- return Entry{ result.value() };
- }
- }
- return {};
- }
-
- virtual Maybe<ResourceName> findName(ResourceId resId) override {
- Maybe<ResourceName> result = mResolver->findName(resId);
- if (result) {
- return result;
- }
-
- for (auto& p : mItems) {
- if (p.second == resId) {
- return p.first;
- }
- }
- return {};
- }
-
-private:
- std::shared_ptr<ResourceTableResolver> mResolver;
- Attribute mAttr;
- std::map<ResourceName, ResourceId> mItems;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MOCK_RESOLVER_H
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index 1e15e2071e65..054b9ee116f4 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -17,19 +17,63 @@
#ifndef AAPT_NAME_MANGLER_H
#define AAPT_NAME_MANGLER_H
+#include "Resource.h"
+
+#include "util/Maybe.h"
+
+#include <set>
#include <string>
namespace aapt {
-struct NameMangler {
+struct NameManglerPolicy {
+ /**
+ * Represents the package we are trying to build. References pointing
+ * to this package are not mangled, and mangled references inherit this package name.
+ */
+ std::u16string targetPackageName;
+
+ /**
+ * We must know which references to mangle, and which to keep (android vs. com.android.support).
+ */
+ std::set<std::u16string> packagesToMangle;
+};
+
+class NameMangler {
+private:
+ NameManglerPolicy mPolicy;
+
+public:
+ NameMangler(NameManglerPolicy policy) : mPolicy(policy) {
+ }
+
+ Maybe<ResourceName> mangleName(const ResourceName& name) {
+ if (mPolicy.targetPackageName == name.package ||
+ mPolicy.packagesToMangle.count(name.package) == 0) {
+ return {};
+ }
+
+ return ResourceName{
+ mPolicy.targetPackageName,
+ name.type,
+ mangleEntry(name.package, name.entry)
+ };
+ }
+
+ bool shouldMangle(const std::u16string& package) const {
+ if (package.empty() || mPolicy.targetPackageName == package) {
+ return false;
+ }
+ return mPolicy.packagesToMangle.count(package) != 0;
+ }
+
/**
- * Mangles the name in `outName` with the `package` and stores the mangled
- * result in `outName`. The mangled name should contain symbols that are
- * illegal to define in XML, so that there will never be name mangling
- * collisions.
+ * Returns a mangled name that is a combination of `name` and `package`.
+ * The mangled name should contain symbols that are illegal to define in XML,
+ * so that there will never be name mangling collisions.
*/
- static void mangle(const std::u16string& package, std::u16string* outName) {
- *outName = package + u"$" + *outName;
+ static std::u16string mangleEntry(const std::u16string& package, const std::u16string& name) {
+ return package + u"$" + name;
}
/**
diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h
deleted file mode 100644
index cb9318e24917..000000000000
--- a/tools/aapt2/Resolver.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_RESOLVER_H
-#define AAPT_RESOLVER_H
-
-#include "Maybe.h"
-#include "Resource.h"
-#include "ResourceValues.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-/**
- * Resolves symbolic references (package:type/entry) into resource IDs/objects.
- */
-class IResolver {
-public:
- virtual ~IResolver() {};
-
- /**
- * Holds the result of a resource name lookup.
- */
- struct Entry {
- /**
- * The ID of the resource. ResourceId::isValid() may
- * return false if the resource has not been assigned
- * an ID.
- */
- ResourceId id;
-
- /**
- * If the resource is an attribute, this will point
- * to a valid Attribute object, or else it will be
- * nullptr.
- */
- const Attribute* attr;
- };
-
- /**
- * Returns a ResourceID if the name is found. The ResourceID
- * may not be valid if the resource was not assigned an ID.
- */
- virtual Maybe<ResourceId> findId(const ResourceName& name) = 0;
-
- /**
- * Returns an Entry if the name is found. Entry::attr
- * may be nullptr if the resource is not an attribute.
- */
- virtual Maybe<Entry> findAttribute(const ResourceName& name) = 0;
-
- /**
- * Find a resource by ID. Resolvers may contain resources without
- * resource IDs assigned to them.
- */
- virtual Maybe<ResourceName> findName(ResourceId resId) = 0;
-};
-
-} // namespace aapt
-
-#endif // AAPT_RESOLVER_H
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index 287d8de1b767..9328b697719d 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -15,7 +15,7 @@
*/
#include "Resource.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
#include <map>
#include <string>
@@ -28,7 +28,7 @@ StringPiece16 toString(ResourceType type) {
case ResourceType::kAnimator: return u"animator";
case ResourceType::kArray: return u"array";
case ResourceType::kAttr: return u"attr";
- case ResourceType::kAttrPrivate: return u"attr";
+ case ResourceType::kAttrPrivate: return u"^attr-private";
case ResourceType::kBool: return u"bool";
case ResourceType::kColor: return u"color";
case ResourceType::kDimen: return u"dimen";
@@ -36,7 +36,6 @@ StringPiece16 toString(ResourceType type) {
case ResourceType::kFraction: return u"fraction";
case ResourceType::kId: return u"id";
case ResourceType::kInteger: return u"integer";
- case ResourceType::kIntegerArray: return u"integer-array";
case ResourceType::kInterpolator: return u"interpolator";
case ResourceType::kLayout: return u"layout";
case ResourceType::kMenu: return u"menu";
@@ -65,7 +64,6 @@ static const std::map<StringPiece16, ResourceType> sResourceTypeMap {
{ u"fraction", ResourceType::kFraction },
{ u"id", ResourceType::kId },
{ u"integer", ResourceType::kInteger },
- { u"integer-array", ResourceType::kIntegerArray },
{ u"interpolator", ResourceType::kInterpolator },
{ u"layout", ResourceType::kLayout },
{ u"menu", ResourceType::kMenu },
@@ -87,4 +85,12 @@ const ResourceType* parseResourceType(const StringPiece16& str) {
return &iter->second;
}
+bool operator<(const ResourceKey& a, const ResourceKey& b) {
+ return std::tie(a.name, a.config) < std::tie(b.name, b.config);
+}
+
+bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b) {
+ return std::tie(a.name, a.config) < std::tie(b.name, b.config);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index fa9ac07b1779..03ca42b286d6 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -17,12 +17,16 @@
#ifndef AAPT_RESOURCE_H
#define AAPT_RESOURCE_H
-#include "StringPiece.h"
+#include "ConfigDescription.h"
+#include "Source.h"
+
+#include "util/StringPiece.h"
#include <iomanip>
#include <limits>
#include <string>
#include <tuple>
+#include <vector>
namespace aapt {
@@ -43,7 +47,6 @@ enum class ResourceType {
kFraction,
kId,
kInteger,
- kIntegerArray,
kInterpolator,
kLayout,
kMenu,
@@ -74,10 +77,11 @@ struct ResourceName {
ResourceType type;
std::u16string entry;
+ ResourceName() : type(ResourceType::kRaw) {}
+ ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e);
+
bool isValid() const;
- bool operator<(const ResourceName& rhs) const;
- bool operator==(const ResourceName& rhs) const;
- bool operator!=(const ResourceName& rhs) const;
+ std::u16string toString() const;
};
/**
@@ -102,10 +106,6 @@ struct ResourceNameRef {
ResourceName toResourceName() const;
bool isValid() const;
-
- bool operator<(const ResourceNameRef& rhs) const;
- bool operator==(const ResourceNameRef& rhs) const;
- bool operator!=(const ResourceNameRef& rhs) const;
};
/**
@@ -125,16 +125,63 @@ struct ResourceId {
ResourceId();
ResourceId(const ResourceId& rhs);
ResourceId(uint32_t resId);
- ResourceId(size_t p, size_t t, size_t e);
+ ResourceId(uint8_t p, uint8_t t, uint16_t e);
bool isValid() const;
uint8_t packageId() const;
uint8_t typeId() const;
uint16_t entryId() const;
- bool operator<(const ResourceId& rhs) const;
- bool operator==(const ResourceId& rhs) const;
};
+struct SourcedResourceName {
+ ResourceName name;
+ size_t line;
+};
+
+struct ResourceFile {
+ // Name
+ ResourceName name;
+
+ // Configuration
+ ConfigDescription config;
+
+ // Source
+ Source source;
+
+ // Exported symbols
+ std::vector<SourcedResourceName> exportedSymbols;
+};
+
+/**
+ * Useful struct used as a key to represent a unique resource in associative containers.
+ */
+struct ResourceKey {
+ ResourceName name;
+ ConfigDescription config;
+};
+
+bool operator<(const ResourceKey& a, const ResourceKey& b);
+
+/**
+ * Useful struct used as a key to represent a unique resource in associative containers.
+ * Holds a reference to the name, so that name better live longer than this key!
+ */
+struct ResourceKeyRef {
+ ResourceNameRef name;
+ ConfigDescription config;
+
+ ResourceKeyRef() = default;
+ ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c) : name(n), config(c) {
+ }
+
+ /**
+ * Prevent taking a reference to a temporary. This is bad.
+ */
+ ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete;
+};
+
+bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b);
+
//
// ResourceId implementation.
//
@@ -148,17 +195,7 @@ inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {
inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
}
-inline ResourceId::ResourceId(size_t p, size_t t, size_t e) : id(0) {
- if (p > std::numeric_limits<uint8_t>::max() ||
- t > std::numeric_limits<uint8_t>::max() ||
- e > std::numeric_limits<uint16_t>::max()) {
- // This will leave the ResourceId in an invalid state.
- return;
- }
-
- id = (static_cast<uint8_t>(p) << 24) |
- (static_cast<uint8_t>(t) << 16) |
- static_cast<uint16_t>(e);
+inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) {
}
inline bool ResourceId::isValid() const {
@@ -177,16 +214,23 @@ inline uint16_t ResourceId::entryId() const {
return static_cast<uint16_t>(id);
}
-inline bool ResourceId::operator<(const ResourceId& rhs) const {
- return id < rhs.id;
+inline bool operator<(const ResourceId& lhs, const ResourceId& rhs) {
+ return lhs.id < rhs.id;
}
-inline bool ResourceId::operator==(const ResourceId& rhs) const {
- return id == rhs.id;
+inline bool operator>(const ResourceId& lhs, const ResourceId& rhs) {
+ return lhs.id > rhs.id;
}
-inline ::std::ostream& operator<<(::std::ostream& out,
- const ResourceId& resId) {
+inline bool operator==(const ResourceId& lhs, const ResourceId& rhs) {
+ return lhs.id == rhs.id;
+}
+
+inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) {
+ return lhs.id != rhs.id;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceId& resId) {
std::ios_base::fmtflags oldFlags = out.flags();
char oldFill = out.fill();
out << "0x" << std::internal << std::setfill('0') << std::setw(8)
@@ -208,25 +252,37 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val)
// ResourceName implementation.
//
+inline ResourceName::ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e) :
+ package(p.toString()), type(t), entry(e.toString()) {
+}
+
inline bool ResourceName::isValid() const {
return !package.empty() && !entry.empty();
}
-inline bool ResourceName::operator<(const ResourceName& rhs) const {
- return std::tie(package, type, entry)
+inline bool operator<(const ResourceName& lhs, const ResourceName& rhs) {
+ return std::tie(lhs.package, lhs.type, lhs.entry)
< std::tie(rhs.package, rhs.type, rhs.entry);
}
-inline bool ResourceName::operator==(const ResourceName& rhs) const {
- return std::tie(package, type, entry)
+inline bool operator==(const ResourceName& lhs, const ResourceName& rhs) {
+ return std::tie(lhs.package, lhs.type, lhs.entry)
== std::tie(rhs.package, rhs.type, rhs.entry);
}
-inline bool ResourceName::operator!=(const ResourceName& rhs) const {
- return std::tie(package, type, entry)
+inline bool operator!=(const ResourceName& lhs, const ResourceName& rhs) {
+ return std::tie(lhs.package, lhs.type, lhs.entry)
!= std::tie(rhs.package, rhs.type, rhs.entry);
}
+inline std::u16string ResourceName::toString() const {
+ std::u16string result;
+ if (!package.empty()) {
+ result = package + u":";
+ }
+ return result + aapt::toString(type).toString() + u"/" + entry;
+}
+
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
if (!name.package.empty()) {
out << name.package << ":";
@@ -263,18 +319,18 @@ inline bool ResourceNameRef::isValid() const {
return !package.empty() && !entry.empty();
}
-inline bool ResourceNameRef::operator<(const ResourceNameRef& rhs) const {
- return std::tie(package, type, entry)
+inline bool operator<(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
+ return std::tie(lhs.package, lhs.type, lhs.entry)
< std::tie(rhs.package, rhs.type, rhs.entry);
}
-inline bool ResourceNameRef::operator==(const ResourceNameRef& rhs) const {
- return std::tie(package, type, entry)
+inline bool operator==(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
+ return std::tie(lhs.package, lhs.type, lhs.entry)
== std::tie(rhs.package, rhs.type, rhs.entry);
}
-inline bool ResourceNameRef::operator!=(const ResourceNameRef& rhs) const {
- return std::tie(package, type, entry)
+inline bool operator!=(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
+ return std::tie(lhs.package, lhs.type, lhs.entry)
!= std::tie(rhs.package, rhs.type, rhs.entry);
}
@@ -285,6 +341,18 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& na
return out << name.type << "/" << name.entry;
}
+inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) {
+ return ResourceNameRef(lhs) < b;
+}
+
+inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) {
+ return ResourceNameRef(lhs) != rhs;
+}
+
+inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) {
+ return lhs.name == rhs.name && lhs.line == rhs.line;
+}
+
} // namespace aapt
#endif // AAPT_RESOURCE_H
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 13f916bfc8f3..9704d97029b7 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -14,489 +14,139 @@
* limitations under the License.
*/
-#include "Logger.h"
#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "ScopedXmlPullParser.h"
-#include "SourceXmlPullParser.h"
-#include "Util.h"
-#include "XliffXmlPullParser.h"
+#include "ValueVisitor.h"
+#include "util/ImmutableMap.h"
+#include "util/Util.h"
+#include "xml/XmlPullParser.h"
+#include <functional>
#include <sstream>
namespace aapt {
-void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
- StringPiece16* outType, StringPiece16* outEntry) {
- const char16_t* start = str.data();
- const char16_t* end = start + str.size();
- const char16_t* current = start;
- while (current != end) {
- if (outType->size() == 0 && *current == u'/') {
- outType->assign(start, current - start);
- start = current + 1;
- } else if (outPackage->size() == 0 && *current == u':') {
- outPackage->assign(start, current - start);
- start = current + 1;
- }
- current++;
- }
- outEntry->assign(start, end - start);
-}
-
-bool ResourceParser::tryParseReference(const StringPiece16& str, ResourceNameRef* outRef,
- bool* outCreate, bool* outPrivate) {
- StringPiece16 trimmedStr(util::trimWhitespace(str));
- if (trimmedStr.empty()) {
- return false;
- }
-
- if (trimmedStr.data()[0] == u'@') {
- size_t offset = 1;
- *outCreate = false;
- if (trimmedStr.data()[1] == u'+') {
- *outCreate = true;
- offset += 1;
- } else if (trimmedStr.data()[1] == u'*') {
- *outPrivate = true;
- offset += 1;
- }
- StringPiece16 package;
- StringPiece16 type;
- StringPiece16 entry;
- extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
- &package, &type, &entry);
-
- const ResourceType* parsedType = parseResourceType(type);
- if (!parsedType) {
- return false;
- }
-
- if (*outCreate && *parsedType != ResourceType::kId) {
- return false;
- }
-
- outRef->package = package;
- outRef->type = *parsedType;
- outRef->entry = entry;
- return true;
- }
- return false;
-}
-
-bool ResourceParser::tryParseAttributeReference(const StringPiece16& str,
- ResourceNameRef* outRef) {
- StringPiece16 trimmedStr(util::trimWhitespace(str));
- if (trimmedStr.empty()) {
- return false;
- }
-
- if (*trimmedStr.data() == u'?') {
- StringPiece16 package;
- StringPiece16 type;
- StringPiece16 entry;
- extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
-
- if (!type.empty() && type != u"attr") {
- return false;
- }
-
- outRef->package = package;
- outRef->type = ResourceType::kAttr;
- outRef->entry = entry;
- return true;
- }
- return false;
-}
+constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
-/*
- * Style parent's are a bit different. We accept the following formats:
- *
- * @[package:]style/<entry>
- * ?[package:]style/<entry>
- * <package>:[style/]<entry>
- * [package:style/]<entry>
+/**
+ * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
*/
-bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference,
- std::string* outError) {
- if (str.empty()) {
- return true;
- }
-
- StringPiece16 name = str;
-
- bool hasLeadingIdentifiers = false;
- bool privateRef = false;
-
- // Skip over these identifiers. A style's parent is a normal reference.
- if (name.data()[0] == u'@' || name.data()[0] == u'?') {
- hasLeadingIdentifiers = true;
- name = name.substr(1, name.size() - 1);
- if (name.data()[0] == u'*') {
- privateRef = true;
- name = name.substr(1, name.size() - 1);
- }
- }
-
- ResourceNameRef ref;
- ref.type = ResourceType::kStyle;
-
- StringPiece16 typeStr;
- extractResourceName(name, &ref.package, &typeStr, &ref.entry);
- if (!typeStr.empty()) {
- // If we have a type, make sure it is a Style.
- const ResourceType* parsedType = parseResourceType(typeStr);
- if (!parsedType || *parsedType != ResourceType::kStyle) {
- std::stringstream err;
- err << "invalid resource type '" << typeStr << "' for parent of style";
- *outError = err.str();
- return false;
- }
- } else {
- // No type was defined, this should not have a leading identifier.
- if (hasLeadingIdentifiers) {
- std::stringstream err;
- err << "invalid parent reference '" << str << "'";
- *outError = err.str();
- return false;
- }
- }
-
- if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
- std::stringstream err;
- err << "invalid parent reference '" << str << "'";
- *outError = err.str();
- return false;
- }
-
- outReference->name = ref.toResourceName();
- outReference->privateReference = privateRef;
- return true;
-}
-
-std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str,
- bool* outCreate) {
- ResourceNameRef ref;
- bool privateRef = false;
- if (tryParseReference(str, &ref, outCreate, &privateRef)) {
- std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
- value->privateReference = privateRef;
- return value;
- }
-
- if (tryParseAttributeReference(str, &ref)) {
- *outCreate = false;
- return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
- }
- return {};
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseNullOrEmpty(const StringPiece16& str) {
- StringPiece16 trimmedStr(util::trimWhitespace(str));
- android::Res_value value = {};
- if (trimmedStr == u"@null") {
- // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
- // Instead we set the data type to TYPE_REFERENCE with a value of 0.
- value.dataType = android::Res_value::TYPE_REFERENCE;
- } else if (trimmedStr == u"@empty") {
- // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
- value.dataType = android::Res_value::TYPE_NULL;
- value.data = android::Res_value::DATA_NULL_EMPTY;
- } else {
- return {};
- }
- return util::make_unique<BinaryPrimitive>(value);
+static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) {
+ return ns.empty() && (name == u"skip" || name == u"eat-comment");
}
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseEnumSymbol(const Attribute& enumAttr,
- const StringPiece16& str) {
- StringPiece16 trimmedStr(util::trimWhitespace(str));
- for (const auto& entry : enumAttr.symbols) {
- // Enum symbols are stored as @package:id/symbol resources,
- // so we need to match against the 'entry' part of the identifier.
- const ResourceName& enumSymbolResourceName = entry.symbol.name;
- if (trimmedStr == enumSymbolResourceName.entry) {
- android::Res_value value = {};
- value.dataType = android::Res_value::TYPE_INT_DEC;
- value.data = entry.value;
- return util::make_unique<BinaryPrimitive>(value);
- }
- }
- return {};
+static uint32_t parseFormatType(const StringPiece16& piece) {
+ if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE;
+ else if (piece == u"string") return android::ResTable_map::TYPE_STRING;
+ else if (piece == u"integer") return android::ResTable_map::TYPE_INTEGER;
+ else if (piece == u"boolean") return android::ResTable_map::TYPE_BOOLEAN;
+ else if (piece == u"color") return android::ResTable_map::TYPE_COLOR;
+ else if (piece == u"float") return android::ResTable_map::TYPE_FLOAT;
+ else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION;
+ else if (piece == u"fraction") return android::ResTable_map::TYPE_FRACTION;
+ else if (piece == u"enum") return android::ResTable_map::TYPE_ENUM;
+ else if (piece == u"flags") return android::ResTable_map::TYPE_FLAGS;
+ return 0;
}
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFlagSymbol(const Attribute& flagAttr,
- const StringPiece16& str) {
- android::Res_value flags = {};
- flags.dataType = android::Res_value::TYPE_INT_DEC;
-
+static uint32_t parseFormatAttribute(const StringPiece16& str) {
+ uint32_t mask = 0;
for (StringPiece16 part : util::tokenize(str, u'|')) {
StringPiece16 trimmedPart = util::trimWhitespace(part);
-
- bool flagSet = false;
- for (const auto& entry : flagAttr.symbols) {
- // Flag symbols are stored as @package:id/symbol resources,
- // so we need to match against the 'entry' part of the identifier.
- const ResourceName& flagSymbolResourceName = entry.symbol.name;
- if (trimmedPart == flagSymbolResourceName.entry) {
- flags.data |= entry.value;
- flagSet = true;
- break;
- }
- }
-
- if (!flagSet) {
- return {};
+ uint32_t type = parseFormatType(trimmedPart);
+ if (type == 0) {
+ return 0;
}
+ mask |= type;
}
- return util::make_unique<BinaryPrimitive>(flags);
-}
-
-static uint32_t parseHex(char16_t c, bool* outError) {
- if (c >= u'0' && c <= u'9') {
- return c - u'0';
- } else if (c >= u'a' && c <= u'f') {
- return c - u'a' + 0xa;
- } else if (c >= u'A' && c <= u'F') {
- return c - u'A' + 0xa;
- } else {
- *outError = true;
- return 0xffffffffu;
- }
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseColor(const StringPiece16& str) {
- StringPiece16 colorStr(util::trimWhitespace(str));
- const char16_t* start = colorStr.data();
- const size_t len = colorStr.size();
- if (len == 0 || start[0] != u'#') {
- return {};
- }
-
- android::Res_value value = {};
- bool error = false;
- if (len == 4) {
- value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
- value.data = 0xff000000u;
- value.data |= parseHex(start[1], &error) << 20;
- value.data |= parseHex(start[1], &error) << 16;
- value.data |= parseHex(start[2], &error) << 12;
- value.data |= parseHex(start[2], &error) << 8;
- value.data |= parseHex(start[3], &error) << 4;
- value.data |= parseHex(start[3], &error);
- } else if (len == 5) {
- value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
- value.data |= parseHex(start[1], &error) << 28;
- value.data |= parseHex(start[1], &error) << 24;
- value.data |= parseHex(start[2], &error) << 20;
- value.data |= parseHex(start[2], &error) << 16;
- value.data |= parseHex(start[3], &error) << 12;
- value.data |= parseHex(start[3], &error) << 8;
- value.data |= parseHex(start[4], &error) << 4;
- value.data |= parseHex(start[4], &error);
- } else if (len == 7) {
- value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
- value.data = 0xff000000u;
- value.data |= parseHex(start[1], &error) << 20;
- value.data |= parseHex(start[2], &error) << 16;
- value.data |= parseHex(start[3], &error) << 12;
- value.data |= parseHex(start[4], &error) << 8;
- value.data |= parseHex(start[5], &error) << 4;
- value.data |= parseHex(start[6], &error);
- } else if (len == 9) {
- value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
- value.data |= parseHex(start[1], &error) << 28;
- value.data |= parseHex(start[2], &error) << 24;
- value.data |= parseHex(start[3], &error) << 20;
- value.data |= parseHex(start[4], &error) << 16;
- value.data |= parseHex(start[5], &error) << 12;
- value.data |= parseHex(start[6], &error) << 8;
- value.data |= parseHex(start[7], &error) << 4;
- value.data |= parseHex(start[8], &error);
- } else {
- return {};
- }
- return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseBool(const StringPiece16& str) {
- StringPiece16 trimmedStr(util::trimWhitespace(str));
- uint32_t data = 0;
- if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
- data = 0xffffffffu;
- } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
- return {};
- }
- android::Res_value value = {};
- value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
- value.data = data;
- return util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseInt(const StringPiece16& str) {
- android::Res_value value;
- if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
- return {};
- }
- return util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFloat(const StringPiece16& str) {
- android::Res_value value;
- if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
- return {};
- }
- return util::make_unique<BinaryPrimitive>(value);
-}
-
-uint32_t ResourceParser::androidTypeToAttributeTypeMask(uint16_t type) {
- switch (type) {
- case android::Res_value::TYPE_NULL:
- case android::Res_value::TYPE_REFERENCE:
- case android::Res_value::TYPE_ATTRIBUTE:
- case android::Res_value::TYPE_DYNAMIC_REFERENCE:
- return android::ResTable_map::TYPE_REFERENCE;
-
- case android::Res_value::TYPE_STRING:
- return android::ResTable_map::TYPE_STRING;
-
- case android::Res_value::TYPE_FLOAT:
- return android::ResTable_map::TYPE_FLOAT;
-
- case android::Res_value::TYPE_DIMENSION:
- return android::ResTable_map::TYPE_DIMENSION;
-
- case android::Res_value::TYPE_FRACTION:
- return android::ResTable_map::TYPE_FRACTION;
-
- case android::Res_value::TYPE_INT_DEC:
- case android::Res_value::TYPE_INT_HEX:
- return android::ResTable_map::TYPE_INTEGER |
- android::ResTable_map::TYPE_ENUM |
- android::ResTable_map::TYPE_FLAGS;
-
- case android::Res_value::TYPE_INT_BOOLEAN:
- return android::ResTable_map::TYPE_BOOLEAN;
-
- case android::Res_value::TYPE_INT_COLOR_ARGB8:
- case android::Res_value::TYPE_INT_COLOR_RGB8:
- case android::Res_value::TYPE_INT_COLOR_ARGB4:
- case android::Res_value::TYPE_INT_COLOR_RGB4:
- return android::ResTable_map::TYPE_COLOR;
-
- default:
- return 0;
- };
+ return mask;
}
-std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
- const StringPiece16& value, uint32_t typeMask,
- std::function<void(const ResourceName&)> onCreateReference) {
- std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
- if (nullOrEmpty) {
- return std::move(nullOrEmpty);
- }
+/**
+ * A parsed resource ready to be added to the ResourceTable.
+ */
+struct ParsedResource {
+ ResourceName name;
+ ConfigDescription config;
+ std::string product;
+ Source source;
+ ResourceId id;
+ Maybe<SymbolState> symbolState;
+ std::u16string comment;
+ std::unique_ptr<Value> value;
+ std::list<ParsedResource> childResources;
+};
- bool create = false;
- std::unique_ptr<Reference> reference = tryParseReference(value, &create);
- if (reference) {
- if (create && onCreateReference) {
- onCreateReference(reference->name);
- }
- return std::move(reference);
+// Recursively adds resources to the ResourceTable.
+static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
+ StringPiece16 trimmedComment = util::trimWhitespace(res->comment);
+ if (trimmedComment.size() != res->comment.size()) {
+ // Only if there was a change do we re-assign.
+ res->comment = trimmedComment.toString();
}
- if (typeMask & android::ResTable_map::TYPE_COLOR) {
- // Try parsing this as a color.
- std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
- if (color) {
- return std::move(color);
+ if (res->symbolState) {
+ Symbol symbol;
+ symbol.state = res->symbolState.value();
+ symbol.source = res->source;
+ symbol.comment = res->comment;
+ if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
+ return false;
}
}
- if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
- // Try parsing this as a boolean.
- std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
- if (boolean) {
- return std::move(boolean);
- }
- }
+ if (res->value) {
+ // Attach the comment, source and config to the value.
+ res->value->setComment(std::move(res->comment));
+ res->value->setSource(std::move(res->source));
- if (typeMask & android::ResTable_map::TYPE_INTEGER) {
- // Try parsing this as an integer.
- std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
- if (integer) {
- return std::move(integer);
+ if (!table->addResource(res->name, res->id, res->config, res->product,
+ std::move(res->value), diag)) {
+ return false;
}
}
- const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT |
- android::ResTable_map::TYPE_DIMENSION |
- android::ResTable_map::TYPE_FRACTION;
- if (typeMask & floatMask) {
- // Try parsing this as a float.
- std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
- if (floatingPoint) {
- if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
- return std::move(floatingPoint);
- }
- }
+ bool error = false;
+ for (ParsedResource& child : res->childResources) {
+ error |= !addResourcesToTable(table, diag, &child);
}
- return {};
+ return !error;
}
-/**
- * We successively try to parse the string as a resource type that the Attribute
- * allows.
- */
-std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
- const StringPiece16& str, const Attribute& attr,
- std::function<void(const ResourceName&)> onCreateReference) {
- const uint32_t typeMask = attr.typeMask;
- std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
- if (value) {
- return value;
- }
-
- if (typeMask & android::ResTable_map::TYPE_ENUM) {
- // Try parsing this as an enum.
- std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
- if (enumValue) {
- return std::move(enumValue);
- }
- }
-
- if (typeMask & android::ResTable_map::TYPE_FLAGS) {
- // Try parsing this as a flag.
- std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
- if (flagValue) {
- return std::move(flagValue);
- }
- }
- return {};
-}
+// Convenient aliases for more readable function calls.
+enum {
+ kAllowRawString = true,
+ kNoRawString = false
+};
-ResourceParser::ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
+ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
const ConfigDescription& config,
- const std::shared_ptr<XmlPullParser>& parser) :
- mTable(table), mSource(source), mConfig(config), mLogger(source),
- mParser(std::make_shared<XliffXmlPullParser>(parser)) {
+ const ResourceParserOptions& options) :
+ mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) {
}
/**
* Build a string from XML that converts nested elements into Span objects.
*/
-bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
StyleString* outStyleString) {
std::vector<Span> spanStack;
+ bool error = false;
outRawString->clear();
outStyleString->spans.clear();
util::StringBuilder builder;
size_t depth = 1;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- const XmlPullParser::Event event = parser->getEvent();
- if (event == XmlPullParser::Event::kEndElement) {
+ while (xml::XmlPullParser::isGoodEvent(parser->next())) {
+ const xml::XmlPullParser::Event event = parser->getEvent();
+ if (event == xml::XmlPullParser::Event::kEndElement) {
+ if (!parser->getElementNamespace().empty()) {
+ // We already warned and skipped the start element, so just skip here too
+ continue;
+ }
+
depth--;
if (depth == 0) {
break;
@@ -506,21 +156,21 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou
outStyleString->spans.push_back(spanStack.back());
spanStack.pop_back();
- } else if (event == XmlPullParser::Event::kText) {
- // TODO(adamlesinski): Verify format strings.
+ } else if (event == xml::XmlPullParser::Event::kText) {
outRawString->append(parser->getText());
builder.append(parser->getText());
- } else if (event == XmlPullParser::Event::kStartElement) {
- if (parser->getElementNamespace().size() > 0) {
- mLogger.warn(parser->getLineNumber())
- << "skipping element '"
- << parser->getElementName()
- << "' with unknown namespace '"
- << parser->getElementNamespace()
- << "'."
- << std::endl;
- XmlPullParser::skipCurrentElement(parser);
+ } else if (event == xml::XmlPullParser::Event::kStartElement) {
+ if (!parser->getElementNamespace().empty()) {
+ if (parser->getElementNamespace() != sXliffNamespaceUri) {
+ // Only warn if this isn't an xliff namespace.
+ mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "skipping element '"
+ << parser->getElementName()
+ << "' with unknown namespace '"
+ << parser->getElementNamespace()
+ << "'");
+ }
continue;
}
depth++;
@@ -536,205 +186,288 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou
}
if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
- mLogger.error(parser->getLineNumber())
- << "style string '"
- << builder.str()
- << "' is too long."
- << std::endl;
- return false;
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "style string '" << builder.str() << "' is too long");
+ error = true;
+ } else {
+ spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
}
- spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
- } else if (event == XmlPullParser::Event::kComment) {
+ } else if (event == xml::XmlPullParser::Event::kComment) {
// Skip
} else {
- mLogger.warn(parser->getLineNumber())
- << "unknown event "
- << event
- << "."
- << std::endl;
+ assert(false);
}
}
assert(spanStack.empty() && "spans haven't been fully processed");
outStyleString->str = builder.str();
- return true;
+ return !error;
}
-bool ResourceParser::parse() {
- while (XmlPullParser::isGoodEvent(mParser->next())) {
- if (mParser->getEvent() != XmlPullParser::Event::kStartElement) {
+bool ResourceParser::parse(xml::XmlPullParser* parser) {
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip comments and text.
continue;
}
- ScopedXmlPullParser parser(mParser.get());
- if (!parser.getElementNamespace().empty() ||
- parser.getElementName() != u"resources") {
- mLogger.error(parser.getLineNumber())
- << "root element must be <resources> in the global namespace."
- << std::endl;
+ if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "root element must be <resources>");
return false;
}
- if (!parseResources(&parser)) {
- return false;
- }
- }
+ error |= !parseResources(parser);
+ break;
+ };
- if (mParser->getEvent() == XmlPullParser::Event::kBadDocument) {
- mLogger.error(mParser->getLineNumber())
- << mParser->getLastError()
- << std::endl;
+ if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "xml parser error: " << parser->getLastError());
return false;
}
- return true;
+ return !error;
}
-bool ResourceParser::parseResources(XmlPullParser* parser) {
- bool success = true;
+bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
+ std::set<ResourceName> strippedResources;
+ bool error = false;
std::u16string comment;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- const XmlPullParser::Event event = parser->getEvent();
- if (event == XmlPullParser::Event::kComment) {
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ const xml::XmlPullParser::Event event = parser->getEvent();
+ if (event == xml::XmlPullParser::Event::kComment) {
comment = parser->getComment();
continue;
}
- if (event == XmlPullParser::Event::kText) {
+ if (event == xml::XmlPullParser::Event::kText) {
if (!util::trimWhitespace(parser->getText()).empty()) {
- comment = u"";
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "plain text not allowed here");
+ error = true;
}
continue;
}
- if (event != XmlPullParser::Event::kStartElement) {
- continue;
- }
+ assert(event == xml::XmlPullParser::Event::kStartElement);
- ScopedXmlPullParser childParser(parser);
-
- if (!childParser.getElementNamespace().empty()) {
+ if (!parser->getElementNamespace().empty()) {
// Skip unknown namespace.
continue;
}
- StringPiece16 name = childParser.getElementName();
- if (name == u"skip" || name == u"eat-comment") {
+ std::u16string elementName = parser->getElementName();
+ if (elementName == u"skip" || elementName == u"eat-comment") {
+ comment = u"";
continue;
}
- if (name == u"private-symbols") {
- // Handle differently.
- mLogger.note(childParser.getLineNumber())
- << "got a <private-symbols> tag."
- << std::endl;
- continue;
+ ParsedResource parsedResource;
+ parsedResource.config = mConfig;
+ parsedResource.source = mSource.withLine(parser->getLineNumber());
+ parsedResource.comment = std::move(comment);
+
+ // Extract the product name if it exists.
+ if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) {
+ parsedResource.product = util::utf16ToUtf8(maybeProduct.value());
}
- const auto endAttrIter = childParser.endAttributes();
- auto attrIter = childParser.findAttribute(u"", u"name");
- if (attrIter == endAttrIter || attrIter->value.empty()) {
- mLogger.error(childParser.getLineNumber())
- << "<" << name << "> tag must have a 'name' attribute."
- << std::endl;
- success = false;
+ // Parse the resource regardless of product.
+ if (!parseResource(parser, &parsedResource)) {
+ error = true;
continue;
}
- // Copy because our iterator will go out of scope when
- // we parse more XML.
- std::u16string attributeName = attrIter->value;
-
- if (name == u"item") {
- // Items simply have their type encoded in the type attribute.
- auto typeIter = childParser.findAttribute(u"", u"type");
- if (typeIter == endAttrIter || typeIter->value.empty()) {
- mLogger.error(childParser.getLineNumber())
- << "<item> must have a 'type' attribute."
- << std::endl;
- success = false;
- continue;
- }
- name = typeIter->value;
+ if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
+ error = true;
}
+ }
- if (name == u"id") {
- success &= mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, attributeName },
- {}, mSource.line(childParser.getLineNumber()),
- util::make_unique<Id>());
- } else if (name == u"string") {
- success &= parseString(&childParser,
- ResourceNameRef{ {}, ResourceType::kString, attributeName });
- } else if (name == u"color") {
- success &= parseColor(&childParser,
- ResourceNameRef{ {}, ResourceType::kColor, attributeName });
- } else if (name == u"drawable") {
- success &= parseColor(&childParser,
- ResourceNameRef{ {}, ResourceType::kDrawable, attributeName });
- } else if (name == u"bool") {
- success &= parsePrimitive(&childParser,
- ResourceNameRef{ {}, ResourceType::kBool, attributeName });
- } else if (name == u"integer") {
- success &= parsePrimitive(
- &childParser,
- ResourceNameRef{ {}, ResourceType::kInteger, attributeName });
- } else if (name == u"dimen") {
- success &= parsePrimitive(&childParser,
- ResourceNameRef{ {}, ResourceType::kDimen, attributeName });
- } else if (name == u"fraction") {
-// success &= parsePrimitive(
-// &childParser,
-// ResourceNameRef{ {}, ResourceType::kFraction, attributeName });
- } else if (name == u"style") {
- success &= parseStyle(&childParser,
- ResourceNameRef{ {}, ResourceType::kStyle, attributeName });
- } else if (name == u"plurals") {
- success &= parsePlural(&childParser,
- ResourceNameRef{ {}, ResourceType::kPlurals, attributeName });
- } else if (name == u"array") {
- success &= parseArray(&childParser,
- ResourceNameRef{ {}, ResourceType::kArray, attributeName },
- android::ResTable_map::TYPE_ANY);
- } else if (name == u"string-array") {
- success &= parseArray(&childParser,
- ResourceNameRef{ {}, ResourceType::kArray, attributeName },
- android::ResTable_map::TYPE_STRING);
- } else if (name == u"integer-array") {
- success &= parseArray(&childParser,
- ResourceNameRef{ {}, ResourceType::kArray, attributeName },
- android::ResTable_map::TYPE_INTEGER);
- } else if (name == u"public") {
- success &= parsePublic(&childParser, attributeName);
- } else if (name == u"declare-styleable") {
- success &= parseDeclareStyleable(
- &childParser,
- ResourceNameRef{ {}, ResourceType::kStyleable, attributeName });
- } else if (name == u"attr") {
- success &= parseAttr(&childParser,
- ResourceNameRef{ {}, ResourceType::kAttr, attributeName });
- } else if (name == u"bag") {
- } else if (name == u"public-padding") {
- } else if (name == u"java-symbol") {
- } else if (name == u"add-resource") {
- }
- }
-
- if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
- mLogger.error(parser->getLineNumber())
- << parser->getLastError()
- << std::endl;
- return false;
+ // Check that we included at least one variant of each stripped resource.
+ for (const ResourceName& strippedResource : strippedResources) {
+ if (!mTable->findResource(strippedResource)) {
+ // Failed to find the resource.
+ mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' "
+ "was filtered out but no product variant remains");
+ error = true;
+ }
}
- return success;
+
+ return !error;
}
+bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ struct ItemTypeFormat {
+ ResourceType type;
+ uint32_t format;
+ };
-enum {
- kAllowRawString = true,
- kNoRawString = false
-};
+ using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*, ParsedResource*)>;
+
+ static const auto elToItemMap = ImmutableMap<std::u16string, ItemTypeFormat>::createPreSorted({
+ { u"bool", { ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN } },
+ { u"color", { ResourceType::kColor, android::ResTable_map::TYPE_COLOR } },
+ { u"dimen", { ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT
+ | android::ResTable_map::TYPE_FRACTION
+ | android::ResTable_map::TYPE_DIMENSION } },
+ { u"drawable", { ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR } },
+ { u"fraction", { ResourceType::kFraction, android::ResTable_map::TYPE_FLOAT
+ | android::ResTable_map::TYPE_FRACTION
+ | android::ResTable_map::TYPE_DIMENSION } },
+ { u"integer", { ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER } },
+ { u"string", { ResourceType::kString, android::ResTable_map::TYPE_STRING } },
+ });
+
+ static const auto elToBagMap = ImmutableMap<std::u16string, BagParseFunc>::createPreSorted({
+ { u"add-resource", std::mem_fn(&ResourceParser::parseAddResource) },
+ { u"array", std::mem_fn(&ResourceParser::parseArray) },
+ { u"attr", std::mem_fn(&ResourceParser::parseAttr) },
+ { u"declare-styleable", std::mem_fn(&ResourceParser::parseDeclareStyleable) },
+ { u"integer-array", std::mem_fn(&ResourceParser::parseIntegerArray) },
+ { u"java-symbol", std::mem_fn(&ResourceParser::parseSymbol) },
+ { u"plurals", std::mem_fn(&ResourceParser::parsePlural) },
+ { u"public", std::mem_fn(&ResourceParser::parsePublic) },
+ { u"public-group", std::mem_fn(&ResourceParser::parsePublicGroup) },
+ { u"string-array", std::mem_fn(&ResourceParser::parseStringArray) },
+ { u"style", std::mem_fn(&ResourceParser::parseStyle) },
+ { u"symbol", std::mem_fn(&ResourceParser::parseSymbol) },
+ });
+
+ std::u16string resourceType = parser->getElementName();
+
+ // The value format accepted for this resource.
+ uint32_t resourceFormat = 0u;
+
+ if (resourceType == u"item") {
+ // Items have their type encoded in the type attribute.
+ if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) {
+ resourceType = maybeType.value().toString();
+ } else {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "<item> must have a 'type' attribute");
+ return false;
+ }
+
+ if (Maybe<StringPiece16> maybeFormat = xml::findNonEmptyAttribute(parser, u"format")) {
+ // An explicit format for this resource was specified. The resource will retain
+ // its type in its name, but the accepted value for this type is overridden.
+ resourceFormat = parseFormatType(maybeFormat.value());
+ if (!resourceFormat) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "'" << maybeFormat.value() << "' is an invalid format");
+ return false;
+ }
+ }
+ }
+
+ // Get the name of the resource. This will be checked later, because not all
+ // XML elements require a name.
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
+
+ if (resourceType == u"id") {
+ if (!maybeName) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName() << "> missing 'name' attribute");
+ return false;
+ }
+
+ outResource->name.type = ResourceType::kId;
+ outResource->name.entry = maybeName.value().toString();
+ outResource->value = util::make_unique<Id>();
+ return true;
+ }
+
+ const auto itemIter = elToItemMap.find(resourceType);
+ if (itemIter != elToItemMap.end()) {
+ // This is an item, record its type and format and start parsing.
+
+ if (!maybeName) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName() << "> missing 'name' attribute");
+ return false;
+ }
+
+ outResource->name.type = itemIter->second.type;
+ outResource->name.entry = maybeName.value().toString();
+
+ // Only use the implicit format for this type if it wasn't overridden.
+ if (!resourceFormat) {
+ resourceFormat = itemIter->second.format;
+ }
+
+ if (!parseItem(parser, outResource, resourceFormat)) {
+ return false;
+ }
+ return true;
+ }
+
+ // This might be a bag or something.
+ const auto bagIter = elToBagMap.find(resourceType);
+ if (bagIter != elToBagMap.end()) {
+ // Ensure we have a name (unless this is a <public-group>).
+ if (resourceType != u"public-group") {
+ if (!maybeName) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName() << "> missing 'name' attribute");
+ return false;
+ }
+
+ outResource->name.entry = maybeName.value().toString();
+ }
+
+ // Call the associated parse method. The type will be filled in by the
+ // parse func.
+ if (!bagIter->second(this, parser, outResource)) {
+ return false;
+ }
+ return true;
+ }
+
+ // Try parsing the elementName (or type) as a resource. These shall only be
+ // resources like 'layout' or 'xml' and they can only be references.
+ const ResourceType* parsedType = parseResourceType(resourceType);
+ if (parsedType) {
+ if (!maybeName) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName() << "> missing 'name' attribute");
+ return false;
+ }
+
+ outResource->name.type = *parsedType;
+ outResource->name.entry = maybeName.value().toString();
+ outResource->value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
+ if (!outResource->value) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid value for type '" << *parsedType << "'. Expected a reference");
+ return false;
+ }
+ return true;
+ }
+
+ mDiag->warn(DiagMessage(outResource->source)
+ << "unknown resource type '" << parser->getElementName() << "'");
+ return false;
+}
+
+bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource,
+ const uint32_t format) {
+ if (format == android::ResTable_map::TYPE_STRING) {
+ return parseString(parser, outResource);
+ }
+
+ outResource->value = parseXml(parser, format, kNoRawString);
+ if (!outResource->value) {
+ mDiag->error(DiagMessage(outResource->source) << "invalid " << outResource->name.type);
+ return false;
+ }
+ return true;
+}
/**
* Reads the entire XML subtree and attempts to parse it as some Item,
@@ -743,8 +476,8 @@ enum {
* an Item. If allowRawValue is false, nullptr is returned in this
* case.
*/
-std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask,
- bool allowRawValue) {
+std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
+ const bool allowRawValue) {
const size_t beginXmlLine = parser->getLineNumber();
std::u16string rawValue;
@@ -753,34 +486,27 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t t
return {};
}
- StringPool& pool = mTable->getValueStringPool();
-
if (!styleString.spans.empty()) {
// This can only be a StyledString.
return util::make_unique<StyledString>(
- pool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
+ mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
}
auto onCreateReference = [&](const ResourceName& name) {
// name.package can be empty here, as it will assume the package name of the table.
- mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>());
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->setSource(mSource.withLine(beginXmlLine));
+ mTable->addResource(name, {}, {}, std::move(id), mDiag);
};
// Process the raw value.
- std::unique_ptr<Item> processedItem = parseItemForAttribute(rawValue, typeMask,
- onCreateReference);
+ std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask,
+ onCreateReference);
if (processedItem) {
// Fix up the reference.
- visitFunc<Reference>(*processedItem, [&](Reference& ref) {
- if (!ref.name.package.empty()) {
- // The package name was set, so lookup its alias.
- parser->applyPackageAlias(&ref.name.package, mTable->getPackage());
- } else {
- // The package name was left empty, so it assumes the default package
- // without alias lookup.
- ref.name.package = mTable->getPackage();
- }
- });
+ if (Reference* ref = valueCast<Reference>(processedItem.get())) {
+ transformReferenceFromNamespace(parser, u"", ref);
+ }
return processedItem;
}
@@ -788,320 +514,428 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t t
if (typeMask & android::ResTable_map::TYPE_STRING) {
// Use the trimmed, escaped string.
return util::make_unique<String>(
- pool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
+ mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
}
- // We can't parse this so return a RawString if we are allowed.
if (allowRawValue) {
+ // We can't parse this so return a RawString if we are allowed.
return util::make_unique<RawString>(
- pool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
+ mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
}
return {};
}
-bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) {
- const SourceLine source = mSource.line(parser->getLineNumber());
-
- // Mark the string as untranslateable if needed.
- const auto endAttrIter = parser->endAttributes();
- auto attrIter = parser->findAttribute(u"", u"untranslateable");
- // bool untranslateable = attrIter != endAttrIter;
- // TODO(adamlesinski): Do something with this (mark the string).
-
- // Deal with the product.
- attrIter = parser->findAttribute(u"", u"product");
- if (attrIter != endAttrIter) {
- if (attrIter->value != u"default" && attrIter->value != u"phone") {
- // TODO(adamlesinski): Match products.
- return true;
+bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ bool formatted = true;
+ if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) {
+ if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid value for 'formatted'. Must be a boolean");
+ return false;
}
}
- std::unique_ptr<Item> processedItem = parseXml(parser, android::ResTable_map::TYPE_STRING,
- kNoRawString);
- if (!processedItem) {
- mLogger.error(source.line)
- << "not a valid string."
- << std::endl;
- return false;
+ bool translateable = mOptions.translatable;
+ if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
+ if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid value for 'translatable'. Must be a boolean");
+ return false;
+ }
}
- return mTable->addResource(resourceName, mConfig, source, std::move(processedItem));
-}
-
-bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) {
- const SourceLine source = mSource.line(parser->getLineNumber());
-
- std::unique_ptr<Item> item = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
- if (!item) {
- mLogger.error(source.line) << "invalid color." << std::endl;
+ outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
+ if (!outResource->value) {
+ mDiag->error(DiagMessage(outResource->source) << "not a valid string");
return false;
}
- return mTable->addResource(resourceName, mConfig, source, std::move(item));
-}
-
-bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) {
- const SourceLine source = mSource.line(parser->getLineNumber());
-
- uint32_t typeMask = 0;
- switch (resourceName.type) {
- case ResourceType::kInteger:
- typeMask |= android::ResTable_map::TYPE_INTEGER;
- break;
- case ResourceType::kDimen:
- typeMask |= android::ResTable_map::TYPE_DIMENSION
- | android::ResTable_map::TYPE_FLOAT
- | android::ResTable_map::TYPE_FRACTION;
- break;
+ if (String* stringValue = valueCast<String>(outResource->value.get())) {
+ stringValue->setTranslateable(translateable);
- case ResourceType::kBool:
- typeMask |= android::ResTable_map::TYPE_BOOLEAN;
- break;
+ if (formatted && translateable) {
+ if (!util::verifyJavaStringFormat(*stringValue->value)) {
+ DiagMessage msg(outResource->source);
+ msg << "multiple substitutions specified in non-positional format; "
+ "did you mean to add the formatted=\"false\" attribute?";
+ if (mOptions.errorOnPositionalArguments) {
+ mDiag->error(msg);
+ return false;
+ }
- default:
- assert(false);
- break;
- }
+ mDiag->warn(msg);
+ }
+ }
- std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
- if (!item) {
- mLogger.error(source.line)
- << "invalid "
- << resourceName.type
- << "."
- << std::endl;
- return false;
+ } else if (StyledString* stringValue = valueCast<StyledString>(outResource->value.get())) {
+ stringValue->setTranslateable(translateable);
}
-
- return mTable->addResource(resourceName, mConfig, source, std::move(item));
+ return true;
}
-bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) {
- const SourceLine source = mSource.line(parser->getLineNumber());
-
- const auto endAttrIter = parser->endAttributes();
- const auto typeAttrIter = parser->findAttribute(u"", u"type");
- if (typeAttrIter == endAttrIter || typeAttrIter->value.empty()) {
- mLogger.error(source.line)
- << "<public> must have a 'type' attribute."
- << std::endl;
+bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
+ if (!maybeType) {
+ mDiag->error(DiagMessage(outResource->source) << "<public> must have a 'type' attribute");
return false;
}
- const ResourceType* parsedType = parseResourceType(typeAttrIter->value);
+ const ResourceType* parsedType = parseResourceType(maybeType.value());
if (!parsedType) {
- mLogger.error(source.line)
- << "invalid resource type '"
- << typeAttrIter->value
- << "' in <public>."
- << std::endl;
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid resource type '" << maybeType.value() << "' in <public>");
return false;
}
- ResourceNameRef resourceName { {}, *parsedType, name };
- ResourceId resourceId;
+ outResource->name.type = *parsedType;
- const auto idAttrIter = parser->findAttribute(u"", u"id");
- if (idAttrIter != endAttrIter && !idAttrIter->value.empty()) {
+ if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) {
android::Res_value val;
- bool result = android::ResTable::stringToInt(idAttrIter->value.data(),
- idAttrIter->value.size(), &val);
- resourceId.id = val.data;
+ bool result = android::ResTable::stringToInt(maybeId.value().data(),
+ maybeId.value().size(), &val);
+ ResourceId resourceId(val.data);
if (!result || !resourceId.isValid()) {
- mLogger.error(source.line)
- << "invalid resource ID '"
- << idAttrIter->value
- << "' in <public>."
- << std::endl;
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid resource ID '" << maybeId.value() << "' in <public>");
return false;
}
+ outResource->id = resourceId;
}
if (*parsedType == ResourceType::kId) {
// An ID marked as public is also the definition of an ID.
- mTable->addResource(resourceName, {}, source, util::make_unique<Id>());
+ outResource->value = util::make_unique<Id>();
}
- return mTable->markPublic(resourceName, resourceId, source);
+ outResource->symbolState = SymbolState::kPublic;
+ return true;
}
-static uint32_t parseFormatType(const StringPiece16& piece) {
- if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE;
- else if (piece == u"string") return android::ResTable_map::TYPE_STRING;
- else if (piece == u"integer") return android::ResTable_map::TYPE_INTEGER;
- else if (piece == u"boolean") return android::ResTable_map::TYPE_BOOLEAN;
- else if (piece == u"color") return android::ResTable_map::TYPE_COLOR;
- else if (piece == u"float") return android::ResTable_map::TYPE_FLOAT;
- else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION;
- else if (piece == u"fraction") return android::ResTable_map::TYPE_FRACTION;
- else if (piece == u"enum") return android::ResTable_map::TYPE_ENUM;
- else if (piece == u"flags") return android::ResTable_map::TYPE_FLAGS;
- return 0;
-}
+bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
+ if (!maybeType) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<public-group> must have a 'type' attribute");
+ return false;
+ }
-static uint32_t parseFormatAttribute(const StringPiece16& str) {
- uint32_t mask = 0;
- for (StringPiece16 part : util::tokenize(str, u'|')) {
- StringPiece16 trimmedPart = util::trimWhitespace(part);
- uint32_t type = parseFormatType(trimmedPart);
- if (type == 0) {
- return 0;
+ const ResourceType* parsedType = parseResourceType(maybeType.value());
+ if (!parsedType) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid resource type '" << maybeType.value() << "' in <public-group>");
+ return false;
+ }
+
+ Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id");
+ if (!maybeId) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<public-group> must have a 'first-id' attribute");
+ return false;
+ }
+
+ android::Res_value val;
+ bool result = android::ResTable::stringToInt(maybeId.value().data(),
+ maybeId.value().size(), &val);
+ ResourceId nextId(val.data);
+ if (!result || !nextId.isValid()) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid resource ID '" << maybeId.value() << "' in <public-group>");
+ return false;
+ }
+
+ std::u16string comment;
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text.
+ continue;
+ }
+
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::u16string& elementNamespace = parser->getElementNamespace();
+ const std::u16string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && elementName == u"public") {
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
+ if (!maybeName) {
+ mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute");
+ error = true;
+ continue;
+ }
+
+ if (xml::findNonEmptyAttribute(parser, u"id")) {
+ mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ if (xml::findNonEmptyAttribute(parser, u"type")) {
+ mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ ParsedResource childResource;
+ childResource.name.type = *parsedType;
+ childResource.name.entry = maybeName.value().toString();
+ childResource.id = nextId;
+ childResource.comment = std::move(comment);
+ childResource.source = itemSource;
+ childResource.symbolState = SymbolState::kPublic;
+ outResource->childResources.push_back(std::move(childResource));
+
+ nextId.id += 1;
+
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
+ error = true;
}
- mask |= type;
}
- return mask;
+ return !error;
}
-bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) {
- const SourceLine source = mSource.line(parser->getLineNumber());
- ResourceName actualName = resourceName.toResourceName();
- std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false);
- if (!attr) {
+bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
+ if (!maybeType) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName() << "> must have a 'type' attribute");
+ return false;
+ }
+
+ const ResourceType* parsedType = parseResourceType(maybeType.value());
+ if (!parsedType) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid resource type '" << maybeType.value()
+ << "' in <" << parser->getElementName() << ">");
return false;
}
- return mTable->addResource(actualName, mConfig, source, std::move(attr));
+
+ outResource->name.type = *parsedType;
+ return true;
+}
+
+bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ if (parseSymbolImpl(parser, outResource)) {
+ outResource->symbolState = SymbolState::kPrivate;
+ return true;
+ }
+ return false;
+}
+
+bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ if (parseSymbolImpl(parser, outResource)) {
+ outResource->symbolState = SymbolState::kUndefined;
+ return true;
+ }
+ return false;
+}
+
+
+bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ return parseAttrImpl(parser, outResource, false);
}
-std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
- ResourceName* resourceName,
- bool weak) {
+bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+ bool weak) {
+ outResource->name.type = ResourceType::kAttr;
+
+ // Attributes only end up in default configuration.
+ if (outResource->config != ConfigDescription::defaultConfig()) {
+ mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '"
+ << outResource->config << "' for attribute " << outResource->name);
+ outResource->config = ConfigDescription::defaultConfig();
+ }
+
uint32_t typeMask = 0;
- const auto endAttrIter = parser->endAttributes();
- const auto formatAttrIter = parser->findAttribute(u"", u"format");
- if (formatAttrIter != endAttrIter) {
- typeMask = parseFormatAttribute(formatAttrIter->value);
+ Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format");
+ if (maybeFormat) {
+ typeMask = parseFormatAttribute(maybeFormat.value());
if (typeMask == 0) {
- mLogger.error(parser->getLineNumber())
- << "invalid attribute format '"
- << formatAttrIter->value
- << "'."
- << std::endl;
- return {};
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid attribute format '" << maybeFormat.value() << "'");
+ return false;
}
}
- // If this is a declaration, the package name may be in the name. Separate these out.
- // Eg. <attr name="android:text" />
- // No format attribute is allowed.
- if (weak && formatAttrIter == endAttrIter) {
- StringPiece16 package, type, name;
- extractResourceName(resourceName->entry, &package, &type, &name);
- if (type.empty() && !package.empty()) {
- resourceName->package = package.toString();
- resourceName->entry = name.toString();
+ Maybe<int32_t> maybeMin, maybeMax;
+
+ if (Maybe<StringPiece16> maybeMinStr = xml::findAttribute(parser, u"min")) {
+ StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value());
+ if (!minStr.empty()) {
+ android::Res_value value;
+ if (android::ResTable::stringToInt(minStr.data(), minStr.size(), &value)) {
+ maybeMin = static_cast<int32_t>(value.data);
+ }
+ }
+
+ if (!maybeMin) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'min' value '" << minStr << "'");
+ return false;
}
}
- std::vector<Attribute::Symbol> items;
+ if (Maybe<StringPiece16> maybeMaxStr = xml::findAttribute(parser, u"max")) {
+ StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value());
+ if (!maxStr.empty()) {
+ android::Res_value value;
+ if (android::ResTable::stringToInt(maxStr.data(), maxStr.size(), &value)) {
+ maybeMax = static_cast<int32_t>(value.data);
+ }
+ }
- bool error = false;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
- continue;
+ if (!maybeMax) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'max' value '" << maxStr << "'");
+ return false;
}
+ }
+
+ if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "'min' and 'max' can only be used when format='integer'");
+ return false;
+ }
- ScopedXmlPullParser childParser(parser);
+ struct SymbolComparator {
+ bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
+ return a.symbol.name.value() < b.symbol.name.value();
+ }
+ };
- const std::u16string& name = childParser.getElementName();
- if (!childParser.getElementNamespace().empty()
- || (name != u"flag" && name != u"enum")) {
- mLogger.error(childParser.getLineNumber())
- << "unexpected tag <"
- << name
- << "> in <attr>."
- << std::endl;
- error = true;
+ std::set<Attribute::Symbol, SymbolComparator> items;
+
+ std::u16string comment;
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text.
continue;
}
- if (name == u"enum") {
- if (typeMask & android::ResTable_map::TYPE_FLAGS) {
- mLogger.error(childParser.getLineNumber())
- << "can not define an <enum>; already defined a <flag>."
- << std::endl;
- error = true;
- continue;
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::u16string& elementNamespace = parser->getElementNamespace();
+ const std::u16string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && (elementName == u"flag" || elementName == u"enum")) {
+ if (elementName == u"enum") {
+ if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+ mDiag->error(DiagMessage(itemSource)
+ << "can not define an <enum>; already defined a <flag>");
+ error = true;
+ continue;
+ }
+ typeMask |= android::ResTable_map::TYPE_ENUM;
+
+ } else if (elementName == u"flag") {
+ if (typeMask & android::ResTable_map::TYPE_ENUM) {
+ mDiag->error(DiagMessage(itemSource)
+ << "can not define a <flag>; already defined an <enum>");
+ error = true;
+ continue;
+ }
+ typeMask |= android::ResTable_map::TYPE_FLAGS;
}
- typeMask |= android::ResTable_map::TYPE_ENUM;
- } else if (name == u"flag") {
- if (typeMask & android::ResTable_map::TYPE_ENUM) {
- mLogger.error(childParser.getLineNumber())
- << "can not define a <flag>; already defined an <enum>."
- << std::endl;
- error = true;
- continue;
- }
- typeMask |= android::ResTable_map::TYPE_FLAGS;
- }
- Attribute::Symbol item;
- if (parseEnumOrFlagItem(&childParser, name, &item)) {
- if (!mTable->addResource(item.symbol.name, mConfig,
- mSource.line(childParser.getLineNumber()),
- util::make_unique<Id>())) {
- error = true;
+ if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
+ Attribute::Symbol& symbol = s.value();
+ ParsedResource childResource;
+ childResource.name = symbol.symbol.name.value();
+ childResource.source = itemSource;
+ childResource.value = util::make_unique<Id>();
+ outResource->childResources.push_back(std::move(childResource));
+
+ symbol.symbol.setComment(std::move(comment));
+ symbol.symbol.setSource(itemSource);
+
+ auto insertResult = items.insert(std::move(symbol));
+ if (!insertResult.second) {
+ const Attribute::Symbol& existingSymbol = *insertResult.first;
+ mDiag->error(DiagMessage(itemSource)
+ << "duplicate symbol '" << existingSymbol.symbol.name.value().entry
+ << "'");
+
+ mDiag->note(DiagMessage(existingSymbol.symbol.getSource())
+ << "first defined here");
+ error = true;
+ }
} else {
- items.push_back(std::move(item));
+ error = true;
}
- } else {
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
error = true;
}
+
+ comment = {};
}
if (error) {
- return {};
+ return false;
}
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
- attr->symbols.swap(items);
+ attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
- return attr;
+ if (maybeMin) {
+ attr->minInt = maybeMin.value();
+ }
+
+ if (maybeMax) {
+ attr->maxInt = maybeMax.value();
+ }
+ outResource->value = std::move(attr);
+ return true;
}
-bool ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
- Attribute::Symbol* outSymbol) {
- const auto attrIterEnd = parser->endAttributes();
- const auto nameAttrIter = parser->findAttribute(u"", u"name");
- if (nameAttrIter == attrIterEnd || nameAttrIter->value.empty()) {
- mLogger.error(parser->getLineNumber())
- << "no attribute 'name' found for tag <" << tag << ">."
- << std::endl;
- return false;
+Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser,
+ const StringPiece16& tag) {
+ const Source source = mSource.withLine(parser->getLineNumber());
+
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
+ if (!maybeName) {
+ mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
+ return {};
}
- const auto valueAttrIter = parser->findAttribute(u"", u"value");
- if (valueAttrIter == attrIterEnd || valueAttrIter->value.empty()) {
- mLogger.error(parser->getLineNumber())
- << "no attribute 'value' found for tag <" << tag << ">."
- << std::endl;
- return false;
+ Maybe<StringPiece16> maybeValue = xml::findNonEmptyAttribute(parser, u"value");
+ if (!maybeValue) {
+ mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
+ return {};
}
android::Res_value val;
- if (!android::ResTable::stringToInt(valueAttrIter->value.data(),
- valueAttrIter->value.size(), &val)) {
- mLogger.error(parser->getLineNumber())
- << "invalid value '"
- << valueAttrIter->value
- << "' for <" << tag << ">; must be an integer."
- << std::endl;
- return false;
+ if (!android::ResTable::stringToInt(maybeValue.value().data(),
+ maybeValue.value().size(), &val)) {
+ mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
+ << "' for <" << tag << ">; must be an integer");
+ return {};
}
- outSymbol->symbol.name = ResourceName {
- mTable->getPackage(), ResourceType::kId, nameAttrIter->value };
- outSymbol->value = val.data;
- return true;
+ return Attribute::Symbol{
+ Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data };
}
-static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) {
+static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) {
str = util::trimWhitespace(str);
- const char16_t* const start = str.data();
+ const char16_t* start = str.data();
const char16_t* const end = start + str.size();
const char16_t* p = start;
+ Reference ref;
+ if (p != end && *p == u'*') {
+ ref.privateReference = true;
+ start++;
+ p++;
+ }
+
StringPiece16 package;
StringPiece16 name;
while (p != end) {
@@ -1113,289 +947,314 @@ static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) {
p++;
}
- outName->package = package.toString();
- outName->type = ResourceType::kAttr;
- if (name.size() == 0) {
- outName->entry = str.toString();
- } else {
- outName->entry = name.toString();
- }
- return true;
+ ref.name = ResourceName(package.toString(), ResourceType::kAttr,
+ name.empty() ? str.toString() : name.toString());
+ return Maybe<Reference>(std::move(ref));
}
-bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) {
- const auto endAttrIter = parser->endAttributes();
- const auto nameAttrIter = parser->findAttribute(u"", u"name");
- if (nameAttrIter == endAttrIter || nameAttrIter->value.empty()) {
- mLogger.error(parser->getLineNumber())
- << "<item> must have a 'name' attribute."
- << std::endl;
+bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
+ const Source source = mSource.withLine(parser->getLineNumber());
+
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
+ if (!maybeName) {
+ mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
return false;
}
- ResourceName key;
- if (!parseXmlAttributeName(nameAttrIter->value, &key)) {
- mLogger.error(parser->getLineNumber())
- << "invalid attribute name '"
- << nameAttrIter->value
- << "'."
- << std::endl;
+ Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value());
+ if (!maybeKey) {
+ mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
return false;
}
- if (!key.package.empty()) {
- // We have a package name set, so lookup its alias.
- parser->applyPackageAlias(&key.package, mTable->getPackage());
- } else {
- // The package name was omitted, so use the default package name with
- // no alias lookup.
- key.package = mTable->getPackage();
- }
+ transformReferenceFromNamespace(parser, u"", &maybeKey.value());
+ maybeKey.value().setSource(source);
std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
if (!value) {
+ mDiag->error(DiagMessage(source) << "could not parse style item");
return false;
}
- style.entries.push_back(Style::Entry{ Reference(key), std::move(value) });
+ style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) });
return true;
}
-bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
- const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ outResource->name.type = ResourceType::kStyle;
+
std::unique_ptr<Style> style = util::make_unique<Style>();
- const auto endAttrIter = parser->endAttributes();
- const auto parentAttrIter = parser->findAttribute(u"", u"parent");
- if (parentAttrIter != endAttrIter) {
- std::string errStr;
- if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) {
- mLogger.error(source.line) << errStr << "." << std::endl;
- return false;
- }
+ Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent");
+ if (maybeParent) {
+ // If the parent is empty, we don't have a parent, but we also don't infer either.
+ if (!maybeParent.value().empty()) {
+ std::string errStr;
+ style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr);
+ if (!style->parent) {
+ mDiag->error(DiagMessage(outResource->source) << errStr);
+ return false;
+ }
- if (!style->parent.name.package.empty()) {
- // Try to interpret the package name as an alias. These take precedence.
- parser->applyPackageAlias(&style->parent.name.package, mTable->getPackage());
- } else {
- // If no package is specified, this can not be an alias and is the local package.
- style->parent.name.package = mTable->getPackage();
+ // Transform the namespace prefix to the actual package name, and mark the reference as
+ // private if appropriate.
+ transformReferenceFromNamespace(parser, u"", &style->parent.value());
}
+
} else {
// No parent was specified, so try inferring it from the style name.
- std::u16string styleName = resourceName.entry.toString();
+ std::u16string styleName = outResource->name.entry;
size_t pos = styleName.find_last_of(u'.');
if (pos != std::string::npos) {
style->parentInferred = true;
- style->parent.name.package = mTable->getPackage();
- style->parent.name.type = ResourceType::kStyle;
- style->parent.name.entry = styleName.substr(0, pos);
+ style->parent = Reference(ResourceName({}, ResourceType::kStyle,
+ styleName.substr(0, pos)));
}
}
- bool success = true;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
continue;
}
- ScopedXmlPullParser childParser(parser);
- const std::u16string& name = childParser.getElementName();
- if (name == u"item") {
- success &= parseUntypedItem(&childParser, *style);
- } else {
- mLogger.error(childParser.getLineNumber())
- << "unexpected tag <"
- << name
- << "> in <style> resource."
- << std::endl;
- success = false;
+ const std::u16string& elementNamespace = parser->getElementNamespace();
+ const std::u16string& elementName = parser->getElementName();
+ if (elementNamespace == u"" && elementName == u"item") {
+ error |= !parseStyleItem(parser, style.get());
+
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << ":" << elementName << ">");
+ error = true;
}
}
- if (!success) {
+ if (error) {
return false;
}
- return mTable->addResource(resourceName, mConfig, source, std::move(style));
+ outResource->value = std::move(style);
+ return true;
+}
+
+bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY);
}
-bool ResourceParser::parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName,
- uint32_t typeMask) {
- const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER);
+}
+
+bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING);
+}
+
+bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+ const uint32_t typeMask) {
+ outResource->name.type = ResourceType::kArray;
+
std::unique_ptr<Array> array = util::make_unique<Array>();
bool error = false;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
continue;
}
- ScopedXmlPullParser childParser(parser);
-
- if (childParser.getElementName() != u"item") {
- mLogger.error(childParser.getLineNumber())
- << "unexpected tag <"
- << childParser.getElementName()
- << "> in <array> resource."
- << std::endl;
- error = true;
- continue;
- }
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::u16string& elementNamespace = parser->getElementNamespace();
+ const std::u16string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && elementName == u"item") {
+ std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
+ if (!item) {
+ mDiag->error(DiagMessage(itemSource) << "could not parse array item");
+ error = true;
+ continue;
+ }
+ item->setSource(itemSource);
+ array->items.emplace_back(std::move(item));
- std::unique_ptr<Item> item = parseXml(&childParser, typeMask, kNoRawString);
- if (!item) {
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "unknown tag <" << elementNamespace << ":" << elementName << ">");
error = true;
- continue;
}
- array->items.emplace_back(std::move(item));
}
if (error) {
return false;
}
- return mTable->addResource(resourceName, mConfig, source, std::move(array));
+ outResource->value = std::move(array);
+ return true;
}
-bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) {
- const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) {
+ outResource->name.type = ResourceType::kPlurals;
+
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
- bool success = true;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
continue;
}
- ScopedXmlPullParser childParser(parser);
-
- if (!childParser.getElementNamespace().empty() ||
- childParser.getElementName() != u"item") {
- success = false;
- continue;
- }
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::u16string& elementNamespace = parser->getElementNamespace();
+ const std::u16string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && elementName == u"item") {
+ Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity");
+ if (!maybeQuantity) {
+ mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
+ << "'quantity'");
+ error = true;
+ continue;
+ }
- const auto endAttrIter = childParser.endAttributes();
- auto attrIter = childParser.findAttribute(u"", u"quantity");
- if (attrIter == endAttrIter || attrIter->value.empty()) {
- mLogger.error(childParser.getLineNumber())
- << "<item> in <plurals> requires attribute 'quantity'."
- << std::endl;
- success = false;
- continue;
- }
+ StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
+ size_t index = 0;
+ if (trimmedQuantity == u"zero") {
+ index = Plural::Zero;
+ } else if (trimmedQuantity == u"one") {
+ index = Plural::One;
+ } else if (trimmedQuantity == u"two") {
+ index = Plural::Two;
+ } else if (trimmedQuantity == u"few") {
+ index = Plural::Few;
+ } else if (trimmedQuantity == u"many") {
+ index = Plural::Many;
+ } else if (trimmedQuantity == u"other") {
+ index = Plural::Other;
+ } else {
+ mDiag->error(DiagMessage(itemSource)
+ << "<item> in <plural> has invalid value '" << trimmedQuantity
+ << "' for attribute 'quantity'");
+ error = true;
+ continue;
+ }
- StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
- size_t index = 0;
- if (trimmedQuantity == u"zero") {
- index = Plural::Zero;
- } else if (trimmedQuantity == u"one") {
- index = Plural::One;
- } else if (trimmedQuantity == u"two") {
- index = Plural::Two;
- } else if (trimmedQuantity == u"few") {
- index = Plural::Few;
- } else if (trimmedQuantity == u"many") {
- index = Plural::Many;
- } else if (trimmedQuantity == u"other") {
- index = Plural::Other;
- } else {
- mLogger.error(childParser.getLineNumber())
- << "<item> in <plural> has invalid value '"
- << trimmedQuantity
- << "' for attribute 'quantity'."
- << std::endl;
- success = false;
- continue;
- }
+ if (plural->values[index]) {
+ mDiag->error(DiagMessage(itemSource)
+ << "duplicate quantity '" << trimmedQuantity << "'");
+ error = true;
+ continue;
+ }
- if (plural->values[index]) {
- mLogger.error(childParser.getLineNumber())
- << "duplicate quantity '"
- << trimmedQuantity
- << "'."
- << std::endl;
- success = false;
- continue;
- }
+ if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING,
+ kNoRawString))) {
+ error = true;
+ }
+ plural->values[index]->setSource(itemSource);
- if (!(plural->values[index] = parseXml(&childParser, android::ResTable_map::TYPE_STRING,
- kNoRawString))) {
- success = false;
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
+ << elementName << ">");
+ error = true;
}
}
- if (!success) {
+ if (error) {
return false;
}
- return mTable->addResource(resourceName, mConfig, source, std::move(plural));
+ outResource->value = std::move(plural);
+ return true;
}
-bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
- const ResourceNameRef& resourceName) {
- const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ outResource->name.type = ResourceType::kStyleable;
+
+ // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
+ outResource->symbolState = SymbolState::kPublic;
+
+ // Declare-styleable only ends up in default config;
+ if (outResource->config != ConfigDescription::defaultConfig()) {
+ mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '"
+ << outResource->config << "' for styleable "
+ << outResource->name.entry);
+ outResource->config = ConfigDescription::defaultConfig();
+ }
+
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
- bool success = true;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ std::u16string comment;
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Ignore text.
continue;
}
- ScopedXmlPullParser childParser(parser);
-
- const std::u16string& elementName = childParser.getElementName();
- if (elementName == u"attr") {
- const auto endAttrIter = childParser.endAttributes();
- auto attrIter = childParser.findAttribute(u"", u"name");
- if (attrIter == endAttrIter || attrIter->value.empty()) {
- mLogger.error(childParser.getLineNumber())
- << "<attr> tag must have a 'name' attribute."
- << std::endl;
- success = false;
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::u16string& elementNamespace = parser->getElementNamespace();
+ const std::u16string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && elementName == u"attr") {
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
+ if (!maybeName) {
+ mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
+ error = true;
continue;
}
- // Copy because our iterator will be invalidated.
- ResourceName attrResourceName = {
- mTable->getPackage(),
- ResourceType::kAttr,
- attrIter->value
- };
-
- std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true);
- if (!attr) {
- success = false;
+ // If this is a declaration, the package name may be in the name. Separate these out.
+ // Eg. <attr name="android:text" />
+ Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value());
+ if (!maybeRef) {
+ mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
+ << maybeName.value() << "'");
+ error = true;
continue;
}
- styleable->entries.emplace_back(attrResourceName);
+ Reference& childRef = maybeRef.value();
+ xml::transformReferenceFromNamespace(parser, u"", &childRef);
- // The package may have been corrected to another package. If that is so,
- // we don't add the declaration.
- if (attrResourceName.package == mTable->getPackage()) {
- success &= mTable->addResource(attrResourceName, mConfig,
- mSource.line(childParser.getLineNumber()),
- std::move(attr));
+ // Create the ParsedResource that will add the attribute to the table.
+ ParsedResource childResource;
+ childResource.name = childRef.name.value();
+ childResource.source = itemSource;
+ childResource.comment = std::move(comment);
+
+ if (!parseAttrImpl(parser, &childResource, true)) {
+ error = true;
+ continue;
}
- } else if (elementName != u"eat-comment" && elementName != u"skip") {
- mLogger.error(childParser.getLineNumber())
- << "<"
- << elementName
- << "> is not allowed inside <declare-styleable>."
- << std::endl;
- success = false;
+ // Create the reference to this attribute.
+ childRef.setComment(childResource.comment);
+ childRef.setSource(itemSource);
+ styleable->entries.push_back(std::move(childRef));
+
+ outResource->childResources.push_back(std::move(childResource));
+
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
+ << elementName << ">");
+ error = true;
}
+
+ comment = {};
}
- if (!success) {
+ if (error) {
return false;
}
- return mTable->addResource(resourceName, mConfig, source, std::move(styleable));
+ outResource->value = std::move(styleable);
+ return true;
}
} // namespace aapt
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 7618999f0023..ee5b33788312 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -18,135 +18,43 @@
#define AAPT_RESOURCE_PARSER_H
#include "ConfigDescription.h"
-#include "Logger.h"
+#include "Diagnostics.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "StringPiece.h"
#include "StringPool.h"
-#include "XmlPullParser.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "xml/XmlPullParser.h"
-#include <istream>
#include <memory>
namespace aapt {
-/*
- * Parses an XML file for resources and adds them to a ResourceTable.
- */
-class ResourceParser {
-public:
- /*
- * Extracts the package, type, and name from a string of the format:
- *
- * [package:]type/name
- *
- * where the package can be empty. Validation must be performed on each
- * individual extracted piece to verify that the pieces are valid.
- */
- static void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
- StringPiece16* outType, StringPiece16* outEntry);
-
- /*
- * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
- * `outReference` set to the parsed reference.
- *
- * If '+' was present in the reference, `outCreate` is set to true.
- * If '*' was present in the reference, `outPrivate` is set to true.
- */
- static bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
- bool* outCreate, bool* outPrivate);
-
- /*
- * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
- * with `outReference` set to the parsed reference.
- */
- static bool tryParseAttributeReference(const StringPiece16& str,
- ResourceNameRef* outReference);
-
- /*
- * Returns true if the string `str` was parsed as a valid reference to a style.
- * The format for a style parent is slightly more flexible than a normal reference:
- *
- * @[package:]style/<entry> or
- * ?[package:]style/<entry> or
- * <package>:[style/]<entry>
- */
- static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference,
- std::string* outError);
-
- /*
- * Returns a Reference object if the string was parsed as a resource or attribute reference,
- * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
- * the '+' was present in the string.
- */
- static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str,
- bool* outCreate);
-
- /*
- * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
- * as one.
- */
- static std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
-
- /*
- * Returns a BinaryPrimitve object representing a color if the string was parsed
- * as one.
- */
- static std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
-
- /*
- * Returns a BinaryPrimitve object representing a boolean if the string was parsed
- * as one.
- */
- static std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
-
- /*
- * Returns a BinaryPrimitve object representing an integer if the string was parsed
- * as one.
- */
- static std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
+struct ParsedResource;
- /*
- * Returns a BinaryPrimitve object representing a floating point number
- * (float, dimension, etc) if the string was parsed as one.
+struct ResourceParserOptions {
+ /**
+ * Whether the default setting for this parser is to allow translation.
*/
- static std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
+ bool translatable = true;
- /*
- * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
- * as one.
+ /**
+ * Whether positional arguments in formatted strings are treated as errors or warnings.
*/
- static std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute& enumAttr,
- const StringPiece16& str);
-
- /*
- * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
- * as one.
- */
- static std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute& enumAttr,
- const StringPiece16& str);
- /*
- * Try to convert a string to an Item for the given attribute. The attribute will
- * restrict what values the string can be converted to.
- * The callback function onCreateReference is called when the parsed item is a
- * reference to an ID that must be created (@+id/foo).
- */
- static std::unique_ptr<Item> parseItemForAttribute(
- const StringPiece16& value, const Attribute& attr,
- std::function<void(const ResourceName&)> onCreateReference = {});
-
- static std::unique_ptr<Item> parseItemForAttribute(
- const StringPiece16& value, uint32_t typeMask,
- std::function<void(const ResourceName&)> onCreateReference = {});
-
- static uint32_t androidTypeToAttributeTypeMask(uint16_t type);
+ bool errorOnPositionalArguments = true;
+};
- ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
- const ConfigDescription& config, const std::shared_ptr<XmlPullParser>& parser);
+/*
+ * Parses an XML file for resources and adds them to a ResourceTable.
+ */
+class ResourceParser {
+public:
+ ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
+ const ConfigDescription& config, const ResourceParserOptions& options = {});
ResourceParser(const ResourceParser&) = delete; // No copy.
- bool parse();
+ bool parse(xml::XmlPullParser* parser);
private:
/*
@@ -155,39 +63,47 @@ private:
* contains the escaped and whitespace trimmed text, while `outRawString`
* contains the unescaped text. Returns true on success.
*/
- bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,\
+ bool flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
StyleString* outStyleString);
/*
- * Parses the XML subtree and converts it to an Item. The type of Item that can be
- * parsed is denoted by the `typeMask`. If `allowRawValue` is true and the subtree
- * can not be parsed as a regular Item, then a RawString is returned. Otherwise
- * this returns nullptr.
+ * Parses the XML subtree and returns an Item.
+ * The type of Item that can be parsed is denoted by the `typeMask`.
+ * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
+ * RawString is returned. Otherwise this returns false;
*/
- std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
-
- bool parseResources(XmlPullParser* parser);
- bool parseString(XmlPullParser* parser, const ResourceNameRef& resourceName);
- bool parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName);
- bool parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName);
- bool parsePublic(XmlPullParser* parser, const StringPiece16& name);
- bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName);
- std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
- ResourceName* resourceName,
- bool weak);
- bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
- Attribute::Symbol* outSymbol);
- bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName);
- bool parseUntypedItem(XmlPullParser* parser, Style& style);
- bool parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName);
- bool parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask);
- bool parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName);
-
- std::shared_ptr<ResourceTable> mTable;
+ std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
+ const bool allowRawValue);
+
+ bool parseResources(xml::XmlPullParser* parser);
+ bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource);
+
+ bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t format);
+ bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
+
+ bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak);
+ Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
+ const StringPiece16& tag);
+ bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseStyleItem(xml::XmlPullParser* parser, Style* style);
+ bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
+ bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
+
+ IDiagnostics* mDiag;
+ ResourceTable* mTable;
Source mSource;
ConfigDescription mConfig;
- SourceLogger mLogger;
- std::shared_ptr<XmlPullParser> mParser;
+ ResourceParserOptions mOptions;
};
} // namespace aapt
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index a93d0ff7a835..3450de9078bb 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -16,8 +16,10 @@
#include "ResourceParser.h"
#include "ResourceTable.h"
+#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "SourceXmlPullParser.h"
+#include "test/Context.h"
+#include "xml/XmlPullParser.h"
#include <gtest/gtest.h>
#include <sstream>
@@ -27,156 +29,47 @@ namespace aapt {
constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) {
- ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_FALSE(create);
- EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) {
- ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create,
- &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_FALSE(create);
- EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) {
- ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
- &create, &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_FALSE(create);
- EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) {
- ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create,
- &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_TRUE(create);
- EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParsePrivateReference) {
- ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create,
- &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_FALSE(create);
- EXPECT_TRUE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) {
- bool create = false;
- bool privateRef = false;
- ResourceNameRef actual;
- EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create,
- &privateRef));
-}
-
-TEST(ResourceParserReferenceTest, ParseStyleParentReference) {
- Reference ref;
- std::string errStr;
- EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr));
- EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
- EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr));
- EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
-
- EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr));
- EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
- EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr));
- EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
-
- EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr));
- EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
- EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr));
- EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
- EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr));
- EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::stringstream input(kXmlPreamble);
+ input << "<attr name=\"foo\"/>" << std::endl;
+ ResourceTable table;
+ ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
+ xml::XmlPullParser xmlParser(input);
+ ASSERT_FALSE(parser.parse(&xmlParser));
}
struct ResourceParserTest : public ::testing::Test {
- virtual void SetUp() override {
- mTable = std::make_shared<ResourceTable>();
- mTable->setPackage(u"android");
+ ResourceTable mTable;
+ std::unique_ptr<IAaptContext> mContext;
+
+ void SetUp() override {
+ mContext = test::ContextBuilder().build();
}
::testing::AssertionResult testParse(const StringPiece& str) {
+ return testParse(str, ConfigDescription{});
+ }
+
+ ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) {
std::stringstream input(kXmlPreamble);
input << "<resources>\n" << str << "\n</resources>" << std::endl;
- ResourceParser parser(mTable, Source{ "test" }, {},
- std::make_shared<SourceXmlPullParser>(input));
- if (parser.parse()) {
+ ResourceParserOptions parserOptions;
+ ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, config,
+ parserOptions);
+ xml::XmlPullParser xmlParser(input);
+ if (parser.parse(&xmlParser)) {
return ::testing::AssertionSuccess();
}
return ::testing::AssertionFailure();
}
-
- template <typename T>
- const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) {
- using std::begin;
- using std::end;
-
- const ResourceTableType* type;
- const ResourceEntry* entry;
- std::tie(type, entry) = mTable->findResource(name);
- if (!type || !entry) {
- return nullptr;
- }
-
- for (const auto& configValue : entry->values) {
- if (configValue.config == config) {
- return dynamic_cast<const T*>(configValue.value.get());
- }
- }
- return nullptr;
- }
-
- template <typename T>
- const T* findResource(const ResourceNameRef& name) {
- return findResource<T>(name, {});
- }
-
- std::shared_ptr<ResourceTable> mTable;
};
-TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) {
- std::stringstream input(kXmlPreamble);
- input << "<attr name=\"foo\"/>" << std::endl;
- ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input));
- ASSERT_FALSE(parser.parse());
-}
-
TEST_F(ResourceParserTest, ParseQuotedString) {
std::string input = "<string name=\"foo\"> \" hey there \" </string>";
ASSERT_TRUE(testParse(input));
- const String* str = findResource<String>(ResourceName{
- u"android", ResourceType::kString, u"foo"});
+ String* str = test::getValue<String>(&mTable, u"@string/foo");
ASSERT_NE(nullptr, str);
EXPECT_EQ(std::u16string(u" hey there "), *str->value);
}
@@ -185,12 +78,30 @@ TEST_F(ResourceParserTest, ParseEscapedString) {
std::string input = "<string name=\"foo\">\\?123</string>";
ASSERT_TRUE(testParse(input));
- const String* str = findResource<String>(ResourceName{
- u"android", ResourceType::kString, u"foo" });
+ String* str = test::getValue<String>(&mTable, u"@string/foo");
ASSERT_NE(nullptr, str);
EXPECT_EQ(std::u16string(u"?123"), *str->value);
}
+TEST_F(ResourceParserTest, ParseFormattedString) {
+ std::string input = "<string name=\"foo\">%d %s</string>";
+ ASSERT_FALSE(testParse(input));
+
+ input = "<string name=\"foo\">%1$d %2$s</string>";
+ ASSERT_TRUE(testParse(input));
+}
+
+TEST_F(ResourceParserTest, IgnoreXliffTags) {
+ std::string input = "<string name=\"foo\" \n"
+ " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>";
+ ASSERT_TRUE(testParse(input));
+
+ String* str = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(StringPiece16(u"There are %1$d apples"), StringPiece16(*str->value));
+}
+
TEST_F(ResourceParserTest, ParseNull) {
std::string input = "<integer name=\"foo\">@null</integer>";
ASSERT_TRUE(testParse(input));
@@ -199,8 +110,7 @@ TEST_F(ResourceParserTest, ParseNull) {
// a non-existing value, and this causes problems in styles when trying to resolve
// an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
// with a data value of 0.
- const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
- u"android", ResourceType::kInteger, u"foo" });
+ BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo");
ASSERT_NE(nullptr, integer);
EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
EXPECT_EQ(0u, integer->value.data);
@@ -210,8 +120,7 @@ TEST_F(ResourceParserTest, ParseEmpty) {
std::string input = "<integer name=\"foo\">@empty</integer>";
ASSERT_TRUE(testParse(input));
- const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
- u"android", ResourceType::kInteger, u"foo" });
+ BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo");
ASSERT_NE(nullptr, integer);
EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
@@ -222,17 +131,51 @@ TEST_F(ResourceParserTest, ParseAttr) {
"<attr name=\"bar\"/>";
ASSERT_TRUE(testParse(input));
- const Attribute* attr = findResource<Attribute>(ResourceName{
- u"android", ResourceType::kAttr, u"foo"});
- EXPECT_NE(nullptr, attr);
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+ ASSERT_NE(nullptr, attr);
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
- attr = findResource<Attribute>(ResourceName{
- u"android", ResourceType::kAttr, u"bar"});
- EXPECT_NE(nullptr, attr);
+ attr = test::getValue<Attribute>(&mTable, u"@attr/bar");
+ ASSERT_NE(nullptr, attr);
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
}
+// Old AAPT allowed attributes to be defined under different configurations, but ultimately
+// stored them with the default configuration. Check that we have the same behavior.
+TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
+ const ConfigDescription watchConfig = test::parseConfigOrDie("watch");
+ std::string input = R"EOF(
+ <attr name="foo" />
+ <declare-styleable name="bar">
+ <attr name="baz" />
+ </declare-styleable>)EOF";
+ ASSERT_TRUE(testParse(input, watchConfig));
+
+ EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, u"@attr/foo", watchConfig));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, u"@attr/baz", watchConfig));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(&mTable, u"@styleable/bar", watchConfig));
+
+ EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, u"@attr/foo"));
+ EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, u"@attr/baz"));
+ EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, u"@styleable/bar"));
+}
+
+TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
+ std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
+ ASSERT_TRUE(testParse(input));
+
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
+ EXPECT_EQ(10, attr->minInt);
+ EXPECT_EQ(23, attr->maxInt);
+}
+
+TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
+ std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
+ ASSERT_FALSE(testParse(input));
+}
+
TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
std::string input = "<declare-styleable name=\"Styleable\">\n"
" <attr name=\"foo\" />\n"
@@ -240,8 +183,7 @@ TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
"<attr name=\"foo\" format=\"string\"/>";
ASSERT_TRUE(testParse(input));
- const Attribute* attr = findResource<Attribute>(ResourceName{
- u"android", ResourceType::kAttr, u"foo"});
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
ASSERT_NE(nullptr, attr);
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
}
@@ -255,8 +197,7 @@ TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
"</declare-styleable>";
ASSERT_TRUE(testParse(input));
- const Attribute* attr = findResource<Attribute>(ResourceName{
- u"android", ResourceType::kAttr, u"foo"});
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
ASSERT_NE(nullptr, attr);
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
}
@@ -269,19 +210,21 @@ TEST_F(ResourceParserTest, ParseEnumAttr) {
"</attr>";
ASSERT_TRUE(testParse(input));
- const Attribute* enumAttr = findResource<Attribute>(ResourceName{
- u"android", ResourceType::kAttr, u"foo"});
+ Attribute* enumAttr = test::getValue<Attribute>(&mTable, u"@attr/foo");
ASSERT_NE(enumAttr, nullptr);
EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
ASSERT_EQ(enumAttr->symbols.size(), 3u);
- EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar");
+ AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name);
+ EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, u"bar");
EXPECT_EQ(enumAttr->symbols[0].value, 0u);
- EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat");
+ AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name);
+ EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, u"bat");
EXPECT_EQ(enumAttr->symbols[1].value, 1u);
- EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz");
+ AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name);
+ EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, u"baz");
EXPECT_EQ(enumAttr->symbols[2].value, 2u);
}
@@ -293,24 +236,26 @@ TEST_F(ResourceParserTest, ParseFlagAttr) {
"</attr>";
ASSERT_TRUE(testParse(input));
- const Attribute* flagAttr = findResource<Attribute>(ResourceName{
- u"android", ResourceType::kAttr, u"foo"});
- ASSERT_NE(flagAttr, nullptr);
+ Attribute* flagAttr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+ ASSERT_NE(nullptr, flagAttr);
EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
ASSERT_EQ(flagAttr->symbols.size(), 3u);
- EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar");
+ AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name);
+ EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, u"bar");
EXPECT_EQ(flagAttr->symbols[0].value, 0u);
- EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat");
+ AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name);
+ EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, u"bat");
EXPECT_EQ(flagAttr->symbols[1].value, 1u);
- EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz");
+ AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name);
+ EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, u"baz");
EXPECT_EQ(flagAttr->symbols[2].value, 2u);
- std::unique_ptr<BinaryPrimitive> flagValue =
- ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat");
- ASSERT_NE(flagValue, nullptr);
+ std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr,
+ u"baz|bat");
+ ASSERT_NE(nullptr, flagValue);
EXPECT_EQ(flagValue->value.data, 1u | 2u);
}
@@ -331,28 +276,32 @@ TEST_F(ResourceParserTest, ParseStyle) {
"</style>";
ASSERT_TRUE(testParse(input));
- const Style* style = findResource<Style>(ResourceName{
- u"android", ResourceType::kStyle, u"foo"});
- ASSERT_NE(style, nullptr);
- EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name);
- ASSERT_EQ(style->entries.size(), 3u);
+ Style* style = test::getValue<Style>(&mTable, u"@style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(test::parseNameOrDie(u"@style/fu"), style->parent.value().name.value());
+ ASSERT_EQ(3u, style->entries.size());
+
+ AAPT_ASSERT_TRUE(style->entries[0].key.name);
+ EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), style->entries[0].key.name.value());
+
+ AAPT_ASSERT_TRUE(style->entries[1].key.name);
+ EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), style->entries[1].key.name.value());
- EXPECT_EQ(style->entries[0].key.name,
- (ResourceName{ u"android", ResourceType::kAttr, u"bar" }));
- EXPECT_EQ(style->entries[1].key.name,
- (ResourceName{ u"android", ResourceType::kAttr, u"bat" }));
- EXPECT_EQ(style->entries[2].key.name,
- (ResourceName{ u"android", ResourceType::kAttr, u"baz" }));
+ AAPT_ASSERT_TRUE(style->entries[2].key.name);
+ EXPECT_EQ(test::parseNameOrDie(u"@attr/baz"), style->entries[2].key.name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
ASSERT_TRUE(testParse(input));
- const Style* style = findResource<Style>(
- ResourceName{ u"android", ResourceType::kStyle, u"foo" });
- ASSERT_NE(style, nullptr);
- EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name);
+ Style* style = test::getValue<Style>(&mTable, u"@style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(test::parseNameOrDie(u"@com.app:style/Theme"), style->parent.value().name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
@@ -360,10 +309,11 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
" name=\"foo\" parent=\"app:Theme\"/>";
ASSERT_TRUE(testParse(input));
- const Style* style = findResource<Style>(ResourceName{
- u"android", ResourceType::kStyle, u"foo" });
- ASSERT_NE(style, nullptr);
- EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name);
+ Style* style = test::getValue<Style>(&mTable, u"@style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(test::parseNameOrDie(u"@android:style/Theme"), style->parent.value().name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
@@ -373,22 +323,21 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
"</style>";
ASSERT_TRUE(testParse(input));
- const Style* style = findResource<Style>(ResourceName{
- u"android", ResourceType::kStyle, u"foo" });
- ASSERT_NE(style, nullptr);
+ Style* style = test::getValue<Style>(&mTable, u"@style/foo");
+ ASSERT_NE(nullptr, style);
ASSERT_EQ(1u, style->entries.size());
- EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"),
- style->entries[0].key.name);
+ EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
std::string input = "<style name=\"foo.bar\"/>";
ASSERT_TRUE(testParse(input));
- const Style* style = findResource<Style>(ResourceName{
- u"android", ResourceType::kStyle, u"foo.bar" });
- ASSERT_NE(style, nullptr);
- EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+ Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie(u"@style/foo"));
EXPECT_TRUE(style->parentInferred);
}
@@ -396,18 +345,27 @@ TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAtt
std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
ASSERT_TRUE(testParse(input));
- const Style* style = findResource<Style>(ResourceName{
- u"android", ResourceType::kStyle, u"foo.bar" });
- ASSERT_NE(style, nullptr);
- EXPECT_FALSE(style->parent.name.isValid());
+ Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar");
+ ASSERT_NE(nullptr, style);
+ AAPT_EXPECT_FALSE(style->parent);
EXPECT_FALSE(style->parentInferred);
}
+TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) {
+ std::string input = R"EOF(<style name="foo" parent="*android:style/bar" />)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ Style* style = test::getValue<Style>(&mTable, u"@style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ EXPECT_TRUE(style->parent.value().privateReference);
+}
+
TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
std::string input = "<string name=\"foo\">@+id/bar</string>";
ASSERT_TRUE(testParse(input));
- const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"});
+ Id* id = test::getValue<Id>(&mTable, u"@id/bar");
ASSERT_NE(id, nullptr);
}
@@ -415,25 +373,57 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
std::string input = "<declare-styleable name=\"foo\">\n"
" <attr name=\"bar\" />\n"
" <attr name=\"bat\" format=\"string|reference\"/>\n"
+ " <attr name=\"baz\">\n"
+ " <enum name=\"foo\" value=\"1\"/>\n"
+ " </attr>\n"
"</declare-styleable>";
ASSERT_TRUE(testParse(input));
- const Attribute* attr = findResource<Attribute>(ResourceName{
- u"android", ResourceType::kAttr, u"bar"});
+ Maybe<ResourceTable::SearchResult> result =
+ mTable.findResource(test::parseNameOrDie(u"@styleable/foo"));
+ AAPT_ASSERT_TRUE(result);
+ EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
+
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/bar");
+ ASSERT_NE(attr, nullptr);
+ EXPECT_TRUE(attr->isWeak());
+
+ attr = test::getValue<Attribute>(&mTable, u"@attr/bat");
ASSERT_NE(attr, nullptr);
EXPECT_TRUE(attr->isWeak());
- attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"});
+ attr = test::getValue<Attribute>(&mTable, u"@attr/baz");
ASSERT_NE(attr, nullptr);
EXPECT_TRUE(attr->isWeak());
+ EXPECT_EQ(1u, attr->symbols.size());
+
+ EXPECT_NE(nullptr, test::getValue<Id>(&mTable, u"@id/foo"));
- const Styleable* styleable = findResource<Styleable>(ResourceName{
- u"android", ResourceType::kStyleable, u"foo" });
+ Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
ASSERT_NE(styleable, nullptr);
+ ASSERT_EQ(3u, styleable->entries.size());
+
+ EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), styleable->entries[0].name.value());
+ EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value());
+}
+
+TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
+ std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n"
+ " <attr name=\"*android:bar\" />\n"
+ " <attr name=\"privAndroid:bat\" />\n"
+ "</declare-styleable>";
+ ASSERT_TRUE(testParse(input));
+ Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
+ ASSERT_NE(nullptr, styleable);
ASSERT_EQ(2u, styleable->entries.size());
- EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name);
- EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name);
+ EXPECT_TRUE(styleable->entries[0].privateReference);
+ AAPT_ASSERT_TRUE(styleable->entries[0].name);
+ EXPECT_EQ(std::u16string(u"android"), styleable->entries[0].name.value().package);
+
+ EXPECT_TRUE(styleable->entries[1].privateReference);
+ AAPT_ASSERT_TRUE(styleable->entries[1].name);
+ EXPECT_EQ(std::u16string(u"android"), styleable->entries[1].name.value().package);
}
TEST_F(ResourceParserTest, ParseArray) {
@@ -444,14 +434,21 @@ TEST_F(ResourceParserTest, ParseArray) {
"</array>";
ASSERT_TRUE(testParse(input));
- const Array* array = findResource<Array>(ResourceName{
- u"android", ResourceType::kArray, u"foo" });
+ Array* array = test::getValue<Array>(&mTable, u"@array/foo");
ASSERT_NE(array, nullptr);
ASSERT_EQ(3u, array->items.size());
- EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get()));
- EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get()));
- EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get()));
+ EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get()));
+ EXPECT_NE(nullptr, valueCast<String>(array->items[1].get()));
+ EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get()));
+}
+
+TEST_F(ResourceParserTest, ParseStringArray) {
+ std::string input = "<string-array name=\"foo\">\n"
+ " <item>\"Werk\"</item>\n"
+ "</string-array>\n";
+ ASSERT_TRUE(testParse(input));
+ EXPECT_NE(nullptr, test::getValue<Array>(&mTable, u"@array/foo"));
}
TEST_F(ResourceParserTest, ParsePlural) {
@@ -463,18 +460,67 @@ TEST_F(ResourceParserTest, ParsePlural) {
}
TEST_F(ResourceParserTest, ParseCommentsWithResource) {
- std::string input = "<!-- This is a comment -->\n"
+ std::string input = "<!--This is a comment-->\n"
"<string name=\"foo\">Hi</string>";
ASSERT_TRUE(testParse(input));
- const ResourceTableType* type;
- const ResourceEntry* entry;
- std::tie(type, entry) = mTable->findResource(ResourceName{
- u"android", ResourceType::kString, u"foo"});
- ASSERT_NE(type, nullptr);
- ASSERT_NE(entry, nullptr);
- ASSERT_FALSE(entry->values.empty());
- EXPECT_EQ(entry->values.front().comment, u"This is a comment");
+ String* value = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), u"This is a comment");
+}
+
+TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
+ std::string input = "<!--One-->\n"
+ "<!--Two-->\n"
+ "<string name=\"foo\">Hi</string>";
+
+ ASSERT_TRUE(testParse(input));
+
+ String* value = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), u"Two");
+}
+
+TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
+ std::string input = "<!--One-->\n"
+ "<string name=\"foo\">\n"
+ " Hi\n"
+ "<!--Two-->\n"
+ "</string>";
+
+ ASSERT_TRUE(testParse(input));
+
+ String* value = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), u"One");
+}
+
+TEST_F(ResourceParserTest, ParseNestedComments) {
+ // We only care about declare-styleable and enum/flag attributes because comments
+ // from those end up in R.java
+ std::string input = R"EOF(
+ <declare-styleable name="foo">
+ <!-- The name of the bar -->
+ <attr name="barName" format="string|reference" />
+ </declare-styleable>
+
+ <attr name="foo">
+ <!-- The very first -->
+ <enum name="one" value="1" />
+ </attr>)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
+ ASSERT_NE(nullptr, styleable);
+ ASSERT_EQ(1u, styleable->entries.size());
+
+ EXPECT_EQ(StringPiece16(u"The name of the bar"), styleable->entries.front().getComment());
+
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+ ASSERT_NE(nullptr, attr);
+ ASSERT_EQ(1u, attr->symbols.size());
+
+ EXPECT_EQ(StringPiece16(u"The very first"), attr->symbols.front().symbol.getComment());
}
/*
@@ -485,8 +531,101 @@ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
std::string input = "<public type=\"id\" name=\"foo\"/>";
ASSERT_TRUE(testParse(input));
- const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" });
+ Id* id = test::getValue<Id>(&mTable, u"@id/foo");
ASSERT_NE(nullptr, id);
}
+TEST_F(ResourceParserTest, KeepAllProducts) {
+ std::string input = R"EOF(
+ <string name="foo" product="phone">hi</string>
+ <string name="foo" product="no-sdcard">ho</string>
+ <string name="bar" product="">wee</string>
+ <string name="baz">woo</string>
+ <string name="bit" product="phablet">hoot</string>
+ <string name="bot" product="default">yes</string>
+ )EOF";
+ ASSERT_TRUE(testParse(input));
+
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo",
+ ConfigDescription::defaultConfig(),
+ "phone"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo",
+ ConfigDescription::defaultConfig(),
+ "no-sdcard"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bar",
+ ConfigDescription::defaultConfig(),
+ ""));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/baz",
+ ConfigDescription::defaultConfig(),
+ ""));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bit",
+ ConfigDescription::defaultConfig(),
+ "phablet"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bot",
+ ConfigDescription::defaultConfig(),
+ "default"));
+}
+
+TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
+ std::string input = R"EOF(
+ <public-group type="attr" first-id="0x01010040">
+ <public name="foo" />
+ <public name="bar" />
+ </public-group>)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ Maybe<ResourceTable::SearchResult> result = mTable.findResource(
+ test::parseNameOrDie(u"@attr/foo"));
+ AAPT_ASSERT_TRUE(result);
+
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ ResourceId actualId(result.value().package->id.value(),
+ result.value().type->id.value(),
+ result.value().entry->id.value());
+ EXPECT_EQ(ResourceId(0x01010040), actualId);
+
+ result = mTable.findResource(test::parseNameOrDie(u"@attr/bar"));
+ AAPT_ASSERT_TRUE(result);
+
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ actualId = ResourceId(result.value().package->id.value(),
+ result.value().type->id.value(),
+ result.value().entry->id.value());
+ EXPECT_EQ(ResourceId(0x01010041), actualId);
+}
+
+TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
+ std::string input = R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF";
+ ASSERT_FALSE(testParse(input));
+}
+
+TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) {
+ std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ Maybe<ResourceTable::SearchResult> result = mTable.findResource(
+ test::parseNameOrDie(u"@string/bar"));
+ AAPT_ASSERT_TRUE(result);
+ const ResourceEntry* entry = result.value().entry;
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state);
+}
+
+TEST_F(ResourceParserTest, ParseItemElementWithFormat) {
+ std::string input = R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ BinaryPrimitive* val = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo");
+ ASSERT_NE(nullptr, val);
+
+ EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index c93ecc768022..8d734f3fc36d 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -15,11 +15,11 @@
*/
#include "ConfigDescription.h"
-#include "Logger.h"
#include "NameMangler.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "Util.h"
+#include "ValueVisitor.h"
+#include "util/Util.h"
#include <algorithm>
#include <androidfw/ResourceTypes.h>
@@ -29,73 +29,180 @@
namespace aapt {
-static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
- return lhs.config < rhs;
-}
-
static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
return lhs->type < rhs;
}
-static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
+template <typename T>
+static bool lessThanStructWithName(const std::unique_ptr<T>& lhs,
+ const StringPiece16& rhs) {
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
}
-ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
- // Make sure attrs always have type ID 1.
- findOrCreateType(ResourceType::kAttr)->typeId = 1;
+ResourceTablePackage* ResourceTable::findPackage(const StringPiece16& name) {
+ const auto last = packages.end();
+ auto iter = std::lower_bound(packages.begin(), last, name,
+ lessThanStructWithName<ResourceTablePackage>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
+ return nullptr;
}
-std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
- auto last = mTypes.end();
- auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType);
- if (iter != last) {
- if ((*iter)->type == type) {
- return *iter;
+ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) {
+ for (auto& package : packages) {
+ if (package->id && package->id.value() == id) {
+ return package.get();
}
}
- return *mTypes.emplace(iter, new ResourceTableType{ type });
+ return nullptr;
}
-std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry(
- std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) {
- auto last = type->entries.end();
- auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry);
- if (iter != last) {
- if (name == (*iter)->name) {
- return *iter;
- }
+ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, Maybe<uint8_t> id) {
+ ResourceTablePackage* package = findOrCreatePackage(name);
+ if (id && !package->id) {
+ package->id = id;
+ return package;
}
- return *type->entries.emplace(iter, new ResourceEntry{ name });
+
+ if (id && package->id && package->id.value() != id.value()) {
+ return nullptr;
+ }
+ return package;
}
-struct IsAttributeVisitor : ConstValueVisitor {
- bool isAttribute = false;
+ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece16& name) {
+ const auto last = packages.end();
+ auto iter = std::lower_bound(packages.begin(), last, name,
+ lessThanStructWithName<ResourceTablePackage>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
+
+ std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>();
+ newPackage->name = name.toString();
+ return packages.emplace(iter, std::move(newPackage))->get();
+}
- void visit(const Attribute&, ValueVisitorArgs&) override {
- isAttribute = true;
+ResourceTableType* ResourceTablePackage::findType(ResourceType type) {
+ const auto last = types.end();
+ auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
+ if (iter != last && (*iter)->type == type) {
+ return iter->get();
}
+ return nullptr;
+}
+
+ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) {
+ const auto last = types.end();
+ auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
+ if (iter != last && (*iter)->type == type) {
+ return iter->get();
+ }
+ return types.emplace(iter, new ResourceTableType(type))->get();
+}
- operator bool() {
- return isAttribute;
+ResourceEntry* ResourceTableType::findEntry(const StringPiece16& name) {
+ const auto last = entries.end();
+ auto iter = std::lower_bound(entries.begin(), last, name,
+ lessThanStructWithName<ResourceEntry>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
}
+ return nullptr;
+}
+
+ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece16& name) {
+ auto last = entries.end();
+ auto iter = std::lower_bound(entries.begin(), last, name,
+ lessThanStructWithName<ResourceEntry>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
+ return entries.emplace(iter, new ResourceEntry(name))->get();
+}
+
+ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) {
+ return findValue(config, StringPiece());
+}
+
+struct ConfigKey {
+ const ConfigDescription* config;
+ const StringPiece& product;
};
+bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
+ int cmp = lhs->config.compare(*rhs.config);
+ if (cmp == 0) {
+ cmp = StringPiece(lhs->product).compare(rhs.product);
+ }
+ return cmp < 0;
+}
+
+ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config,
+ const StringPiece& product) {
+ auto iter = std::lower_bound(values.begin(), values.end(),
+ ConfigKey{ &config, product }, ltConfigKeyRef);
+ if (iter != values.end()) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config && StringPiece(value->product) == product) {
+ return value;
+ }
+ }
+ return nullptr;
+}
+
+ResourceConfigValue* ResourceEntry::findOrCreateValue(const ConfigDescription& config,
+ const StringPiece& product) {
+ auto iter = std::lower_bound(values.begin(), values.end(),
+ ConfigKey{ &config, product }, ltConfigKeyRef);
+ if (iter != values.end()) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config && StringPiece(value->product) == product) {
+ return value;
+ }
+ }
+ ResourceConfigValue* newValue = values.insert(
+ iter, util::make_unique<ResourceConfigValue>(config, product))->get();
+ return newValue;
+}
+
+std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(const ConfigDescription& config) {
+ std::vector<ResourceConfigValue*> results;
+
+ auto iter = values.begin();
+ for (; iter != values.end(); ++iter) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config) {
+ results.push_back(value);
+ ++iter;
+ break;
+ }
+ }
+
+ for (; iter != values.end(); ++iter) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config) {
+ results.push_back(value);
+ }
+ }
+ return results;
+}
+
/**
* The default handler for collisions. A return value of -1 means keep the
* existing value, 0 means fail, and +1 means take the incoming value.
*/
-static int defaultCollisionHandler(const Value& existing, const Value& incoming) {
- IsAttributeVisitor existingIsAttr, incomingIsAttr;
- existing.accept(existingIsAttr, {});
- incoming.accept(incomingIsAttr, {});
+int ResourceTable::resolveValueCollision(Value* existing, Value* incoming) {
+ Attribute* existingAttr = valueCast<Attribute>(existing);
+ Attribute* incomingAttr = valueCast<Attribute>(incoming);
- if (!incomingIsAttr) {
- if (incoming.isWeak()) {
+ if (!incomingAttr) {
+ if (incoming->isWeak()) {
// We're trying to add a weak resource but a resource
// already exists. Keep the existing.
return -1;
- } else if (existing.isWeak()) {
+ } else if (existing->isWeak()) {
// Override the weak resource with the new strong resource.
return 1;
}
@@ -104,8 +211,8 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming)
return 0;
}
- if (!existingIsAttr) {
- if (existing.isWeak()) {
+ if (!existingAttr) {
+ if (existing->isWeak()) {
// The existing value is not an attribute and it is weak,
// so take the incoming attribute value.
return 1;
@@ -115,27 +222,27 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming)
return 0;
}
+ assert(incomingAttr && existingAttr);
+
//
// Attribute specific handling. At this point we know both
// values are attributes. Since we can declare and define
// attributes all-over, we do special handling to see
// which definition sticks.
//
- const Attribute& existingAttr = static_cast<const Attribute&>(existing);
- const Attribute& incomingAttr = static_cast<const Attribute&>(incoming);
- if (existingAttr.typeMask == incomingAttr.typeMask) {
+ if (existingAttr->typeMask == incomingAttr->typeMask) {
// The two attributes are both DECLs, but they are plain attributes
// with the same formats.
// Keep the strongest one.
- return existingAttr.isWeak() ? 1 : -1;
+ return existingAttr->isWeak() ? 1 : -1;
}
- if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
+ if (existingAttr->isWeak() && existingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
// Any incoming attribute is better than this.
return 1;
}
- if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
+ if (incomingAttr->isWeak() && incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
// The incoming attribute may be a USE instead of a DECL.
// Keep the existing attribute.
return -1;
@@ -146,285 +253,281 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming)
static constexpr const char16_t* kValidNameChars = u"._-";
static constexpr const char16_t* kValidNameMangledChars = u"._-$";
-bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
- const SourceLine& source, std::unique_ptr<Value> value) {
- return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars);
+bool ResourceTable::addResource(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag) {
+ return addResourceImpl(name, {}, config, product, std::move(value), kValidNameChars,
+ resolveValueCollision, diag);
+}
+
+bool ResourceTable::addResource(const ResourceNameRef& name,
+ const ResourceId resId,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag) {
+ return addResourceImpl(name, resId, config, product, std::move(value), kValidNameChars,
+ resolveValueCollision, diag);
}
-bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const SourceLine& source,
- std::unique_ptr<Value> value) {
- return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars);
+bool ResourceTable::addFileReference(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ IDiagnostics* diag) {
+ return addFileReferenceImpl(name, config, source, path, nullptr, kValidNameChars, diag);
+}
+
+bool ResourceTable::addFileReferenceAllowMangled(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ io::IFile* file,
+ IDiagnostics* diag) {
+ return addFileReferenceImpl(name, config, source, path, file, kValidNameMangledChars, diag);
+}
+
+bool ResourceTable::addFileReferenceImpl(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ io::IFile* file,
+ const char16_t* validChars,
+ IDiagnostics* diag) {
+ std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
+ stringPool.makeRef(path));
+ fileRef->setSource(source);
+ fileRef->file = file;
+ return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
+ kValidNameChars, resolveValueCollision, diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
const ConfigDescription& config,
- const SourceLine& source,
- std::unique_ptr<Value> value) {
- return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
- kValidNameMangledChars);
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag) {
+ return addResourceImpl(name, ResourceId{}, config, product, std::move(value),
+ kValidNameMangledChars, resolveValueCollision, diag);
}
-bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const SourceLine& source,
- std::unique_ptr<Value> value, const char16_t* validChars) {
- if (!name.package.empty() && name.package != mPackage) {
- Logger::error(source)
- << "resource '"
- << name
- << "' has incompatible package. Must be '"
- << mPackage
- << "'."
- << std::endl;
- return false;
- }
+bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
+ const ResourceId id,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag) {
+ return addResourceImpl(name, id, config, product, std::move(value), kValidNameMangledChars,
+ resolveValueCollision, diag);
+}
+
+bool ResourceTable::addResourceImpl(const ResourceNameRef& name,
+ const ResourceId resId,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ const char16_t* validChars,
+ std::function<int(Value*,Value*)> conflictResolver,
+ IDiagnostics* diag) {
+ assert(value && "value can't be nullptr");
+ assert(diag && "diagnostics can't be nullptr");
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
if (badCharIter != name.entry.end()) {
- Logger::error(source)
- << "resource '"
- << name
- << "' has invalid entry name '"
- << name.entry
- << "'. Invalid character '"
- << StringPiece16(badCharIter, 1)
- << "'."
- << std::endl;
+ diag->error(DiagMessage(value->getSource())
+ << "resource '"
+ << name
+ << "' has invalid entry name '"
+ << name.entry
+ << "'. Invalid character '"
+ << StringPiece16(badCharIter, 1)
+ << "'");
return false;
}
- std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
- if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
- type->typeId != resId.typeId()) {
- Logger::error(source)
- << "trying to add resource '"
- << name
- << "' with ID "
- << resId
- << " but type '"
- << type->type
- << "' already has ID "
- << std::hex << type->typeId << std::dec
- << "."
- << std::endl;
+ ResourceTablePackage* package = findOrCreatePackage(name.package);
+ if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
+ diag->error(DiagMessage(value->getSource())
+ << "trying to add resource '"
+ << name
+ << "' with ID "
+ << resId
+ << " but package '"
+ << package->name
+ << "' already has ID "
+ << std::hex << (int) package->id.value() << std::dec);
return false;
}
- std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
- if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
- entry->entryId != resId.entryId()) {
- Logger::error(source)
- << "trying to add resource '"
- << name
- << "' with ID "
- << resId
- << " but resource already has ID "
- << ResourceId(mPackageId, type->typeId, entry->entryId)
- << "."
- << std::endl;
+ ResourceTableType* type = package->findOrCreateType(name.type);
+ if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
+ diag->error(DiagMessage(value->getSource())
+ << "trying to add resource '"
+ << name
+ << "' with ID "
+ << resId
+ << " but type '"
+ << type->type
+ << "' already has ID "
+ << std::hex << (int) type->id.value() << std::dec);
return false;
}
- const auto endIter = std::end(entry->values);
- auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs);
- if (iter == endIter || iter->config != config) {
- // This resource did not exist before, add it.
- entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
+ ResourceEntry* entry = type->findOrCreateEntry(name.entry);
+ if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
+ diag->error(DiagMessage(value->getSource())
+ << "trying to add resource '"
+ << name
+ << "' with ID "
+ << resId
+ << " but resource already has ID "
+ << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
+ return false;
+ }
+
+ ResourceConfigValue* configValue = entry->findOrCreateValue(config, product);
+ if (!configValue->value) {
+ // Resource does not exist, add it now.
+ configValue->value = std::move(value);
+
} else {
- int collisionResult = defaultCollisionHandler(*iter->value, *value);
+ int collisionResult = conflictResolver(configValue->value.get(), value.get());
if (collisionResult > 0) {
// Take the incoming value.
- *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
+ configValue->value = std::move(value);
} else if (collisionResult == 0) {
- Logger::error(source)
- << "duplicate value for resource '" << name << "' "
- << "with config '" << iter->config << "'."
- << std::endl;
-
- Logger::error(iter->source)
- << "resource previously defined here."
- << std::endl;
+ diag->error(DiagMessage(value->getSource())
+ << "duplicate value for resource '" << name << "' "
+ << "with config '" << config << "'");
+ diag->error(DiagMessage(configValue->value->getSource())
+ << "resource previously defined here");
return false;
}
}
if (resId.isValid()) {
- type->typeId = resId.typeId();
- entry->entryId = resId.entryId();
+ package->id = resId.packageId();
+ type->id = resId.typeId();
+ entry->id = resId.entryId();
}
return true;
}
-bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
- const SourceLine& source) {
- return markPublicImpl(name, resId, source, kValidNameChars);
+bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId resId,
+ const Symbol& symbol, IDiagnostics* diag) {
+ return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
}
-bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
- const SourceLine& source) {
- return markPublicImpl(name, resId, source, kValidNameMangledChars);
+bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
+ const ResourceId resId,
+ const Symbol& symbol, IDiagnostics* diag) {
+ return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
}
-bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
- const SourceLine& source, const char16_t* validChars) {
- if (!name.package.empty() && name.package != mPackage) {
- Logger::error(source)
- << "resource '"
- << name
- << "' has incompatible package. Must be '"
- << mPackage
- << "'."
- << std::endl;
- return false;
- }
+bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
+ const Symbol& symbol, const char16_t* validChars,
+ IDiagnostics* diag) {
+ assert(diag && "diagnostics can't be nullptr");
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
if (badCharIter != name.entry.end()) {
- Logger::error(source)
- << "resource '"
- << name
- << "' has invalid entry name '"
- << name.entry
- << "'. Invalid character '"
- << StringPiece16(badCharIter, 1)
- << "'."
- << std::endl;
+ diag->error(DiagMessage(symbol.source)
+ << "resource '"
+ << name
+ << "' has invalid entry name '"
+ << name.entry
+ << "'. Invalid character '"
+ << StringPiece16(badCharIter, 1)
+ << "'");
return false;
}
- std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
- if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
- type->typeId != resId.typeId()) {
- Logger::error(source)
- << "trying to make resource '"
- << name
- << "' public with ID "
- << resId
- << " but type '"
- << type->type
- << "' already has ID "
- << std::hex << type->typeId << std::dec
- << "."
- << std::endl;
+ ResourceTablePackage* package = findOrCreatePackage(name.package);
+ if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
+ diag->error(DiagMessage(symbol.source)
+ << "trying to add resource '"
+ << name
+ << "' with ID "
+ << resId
+ << " but package '"
+ << package->name
+ << "' already has ID "
+ << std::hex << (int) package->id.value() << std::dec);
return false;
}
- std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
- if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
- entry->entryId != resId.entryId()) {
- Logger::error(source)
- << "trying to make resource '"
- << name
- << "' public with ID "
- << resId
- << " but resource already has ID "
- << ResourceId(mPackageId, type->typeId, entry->entryId)
- << "."
- << std::endl;
+ ResourceTableType* type = package->findOrCreateType(name.type);
+ if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
+ diag->error(DiagMessage(symbol.source)
+ << "trying to add resource '"
+ << name
+ << "' with ID "
+ << resId
+ << " but type '"
+ << type->type
+ << "' already has ID "
+ << std::hex << (int) type->id.value() << std::dec);
return false;
}
- type->publicStatus.isPublic = true;
- entry->publicStatus.isPublic = true;
- entry->publicStatus.source = source;
+ ResourceEntry* entry = type->findOrCreateEntry(name.entry);
+ if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
+ diag->error(DiagMessage(symbol.source)
+ << "trying to add resource '"
+ << name
+ << "' with ID "
+ << resId
+ << " but resource already has ID "
+ << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
+ return false;
+ }
if (resId.isValid()) {
- type->typeId = resId.typeId();
- entry->entryId = resId.entryId();
+ package->id = resId.packageId();
+ type->id = resId.typeId();
+ entry->id = resId.entryId();
}
- return true;
-}
-bool ResourceTable::merge(ResourceTable&& other) {
- const bool mangleNames = mPackage != other.getPackage();
- std::u16string mangledName;
-
- for (auto& otherType : other) {
- std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type);
- if (otherType->publicStatus.isPublic) {
- if (type->publicStatus.isPublic && type->typeId != otherType->typeId) {
- Logger::error() << "can not merge type '" << type->type
- << "': conflicting public IDs "
- << "(" << type->typeId << " vs " << otherType->typeId << ")."
- << std::endl;
- return false;
- }
- type->publicStatus = std::move(otherType->publicStatus);
- type->typeId = otherType->typeId;
- }
+ // Only mark the type state as public, it doesn't care about being private.
+ if (symbol.state == SymbolState::kPublic) {
+ type->symbolStatus.state = SymbolState::kPublic;
+ }
- for (auto& otherEntry : otherType->entries) {
- const std::u16string* nameToAdd = &otherEntry->name;
- if (mangleNames) {
- mangledName = otherEntry->name;
- NameMangler::mangle(other.getPackage(), &mangledName);
- nameToAdd = &mangledName;
- }
-
- std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd);
- if (otherEntry->publicStatus.isPublic) {
- if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) {
- Logger::error() << "can not merge entry '" << type->type << "/" << entry->name
- << "': conflicting public IDs "
- << "(" << entry->entryId << " vs " << entry->entryId << ")."
- << std::endl;
- return false;
- }
- entry->publicStatus = std::move(otherEntry->publicStatus);
- entry->entryId = otherEntry->entryId;
- }
-
- for (ResourceConfigValue& otherValue : otherEntry->values) {
- auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
- otherValue.config, compareConfigs);
- if (iter != entry->values.end() && iter->config == otherValue.config) {
- int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value);
- if (collisionResult > 0) {
- // Take the incoming value.
- iter->source = std::move(otherValue.source);
- iter->comment = std::move(otherValue.comment);
- iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool));
- } else if (collisionResult == 0) {
- ResourceNameRef resourceName = { mPackage, type->type, entry->name };
- Logger::error(otherValue.source)
- << "resource '" << resourceName << "' has a conflicting value for "
- << "configuration (" << otherValue.config << ")."
- << std::endl;
- Logger::note(iter->source) << "originally defined here." << std::endl;
- return false;
- }
- } else {
- entry->values.insert(iter, ResourceConfigValue{
- otherValue.config,
- std::move(otherValue.source),
- std::move(otherValue.comment),
- std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)),
- });
- }
- }
- }
+ if (symbol.state == SymbolState::kUndefined &&
+ entry->symbolStatus.state != SymbolState::kUndefined) {
+ // We can't undefine a symbol (remove its visibility). Ignore.
+ return true;
+ }
+
+ if (symbol.state == SymbolState::kPrivate &&
+ entry->symbolStatus.state == SymbolState::kPublic) {
+ // We can't downgrade public to private. Ignore.
+ return true;
}
+
+ entry->symbolStatus = std::move(symbol);
return true;
}
-std::tuple<const ResourceTableType*, const ResourceEntry*>
-ResourceTable::findResource(const ResourceNameRef& name) const {
- if (name.package != mPackage) {
+Maybe<ResourceTable::SearchResult>
+ResourceTable::findResource(const ResourceNameRef& name) {
+ ResourceTablePackage* package = findPackage(name.package);
+ if (!package) {
return {};
}
- auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
- if (iter == mTypes.end() || (*iter)->type != name.type) {
+ ResourceTableType* type = package->findType(name.type);
+ if (!type) {
return {};
}
- const std::unique_ptr<ResourceTableType>& type = *iter;
- auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry,
- lessThanEntry);
- if (iter2 == type->entries.end() || name.entry != (*iter2)->name) {
+ ResourceEntry* entry = type->findEntry(name.entry);
+ if (!entry) {
return {};
}
- return std::make_tuple(iter->get(), iter2->get());
+ return SearchResult{ package, type, entry };
}
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 706f56a2776f..7f5c2b8c0f37 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -18,46 +18,68 @@
#define AAPT_RESOURCE_TABLE_H
#include "ConfigDescription.h"
+#include "Diagnostics.h"
#include "Resource.h"
#include "ResourceValues.h"
#include "Source.h"
#include "StringPool.h"
+#include "io/File.h"
+#include <android-base/macros.h>
+#include <map>
#include <memory>
#include <string>
#include <tuple>
+#include <unordered_map>
#include <vector>
namespace aapt {
+enum class SymbolState {
+ kUndefined,
+ kPublic,
+ kPrivate
+};
+
/**
* The Public status of a resource.
*/
-struct Public {
- bool isPublic = false;
- SourceLine source;
+struct Symbol {
+ SymbolState state = SymbolState::kUndefined;
+ Source source;
std::u16string comment;
};
-/**
- * The resource value for a specific configuration.
- */
-struct ResourceConfigValue {
- ConfigDescription config;
- SourceLine source;
- std::u16string comment;
+class ResourceConfigValue {
+public:
+ /**
+ * The configuration for which this value is defined.
+ */
+ const ConfigDescription config;
+
+ /**
+ * The product for which this value is defined.
+ */
+ const std::string product;
+
+ /**
+ * The actual Value.
+ */
std::unique_ptr<Value> value;
+
+ ResourceConfigValue(const ConfigDescription& config, const StringPiece& product) :
+ config(config), product(product.toString()) { }
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
};
/**
* Represents a resource entry, which may have
* varying values for each defined configuration.
*/
-struct ResourceEntry {
- enum {
- kUnsetEntryId = 0xffffffffu
- };
-
+class ResourceEntry {
+public:
/**
* The name of the resource. Immutable, as
* this determines the order of this resource
@@ -68,32 +90,36 @@ struct ResourceEntry {
/**
* The entry ID for this resource.
*/
- size_t entryId;
+ Maybe<uint16_t> id;
/**
- * Whether this resource is public (and must maintain the same
- * entry ID across builds).
+ * Whether this resource is public (and must maintain the same entry ID across builds).
*/
- Public publicStatus;
+ Symbol symbolStatus;
/**
* The resource's values for each configuration.
*/
- std::vector<ResourceConfigValue> values;
+ std::vector<std::unique_ptr<ResourceConfigValue>> values;
+
+ ResourceEntry(const StringPiece16& name) : name(name.toString()) { }
- inline ResourceEntry(const StringPiece16& _name);
- inline ResourceEntry(const ResourceEntry* rhs);
+ ResourceConfigValue* findValue(const ConfigDescription& config);
+ ResourceConfigValue* findValue(const ConfigDescription& config, const StringPiece& product);
+ ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
+ const StringPiece& product);
+ std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config);
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
};
/**
* Represents a resource type, which holds entries defined
* for this type.
*/
-struct ResourceTableType {
- enum {
- kUnsetTypeId = 0xffffffffu
- };
-
+class ResourceTableType {
+public:
/**
* The logical type of resource (string, drawable, layout, etc.).
*/
@@ -102,21 +128,49 @@ struct ResourceTableType {
/**
* The type ID for this resource.
*/
- size_t typeId;
+ Maybe<uint8_t> id;
/**
* Whether this type is public (and must maintain the same
* type ID across builds).
*/
- Public publicStatus;
+ Symbol symbolStatus;
/**
* List of resources for this type.
*/
std::vector<std::unique_ptr<ResourceEntry>> entries;
- ResourceTableType(const ResourceType _type);
- ResourceTableType(const ResourceTableType* rhs);
+ explicit ResourceTableType(const ResourceType type) : type(type) { }
+
+ ResourceEntry* findEntry(const StringPiece16& name);
+ ResourceEntry* findOrCreateEntry(const StringPiece16& name);
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
+};
+
+enum class PackageType {
+ System,
+ Vendor,
+ App,
+ Dynamic
+};
+
+class ResourceTablePackage {
+public:
+ PackageType type = PackageType::App;
+ Maybe<uint8_t> id;
+ std::u16string name;
+
+ std::vector<std::unique_ptr<ResourceTableType>> types;
+
+ ResourceTablePackage() = default;
+ ResourceTableType* findType(ResourceType type);
+ ResourceTableType* findOrCreateType(const ResourceType type);
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
};
/**
@@ -125,153 +179,134 @@ struct ResourceTableType {
*/
class ResourceTable {
public:
- using iterator = std::vector<std::unique_ptr<ResourceTableType>>::iterator;
- using const_iterator = std::vector<std::unique_ptr<ResourceTableType>>::const_iterator;
-
- enum {
- kUnsetPackageId = 0xffffffff
- };
-
- ResourceTable();
-
- size_t getPackageId() const;
- void setPackageId(size_t packageId);
+ ResourceTable() = default;
- const std::u16string& getPackage() const;
- void setPackage(const StringPiece16& package);
-
- bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
- const SourceLine& source, std::unique_ptr<Value> value);
+ /**
+ * When a collision of resources occurs, this method decides which value to keep.
+ * Returns -1 if the existing value should be chosen.
+ * Returns 0 if the collision can not be resolved (error).
+ * Returns 1 if the incoming value should be chosen.
+ */
+ static int resolveValueCollision(Value* existing, Value* incoming);
+
+ bool addResource(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag);
+
+ bool addResource(const ResourceNameRef& name,
+ const ResourceId resId,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag);
+
+ bool addFileReference(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ IDiagnostics* diag);
+
+ bool addFileReferenceAllowMangled(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ io::IFile* file,
+ IDiagnostics* diag);
/**
* Same as addResource, but doesn't verify the validity of the name. This is used
* when loading resources from an existing binary resource table that may have mangled
* names.
*/
- bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
- const SourceLine& source, std::unique_ptr<Value> value);
+ bool addResourceAllowMangled(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag);
+
+ bool addResourceAllowMangled(const ResourceNameRef& name,
+ const ResourceId id,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag);
+
+ bool setSymbolState(const ResourceNameRef& name,
+ const ResourceId resId,
+ const Symbol& symbol,
+ IDiagnostics* diag);
+
+ bool setSymbolStateAllowMangled(const ResourceNameRef& name,
+ const ResourceId resId,
+ const Symbol& symbol,
+ IDiagnostics* diag);
+
+ struct SearchResult {
+ ResourceTablePackage* package;
+ ResourceTableType* type;
+ ResourceEntry* entry;
+ };
- bool addResource(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const SourceLine& source,
- std::unique_ptr<Value> value);
+ Maybe<SearchResult> findResource(const ResourceNameRef& name);
- bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source);
- bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
- const SourceLine& source);
+ /**
+ * The string pool used by this resource table. Values that reference strings must use
+ * this pool to create their strings.
+ *
+ * NOTE: `stringPool` must come before `packages` so that it is destroyed after.
+ * When `string pool` references are destroyed (as they will be when `packages`
+ * is destroyed), they decrement a refCount, which would cause invalid
+ * memory access if the pool was already destroyed.
+ */
+ StringPool stringPool;
- /*
- * Merges the resources from `other` into this table, mangling the names of the resources
- * if `other` has a different package name.
+ /**
+ * The list of packages in this table, sorted alphabetically by package name.
*/
- bool merge(ResourceTable&& other);
+ std::vector<std::unique_ptr<ResourceTablePackage>> packages;
/**
- * Returns the string pool used by this ResourceTable.
- * Values that reference strings should use this pool to create
- * their strings.
+ * Returns the package struct with the given name, or nullptr if such a package does not
+ * exist. The empty string is a valid package and typically is used to represent the
+ * 'current' package before it is known to the ResourceTable.
*/
- StringPool& getValueStringPool();
- const StringPool& getValueStringPool() const;
+ ResourceTablePackage* findPackage(const StringPiece16& name);
- std::tuple<const ResourceTableType*, const ResourceEntry*>
- findResource(const ResourceNameRef& name) const;
+ ResourceTablePackage* findPackageById(uint8_t id);
- iterator begin();
- iterator end();
- const_iterator begin() const;
- const_iterator end() const;
+ ResourceTablePackage* createPackage(const StringPiece16& name, Maybe<uint8_t> id = {});
private:
- std::unique_ptr<ResourceTableType>& findOrCreateType(ResourceType type);
- std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type,
- const StringPiece16& name);
-
- bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const SourceLine& source,
- std::unique_ptr<Value> value, const char16_t* validChars);
- bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
- const SourceLine& source, const char16_t* validChars);
-
- std::u16string mPackage;
- size_t mPackageId;
-
- // StringPool must come before mTypes so that it is destroyed after.
- // When StringPool references are destroyed (as they will be when mTypes
- // is destroyed), they decrement a refCount, which would cause invalid
- // memory access if the pool was already destroyed.
- StringPool mValuePool;
-
- std::vector<std::unique_ptr<ResourceTableType>> mTypes;
+ ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
+
+ bool addFileReferenceImpl(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ io::IFile* file,
+ const char16_t* validChars,
+ IDiagnostics* diag);
+
+ bool addResourceImpl(const ResourceNameRef& name,
+ ResourceId resId,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ const char16_t* validChars,
+ std::function<int(Value*,Value*)> conflictResolver,
+ IDiagnostics* diag);
+
+ bool setSymbolStateImpl(const ResourceNameRef& name,
+ ResourceId resId,
+ const Symbol& symbol,
+ const char16_t* validChars,
+ IDiagnostics* diag);
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceTable);
};
-//
-// ResourceEntry implementation.
-//
-
-inline ResourceEntry::ResourceEntry(const StringPiece16& _name) :
- name(_name.toString()), entryId(kUnsetEntryId) {
-}
-
-inline ResourceEntry::ResourceEntry(const ResourceEntry* rhs) :
- name(rhs->name), entryId(rhs->entryId), publicStatus(rhs->publicStatus) {
-}
-
-//
-// ResourceTableType implementation.
-//
-
-inline ResourceTableType::ResourceTableType(const ResourceType _type) :
- type(_type), typeId(kUnsetTypeId) {
-}
-
-inline ResourceTableType::ResourceTableType(const ResourceTableType* rhs) :
- type(rhs->type), typeId(rhs->typeId), publicStatus(rhs->publicStatus) {
-}
-
-//
-// ResourceTable implementation.
-//
-
-inline StringPool& ResourceTable::getValueStringPool() {
- return mValuePool;
-}
-
-inline const StringPool& ResourceTable::getValueStringPool() const {
- return mValuePool;
-}
-
-inline ResourceTable::iterator ResourceTable::begin() {
- return mTypes.begin();
-}
-
-inline ResourceTable::iterator ResourceTable::end() {
- return mTypes.end();
-}
-
-inline ResourceTable::const_iterator ResourceTable::begin() const {
- return mTypes.begin();
-}
-
-inline ResourceTable::const_iterator ResourceTable::end() const {
- return mTypes.end();
-}
-
-inline const std::u16string& ResourceTable::getPackage() const {
- return mPackage;
-}
-
-inline size_t ResourceTable::getPackageId() const {
- return mPackageId;
-}
-
-inline void ResourceTable::setPackage(const StringPiece16& package) {
- mPackage = package.toString();
-}
-
-inline void ResourceTable::setPackageId(size_t packageId) {
- mPackageId = packageId;
-}
-
} // namespace aapt
#endif // AAPT_RESOURCE_TABLE_H
diff --git a/tools/aapt2/ResourceTableResolver.cpp b/tools/aapt2/ResourceTableResolver.cpp
deleted file mode 100644
index 910c2c07fb84..000000000000
--- a/tools/aapt2/ResourceTableResolver.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Maybe.h"
-#include "NameMangler.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "Util.h"
-
-#include <androidfw/AssetManager.h>
-#include <androidfw/ResourceTypes.h>
-#include <memory>
-#include <vector>
-
-namespace aapt {
-
-ResourceTableResolver::ResourceTableResolver(
- std::shared_ptr<const ResourceTable> table,
- const std::vector<std::shared_ptr<const android::AssetManager>>& sources) :
- mTable(table), mSources(sources) {
- for (const auto& assetManager : mSources) {
- const android::ResTable& resTable = assetManager->getResources(false);
- const size_t packageCount = resTable.getBasePackageCount();
- for (size_t i = 0; i < packageCount; i++) {
- std::u16string packageName = resTable.getBasePackageName(i).string();
- mIncludedPackages.insert(std::move(packageName));
- }
- }
-}
-
-Maybe<ResourceId> ResourceTableResolver::findId(const ResourceName& name) {
- Maybe<Entry> result = findAttribute(name);
- if (result) {
- return result.value().id;
- }
- return {};
-}
-
-Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& name) {
- auto cacheIter = mCache.find(name);
- if (cacheIter != std::end(mCache)) {
- return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
- }
-
- ResourceName mangledName;
- const ResourceName* nameToSearch = &name;
- if (name.package != mTable->getPackage()) {
- // This may be a reference to an included resource or
- // to a mangled resource.
- if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) {
- // This is not in our included set, so mangle the name and
- // check for that.
- mangledName.entry = name.entry;
- NameMangler::mangle(name.package, &mangledName.entry);
- mangledName.package = mTable->getPackage();
- mangledName.type = name.type;
- nameToSearch = &mangledName;
- } else {
- const CacheEntry* cacheEntry = buildCacheEntry(name);
- if (cacheEntry) {
- return Entry{ cacheEntry->id, cacheEntry->attr.get() };
- }
- return {};
- }
- }
-
- const ResourceTableType* type;
- const ResourceEntry* entry;
- std::tie(type, entry) = mTable->findResource(*nameToSearch);
- if (type && entry) {
- Entry result = {};
- if (mTable->getPackageId() != ResourceTable::kUnsetPackageId &&
- type->typeId != ResourceTableType::kUnsetTypeId &&
- entry->entryId != ResourceEntry::kUnsetEntryId) {
- result.id = ResourceId(mTable->getPackageId(), type->typeId, entry->entryId);
- }
-
- if (!entry->values.empty()) {
- visitFunc<Attribute>(*entry->values.front().value, [&result](Attribute& attr) {
- result.attr = &attr;
- });
- }
- return result;
- }
- return {};
-}
-
-Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) {
- for (const auto& assetManager : mSources) {
- const android::ResTable& table = assetManager->getResources(false);
-
- android::ResTable::resource_name resourceName;
- if (!table.getResourceName(resId.id, false, &resourceName)) {
- continue;
- }
-
- const ResourceType* type = parseResourceType(StringPiece16(resourceName.type,
- resourceName.typeLen));
- assert(type);
- return ResourceName{
- { resourceName.package, resourceName.packageLen },
- *type,
- { resourceName.name, resourceName.nameLen } };
- }
- return {};
-}
-
-/**
- * This is called when we need to lookup a resource name in the AssetManager.
- * Since the values in the AssetManager are not parsed like in a ResourceTable,
- * we must create Attribute objects here if we find them.
- */
-const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry(
- const ResourceName& name) {
- for (const auto& assetManager : mSources) {
- const android::ResTable& table = assetManager->getResources(false);
-
- const StringPiece16 type16 = toString(name.type);
- ResourceId resId {
- table.identifierForName(
- name.entry.data(), name.entry.size(),
- type16.data(), type16.size(),
- name.package.data(), name.package.size())
- };
-
- if (!resId.isValid()) {
- continue;
- }
-
- CacheEntry& entry = mCache[name];
- entry.id = resId;
-
- //
- // Now check to see if this resource is an Attribute.
- //
-
- const android::ResTable::bag_entry* bagBegin;
- ssize_t bags = table.lockBag(resId.id, &bagBegin);
- if (bags < 1) {
- table.unlockBag(bagBegin);
- return &entry;
- }
-
- // Look for the ATTR_TYPE key in the bag and check the types it supports.
- uint32_t attrTypeMask = 0;
- for (ssize_t i = 0; i < bags; i++) {
- if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
- attrTypeMask = bagBegin[i].map.value.data;
- }
- }
-
- entry.attr = util::make_unique<Attribute>(false);
-
- if (attrTypeMask & android::ResTable_map::TYPE_ENUM ||
- attrTypeMask & android::ResTable_map::TYPE_FLAGS) {
- for (ssize_t i = 0; i < bags; i++) {
- if (Res_INTERNALID(bagBegin[i].map.name.ident)) {
- // Internal IDs are special keys, which are not enum/flag symbols, so skip.
- continue;
- }
-
- android::ResTable::resource_name symbolName;
- bool result = table.getResourceName(bagBegin[i].map.name.ident, false,
- &symbolName);
- assert(result);
- const ResourceType* type = parseResourceType(
- StringPiece16(symbolName.type, symbolName.typeLen));
- assert(type);
-
- entry.attr->symbols.push_back(Attribute::Symbol{
- Reference(ResourceNameRef(
- StringPiece16(symbolName.package, symbolName.packageLen),
- *type,
- StringPiece16(symbolName.name, symbolName.nameLen))),
- bagBegin[i].map.value.data
- });
- }
- }
-
- entry.attr->typeMask |= attrTypeMask;
- table.unlockBag(bagBegin);
- return &entry;
- }
- return nullptr;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ResourceTableResolver.h b/tools/aapt2/ResourceTableResolver.h
deleted file mode 100644
index 8f6b0b5993e4..000000000000
--- a/tools/aapt2/ResourceTableResolver.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_RESOURCE_TABLE_RESOLVER_H
-#define AAPT_RESOURCE_TABLE_RESOLVER_H
-
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-
-#include <androidfw/AssetManager.h>
-#include <memory>
-#include <vector>
-#include <unordered_set>
-
-namespace aapt {
-
-/**
- * Encapsulates the search of library sources as well as the local ResourceTable.
- */
-class ResourceTableResolver : public IResolver {
-public:
- /**
- * Creates a resolver with a local ResourceTable and an AssetManager
- * loaded with library packages.
- */
- ResourceTableResolver(
- std::shared_ptr<const ResourceTable> table,
- const std::vector<std::shared_ptr<const android::AssetManager>>& sources);
-
- ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable.
-
- virtual Maybe<ResourceId> findId(const ResourceName& name) override;
-
- virtual Maybe<Entry> findAttribute(const ResourceName& name) override;
-
- virtual Maybe<ResourceName> findName(ResourceId resId) override;
-
-private:
- struct CacheEntry {
- ResourceId id;
- std::unique_ptr<Attribute> attr;
- };
-
- const CacheEntry* buildCacheEntry(const ResourceName& name);
-
- std::shared_ptr<const ResourceTable> mTable;
- std::vector<std::shared_ptr<const android::AssetManager>> mSources;
- std::map<ResourceName, CacheEntry> mCache;
- std::unordered_set<std::u16string> mIncludedPackages;
-};
-
-} // namespace aapt
-
-#endif // AAPT_RESOURCE_TABLE_RESOLVER_H
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 06d8699730de..d6c52ab83d93 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -14,9 +14,12 @@
* limitations under the License.
*/
+#include "Diagnostics.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+
+#include "test/Builders.h"
#include <algorithm>
#include <gtest/gtest.h>
@@ -25,204 +28,126 @@
namespace aapt {
-struct TestValue : public Value {
- std::u16string value;
-
- TestValue(StringPiece16 str) : value(str.toString()) {
- }
-
- TestValue* clone(StringPool* /*newPool*/) const override {
- return new TestValue(value);
- }
-
- void print(std::ostream& out) const override {
- out << "(test) " << value;
- }
-
- virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
- virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
-};
-
-struct TestWeakValue : public Value {
- bool isWeak() const override {
- return true;
- }
-
- TestWeakValue* clone(StringPool* /*newPool*/) const override {
- return new TestWeakValue();
- }
-
- void print(std::ostream& out) const override {
- out << "(test) [weak]";
- }
-
- virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
- virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
-};
-
TEST(ResourceTableTest, FailToAddResourceWithBadName) {
ResourceTable table;
- table.setPackage(u"android");
EXPECT_FALSE(table.addResource(
- ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
- {}, SourceLine{ "test.xml", 21 },
- util::make_unique<TestValue>(u"rawValue")));
+ ResourceNameRef(u"android", ResourceType::kId, u"hey,there"),
+ ConfigDescription{}, "",
+ test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+ test::getDiagnostics()));
EXPECT_FALSE(table.addResource(
- ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
- {}, SourceLine{ "test.xml", 21 },
- util::make_unique<TestValue>(u"rawValue")));
+ ResourceNameRef(u"android", ResourceType::kId, u"hey:there"),
+ ConfigDescription{}, "",
+ test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+ test::getDiagnostics()));
}
TEST(ResourceTableTest, AddOneResource) {
- const std::u16string kAndroidPackage = u"android";
-
ResourceTable table;
- table.setPackage(kAndroidPackage);
-
- const ResourceName name = { kAndroidPackage, ResourceType::kAttr, u"id" };
-
- EXPECT_TRUE(table.addResource(name, {}, SourceLine{ "test/path/file.xml", 23 },
- util::make_unique<TestValue>(u"rawValue")));
- const ResourceTableType* type;
- const ResourceEntry* entry;
- std::tie(type, entry) = table.findResource(name);
- ASSERT_NE(nullptr, type);
- ASSERT_NE(nullptr, entry);
- EXPECT_EQ(name.entry, entry->name);
+ EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"),
+ ConfigDescription{},
+ "",
+ test::ValueBuilder<Id>()
+ .setSource("test/path/file.xml", 23u).build(),
+ test::getDiagnostics()));
- ASSERT_NE(std::end(entry->values),
- std::find_if(std::begin(entry->values), std::end(entry->values),
- [](const ResourceConfigValue& val) -> bool {
- return val.config == ConfigDescription{};
- }));
+ ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
}
TEST(ResourceTableTest, AddMultipleResources) {
- const std::u16string kAndroidPackage = u"android";
ResourceTable table;
- table.setPackage(kAndroidPackage);
ConfigDescription config;
ConfigDescription languageConfig;
memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
EXPECT_TRUE(table.addResource(
- ResourceName{ kAndroidPackage, ResourceType::kAttr, u"layout_width" },
- config, SourceLine{ "test/path/file.xml", 10 },
- util::make_unique<TestValue>(u"rawValue")));
+ test::parseNameOrDie(u"@android:attr/layout_width"),
+ config,
+ "",
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
+ test::getDiagnostics()));
EXPECT_TRUE(table.addResource(
- ResourceName{ kAndroidPackage, ResourceType::kAttr, u"id" },
- config, SourceLine{ "test/path/file.xml", 12 },
- util::make_unique<TestValue>(u"rawValue")));
+ test::parseNameOrDie(u"@android:attr/id"),
+ config,
+ "",
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
+ test::getDiagnostics()));
EXPECT_TRUE(table.addResource(
- ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
- config, SourceLine{ "test/path/file.xml", 14 },
- util::make_unique<TestValue>(u"Ok")));
+ test::parseNameOrDie(u"@android:string/ok"),
+ config,
+ "",
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
+ test::getDiagnostics()));
EXPECT_TRUE(table.addResource(
- ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
- languageConfig, SourceLine{ "test/path/file.xml", 20 },
- util::make_unique<TestValue>(u"Tak")));
-
- const auto endTypeIter = std::end(table);
- auto typeIter = std::begin(table);
-
- ASSERT_NE(endTypeIter, typeIter);
- EXPECT_EQ(ResourceType::kAttr, (*typeIter)->type);
-
- {
- const std::unique_ptr<ResourceTableType>& type = *typeIter;
- const auto endEntryIter = std::end(type->entries);
- auto entryIter = std::begin(type->entries);
- ASSERT_NE(endEntryIter, entryIter);
- EXPECT_EQ(std::u16string(u"id"), (*entryIter)->name);
-
- ++entryIter;
- ASSERT_NE(endEntryIter, entryIter);
- EXPECT_EQ(std::u16string(u"layout_width"), (*entryIter)->name);
-
- ++entryIter;
- ASSERT_EQ(endEntryIter, entryIter);
- }
-
- ++typeIter;
- ASSERT_NE(endTypeIter, typeIter);
- EXPECT_EQ(ResourceType::kString, (*typeIter)->type);
-
- {
- const std::unique_ptr<ResourceTableType>& type = *typeIter;
- const auto endEntryIter = std::end(type->entries);
- auto entryIter = std::begin(type->entries);
- ASSERT_NE(endEntryIter, entryIter);
- EXPECT_EQ(std::u16string(u"ok"), (*entryIter)->name);
-
- {
- const std::unique_ptr<ResourceEntry>& entry = *entryIter;
- const auto endConfigIter = std::end(entry->values);
- auto configIter = std::begin(entry->values);
-
- ASSERT_NE(endConfigIter, configIter);
- EXPECT_EQ(config, configIter->config);
- const TestValue* value =
- dynamic_cast<const TestValue*>(configIter->value.get());
- ASSERT_NE(nullptr, value);
- EXPECT_EQ(std::u16string(u"Ok"), value->value);
-
- ++configIter;
- ASSERT_NE(endConfigIter, configIter);
- EXPECT_EQ(languageConfig, configIter->config);
- EXPECT_NE(nullptr, configIter->value);
-
- value = dynamic_cast<const TestValue*>(configIter->value.get());
- ASSERT_NE(nullptr, value);
- EXPECT_EQ(std::u16string(u"Tak"), value->value);
-
- ++configIter;
- EXPECT_EQ(endConfigIter, configIter);
- }
-
- ++entryIter;
- ASSERT_EQ(endEntryIter, entryIter);
- }
-
- ++typeIter;
- EXPECT_EQ(endTypeIter, typeIter);
+ test::parseNameOrDie(u"@android:string/ok"),
+ languageConfig,
+ "",
+ test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
+ .setSource("test/path/file.xml", 20u)
+ .build(),
+ test::getDiagnostics()));
+
+ ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width"));
+ ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
+ ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:string/ok"));
+ ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, u"@android:string/ok",
+ languageConfig));
}
TEST(ResourceTableTest, OverrideWeakResourceValue) {
- const std::u16string kAndroid = u"android";
+ ResourceTable table;
+
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
+ "", util::make_unique<Attribute>(true), test::getDiagnostics()));
+ Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_TRUE(attr->isWeak());
+
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
+ "", util::make_unique<Attribute>(false), test::getDiagnostics()));
+
+ attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_FALSE(attr->isWeak());
+}
+
+TEST(ResourceTableTest, ProductVaryingValues) {
ResourceTable table;
- table.setPackage(kAndroid);
- table.setPackageId(0x01);
-
- ASSERT_TRUE(table.addResource(
- ResourceName{ kAndroid, ResourceType::kAttr, u"foo" },
- {}, {}, util::make_unique<TestWeakValue>()));
-
- const ResourceTableType* type;
- const ResourceEntry* entry;
- std::tie(type, entry) = table.findResource(
- ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
- ASSERT_NE(nullptr, type);
- ASSERT_NE(nullptr, entry);
- ASSERT_EQ(entry->values.size(), 1u);
- EXPECT_TRUE(entry->values.front().value->isWeak());
-
- ASSERT_TRUE(table.addResource(ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, {}, {},
- util::make_unique<TestValue>(u"bar")));
-
- std::tie(type, entry) = table.findResource(
- ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
- ASSERT_NE(nullptr, type);
- ASSERT_NE(nullptr, entry);
- ASSERT_EQ(entry->values.size(), 1u);
- EXPECT_FALSE(entry->values.front().value->isWeak());
+
+ EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"),
+ test::parseConfigOrDie("land"),
+ "tablet",
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
+ EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"),
+ test::parseConfigOrDie("land"),
+ "phone",
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
+
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo",
+ test::parseConfigOrDie("land"),
+ "tablet"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo",
+ test::parseConfigOrDie("land"),
+ "phone"));
+
+ Maybe<ResourceTable::SearchResult> sr = table.findResource(
+ test::parseNameOrDie(u"@android:string/foo"));
+ AAPT_ASSERT_TRUE(sr);
+ std::vector<ResourceConfigValue*> values = sr.value().entry->findAllValues(
+ test::parseConfigOrDie("land"));
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(std::string("phone"), values[0]->product);
+ EXPECT_EQ(std::string("tablet"), values[1]->product);
}
} // namespace aapt
diff --git a/tools/aapt2/ResourceTypeExtensions.h b/tools/aapt2/ResourceTypeExtensions.h
deleted file mode 100644
index dcbe9233f6b0..000000000000
--- a/tools/aapt2/ResourceTypeExtensions.h
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H
-#define AAPT_RESOURCE_TYPE_EXTENSIONS_H
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-/**
- * New android::ResChunk_header types defined
- * for AAPT to use.
- *
- * TODO(adamlesinski): Consider reserving these
- * enums in androidfw/ResourceTypes.h to avoid
- * future collisions.
- */
-enum {
- RES_TABLE_PUBLIC_TYPE = 0x000d,
-
- /**
- * A chunk that holds the string pool
- * for source entries (path/to/source:line).
- */
- RES_TABLE_SOURCE_POOL_TYPE = 0x000e,
-
- /**
- * A chunk holding names of externally
- * defined symbols and offsets to where
- * they are referenced in the table.
- */
- RES_TABLE_SYMBOL_TABLE_TYPE = 0x000f,
-};
-
-/**
- * New resource types that are meant to only be used
- * by AAPT and will not end up on the device.
- */
-struct ExtendedTypes {
- enum {
- /**
- * A raw string value that hasn't had its escape sequences
- * processed nor whitespace removed.
- */
- TYPE_RAW_STRING = 0xfe
- };
-};
-
-struct Public_header {
- android::ResChunk_header header;
-
- /**
- * The ID of the type this structure refers to.
- */
- uint8_t typeId;
-
- /**
- * Reserved. Must be 0.
- */
- uint8_t res0;
-
- /**
- * Reserved. Must be 0.
- */
- uint16_t res1;
-
- /**
- * Number of public entries.
- */
- uint32_t count;
-};
-
-struct Public_entry {
- uint16_t entryId;
- uint16_t res0;
- android::ResStringPool_ref key;
- android::ResStringPool_ref source;
- uint32_t sourceLine;
-};
-
-/**
- * A chunk with type RES_TABLE_SYMBOL_TABLE_TYPE.
- * Following the header are count number of SymbolTable_entry
- * structures, followed by an android::ResStringPool_header.
- */
-struct SymbolTable_header {
- android::ResChunk_header header;
-
- /**
- * Number of SymbolTable_entry structures following
- * this header.
- */
- uint32_t count;
-};
-
-struct SymbolTable_entry {
- /**
- * Offset from the beginning of the resource table
- * where the symbol entry is referenced.
- */
- uint32_t offset;
-
- /**
- * The index into the string pool where the name of this
- * symbol exists.
- */
- uint32_t stringIndex;
-};
-
-/**
- * A structure representing the source of a resourc entry.
- * Appears after an android::ResTable_entry or android::ResTable_map_entry.
- *
- * TODO(adamlesinski): This causes some issues when runtime code checks
- * the size of an android::ResTable_entry. It assumes it is an
- * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
- * which may not be true if this structure is present.
- */
-struct ResTable_entry_source {
- /**
- * Index into the source string pool.
- */
- uint32_t pathIndex;
-
- /**
- * Line number this resource was defined on.
- */
- uint32_t line;
-};
-
-} // namespace aapt
-
-#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
new file mode 100644
index 000000000000..74c48b0f8426
--- /dev/null
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NameMangler.h"
+#include "ResourceUtils.h"
+#include "flatten/ResourceTypeExtensions.h"
+#include "util/Files.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <sstream>
+
+namespace aapt {
+namespace ResourceUtils {
+
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+ StringPiece16* outType, StringPiece16* outEntry) {
+ bool hasPackageSeparator = false;
+ bool hasTypeSeparator = false;
+ const char16_t* start = str.data();
+ const char16_t* end = start + str.size();
+ const char16_t* current = start;
+ while (current != end) {
+ if (outType->size() == 0 && *current == u'/') {
+ hasTypeSeparator = true;
+ outType->assign(start, current - start);
+ start = current + 1;
+ } else if (outPackage->size() == 0 && *current == u':') {
+ hasPackageSeparator = true;
+ outPackage->assign(start, current - start);
+ start = current + 1;
+ }
+ current++;
+ }
+ outEntry->assign(start, end - start);
+
+ return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
+}
+
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
+ if (str.empty()) {
+ return false;
+ }
+
+ size_t offset = 0;
+ bool priv = false;
+ if (str.data()[0] == u'*') {
+ priv = true;
+ offset = 1;
+ }
+
+ StringPiece16 package;
+ StringPiece16 type;
+ StringPiece16 entry;
+ if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
+ return false;
+ }
+
+ const ResourceType* parsedType = parseResourceType(type);
+ if (!parsedType) {
+ return false;
+ }
+
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (outRef) {
+ outRef->package = package;
+ outRef->type = *parsedType;
+ outRef->entry = entry;
+ }
+
+ if (outPrivate) {
+ *outPrivate = priv;
+ }
+ return true;
+}
+
+bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
+ bool* outPrivate) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ if (trimmedStr.empty()) {
+ return false;
+ }
+
+ bool create = false;
+ bool priv = false;
+ if (trimmedStr.data()[0] == u'@') {
+ size_t offset = 1;
+ if (trimmedStr.data()[1] == u'+') {
+ create = true;
+ offset += 1;
+ }
+
+ ResourceNameRef name;
+ if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
+ &name, &priv)) {
+ return false;
+ }
+
+ if (create && priv) {
+ return false;
+ }
+
+ if (create && name.type != ResourceType::kId) {
+ return false;
+ }
+
+ if (outRef) {
+ *outRef = name;
+ }
+
+ if (outCreate) {
+ *outCreate = create;
+ }
+
+ if (outPrivate) {
+ *outPrivate = priv;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool isReference(const StringPiece16& str) {
+ return tryParseReference(str, nullptr, nullptr, nullptr);
+}
+
+bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ if (trimmedStr.empty()) {
+ return false;
+ }
+
+ if (*trimmedStr.data() == u'?') {
+ StringPiece16 package;
+ StringPiece16 type;
+ StringPiece16 entry;
+ if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
+ &package, &type, &entry)) {
+ return false;
+ }
+
+ if (!type.empty() && type != u"attr") {
+ return false;
+ }
+
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (outRef) {
+ outRef->package = package;
+ outRef->type = ResourceType::kAttr;
+ outRef->entry = entry;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool isAttributeReference(const StringPiece16& str) {
+ return tryParseAttributeReference(str, nullptr);
+}
+
+/*
+ * Style parent's are a bit different. We accept the following formats:
+ *
+ * @[[*]package:][style/]<entry>
+ * ?[[*]package:]style/<entry>
+ * <[*]package>:[style/]<entry>
+ * [[*]package:style/]<entry>
+ */
+Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
+ if (str.empty()) {
+ return {};
+ }
+
+ StringPiece16 name = str;
+
+ bool hasLeadingIdentifiers = false;
+ bool privateRef = false;
+
+ // Skip over these identifiers. A style's parent is a normal reference.
+ if (name.data()[0] == u'@' || name.data()[0] == u'?') {
+ hasLeadingIdentifiers = true;
+ name = name.substr(1, name.size() - 1);
+ }
+
+ if (name.data()[0] == u'*') {
+ privateRef = true;
+ name = name.substr(1, name.size() - 1);
+ }
+
+ ResourceNameRef ref;
+ ref.type = ResourceType::kStyle;
+
+ StringPiece16 typeStr;
+ extractResourceName(name, &ref.package, &typeStr, &ref.entry);
+ if (!typeStr.empty()) {
+ // If we have a type, make sure it is a Style.
+ const ResourceType* parsedType = parseResourceType(typeStr);
+ if (!parsedType || *parsedType != ResourceType::kStyle) {
+ std::stringstream err;
+ err << "invalid resource type '" << typeStr << "' for parent of style";
+ *outError = err.str();
+ return {};
+ }
+ }
+
+ if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *outError = err.str();
+ return {};
+ }
+
+ Reference result(ref);
+ result.privateReference = privateRef;
+ return result;
+}
+
+std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) {
+ ResourceNameRef ref;
+ bool privateRef = false;
+ if (tryParseReference(str, &ref, outCreate, &privateRef)) {
+ std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
+ value->privateReference = privateRef;
+ return value;
+ }
+
+ if (tryParseAttributeReference(str, &ref)) {
+ if (outCreate) {
+ *outCreate = false;
+ }
+ return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+ }
+ return {};
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ android::Res_value value = { };
+ if (trimmedStr == u"@null") {
+ // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
+ // Instead we set the data type to TYPE_REFERENCE with a value of 0.
+ value.dataType = android::Res_value::TYPE_REFERENCE;
+ } else if (trimmedStr == u"@empty") {
+ // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
+ value.dataType = android::Res_value::TYPE_NULL;
+ value.data = android::Res_value::DATA_NULL_EMPTY;
+ } else {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
+ const StringPiece16& str) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ for (const Attribute::Symbol& symbol : enumAttr->symbols) {
+ // Enum symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
+ if (trimmedStr == enumSymbolResourceName.entry) {
+ android::Res_value value = { };
+ value.dataType = android::Res_value::TYPE_INT_DEC;
+ value.data = symbol.value;
+ return util::make_unique<BinaryPrimitive>(value);
+ }
+ }
+ return {};
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
+ const StringPiece16& str) {
+ android::Res_value flags = { };
+ flags.dataType = android::Res_value::TYPE_INT_DEC;
+ flags.data = 0u;
+
+ if (util::trimWhitespace(str).empty()) {
+ // Empty string is a valid flag (0).
+ return util::make_unique<BinaryPrimitive>(flags);
+ }
+
+ for (StringPiece16 part : util::tokenize(str, u'|')) {
+ StringPiece16 trimmedPart = util::trimWhitespace(part);
+
+ bool flagSet = false;
+ for (const Attribute::Symbol& symbol : flagAttr->symbols) {
+ // Flag symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
+ if (trimmedPart == flagSymbolResourceName.entry) {
+ flags.data |= symbol.value;
+ flagSet = true;
+ break;
+ }
+ }
+
+ if (!flagSet) {
+ return {};
+ }
+ }
+ return util::make_unique<BinaryPrimitive>(flags);
+}
+
+static uint32_t parseHex(char16_t c, bool* outError) {
+ if (c >= u'0' && c <= u'9') {
+ return c - u'0';
+ } else if (c >= u'a' && c <= u'f') {
+ return c - u'a' + 0xa;
+ } else if (c >= u'A' && c <= u'F') {
+ return c - u'A' + 0xa;
+ } else {
+ *outError = true;
+ return 0xffffffffu;
+ }
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) {
+ StringPiece16 colorStr(util::trimWhitespace(str));
+ const char16_t* start = colorStr.data();
+ const size_t len = colorStr.size();
+ if (len == 0 || start[0] != u'#') {
+ return {};
+ }
+
+ android::Res_value value = { };
+ bool error = false;
+ if (len == 4) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
+ value.data = 0xff000000u;
+ value.data |= parseHex(start[1], &error) << 20;
+ value.data |= parseHex(start[1], &error) << 16;
+ value.data |= parseHex(start[2], &error) << 12;
+ value.data |= parseHex(start[2], &error) << 8;
+ value.data |= parseHex(start[3], &error) << 4;
+ value.data |= parseHex(start[3], &error);
+ } else if (len == 5) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
+ value.data |= parseHex(start[1], &error) << 28;
+ value.data |= parseHex(start[1], &error) << 24;
+ value.data |= parseHex(start[2], &error) << 20;
+ value.data |= parseHex(start[2], &error) << 16;
+ value.data |= parseHex(start[3], &error) << 12;
+ value.data |= parseHex(start[3], &error) << 8;
+ value.data |= parseHex(start[4], &error) << 4;
+ value.data |= parseHex(start[4], &error);
+ } else if (len == 7) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
+ value.data = 0xff000000u;
+ value.data |= parseHex(start[1], &error) << 20;
+ value.data |= parseHex(start[2], &error) << 16;
+ value.data |= parseHex(start[3], &error) << 12;
+ value.data |= parseHex(start[4], &error) << 8;
+ value.data |= parseHex(start[5], &error) << 4;
+ value.data |= parseHex(start[6], &error);
+ } else if (len == 9) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
+ value.data |= parseHex(start[1], &error) << 28;
+ value.data |= parseHex(start[2], &error) << 24;
+ value.data |= parseHex(start[3], &error) << 20;
+ value.data |= parseHex(start[4], &error) << 16;
+ value.data |= parseHex(start[5], &error) << 12;
+ value.data |= parseHex(start[6], &error) << 8;
+ value.data |= parseHex(start[7], &error) << 4;
+ value.data |= parseHex(start[8], &error);
+ } else {
+ return {};
+ }
+ return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
+}
+
+bool tryParseBool(const StringPiece16& str, bool* outValue) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ if (trimmedStr == u"true" || trimmedStr == u"TRUE" || trimmedStr == u"True") {
+ if (outValue) {
+ *outValue = true;
+ }
+ return true;
+ } else if (trimmedStr == u"false" || trimmedStr == u"FALSE" || trimmedStr == u"False") {
+ if (outValue) {
+ *outValue = false;
+ }
+ return true;
+ }
+ return false;
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
+ bool result = false;
+ if (tryParseBool(str, &result)) {
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+
+ if (result) {
+ value.data = 0xffffffffu;
+ } else {
+ value.data = 0;
+ }
+ return util::make_unique<BinaryPrimitive>(value);
+ }
+ return {};
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
+ android::Res_value value;
+ if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) {
+ android::Res_value value;
+ if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
+}
+
+uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
+ switch (type) {
+ case android::Res_value::TYPE_NULL:
+ case android::Res_value::TYPE_REFERENCE:
+ case android::Res_value::TYPE_ATTRIBUTE:
+ case android::Res_value::TYPE_DYNAMIC_REFERENCE:
+ return android::ResTable_map::TYPE_REFERENCE;
+
+ case android::Res_value::TYPE_STRING:
+ return android::ResTable_map::TYPE_STRING;
+
+ case android::Res_value::TYPE_FLOAT:
+ return android::ResTable_map::TYPE_FLOAT;
+
+ case android::Res_value::TYPE_DIMENSION:
+ return android::ResTable_map::TYPE_DIMENSION;
+
+ case android::Res_value::TYPE_FRACTION:
+ return android::ResTable_map::TYPE_FRACTION;
+
+ case android::Res_value::TYPE_INT_DEC:
+ case android::Res_value::TYPE_INT_HEX:
+ return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
+ | android::ResTable_map::TYPE_FLAGS;
+
+ case android::Res_value::TYPE_INT_BOOLEAN:
+ return android::ResTable_map::TYPE_BOOLEAN;
+
+ case android::Res_value::TYPE_INT_COLOR_ARGB8:
+ case android::Res_value::TYPE_INT_COLOR_RGB8:
+ case android::Res_value::TYPE_INT_COLOR_ARGB4:
+ case android::Res_value::TYPE_INT_COLOR_RGB4:
+ return android::ResTable_map::TYPE_COLOR;
+
+ default:
+ return 0;
+ };
+}
+
+std::unique_ptr<Item> parseItemForAttribute(
+ const StringPiece16& value, uint32_t typeMask,
+ std::function<void(const ResourceName&)> onCreateReference) {
+ std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
+ if (nullOrEmpty) {
+ return std::move(nullOrEmpty);
+ }
+
+ bool create = false;
+ std::unique_ptr<Reference> reference = tryParseReference(value, &create);
+ if (reference) {
+ if (create && onCreateReference) {
+ onCreateReference(reference->name.value());
+ }
+ return std::move(reference);
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_COLOR) {
+ // Try parsing this as a color.
+ std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
+ if (color) {
+ return std::move(color);
+ }
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ // Try parsing this as a boolean.
+ std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
+ if (boolean) {
+ return std::move(boolean);
+ }
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+ // Try parsing this as an integer.
+ std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
+ if (integer) {
+ return std::move(integer);
+ }
+ }
+
+ const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
+ | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
+ if (typeMask & floatMask) {
+ // Try parsing this as a float.
+ std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
+ if (floatingPoint) {
+ if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
+ return std::move(floatingPoint);
+ }
+ }
+ }
+ return {};
+}
+
+/**
+ * We successively try to parse the string as a resource type that the Attribute
+ * allows.
+ */
+std::unique_ptr<Item> parseItemForAttribute(
+ const StringPiece16& str, const Attribute* attr,
+ std::function<void(const ResourceName&)> onCreateReference) {
+ const uint32_t typeMask = attr->typeMask;
+ std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
+ if (value) {
+ return value;
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_ENUM) {
+ // Try parsing this as an enum.
+ std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
+ if (enumValue) {
+ return std::move(enumValue);
+ }
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+ // Try parsing this as a flag.
+ std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
+ if (flagValue) {
+ return std::move(flagValue);
+ }
+ }
+ return {};
+}
+
+std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler) {
+ std::stringstream out;
+ out << "res/" << resFile.name.type;
+ if (resFile.config != ConfigDescription{}) {
+ out << "-" << resFile.config;
+ }
+ out << "/";
+
+ if (mangler && mangler->shouldMangle(resFile.name.package)) {
+ out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
+ } else {
+ out << resFile.name.entry;
+ }
+ out << file::getExtension(resFile.source.path);
+ return out.str();
+}
+
+} // namespace ResourceUtils
+} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
new file mode 100644
index 000000000000..a0fbcc6e700b
--- /dev/null
+++ b/tools/aapt2/ResourceUtils.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOURCEUTILS_H
+#define AAPT_RESOURCEUTILS_H
+
+#include "NameMangler.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "util/StringPiece.h"
+
+#include <functional>
+#include <memory>
+
+namespace aapt {
+namespace ResourceUtils {
+
+/*
+ * Extracts the package, type, and name from a string of the format:
+ *
+ * [package:]type/name
+ *
+ * where the package can be empty. Validation must be performed on each
+ * individual extracted piece to verify that the pieces are valid.
+ * Returns false if there was no package but a ':' was present.
+ */
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+ StringPiece16* outType, StringPiece16* outEntry);
+
+/**
+ * Returns true if the string was parsed as a resource name ([*][package:]type/name), with
+ * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix
+ * was present.
+ */
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource,
+ bool* outPrivate = nullptr);
+
+/*
+ * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
+ * `outReference` set to the parsed reference.
+ *
+ * If '+' was present in the reference, `outCreate` is set to true.
+ * If '*' was present in the reference, `outPrivate` is set to true.
+ */
+bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
+ bool* outCreate = nullptr, bool* outPrivate = nullptr);
+
+/*
+ * Returns true if the string is in the form of a resource reference (@[+][package:]type/name).
+ */
+bool isReference(const StringPiece16& str);
+
+/*
+ * Returns true if the string was parsed as an attribute reference (?[package:][type/]name),
+ * with `outReference` set to the parsed reference.
+ */
+bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
+
+/**
+ * Returns true if the string is in the form of an attribute reference(?[package:][type/]name).
+ */
+bool isAttributeReference(const StringPiece16& str);
+
+/**
+ * Returns true if the value is a boolean, putting the result in `outValue`.
+ */
+bool tryParseBool(const StringPiece16& str, bool* outValue);
+
+/*
+ * Returns a Reference, or None Maybe instance if the string `str` was parsed as a
+ * valid reference to a style.
+ * The format for a style parent is slightly more flexible than a normal reference:
+ *
+ * @[package:]style/<entry> or
+ * ?[package:]style/<entry> or
+ * <package>:[style/]<entry>
+ */
+Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError);
+
+/*
+ * Returns a Reference object if the string was parsed as a resource or attribute reference,
+ * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
+ * the '+' was present in the string.
+ */
+std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate = nullptr);
+
+/*
+ * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a color if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a boolean if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing an integer if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a floating point number
+ * (float, dimension, etc) if the string was parsed as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
+ const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr,
+ const StringPiece16& str);
+/*
+ * Try to convert a string to an Item for the given attribute. The attribute will
+ * restrict what values the string can be converted to.
+ * The callback function onCreateReference is called when the parsed item is a
+ * reference to an ID that must be created (@+id/foo).
+ */
+std::unique_ptr<Item> parseItemForAttribute(
+ const StringPiece16& value, const Attribute* attr,
+ std::function<void(const ResourceName&)> onCreateReference = {});
+
+std::unique_ptr<Item> parseItemForAttribute(
+ const StringPiece16& value, uint32_t typeMask,
+ std::function<void(const ResourceName&)> onCreateReference = {});
+
+uint32_t androidTypeToAttributeTypeMask(uint16_t type);
+
+/**
+ * Returns a string path suitable for use within an APK. The path will look like:
+ *
+ * res/type[-config]/<name>.<ext>
+ *
+ * Then name may be mangled if a NameMangler is supplied (can be nullptr) and the package
+ * requires mangling.
+ */
+std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler);
+
+} // namespace ResourceUtils
+} // namespace aapt
+
+#endif /* AAPT_RESOURCEUTILS_H */
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
new file mode 100644
index 000000000000..7425f97ef8de
--- /dev/null
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Resource.h"
+#include "ResourceUtils.h"
+#include "test/Builders.h"
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ResourceUtilsTest, ParseBool) {
+ bool val = false;
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"true", &val));
+ EXPECT_TRUE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"TRUE", &val));
+ EXPECT_TRUE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"True", &val));
+ EXPECT_TRUE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"false", &val));
+ EXPECT_FALSE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"FALSE", &val));
+ EXPECT_FALSE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"False", &val));
+ EXPECT_FALSE(val);
+}
+
+TEST(ResourceUtilsTest, ParseResourceName) {
+ ResourceNameRef actual;
+ bool actualPriv = false;
+ EXPECT_TRUE(ResourceUtils::parseResourceName(u"android:color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+ EXPECT_FALSE(actualPriv);
+
+ EXPECT_TRUE(ResourceUtils::parseResourceName(u"color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, u"foo"), actual);
+ EXPECT_FALSE(actualPriv);
+
+ EXPECT_TRUE(ResourceUtils::parseResourceName(u"*android:color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+ EXPECT_TRUE(actualPriv);
+
+ EXPECT_FALSE(ResourceUtils::parseResourceName(StringPiece16(), &actual, &actualPriv));
+}
+
+TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
+ ResourceNameRef expected({}, ResourceType::kColor, u"foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
+ ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::tryParseReference(u"@android:color/foo", &actual, &create,
+ &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
+ ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
+ &create, &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
+ ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::tryParseReference(u"@+android:id/foo", &actual, &create,
+ &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_TRUE(create);
+ EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParsePrivateReference) {
+ ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::tryParseReference(u"@*android:id/foo", &actual, &create,
+ &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_TRUE(privateRef);
+}
+
+TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
+ bool create = false;
+ bool privateRef = false;
+ ResourceNameRef actual;
+ EXPECT_FALSE(ResourceUtils::tryParseReference(u"@+android:color/foo", &actual, &create,
+ &privateRef));
+}
+
+TEST(ResourceUtilsTest, ParseAttributeReferences) {
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:foo"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?attr/foo"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:attr/foo"));
+}
+
+TEST(ResourceUtilsTest, FailParseIncompleteReference) {
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?style/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:style/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?/foo"));
+}
+
+TEST(ResourceUtilsTest, ParseStyleParentReference) {
+ const ResourceName kAndroidStyleFooName(u"android", ResourceType::kStyle, u"foo");
+ const ResourceName kStyleFooName({}, ResourceType::kStyle, u"foo");
+
+ std::string errStr;
+ Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::parseStyleParentReference(u"@style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+ ref = ResourceUtils::parseStyleParentReference(u"?android:style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::parseStyleParentReference(u"?style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+ ref = ResourceUtils::parseStyleParentReference(u"android:style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::parseStyleParentReference(u"android:foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::parseStyleParentReference(u"@android:foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::parseStyleParentReference(u"foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+ ref = ResourceUtils::parseStyleParentReference(u"*android:style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ EXPECT_TRUE(ref.value().privateReference);
+}
+
+TEST(ResourceUtilsTest, ParseEmptyFlag) {
+ std::unique_ptr<Attribute> attr = test::AttributeBuilder(false)
+ .setTypeMask(android::ResTable_map::TYPE_FLAGS)
+ .addItem(u"one", 0x01)
+ .addItem(u"two", 0x02)
+ .build();
+
+ std::unique_ptr<BinaryPrimitive> result = ResourceUtils::tryParseFlagSymbol(attr.get(), u"");
+ ASSERT_NE(nullptr, result);
+ EXPECT_EQ(0u, result->value.data);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index aabb375e6c5e..dd7ff013e524 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,42 +15,45 @@
*/
#include "Resource.h"
-#include "ResourceTypeExtensions.h"
+#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "Util.h"
+#include "ValueVisitor.h"
+#include "io/File.h"
+#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
#include <limits>
namespace aapt {
-bool Value::isItem() const {
- return false;
+template <typename Derived>
+void BaseValue<Derived>::accept(RawValueVisitor* visitor) {
+ visitor->visit(static_cast<Derived*>(this));
}
-bool Value::isWeak() const {
- return false;
-}
-
-bool Item::isItem() const {
- return true;
+template <typename Derived>
+void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
+ visitor->visit(static_cast<Derived*>(this));
}
RawString::RawString(const StringPool::Ref& ref) : value(ref) {
}
RawString* RawString::clone(StringPool* newPool) const {
- return new RawString(newPool->makeRef(*value));
+ RawString* rs = new RawString(newPool->makeRef(*value));
+ rs->mComment = mComment;
+ rs->mSource = mSource;
+ return rs;
}
-bool RawString::flatten(android::Res_value& outValue) const {
- outValue.dataType = ExtendedTypes::TYPE_RAW_STRING;
- outValue.data = static_cast<uint32_t>(value.getIndex());
+bool RawString::flatten(android::Res_value* outValue) const {
+ outValue->dataType = android::Res_value::TYPE_STRING;
+ outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
return true;
}
-void RawString::print(std::ostream& out) const {
- out << "(raw string) " << *value;
+void RawString::print(std::ostream* out) const {
+ *out << "(raw string) " << *value;
}
Reference::Reference() : referenceType(Reference::Type::kResource) {
@@ -63,177 +66,200 @@ Reference::Reference(const ResourceNameRef& n, Type t) :
Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
}
-bool Reference::flatten(android::Res_value& outValue) const {
- outValue.dataType = (referenceType == Reference::Type::kResource)
- ? android::Res_value::TYPE_REFERENCE
- : android::Res_value::TYPE_ATTRIBUTE;
- outValue.data = id.id;
+bool Reference::flatten(android::Res_value* outValue) const {
+ outValue->dataType = (referenceType == Reference::Type::kResource) ?
+ android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE;
+ outValue->data = util::hostToDevice32(id ? id.value().id : 0);
return true;
}
Reference* Reference::clone(StringPool* /*newPool*/) const {
- Reference* ref = new Reference();
- ref->referenceType = referenceType;
- ref->name = name;
- ref->id = id;
- return ref;
+ return new Reference(*this);
}
-void Reference::print(std::ostream& out) const {
- out << "(reference) ";
+void Reference::print(std::ostream* out) const {
+ *out << "(reference) ";
if (referenceType == Reference::Type::kResource) {
- out << "@";
+ *out << "@";
+ if (privateReference) {
+ *out << "*";
+ }
} else {
- out << "?";
+ *out << "?";
}
- if (name.isValid()) {
- out << name;
+ if (name) {
+ *out << name.value();
}
- if (id.isValid() || Res_INTERNALID(id.id)) {
- out << " " << id;
+ if (id && !Res_INTERNALID(id.value().id)) {
+ *out << " " << id.value();
}
}
-bool Id::isWeak() const {
+bool Id::flatten(android::Res_value* out) const {
+ out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
+ out->data = util::hostToDevice32(0);
return true;
}
-bool Id::flatten(android::Res_value& out) const {
- out.dataType = android::Res_value::TYPE_INT_BOOLEAN;
- out.data = 0;
- return true;
+Id* Id::clone(StringPool* /*newPool*/) const {
+ return new Id(*this);
}
-Id* Id::clone(StringPool* /*newPool*/) const {
- return new Id();
+void Id::print(std::ostream* out) const {
+ *out << "(id)";
}
-void Id::print(std::ostream& out) const {
- out << "(id)";
+String::String(const StringPool::Ref& ref) : value(ref), mTranslateable(true) {
}
-String::String(const StringPool::Ref& ref) : value(ref) {
+void String::setTranslateable(bool val) {
+ mTranslateable = val;
}
-bool String::flatten(android::Res_value& outValue) const {
- // Verify that our StringPool index is within encodeable limits.
+bool String::isTranslateable() const {
+ return mTranslateable;
+}
+
+bool String::flatten(android::Res_value* outValue) const {
+ // Verify that our StringPool index is within encode-able limits.
if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
return false;
}
- outValue.dataType = android::Res_value::TYPE_STRING;
- outValue.data = static_cast<uint32_t>(value.getIndex());
+ outValue->dataType = android::Res_value::TYPE_STRING;
+ outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
return true;
}
String* String::clone(StringPool* newPool) const {
- return new String(newPool->makeRef(*value));
+ String* str = new String(newPool->makeRef(*value));
+ str->mComment = mComment;
+ str->mSource = mSource;
+ return str;
+}
+
+void String::print(std::ostream* out) const {
+ *out << "(string) \"" << *value << "\"";
}
-void String::print(std::ostream& out) const {
- out << "(string) \"" << *value << "\"";
+StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref), mTranslateable(true) {
}
-StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
+void StyledString::setTranslateable(bool val) {
+ mTranslateable = val;
}
-bool StyledString::flatten(android::Res_value& outValue) const {
+bool StyledString::isTranslateable() const {
+ return mTranslateable;
+}
+
+bool StyledString::flatten(android::Res_value* outValue) const {
if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
return false;
}
- outValue.dataType = android::Res_value::TYPE_STRING;
- outValue.data = static_cast<uint32_t>(value.getIndex());
+ outValue->dataType = android::Res_value::TYPE_STRING;
+ outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
return true;
}
StyledString* StyledString::clone(StringPool* newPool) const {
- return new StyledString(newPool->makeRef(value));
+ StyledString* str = new StyledString(newPool->makeRef(value));
+ str->mComment = mComment;
+ str->mSource = mSource;
+ return str;
}
-void StyledString::print(std::ostream& out) const {
- out << "(styled string) \"" << *value->str << "\"";
+void StyledString::print(std::ostream* out) const {
+ *out << "(styled string) \"" << *value->str << "\"";
}
FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
}
-bool FileReference::flatten(android::Res_value& outValue) const {
+bool FileReference::flatten(android::Res_value* outValue) const {
if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
return false;
}
- outValue.dataType = android::Res_value::TYPE_STRING;
- outValue.data = static_cast<uint32_t>(path.getIndex());
+ outValue->dataType = android::Res_value::TYPE_STRING;
+ outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex()));
return true;
}
FileReference* FileReference::clone(StringPool* newPool) const {
- return new FileReference(newPool->makeRef(*path));
+ FileReference* fr = new FileReference(newPool->makeRef(*path));
+ fr->file = file;
+ fr->mComment = mComment;
+ fr->mSource = mSource;
+ return fr;
}
-void FileReference::print(std::ostream& out) const {
- out << "(file) " << *path;
+void FileReference::print(std::ostream* out) const {
+ *out << "(file) " << *path;
}
BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
}
-bool BinaryPrimitive::flatten(android::Res_value& outValue) const {
- outValue = value;
+BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
+ value.dataType = dataType;
+ value.data = data;
+}
+
+bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
+ outValue->dataType = value.dataType;
+ outValue->data = util::hostToDevice32(value.data);
return true;
}
BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
- return new BinaryPrimitive(value);
+ return new BinaryPrimitive(*this);
}
-void BinaryPrimitive::print(std::ostream& out) const {
+void BinaryPrimitive::print(std::ostream* out) const {
switch (value.dataType) {
case android::Res_value::TYPE_NULL:
- out << "(null)";
+ *out << "(null)";
break;
case android::Res_value::TYPE_INT_DEC:
- out << "(integer) " << value.data;
+ *out << "(integer) " << static_cast<int32_t>(value.data);
break;
case android::Res_value::TYPE_INT_HEX:
- out << "(integer) " << std::hex << value.data << std::dec;
+ *out << "(integer) " << std::hex << value.data << std::dec;
break;
case android::Res_value::TYPE_INT_BOOLEAN:
- out << "(boolean) " << (value.data != 0 ? "true" : "false");
+ *out << "(boolean) " << (value.data != 0 ? "true" : "false");
break;
case android::Res_value::TYPE_INT_COLOR_ARGB8:
case android::Res_value::TYPE_INT_COLOR_RGB8:
case android::Res_value::TYPE_INT_COLOR_ARGB4:
case android::Res_value::TYPE_INT_COLOR_RGB4:
- out << "(color) #" << std::hex << value.data << std::dec;
+ *out << "(color) #" << std::hex << value.data << std::dec;
break;
default:
- out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
- << std::hex << value.data << std::dec;
+ *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
+ << std::hex << value.data << std::dec;
break;
}
}
-Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) {
-}
-
-bool Attribute::isWeak() const {
- return weak;
+Attribute::Attribute(bool w, uint32_t t) :
+ typeMask(t),
+ minInt(std::numeric_limits<int32_t>::min()),
+ maxInt(std::numeric_limits<int32_t>::max()) {
+ mWeak = w;
}
Attribute* Attribute::clone(StringPool* /*newPool*/) const {
- Attribute* attr = new Attribute(weak);
- attr->typeMask = typeMask;
- std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
- return attr;
+ return new Attribute(*this);
}
-void Attribute::printMask(std::ostream& out) const {
+void Attribute::printMask(std::ostream* out) const {
if (typeMask == android::ResTable_map::TYPE_ANY) {
- out << "any";
+ *out << "any";
return;
}
@@ -242,110 +268,189 @@ void Attribute::printMask(std::ostream& out) const {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "reference";
+ *out << "reference";
}
if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "string";
+ *out << "string";
}
if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "integer";
+ *out << "integer";
}
if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "boolean";
+ *out << "boolean";
}
if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "color";
+ *out << "color";
}
if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "float";
+ *out << "float";
}
if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "dimension";
+ *out << "dimension";
}
if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "fraction";
+ *out << "fraction";
}
if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "enum";
+ *out << "enum";
}
if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
if (!set) {
set = true;
} else {
- out << "|";
+ *out << "|";
}
- out << "flags";
+ *out << "flags";
}
}
-void Attribute::print(std::ostream& out) const {
- out << "(attr) ";
+void Attribute::print(std::ostream* out) const {
+ *out << "(attr) ";
printMask(out);
- out << " ["
- << util::joiner(symbols.begin(), symbols.end(), ", ")
- << "]";
+ if (!symbols.empty()) {
+ *out << " ["
+ << util::joiner(symbols.begin(), symbols.end(), ", ")
+ << "]";
+ }
- if (weak) {
- out << " [weak]";
+ if (isWeak()) {
+ *out << " [weak]";
}
}
+static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+ const Item* value) {
+ *msg << "expected";
+ if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ *msg << " boolean";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
+ *msg << " color";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
+ *msg << " dimension";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
+ *msg << " enum";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
+ *msg << " flags";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
+ *msg << " float";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
+ *msg << " fraction";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
+ *msg << " integer";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
+ *msg << " reference";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
+ *msg << " string";
+ }
+
+ *msg << " but got " << *value;
+}
+
+bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
+ android::Res_value val = {};
+ item->flatten(&val);
+
+ // Always allow references.
+ const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE;
+ if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+ if (outMsg) {
+ buildAttributeMismatchMessage(outMsg, this, item);
+ }
+ return false;
+
+ } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) &
+ android::ResTable_map::TYPE_INTEGER) {
+ if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) {
+ if (outMsg) {
+ *outMsg << *item << " is less than minimum integer " << minInt;
+ }
+ return false;
+ } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) {
+ if (outMsg) {
+ *outMsg << *item << " is greater than maximum integer " << maxInt;
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
Style* Style::clone(StringPool* newPool) const {
Style* style = new Style();
style->parent = parent;
style->parentInferred = parentInferred;
+ style->mComment = mComment;
+ style->mSource = mSource;
for (auto& entry : entries) {
style->entries.push_back(Entry{
entry.key,
@@ -355,38 +460,50 @@ Style* Style::clone(StringPool* newPool) const {
return style;
}
-void Style::print(std::ostream& out) const {
- out << "(style) ";
- if (!parent.name.entry.empty()) {
- out << parent.name;
+void Style::print(std::ostream* out) const {
+ *out << "(style) ";
+ if (parent && parent.value().name) {
+ if (parent.value().privateReference) {
+ *out << "*";
+ }
+ *out << parent.value().name.value();
}
- out << " ["
+ *out << " ["
<< util::joiner(entries.begin(), entries.end(), ", ")
<< "]";
}
static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) {
- out << value.key.name << " = ";
- value.value->print(out);
+ if (value.key.name) {
+ out << value.key.name.value();
+ } else {
+ out << "???";
+ }
+ out << " = ";
+ value.value->print(&out);
return out;
}
Array* Array::clone(StringPool* newPool) const {
Array* array = new Array();
+ array->mComment = mComment;
+ array->mSource = mSource;
for (auto& item : items) {
array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool)));
}
return array;
}
-void Array::print(std::ostream& out) const {
- out << "(array) ["
+void Array::print(std::ostream* out) const {
+ *out << "(array) ["
<< util::joiner(items.begin(), items.end(), ", ")
<< "]";
}
Plural* Plural::clone(StringPool* newPool) const {
Plural* p = new Plural();
+ p->mComment = mComment;
+ p->mSource = mSource;
const size_t count = values.size();
for (size_t i = 0; i < count; i++) {
if (values[i]) {
@@ -396,8 +513,8 @@ Plural* Plural::clone(StringPool* newPool) const {
return p;
}
-void Plural::print(std::ostream& out) const {
- out << "(plural)";
+void Plural::print(std::ostream* out) const {
+ *out << "(plural)";
}
static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
@@ -405,13 +522,11 @@ static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Ite
}
Styleable* Styleable::clone(StringPool* /*newPool*/) const {
- Styleable* styleable = new Styleable();
- std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
- return styleable;
+ return new Styleable(*this);
}
-void Styleable::print(std::ostream& out) const {
- out << "(styleable) " << " ["
+void Styleable::print(std::ostream* out) const {
+ *out << "(styleable) " << " ["
<< util::joiner(entries.begin(), entries.end(), ", ")
<< "]";
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index ef6594e6f231..43354acf1d0b 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -17,8 +17,11 @@
#ifndef AAPT_RESOURCE_VALUES_H
#define AAPT_RESOURCE_VALUES_H
+#include "Diagnostics.h"
#include "Resource.h"
#include "StringPool.h"
+#include "io/File.h"
+#include "util/Maybe.h"
#include <array>
#include <androidfw/ResourceTypes.h>
@@ -27,9 +30,7 @@
namespace aapt {
-struct ValueVisitor;
-struct ConstValueVisitor;
-struct ValueVisitorArgs;
+struct RawValueVisitor;
/**
* A resource value. This is an all-encompassing representation
@@ -39,26 +40,54 @@ struct ValueVisitorArgs;
* but it is the simplest strategy.
*/
struct Value {
+ virtual ~Value() = default;
+
/**
- * Whether or not this is an Item.
+ * Whether this value is weak and can be overridden without
+ * warning or error. Default is false.
*/
- virtual bool isItem() const;
+ bool isWeak() const {
+ return mWeak;
+ }
+
+ void setWeak(bool val) {
+ mWeak = val;
+ }
/**
- * Whether this value is weak and can be overriden without
- * warning or error. Default for base class is false.
+ * Returns the source where this value was defined.
*/
- virtual bool isWeak() const;
+ const Source& getSource() const {
+ return mSource;
+ }
+
+ void setSource(const Source& source) {
+ mSource = source;
+ }
+
+ void setSource(Source&& source) {
+ mSource = std::move(source);
+ }
/**
- * Calls the appropriate overload of ValueVisitor.
+ * Returns the comment that was associated with this resource.
*/
- virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) = 0;
+ StringPiece16 getComment() const {
+ return mComment;
+ }
+
+ void setComment(const StringPiece16& str) {
+ mComment = str.toString();
+ }
+
+ void setComment(std::u16string&& str) {
+ mComment = std::move(str);
+ }
/**
- * Const version of accept().
+ * Calls the appropriate overload of ValueVisitor.
*/
- virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const = 0;
+ virtual void accept(RawValueVisitor* visitor) = 0;
/**
* Clone the value.
@@ -68,7 +97,12 @@ struct Value {
/**
* Human readable printout of this value.
*/
- virtual void print(std::ostream& out) const = 0;
+ virtual void print(std::ostream* out) const = 0;
+
+protected:
+ Source mSource;
+ std::u16string mComment;
+ bool mWeak = false;
};
/**
@@ -76,8 +110,7 @@ struct Value {
*/
template <typename Derived>
struct BaseValue : public Value {
- virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
- virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+ void accept(RawValueVisitor* visitor) override;
};
/**
@@ -85,20 +118,15 @@ struct BaseValue : public Value {
*/
struct Item : public Value {
/**
- * An Item is, of course, an Item.
- */
- virtual bool isItem() const override;
-
- /**
* Clone the Item.
*/
virtual Item* clone(StringPool* newPool) const override = 0;
/**
* Fills in an android::Res_value structure with this Item's binary representation.
- * Returns false if an error ocurred.
+ * Returns false if an error occurred.
*/
- virtual bool flatten(android::Res_value& outValue) const = 0;
+ virtual bool flatten(android::Res_value* outValue) const = 0;
};
/**
@@ -106,8 +134,7 @@ struct Item : public Value {
*/
template <typename Derived>
struct BaseItem : public Item {
- virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
- virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+ void accept(RawValueVisitor* visitor) override;
};
/**
@@ -122,28 +149,28 @@ struct Reference : public BaseItem<Reference> {
kAttribute,
};
- ResourceName name;
- ResourceId id;
+ Maybe<ResourceName> name;
+ Maybe<ResourceId> id;
Reference::Type referenceType;
bool privateReference = false;
Reference();
- Reference(const ResourceNameRef& n, Type type = Type::kResource);
- Reference(const ResourceId& i, Type type = Type::kResource);
+ explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
+ explicit Reference(const ResourceId& i, Type type = Type::kResource);
- bool flatten(android::Res_value& outValue) const override;
+ bool flatten(android::Res_value* outValue) const override;
Reference* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
};
/**
* An ID resource. Has no real value, just a place holder.
*/
struct Id : public BaseItem<Id> {
- bool isWeak() const override;
- bool flatten(android::Res_value& out) const override;
+ Id() { mWeak = true; }
+ bool flatten(android::Res_value* out) const override;
Id* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
};
/**
@@ -156,9 +183,9 @@ struct RawString : public BaseItem<RawString> {
RawString(const StringPool::Ref& ref);
- bool flatten(android::Res_value& outValue) const override;
+ bool flatten(android::Res_value* outValue) const override;
RawString* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
};
struct String : public BaseItem<String> {
@@ -166,9 +193,17 @@ struct String : public BaseItem<String> {
String(const StringPool::Ref& ref);
- bool flatten(android::Res_value& outValue) const override;
+ // Whether the string is marked as translateable. This does not persist when flattened.
+ // It is only used during compilation phase.
+ void setTranslateable(bool val);
+ bool isTranslateable() const;
+
+ bool flatten(android::Res_value* outValue) const override;
String* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
+
+private:
+ bool mTranslateable;
};
struct StyledString : public BaseItem<StyledString> {
@@ -176,20 +211,33 @@ struct StyledString : public BaseItem<StyledString> {
StyledString(const StringPool::StyleRef& ref);
- bool flatten(android::Res_value& outValue) const override;
+ // Whether the string is marked as translateable. This does not persist when flattened.
+ // It is only used during compilation phase.
+ void setTranslateable(bool val);
+ bool isTranslateable() const;
+
+ bool flatten(android::Res_value* outValue) const override;
StyledString* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
+
+private:
+ bool mTranslateable;
};
struct FileReference : public BaseItem<FileReference> {
StringPool::Ref path;
+ /**
+ * A handle to the file object from which this file can be read.
+ */
+ io::IFile* file = nullptr;
+
FileReference() = default;
FileReference(const StringPool::Ref& path);
- bool flatten(android::Res_value& outValue) const override;
+ bool flatten(android::Res_value* outValue) const override;
FileReference* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
};
/**
@@ -200,10 +248,11 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
BinaryPrimitive() = default;
BinaryPrimitive(const android::Res_value& val);
+ BinaryPrimitive(uint8_t dataType, uint32_t data);
- bool flatten(android::Res_value& outValue) const override;
+ bool flatten(android::Res_value* outValue) const override;
BinaryPrimitive* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
};
struct Attribute : public BaseValue<Attribute> {
@@ -212,18 +261,17 @@ struct Attribute : public BaseValue<Attribute> {
uint32_t value;
};
- bool weak;
uint32_t typeMask;
- uint32_t minInt;
- uint32_t maxInt;
+ int32_t minInt;
+ int32_t maxInt;
std::vector<Symbol> symbols;
Attribute(bool w, uint32_t t = 0u);
- bool isWeak() const override;
- virtual Attribute* clone(StringPool* newPool) const override;
- void printMask(std::ostream& out) const;
- virtual void print(std::ostream& out) const override;
+ Attribute* clone(StringPool* newPool) const override;
+ void printMask(std::ostream* out) const;
+ void print(std::ostream* out) const override;
+ bool matches(const Item* item, DiagMessage* outMsg) const;
};
struct Style : public BaseValue<Style> {
@@ -232,7 +280,7 @@ struct Style : public BaseValue<Style> {
std::unique_ptr<Item> value;
};
- Reference parent;
+ Maybe<Reference> parent;
/**
* If set to true, the parent was auto inferred from the
@@ -243,14 +291,14 @@ struct Style : public BaseValue<Style> {
std::vector<Entry> entries;
Style* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
};
struct Array : public BaseValue<Array> {
std::vector<std::unique_ptr<Item>> items;
Array* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
};
struct Plural : public BaseValue<Plural> {
@@ -267,180 +315,31 @@ struct Plural : public BaseValue<Plural> {
std::array<std::unique_ptr<Item>, Count> values;
Plural* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
};
struct Styleable : public BaseValue<Styleable> {
std::vector<Reference> entries;
Styleable* clone(StringPool* newPool) const override;
- void print(std::ostream& out) const override;
+ void print(std::ostream* out) const override;
};
/**
* Stream operator for printing Value objects.
*/
inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) {
- value.print(out);
+ value.print(&out);
return out;
}
inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
- return out << s.symbol.name.entry << "=" << s.value;
-}
-
-/**
- * The argument object that gets passed through the value
- * back to the ValueVisitor. Subclasses of ValueVisitor should
- * subclass ValueVisitorArgs to contain the data they need
- * to operate.
- */
-struct ValueVisitorArgs {};
-
-/**
- * Visits a value and runs the appropriate method based on its type.
- */
-struct ValueVisitor {
- virtual void visit(Reference& reference, ValueVisitorArgs& args) {
- visitItem(reference, args);
- }
-
- virtual void visit(RawString& string, ValueVisitorArgs& args) {
- visitItem(string, args);
- }
-
- virtual void visit(String& string, ValueVisitorArgs& args) {
- visitItem(string, args);
+ if (s.symbol.name) {
+ out << s.symbol.name.value().entry;
+ } else {
+ out << "???";
}
-
- virtual void visit(StyledString& string, ValueVisitorArgs& args) {
- visitItem(string, args);
- }
-
- virtual void visit(FileReference& file, ValueVisitorArgs& args) {
- visitItem(file, args);
- }
-
- virtual void visit(Id& id, ValueVisitorArgs& args) {
- visitItem(id, args);
- }
-
- virtual void visit(BinaryPrimitive& primitive, ValueVisitorArgs& args) {
- visitItem(primitive, args);
- }
-
- virtual void visit(Attribute& attr, ValueVisitorArgs& args) {}
- virtual void visit(Style& style, ValueVisitorArgs& args) {}
- virtual void visit(Array& array, ValueVisitorArgs& args) {}
- virtual void visit(Plural& array, ValueVisitorArgs& args) {}
- virtual void visit(Styleable& styleable, ValueVisitorArgs& args) {}
-
- virtual void visitItem(Item& item, ValueVisitorArgs& args) {}
-};
-
-/**
- * Const version of ValueVisitor.
- */
-struct ConstValueVisitor {
- virtual void visit(const Reference& reference, ValueVisitorArgs& args) {
- visitItem(reference, args);
- }
-
- virtual void visit(const RawString& string, ValueVisitorArgs& args) {
- visitItem(string, args);
- }
-
- virtual void visit(const String& string, ValueVisitorArgs& args) {
- visitItem(string, args);
- }
-
- virtual void visit(const StyledString& string, ValueVisitorArgs& args) {
- visitItem(string, args);
- }
-
- virtual void visit(const FileReference& file, ValueVisitorArgs& args) {
- visitItem(file, args);
- }
-
- virtual void visit(const Id& id, ValueVisitorArgs& args) {
- visitItem(id, args);
- }
-
- virtual void visit(const BinaryPrimitive& primitive, ValueVisitorArgs& args) {
- visitItem(primitive, args);
- }
-
- virtual void visit(const Attribute& attr, ValueVisitorArgs& args) {}
- virtual void visit(const Style& style, ValueVisitorArgs& args) {}
- virtual void visit(const Array& array, ValueVisitorArgs& args) {}
- virtual void visit(const Plural& array, ValueVisitorArgs& args) {}
- virtual void visit(const Styleable& styleable, ValueVisitorArgs& args) {}
-
- virtual void visitItem(const Item& item, ValueVisitorArgs& args) {}
-};
-
-/**
- * Convenience Visitor that forwards a specific type to a function.
- * Args are not used as the function can bind variables. Do not use
- * directly, use the wrapper visitFunc() method.
- */
-template <typename T, typename TFunc>
-struct ValueVisitorFunc : ValueVisitor {
- TFunc func;
-
- ValueVisitorFunc(TFunc f) : func(f) {
- }
-
- void visit(T& value, ValueVisitorArgs&) override {
- func(value);
- }
-};
-
-/**
- * Const version of ValueVisitorFunc.
- */
-template <typename T, typename TFunc>
-struct ConstValueVisitorFunc : ConstValueVisitor {
- TFunc func;
-
- ConstValueVisitorFunc(TFunc f) : func(f) {
- }
-
- void visit(const T& value, ValueVisitorArgs&) override {
- func(value);
- }
-};
-
-template <typename T, typename TFunc>
-void visitFunc(Value& value, TFunc f) {
- ValueVisitorFunc<T, TFunc> visitor(f);
- value.accept(visitor, ValueVisitorArgs{});
-}
-
-template <typename T, typename TFunc>
-void visitFunc(const Value& value, TFunc f) {
- ConstValueVisitorFunc<T, TFunc> visitor(f);
- value.accept(visitor, ValueVisitorArgs{});
-}
-
-template <typename Derived>
-void BaseValue<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
- visitor.visit(static_cast<Derived&>(*this), args);
-}
-
-template <typename Derived>
-void BaseValue<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
- visitor.visit(static_cast<const Derived&>(*this), args);
-}
-
-template <typename Derived>
-void BaseItem<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
- visitor.visit(static_cast<Derived&>(*this), args);
-}
-
-template <typename Derived>
-void BaseItem<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
- visitor.visit(static_cast<const Derived&>(*this), args);
+ return out << "=" << s.value;
}
} // namespace aapt
diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp
index d957999f492b..48dc521d843c 100644
--- a/tools/aapt2/Resource_test.cpp
+++ b/tools/aapt2/Resource_test.cpp
@@ -69,10 +69,6 @@ TEST(ResourceTypeTest, ParseResourceTypes) {
ASSERT_NE(type, nullptr);
EXPECT_EQ(*type, ResourceType::kInteger);
- type = parseResourceType(u"integer-array");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kIntegerArray);
-
type = parseResourceType(u"interpolator");
ASSERT_NE(type, nullptr);
EXPECT_EQ(*type, ResourceType::kInterpolator);
diff --git a/tools/aapt2/ScopedXmlPullParser.cpp b/tools/aapt2/ScopedXmlPullParser.cpp
deleted file mode 100644
index 48da93edaa02..000000000000
--- a/tools/aapt2/ScopedXmlPullParser.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ScopedXmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-ScopedXmlPullParser::ScopedXmlPullParser(XmlPullParser* parser) :
- mParser(parser), mDepth(parser->getDepth()), mDone(false) {
-}
-
-ScopedXmlPullParser::~ScopedXmlPullParser() {
- while (isGoodEvent(next()));
-}
-
-XmlPullParser::Event ScopedXmlPullParser::next() {
- if (mDone) {
- return Event::kEndDocument;
- }
-
- const Event event = mParser->next();
- if (mParser->getDepth() <= mDepth) {
- mDone = true;
- }
- return event;
-}
-
-XmlPullParser::Event ScopedXmlPullParser::getEvent() const {
- return mParser->getEvent();
-}
-
-const std::string& ScopedXmlPullParser::getLastError() const {
- return mParser->getLastError();
-}
-
-const std::u16string& ScopedXmlPullParser::getComment() const {
- return mParser->getComment();
-}
-
-size_t ScopedXmlPullParser::getLineNumber() const {
- return mParser->getLineNumber();
-}
-
-size_t ScopedXmlPullParser::getDepth() const {
- const size_t depth = mParser->getDepth();
- if (depth < mDepth) {
- return 0;
- }
- return depth - mDepth;
-}
-
-const std::u16string& ScopedXmlPullParser::getText() const {
- return mParser->getText();
-}
-
-const std::u16string& ScopedXmlPullParser::getNamespacePrefix() const {
- return mParser->getNamespacePrefix();
-}
-
-const std::u16string& ScopedXmlPullParser::getNamespaceUri() const {
- return mParser->getNamespaceUri();
-}
-
-bool ScopedXmlPullParser::applyPackageAlias(std::u16string* package,
- const std::u16string& defaultPackage) const {
- return mParser->applyPackageAlias(package, defaultPackage);
-}
-
-const std::u16string& ScopedXmlPullParser::getElementNamespace() const {
- return mParser->getElementNamespace();
-}
-
-const std::u16string& ScopedXmlPullParser::getElementName() const {
- return mParser->getElementName();
-}
-
-size_t ScopedXmlPullParser::getAttributeCount() const {
- return mParser->getAttributeCount();
-}
-
-XmlPullParser::const_iterator ScopedXmlPullParser::beginAttributes() const {
- return mParser->beginAttributes();
-}
-
-XmlPullParser::const_iterator ScopedXmlPullParser::endAttributes() const {
- return mParser->endAttributes();
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ScopedXmlPullParser.h b/tools/aapt2/ScopedXmlPullParser.h
deleted file mode 100644
index a040f6097fc3..000000000000
--- a/tools/aapt2/ScopedXmlPullParser.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_SCOPED_XML_PULL_PARSER_H
-#define AAPT_SCOPED_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-/**
- * An XmlPullParser that will not read past the depth
- * of the underlying parser. When this parser is destroyed,
- * it moves the underlying parser to the same depth it
- * started with.
- *
- * You can write code like this:
- *
- * while (XmlPullParser::isGoodEvent(parser.next())) {
- * if (parser.getEvent() != XmlPullParser::Event::StartElement) {
- * continue;
- * }
- *
- * ScopedXmlPullParser scoped(parser);
- * if (parser.getElementName() == u"id") {
- * // do work.
- * } else {
- * // do nothing, as all the sub elements will be skipped
- * // when scoped goes out of scope.
- * }
- * }
- */
-class ScopedXmlPullParser : public XmlPullParser {
-public:
- ScopedXmlPullParser(XmlPullParser* parser);
- ScopedXmlPullParser(const ScopedXmlPullParser&) = delete;
- ScopedXmlPullParser& operator=(const ScopedXmlPullParser&) = delete;
- ~ScopedXmlPullParser();
-
- Event getEvent() const override;
- const std::string& getLastError() const override;
- Event next() override;
-
- const std::u16string& getComment() const override;
- size_t getLineNumber() const override;
- size_t getDepth() const override;
-
- const std::u16string& getText() const override;
-
- const std::u16string& getNamespacePrefix() const override;
- const std::u16string& getNamespaceUri() const override;
- bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
- const override;
-
- const std::u16string& getElementNamespace() const override;
- const std::u16string& getElementName() const override;
-
- const_iterator beginAttributes() const override;
- const_iterator endAttributes() const override;
- size_t getAttributeCount() const override;
-
-private:
- XmlPullParser* mParser;
- size_t mDepth;
- bool mDone;
-};
-
-} // namespace aapt
-
-#endif // AAPT_SCOPED_XML_PULL_PARSER_H
diff --git a/tools/aapt2/ScopedXmlPullParser_test.cpp b/tools/aapt2/ScopedXmlPullParser_test.cpp
deleted file mode 100644
index 342f305bb11d..000000000000
--- a/tools/aapt2/ScopedXmlPullParser_test.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ScopedXmlPullParser.h"
-#include "SourceXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-TEST(ScopedXmlPullParserTest, StopIteratingAtNoNZeroDepth) {
- std::stringstream input;
- input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
- << "<resources><string></string></resources>" << std::endl;
-
- SourceXmlPullParser sourceParser(input);
- EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
- EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
- EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
- EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
- {
- ScopedXmlPullParser scopedParser(&sourceParser);
- EXPECT_EQ(XmlPullParser::Event::kEndElement, scopedParser.next());
- EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
- EXPECT_EQ(XmlPullParser::Event::kEndDocument, scopedParser.next());
- }
-
- EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
- EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
- EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
-}
-
-TEST(ScopedXmlPullParserTest, FinishCurrentElementOnDestruction) {
- std::stringstream input;
- input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
- << "<resources><string></string></resources>" << std::endl;
-
- SourceXmlPullParser sourceParser(input);
- EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
- EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
- EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
- EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
- {
- ScopedXmlPullParser scopedParser(&sourceParser);
- EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
- }
-
- EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
- EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
- EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
-}
-
-TEST(ScopedXmlPullParserTest, NestedParsersOperateCorrectly) {
- std::stringstream input;
- input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
- << "<resources><string><foo></foo></string></resources>" << std::endl;
-
- SourceXmlPullParser sourceParser(input);
- EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
- EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
- EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
- EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
- {
- ScopedXmlPullParser scopedParser(&sourceParser);
- EXPECT_EQ(std::u16string(u"string"), scopedParser.getElementName());
- while (XmlPullParser::isGoodEvent(scopedParser.next())) {
- if (scopedParser.getEvent() != XmlPullParser::Event::kStartElement) {
- continue;
- }
-
- ScopedXmlPullParser subScopedParser(&scopedParser);
- EXPECT_EQ(std::u16string(u"foo"), subScopedParser.getElementName());
- }
- }
-
- EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
- EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
- EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 9bdae490412f..c2a22bf2a373 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -34,8 +34,9 @@ static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = {
{ 0x02bd, SDK_FROYO },
{ 0x02cb, SDK_GINGERBREAD },
{ 0x0361, SDK_HONEYCOMB },
- { 0x0366, SDK_HONEYCOMB_MR1 },
- { 0x03a6, SDK_HONEYCOMB_MR2 },
+ { 0x0363, SDK_HONEYCOMB_MR1 },
+ { 0x0366, SDK_HONEYCOMB_MR2 },
+ { 0x03a6, SDK_ICE_CREAM_SANDWICH },
{ 0x03ae, SDK_JELLY_BEAN },
{ 0x03cc, SDK_JELLY_BEAN_MR1 },
{ 0x03da, SDK_JELLY_BEAN_MR2 },
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 803da03743c5..282ed9a56f5c 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -42,6 +42,7 @@ enum {
SDK_KITKAT_WATCH = 20,
SDK_LOLLIPOP = 21,
SDK_LOLLIPOP_MR1 = 22,
+ SDK_MARSHMALLOW = 23,
};
size_t findAttributeSdkLevel(ResourceId id);
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
new file mode 100644
index 000000000000..e81f412dda15
--- /dev/null
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SdkConstants.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(SdkConstantsTest, FirstAttributeIsSdk1) {
+ EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000)));
+}
+
+TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) {
+ EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7)));
+ EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce)));
+
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8)));
+
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff)));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 3606488591ba..319528e0ea1b 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -17,72 +17,62 @@
#ifndef AAPT_SOURCE_H
#define AAPT_SOURCE_H
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
#include <ostream>
#include <string>
-#include <tuple>
namespace aapt {
-struct SourceLineColumn;
-struct SourceLine;
-
/**
* Represents a file on disk. Used for logging and
* showing errors.
*/
struct Source {
std::string path;
+ Maybe<size_t> line;
- inline SourceLine line(size_t line) const;
-};
+ Source() = default;
-/**
- * Represents a file on disk and a line number in that file.
- * Used for logging and showing errors.
- */
-struct SourceLine {
- std::string path;
- size_t line;
+ inline Source(const StringPiece& path) : path(path.toString()) {
+ }
- inline SourceLineColumn column(size_t column) const;
-};
+ inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) {
+ }
-/**
- * Represents a file on disk and a line:column number in that file.
- * Used for logging and showing errors.
- */
-struct SourceLineColumn {
- std::string path;
- size_t line;
- size_t column;
+ inline Source withLine(size_t line) const {
+ return Source(path, line);
+ }
};
//
// Implementations
//
-SourceLine Source::line(size_t line) const {
- return SourceLine{ path, line };
-}
-
-SourceLineColumn SourceLine::column(size_t column) const {
- return SourceLineColumn{ path, line, column };
-}
-
inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
- return out << source.path;
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const SourceLine& source) {
- return out << source.path << ":" << source.line;
+ out << source.path;
+ if (source.line) {
+ out << ":" << source.line.value();
+ }
+ return out;
}
-inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& source) {
- return out << source.path << ":" << source.line << ":" << source.column;
+inline bool operator==(const Source& lhs, const Source& rhs) {
+ return lhs.path == rhs.path && lhs.line == rhs.line;
}
-inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) {
- return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, rhs.line);
+inline bool operator<(const Source& lhs, const Source& rhs) {
+ int cmp = lhs.path.compare(rhs.path);
+ if (cmp < 0) return true;
+ if (cmp > 0) return false;
+ if (lhs.line) {
+ if (rhs.line) {
+ return lhs.line.value() < rhs.line.value();
+ }
+ return false;
+ }
+ return bool(rhs.line);
}
} // namespace aapt
diff --git a/tools/aapt2/SourceXmlPullParser.h b/tools/aapt2/SourceXmlPullParser.h
deleted file mode 100644
index 15936d655745..000000000000
--- a/tools/aapt2/SourceXmlPullParser.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_SOURCE_XML_PULL_PARSER_H
-#define AAPT_SOURCE_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <istream>
-#include <libexpat/expat.h>
-#include <queue>
-#include <stack>
-#include <string>
-#include <vector>
-
-namespace aapt {
-
-class SourceXmlPullParser : public XmlPullParser {
-public:
- SourceXmlPullParser(std::istream& in);
- SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete;
- ~SourceXmlPullParser();
-
- Event getEvent() const override;
- const std::string& getLastError() const override ;
- Event next() override ;
-
- const std::u16string& getComment() const override;
- size_t getLineNumber() const override;
- size_t getDepth() const override;
-
- const std::u16string& getText() const override;
-
- const std::u16string& getNamespacePrefix() const override;
- const std::u16string& getNamespaceUri() const override;
- bool applyPackageAlias(std::u16string* package,
- const std::u16string& defaultPackage) const override;
-
-
- const std::u16string& getElementNamespace() const override;
- const std::u16string& getElementName() const override;
-
- const_iterator beginAttributes() const override;
- const_iterator endAttributes() const override;
- size_t getAttributeCount() const override;
-
-private:
- static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
- static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
- static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
- static void XMLCALL endElementHandler(void* userData, const char* name);
- static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
- static void XMLCALL commentDataHandler(void* userData, const char* comment);
-
- struct EventData {
- Event event;
- size_t lineNumber;
- size_t depth;
- std::u16string data1;
- std::u16string data2;
- std::u16string comment;
- std::vector<Attribute> attributes;
- };
-
- std::istream& mIn;
- XML_Parser mParser;
- char mBuffer[16384];
- std::queue<EventData> mEventQueue;
- std::string mLastError;
- const std::u16string mEmpty;
- size_t mDepth;
- std::stack<std::u16string> mNamespaceUris;
- std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
-};
-
-} // namespace aapt
-
-#endif // AAPT_SOURCE_XML_PULL_PARSER_H
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index c19aa98a70ac..aadb00b6be2a 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-#include "BigBuffer.h"
-#include "StringPiece.h"
#include "StringPool.h"
-#include "Util.h"
+#include "util/BigBuffer.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
#include <algorithm>
#include <androidfw/ResourceTypes.h>
@@ -219,7 +219,7 @@ void StringPool::prune() {
auto indexIter = std::begin(mIndexedStrings);
while (indexIter != iterEnd) {
if (indexIter->second->ref <= 0) {
- mIndexedStrings.erase(indexIter++);
+ indexIter = mIndexedStrings.erase(indexIter);
} else {
++indexIter;
}
@@ -241,6 +241,12 @@ void StringPool::prune() {
// a deleted string from the StyleEntry.
mStrings.erase(endIter2, std::end(mStrings));
mStyles.erase(endIter3, std::end(mStyles));
+
+ // Reassign the indices.
+ const size_t len = mStrings.size();
+ for (size_t index = 0; index < len; index++) {
+ mStrings[index]->index = index;
+ }
}
void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) {
@@ -336,7 +342,14 @@ bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
// Encode the actual UTF16 string length.
data = encodeLength(data, entry->value.size());
- strncpy16(data, entry->value.data(), entry->value.size());
+ const size_t byteLength = entry->value.size() * sizeof(char16_t);
+
+ // NOTE: For some reason, strncpy16(data, entry->value.data(), entry->value.size())
+ // truncates the string.
+ memcpy(data, entry->value.data(), byteLength);
+
+ // The null-terminating character is already here due to the block of data being set
+ // to 0s on allocation.
}
}
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 14304a6e6b1a..509e3041e081 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -17,9 +17,9 @@
#ifndef AAPT_STRING_POOL_H
#define AAPT_STRING_POOL_H
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
#include "ConfigDescription.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
#include <functional>
#include <map>
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 9552937d4ad4..2b2d348fd17c 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -15,13 +15,11 @@
*/
#include "StringPool.h"
-#include "Util.h"
+#include "util/Util.h"
#include <gtest/gtest.h>
#include <string>
-using namespace android;
-
namespace aapt {
TEST(StringPoolTest, InsertOneString) {
@@ -67,15 +65,23 @@ TEST(StringPoolTest, MaintainInsertionOrderIndex) {
TEST(StringPoolTest, PruneStringsWithNoReferences) {
StringPool pool;
+ StringPool::Ref refA = pool.makeRef(u"foo");
{
StringPool::Ref ref = pool.makeRef(u"wut");
EXPECT_EQ(*ref, u"wut");
- EXPECT_EQ(1u, pool.size());
+ EXPECT_EQ(2u, pool.size());
}
+ StringPool::Ref refB = pool.makeRef(u"bar");
- EXPECT_EQ(1u, pool.size());
+ EXPECT_EQ(3u, pool.size());
pool.prune();
- EXPECT_EQ(0u, pool.size());
+ EXPECT_EQ(2u, pool.size());
+ StringPool::const_iterator iter = begin(pool);
+ EXPECT_EQ((*iter)->value, u"foo");
+ EXPECT_LT((*iter)->index, 2u);
+ ++iter;
+ EXPECT_EQ((*iter)->value, u"bar");
+ EXPECT_LT((*iter)->index, 2u);
}
TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
@@ -163,18 +169,40 @@ TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
}
TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
+ using namespace android; // For NO_ERROR on Windows.
+
StringPool pool;
BigBuffer buffer(1024);
StringPool::flattenUtf8(&buffer, pool);
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- android::ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+}
+
+TEST(StringPoolTest, FlattenOddCharactersUtf16) {
+ using namespace android; // For NO_ERROR on Windows.
+
+ StringPool pool;
+ pool.makeRef(u"\u093f");
+ BigBuffer buffer(1024);
+ StringPool::flattenUtf16(&buffer, pool);
+
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+ size_t len = 0;
+ const char16_t* str = test.stringAt(0, &len);
+ EXPECT_EQ(1u, len);
+ EXPECT_EQ(u'\u093f', *str);
+ EXPECT_EQ(0u, str[1]);
}
constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
TEST(StringPoolTest, FlattenUtf8) {
+ using namespace android; // For NO_ERROR on Windows.
+
StringPool pool;
StringPool::Ref ref1 = pool.makeRef(u"hello");
@@ -195,8 +223,8 @@ TEST(StringPoolTest, FlattenUtf8) {
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
{
- android::ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
EXPECT_EQ(util::getString(test, 0), u"hello");
EXPECT_EQ(util::getString(test, 1), u"goodbye");
diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp
deleted file mode 100644
index b7c04f06cff5..000000000000
--- a/tools/aapt2/TableFlattener.cpp
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BigBuffer.h"
-#include "ConfigDescription.h"
-#include "Logger.h"
-#include "ResourceTable.h"
-#include "ResourceTypeExtensions.h"
-#include "ResourceValues.h"
-#include "StringPool.h"
-#include "TableFlattener.h"
-#include "Util.h"
-
-#include <algorithm>
-#include <androidfw/ResourceTypes.h>
-#include <sstream>
-
-namespace aapt {
-
-struct FlatEntry {
- const ResourceEntry* entry;
- const Value* value;
- uint32_t entryKey;
- uint32_t sourcePathKey;
- uint32_t sourceLine;
-};
-
-/**
- * Visitor that knows how to encode Map values.
- */
-class MapFlattener : public ConstValueVisitor {
-public:
- MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) :
- mOut(out), mSymbols(symbols) {
- mMap = mOut->nextBlock<android::ResTable_map_entry>();
- mMap->key.index = flatEntry.entryKey;
- mMap->flags = android::ResTable_entry::FLAG_COMPLEX;
- if (flatEntry.entry->publicStatus.isPublic) {
- mMap->flags |= android::ResTable_entry::FLAG_PUBLIC;
- }
- if (flatEntry.value->isWeak()) {
- mMap->flags |= android::ResTable_entry::FLAG_WEAK;
- }
-
- ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>();
- sourceBlock->pathIndex = flatEntry.sourcePathKey;
- sourceBlock->line = flatEntry.sourceLine;
-
- mMap->size = sizeof(*mMap) + sizeof(*sourceBlock);
- }
-
- void flattenParent(const Reference& ref) {
- if (!ref.id.isValid()) {
- mSymbols->push_back({
- ResourceNameRef(ref.name),
- (mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry)
- });
- }
- mMap->parent.ident = ref.id.id;
- }
-
- void flattenEntry(const Reference& key, const Item& value) {
- mMap->count++;
-
- android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
-
- // Write the key.
- if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) {
- assert(!key.name.entry.empty());
- mSymbols->push_back(std::make_pair(ResourceNameRef(key.name),
- mOut->size() - sizeof(*outMapEntry)));
- }
- outMapEntry->name.ident = key.id.id;
-
- // Write the value.
- value.flatten(outMapEntry->value);
-
- if (outMapEntry->value.data == 0x0) {
- visitFunc<Reference>(value, [&](const Reference& reference) {
- mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
- mOut->size() - sizeof(outMapEntry->value.data)));
- });
- }
- outMapEntry->value.size = sizeof(outMapEntry->value);
- }
-
- void flattenValueOnly(const Item& value) {
- mMap->count++;
-
- android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
-
- // Write the value.
- value.flatten(outMapEntry->value);
-
- if (outMapEntry->value.data == 0x0) {
- visitFunc<Reference>(value, [&](const Reference& reference) {
- mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
- mOut->size() - sizeof(outMapEntry->value.data)));
- });
- }
- outMapEntry->value.size = sizeof(outMapEntry->value);
- }
-
- static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
- return lhs->key.id < rhs->key.id;
- }
-
- void visit(const Style& style, ValueVisitorArgs&) override {
- if (style.parent.name.isValid()) {
- flattenParent(style.parent);
- }
-
- // First sort the entries by ID.
- std::vector<const Style::Entry*> sortedEntries;
- for (const auto& styleEntry : style.entries) {
- auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(),
- &styleEntry, compareStyleEntries);
- sortedEntries.insert(iter, &styleEntry);
- }
-
- for (const Style::Entry* styleEntry : sortedEntries) {
- flattenEntry(styleEntry->key, *styleEntry->value);
- }
- }
-
- void visit(const Attribute& attr, ValueVisitorArgs&) override {
- android::Res_value tempVal;
- tempVal.dataType = android::Res_value::TYPE_INT_DEC;
- tempVal.data = attr.typeMask;
- flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}),
- BinaryPrimitive(tempVal));
-
- for (const auto& symbol : attr.symbols) {
- tempVal.data = symbol.value;
- flattenEntry(symbol.symbol, BinaryPrimitive(tempVal));
- }
- }
-
- void visit(const Styleable& styleable, ValueVisitorArgs&) override {
- for (const auto& attr : styleable.entries) {
- flattenEntry(attr, BinaryPrimitive(android::Res_value{}));
- }
- }
-
- void visit(const Array& array, ValueVisitorArgs&) override {
- for (const auto& item : array.items) {
- flattenValueOnly(*item);
- }
- }
-
- void visit(const Plural& plural, ValueVisitorArgs&) override {
- const size_t count = plural.values.size();
- for (size_t i = 0; i < count; i++) {
- if (!plural.values[i]) {
- continue;
- }
-
- ResourceId q;
- switch (i) {
- case Plural::Zero:
- q.id = android::ResTable_map::ATTR_ZERO;
- break;
-
- case Plural::One:
- q.id = android::ResTable_map::ATTR_ONE;
- break;
-
- case Plural::Two:
- q.id = android::ResTable_map::ATTR_TWO;
- break;
-
- case Plural::Few:
- q.id = android::ResTable_map::ATTR_FEW;
- break;
-
- case Plural::Many:
- q.id = android::ResTable_map::ATTR_MANY;
- break;
-
- case Plural::Other:
- q.id = android::ResTable_map::ATTR_OTHER;
- break;
-
- default:
- assert(false);
- break;
- }
-
- flattenEntry(Reference(q), *plural.values[i]);
- }
- }
-
-private:
- BigBuffer* mOut;
- SymbolEntryVector* mSymbols;
- android::ResTable_map_entry* mMap;
-};
-
-/**
- * Flattens a value, with special handling for References.
- */
-struct ValueFlattener : ConstValueVisitor {
- ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) :
- result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) {
- mOutValue = mOut->nextBlock<android::Res_value>();
- }
-
- virtual void visit(const Reference& ref, ValueVisitorArgs& a) override {
- visitItem(ref, a);
- if (mOutValue->data == 0x0) {
- mSymbols->push_back({
- ResourceNameRef(ref.name),
- mOut->size() - sizeof(mOutValue->data)});
- }
- }
-
- virtual void visitItem(const Item& item, ValueVisitorArgs&) override {
- result = item.flatten(*mOutValue);
- mOutValue->res0 = 0;
- mOutValue->size = sizeof(*mOutValue);
- }
-
- bool result;
-
-private:
- BigBuffer* mOut;
- android::Res_value* mOutValue;
- SymbolEntryVector* mSymbols;
-};
-
-TableFlattener::TableFlattener(Options options)
-: mOptions(options) {
-}
-
-bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
- SymbolEntryVector* symbols) {
- if (flatEntry.value->isItem()) {
- android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>();
-
- if (flatEntry.entry->publicStatus.isPublic) {
- entry->flags |= android::ResTable_entry::FLAG_PUBLIC;
- }
-
- if (flatEntry.value->isWeak()) {
- entry->flags |= android::ResTable_entry::FLAG_WEAK;
- }
-
- entry->key.index = flatEntry.entryKey;
- entry->size = sizeof(*entry);
-
- if (mOptions.useExtendedChunks) {
- // Write the extra source block. This will be ignored by
- // the Android runtime.
- ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>();
- sourceBlock->pathIndex = flatEntry.sourcePathKey;
- sourceBlock->line = flatEntry.sourceLine;
- entry->size += sizeof(*sourceBlock);
- }
-
- const Item* item = static_cast<const Item*>(flatEntry.value);
- ValueFlattener flattener(out, symbols);
- item->accept(flattener, {});
- return flattener.result;
- }
-
- MapFlattener flattener(out, flatEntry, symbols);
- flatEntry.value->accept(flattener, {});
- return true;
-}
-
-bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
- const size_t beginning = out->size();
-
- if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
- Logger::error()
- << "ResourceTable has no package ID set."
- << std::endl;
- return false;
- }
-
- SymbolEntryVector symbolEntries;
-
- StringPool typePool;
- StringPool keyPool;
- StringPool sourcePool;
-
- // Sort the types by their IDs. They will be inserted into the StringPool
- // in this order.
- std::vector<ResourceTableType*> sortedTypes;
- for (const auto& type : table) {
- if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
- continue;
- }
-
- auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
- [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
- return lhs->typeId < rhs->typeId;
- });
- sortedTypes.insert(iter, type.get());
- }
-
- BigBuffer typeBlock(1024);
- size_t expectedTypeId = 1;
- for (const ResourceTableType* type : sortedTypes) {
- if (type->typeId == ResourceTableType::kUnsetTypeId
- || type->typeId == 0) {
- Logger::error()
- << "resource type '"
- << type->type
- << "' from package '"
- << table.getPackage()
- << "' has no ID."
- << std::endl;
- return false;
- }
-
- // If there is a gap in the type IDs, fill in the StringPool
- // with empty values until we reach the ID we expect.
- while (type->typeId > expectedTypeId) {
- std::u16string typeName(u"?");
- typeName += expectedTypeId;
- typePool.makeRef(typeName);
- expectedTypeId++;
- }
- expectedTypeId++;
- typePool.makeRef(toString(type->type));
-
- android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
- spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
- spec->header.headerSize = sizeof(*spec);
- spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
- spec->id = type->typeId;
- spec->entryCount = type->entries.size();
-
- if (type->entries.empty()) {
- continue;
- }
-
- // Reserve space for the masks of each resource in this type. These
- // show for which configuration axis the resource changes.
- uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
-
- // Sort the entries by entry ID and write their configuration masks.
- std::vector<ResourceEntry*> entries;
- const size_t entryCount = type->entries.size();
- for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
- const auto& entry = type->entries[entryIndex];
-
- if (entry->entryId == ResourceEntry::kUnsetEntryId) {
- Logger::error()
- << "resource '"
- << ResourceName{ table.getPackage(), type->type, entry->name }
- << "' has no ID."
- << std::endl;
- return false;
- }
-
- auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
- [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
- return lhs->entryId < rhs->entryId;
- });
- entries.insert(iter, entry.get());
-
- // Populate the config masks for this entry.
- if (entry->publicStatus.isPublic) {
- configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
- }
-
- const size_t configCount = entry->values.size();
- for (size_t i = 0; i < configCount; i++) {
- const ConfigDescription& config = entry->values[i].config;
- for (size_t j = i + 1; j < configCount; j++) {
- configMasks[entry->entryId] |= config.diff(entry->values[j].config);
- }
- }
- }
-
- const size_t beforePublicHeader = typeBlock.size();
- Public_header* publicHeader = nullptr;
- if (mOptions.useExtendedChunks) {
- publicHeader = typeBlock.nextBlock<Public_header>();
- publicHeader->header.type = RES_TABLE_PUBLIC_TYPE;
- publicHeader->header.headerSize = sizeof(*publicHeader);
- publicHeader->typeId = type->typeId;
- }
-
- // The binary resource table lists resource entries for each configuration.
- // We store them inverted, where a resource entry lists the values for each
- // configuration available. Here we reverse this to match the binary table.
- std::map<ConfigDescription, std::vector<FlatEntry>> data;
- for (const ResourceEntry* entry : entries) {
- size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
-
- if (keyIndex > std::numeric_limits<uint32_t>::max()) {
- Logger::error()
- << "resource key string pool exceeded max size."
- << std::endl;
- return false;
- }
-
- if (publicHeader && entry->publicStatus.isPublic) {
- // Write the public status of this entry.
- Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>();
- publicEntry->entryId = static_cast<uint32_t>(entry->entryId);
- publicEntry->key.index = static_cast<uint32_t>(keyIndex);
- publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef(
- util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
- publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line);
- publicHeader->count += 1;
- }
-
- for (const auto& configValue : entry->values) {
- data[configValue.config].push_back(FlatEntry{
- entry,
- configValue.value.get(),
- static_cast<uint32_t>(keyIndex),
- static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
- configValue.source.path)).getIndex()),
- static_cast<uint32_t>(configValue.source.line)
- });
- }
- }
-
- if (publicHeader) {
- typeBlock.align4();
- publicHeader->header.size =
- static_cast<uint32_t>(typeBlock.size() - beforePublicHeader);
- }
-
- // Begin flattening a configuration for the current type.
- for (const auto& entry : data) {
- const size_t typeHeaderStart = typeBlock.size();
- android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
- typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
- typeHeader->header.headerSize = sizeof(*typeHeader);
- typeHeader->id = type->typeId;
- typeHeader->entryCount = type->entries.size();
- typeHeader->entriesStart = typeHeader->header.headerSize
- + (sizeof(uint32_t) * type->entries.size());
- typeHeader->config = entry.first;
-
- uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
- memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
-
- const size_t entryStart = typeBlock.size();
- for (const FlatEntry& flatEntry : entry.second) {
- assert(flatEntry.entry->entryId < type->entries.size());
- indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart;
- if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) {
- Logger::error()
- << "failed to flatten resource '"
- << ResourceNameRef {
- table.getPackage(), type->type, flatEntry.entry->name }
- << "' for configuration '"
- << entry.first
- << "'."
- << std::endl;
- return false;
- }
- }
-
- typeBlock.align4();
- typeHeader->header.size = typeBlock.size() - typeHeaderStart;
- }
- }
-
- const size_t beforeTable = out->size();
- android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
- header->header.type = android::RES_TABLE_TYPE;
- header->header.headerSize = sizeof(*header);
- header->packageCount = 1;
-
- SymbolTable_entry* symbolEntryData = nullptr;
- if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
- const size_t beforeSymbolTable = out->size();
- StringPool symbolPool;
- SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
- symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
- symbolHeader->header.headerSize = sizeof(*symbolHeader);
- symbolHeader->count = symbolEntries.size();
-
- symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
-
- size_t i = 0;
- for (const auto& entry : symbolEntries) {
- symbolEntryData[i].offset = entry.second;
- StringPool::Ref ref = symbolPool.makeRef(
- entry.first.package.toString() + u":" +
- toString(entry.first.type).toString() + u"/" +
- entry.first.entry.toString());
- symbolEntryData[i].stringIndex = ref.getIndex();
- i++;
- }
-
- StringPool::flattenUtf8(out, symbolPool);
- out->align4();
- symbolHeader->header.size = out->size() - beforeSymbolTable;
- }
-
- if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
- const size_t beforeSourcePool = out->size();
- android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
- sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
- sourceHeader->headerSize = sizeof(*sourceHeader);
- StringPool::flattenUtf8(out, sourcePool);
- out->align4();
- sourceHeader->size = out->size() - beforeSourcePool;
- }
-
- StringPool::flattenUtf8(out, table.getValueStringPool());
-
- const size_t beforePackageIndex = out->size();
- android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
- package->header.type = android::RES_TABLE_PACKAGE_TYPE;
- package->header.headerSize = sizeof(*package);
-
- if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
- Logger::error()
- << "package ID 0x'"
- << std::hex << table.getPackageId() << std::dec
- << "' is invalid."
- << std::endl;
- return false;
- }
- package->id = table.getPackageId();
-
- if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
- Logger::error()
- << "package name '"
- << table.getPackage()
- << "' is too long."
- << std::endl;
- return false;
- }
- memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
- table.getPackage().length() * sizeof(char16_t));
- package->name[table.getPackage().length()] = 0;
-
- package->typeStrings = package->header.headerSize;
- StringPool::flattenUtf16(out, typePool);
- package->keyStrings = out->size() - beforePackageIndex;
- StringPool::flattenUtf16(out, keyPool);
-
- if (symbolEntryData != nullptr) {
- for (size_t i = 0; i < symbolEntries.size(); i++) {
- symbolEntryData[i].offset += out->size() - beginning;
- }
- }
-
- out->appendBuffer(std::move(typeBlock));
-
- package->header.size = out->size() - beforePackageIndex;
- header->header.size = out->size() - beforeTable;
- return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/TableFlattener.h b/tools/aapt2/TableFlattener.h
deleted file mode 100644
index ccbb737059f9..000000000000
--- a/tools/aapt2/TableFlattener.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_TABLE_FLATTENER_H
-#define AAPT_TABLE_FLATTENER_H
-
-#include "BigBuffer.h"
-#include "ResourceTable.h"
-
-namespace aapt {
-
-using SymbolEntryVector = std::vector<std::pair<ResourceNameRef, uint32_t>>;
-
-struct FlatEntry;
-
-/**
- * Flattens a ResourceTable into a binary format suitable
- * for loading into a ResTable on the host or device.
- */
-struct TableFlattener {
- /**
- * A set of options for this TableFlattener.
- */
- struct Options {
- /**
- * Specifies whether to output extended chunks, like
- * source information and mising symbol entries. Default
- * is true.
- *
- * Set this to false when emitting the final table to be used
- * on device.
- */
- bool useExtendedChunks = true;
- };
-
- TableFlattener(Options options);
-
- bool flatten(BigBuffer* out, const ResourceTable& table);
-
-private:
- bool flattenValue(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols);
-
- Options mOptions;
-};
-
-} // namespace aapt
-
-#endif // AAPT_TABLE_FLATTENER_H
diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp
deleted file mode 100644
index 0b08d240cad3..000000000000
--- a/tools/aapt2/Util_test.cpp
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-#include <string>
-
-#include "StringPiece.h"
-#include "Util.h"
-
-namespace aapt {
-
-TEST(UtilTest, TrimOnlyWhitespace) {
- const std::u16string full = u"\n ";
-
- StringPiece16 trimmed = util::trimWhitespace(full);
- EXPECT_TRUE(trimmed.empty());
- EXPECT_EQ(0u, trimmed.size());
-}
-
-TEST(UtilTest, StringEndsWith) {
- EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml"));
-}
-
-TEST(UtilTest, StringStartsWith) {
- EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he"));
-}
-
-TEST(UtilTest, StringBuilderWhitespaceRemoval) {
- EXPECT_EQ(StringPiece16(u"hey guys this is so cool"),
- util::StringBuilder().append(u" hey guys ")
- .append(u" this is so cool ")
- .str());
-
- EXPECT_EQ(StringPiece16(u" wow, so many \t spaces. what?"),
- util::StringBuilder().append(u" \" wow, so many \t ")
- .append(u"spaces. \"what? ")
- .str());
-
- EXPECT_EQ(StringPiece16(u"where is the pie?"),
- util::StringBuilder().append(u" where \t ")
- .append(u" \nis the "" pie?")
- .str());
-}
-
-TEST(UtilTest, StringBuilderEscaping) {
- EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"),
- util::StringBuilder().append(u" hey guys\\n ")
- .append(u" this \\t is so\\\\ cool ")
- .str());
-
- EXPECT_EQ(StringPiece16(u"@?#\\\'"),
- util::StringBuilder().append(u"\\@\\?\\#\\\\\\'")
- .str());
-}
-
-TEST(UtilTest, StringBuilderMisplacedQuote) {
- util::StringBuilder builder{};
- EXPECT_FALSE(builder.append(u"they're coming!"));
-}
-
-TEST(UtilTest, StringBuilderUnicodeCodes) {
- EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"),
- util::StringBuilder().append(u"\\u00AF\\u0AF0 woah")
- .str());
-
- EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo"));
-}
-
-TEST(UtilTest, TokenizeInput) {
- auto tokenizer = util::tokenize(StringPiece16(u"this| is|the|end"), u'|');
- auto iter = tokenizer.begin();
- ASSERT_EQ(*iter, StringPiece16(u"this"));
- ++iter;
- ASSERT_EQ(*iter, StringPiece16(u" is"));
- ++iter;
- ASSERT_EQ(*iter, StringPiece16(u"the"));
- ++iter;
- ASSERT_EQ(*iter, StringPiece16(u"end"));
- ++iter;
- ASSERT_EQ(tokenizer.end(), iter);
-}
-
-TEST(UtilTest, IsJavaClassName) {
- EXPECT_TRUE(util::isJavaClassName(u"android.test.Class"));
- EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner"));
- EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class"));
- EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_"));
- EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner"));
- EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$"));
- EXPECT_FALSE(util::isJavaClassName(u".test.Class"));
- EXPECT_FALSE(util::isJavaClassName(u"android"));
-}
-
-TEST(UtilTest, FullyQualifiedClassName) {
- Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
- ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"android.asdf");
-
- res = util::getFullyQualifiedClassName(u"android", u".asdf");
- ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"android.asdf");
-
- res = util::getFullyQualifiedClassName(u"android", u".a.b");
- ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"android.a.b");
-
- res = util::getFullyQualifiedClassName(u"android", u"a.b");
- ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"a.b");
-
- res = util::getFullyQualifiedClassName(u"", u"a.b");
- ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"a.b");
-
- res = util::getFullyQualifiedClassName(u"", u"");
- ASSERT_FALSE(res);
-
- res = util::getFullyQualifiedClassName(u"android", u"./Apple");
- ASSERT_FALSE(res);
-}
-
-
-} // namespace aapt
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
new file mode 100644
index 000000000000..ea2aa55764c1
--- /dev/null
+++ b/tools/aapt2/ValueVisitor.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_VALUE_VISITOR_H
+#define AAPT_VALUE_VISITOR_H
+
+#include "ResourceValues.h"
+#include "ResourceTable.h"
+
+namespace aapt {
+
+/**
+ * Visits a value and invokes the appropriate method based on its type. Does not traverse
+ * into compound types. Use ValueVisitor for that.
+ */
+struct RawValueVisitor {
+ virtual ~RawValueVisitor() = default;
+
+ virtual void visitItem(Item* value) {}
+ virtual void visit(Reference* value) { visitItem(value); }
+ virtual void visit(RawString* value) { visitItem(value); }
+ virtual void visit(String* value) { visitItem(value); }
+ virtual void visit(StyledString* value) { visitItem(value); }
+ virtual void visit(FileReference* value) { visitItem(value); }
+ virtual void visit(Id* value) { visitItem(value); }
+ virtual void visit(BinaryPrimitive* value) { visitItem(value); }
+
+ virtual void visit(Attribute* value) {}
+ virtual void visit(Style* value) {}
+ virtual void visit(Array* value) {}
+ virtual void visit(Plural* value) {}
+ virtual void visit(Styleable* value) {}
+};
+
+#define DECL_VISIT_COMPOUND_VALUE(T) \
+ virtual void visit(T* value) { \
+ visitSubValues(value); \
+ }
+
+/**
+ * Visits values, and if they are compound values, visits the components as well.
+ */
+struct ValueVisitor : public RawValueVisitor {
+ // The compiler will think we're hiding an overload, when we actually intend
+ // to call into RawValueVisitor. This will expose the visit methods in the super
+ // class so the compiler knows we are trying to call them.
+ using RawValueVisitor::visit;
+
+ void visitSubValues(Attribute* attribute) {
+ for (Attribute::Symbol& symbol : attribute->symbols) {
+ visit(&symbol.symbol);
+ }
+ }
+
+ void visitSubValues(Style* style) {
+ if (style->parent) {
+ visit(&style->parent.value());
+ }
+
+ for (Style::Entry& entry : style->entries) {
+ visit(&entry.key);
+ entry.value->accept(this);
+ }
+ }
+
+ void visitSubValues(Array* array) {
+ for (std::unique_ptr<Item>& item : array->items) {
+ item->accept(this);
+ }
+ }
+
+ void visitSubValues(Plural* plural) {
+ for (std::unique_ptr<Item>& item : plural->values) {
+ if (item) {
+ item->accept(this);
+ }
+ }
+ }
+
+ void visitSubValues(Styleable* styleable) {
+ for (Reference& reference : styleable->entries) {
+ visit(&reference);
+ }
+ }
+
+ DECL_VISIT_COMPOUND_VALUE(Attribute);
+ DECL_VISIT_COMPOUND_VALUE(Style);
+ DECL_VISIT_COMPOUND_VALUE(Array);
+ DECL_VISIT_COMPOUND_VALUE(Plural);
+ DECL_VISIT_COMPOUND_VALUE(Styleable);
+};
+
+/**
+ * Do not use directly. Helper struct for dyn_cast.
+ */
+template <typename T>
+struct DynCastVisitor : public RawValueVisitor {
+ T* value = nullptr;
+
+ void visit(T* v) override {
+ value = v;
+ }
+};
+
+/**
+ * Specialization that checks if the value is an Item.
+ */
+template <>
+struct DynCastVisitor<Item> : public RawValueVisitor {
+ Item* value = nullptr;
+
+ void visitItem(Item* item) override {
+ value = item;
+ }
+};
+
+/**
+ * Returns a valid pointer to T if the Value is of subtype T.
+ * Otherwise, returns nullptr.
+ */
+template <typename T>
+T* valueCast(Value* value) {
+ if (!value) {
+ return nullptr;
+ }
+ DynCastVisitor<T> visitor;
+ value->accept(&visitor);
+ return visitor.value;
+}
+
+
+inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) {
+ for (auto& type : pkg->types) {
+ for (auto& entry : type->entries) {
+ for (auto& configValue : entry->values) {
+ configValue->value->accept(visitor);
+ }
+ }
+ }
+}
+
+inline void visitAllValuesInTable(ResourceTable* table, RawValueVisitor* visitor) {
+ for (auto& pkg : table->packages) {
+ visitAllValuesInPackage(pkg.get(), visitor);
+ }
+}
+
+} // namespace aapt
+
+#endif // AAPT_VALUE_VISITOR_H
diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp
new file mode 100644
index 000000000000..1624079727bb
--- /dev/null
+++ b/tools/aapt2/ValueVisitor_test.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <string>
+
+#include "ResourceValues.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
+#include "test/Builders.h"
+
+namespace aapt {
+
+struct SingleReferenceVisitor : public ValueVisitor {
+ using ValueVisitor::visit;
+
+ Reference* visited = nullptr;
+
+ void visit(Reference* ref) override {
+ visited = ref;
+ }
+};
+
+struct StyleVisitor : public ValueVisitor {
+ using ValueVisitor::visit;
+
+ std::list<Reference*> visitedRefs;
+ Style* visitedStyle = nullptr;
+
+ void visit(Reference* ref) override {
+ visitedRefs.push_back(ref);
+ }
+
+ void visit(Style* style) override {
+ visitedStyle = style;
+ ValueVisitor::visit(style);
+ }
+};
+
+TEST(ValueVisitorTest, VisitsReference) {
+ Reference ref(ResourceName{u"android", ResourceType::kAttr, u"foo"});
+ SingleReferenceVisitor visitor;
+ ref.accept(&visitor);
+
+ EXPECT_EQ(visitor.visited, &ref);
+}
+
+TEST(ValueVisitorTest, VisitsReferencesInStyle) {
+ std::unique_ptr<Style> style = test::StyleBuilder()
+ .setParent(u"@android:style/foo")
+ .addItem(u"@android:attr/one", test::buildReference(u"@android:id/foo"))
+ .build();
+
+ StyleVisitor visitor;
+ style->accept(&visitor);
+
+ ASSERT_EQ(style.get(), visitor.visitedStyle);
+
+ // Entry attribute references, plus the parent reference, plus one value reference.
+ ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
+}
+
+TEST(ValueVisitorTest, ValueCast) {
+ std::unique_ptr<Reference> ref = test::buildReference(u"@android:color/white");
+ EXPECT_NE(valueCast<Reference>(ref.get()), nullptr);
+
+ std::unique_ptr<Style> style = test::StyleBuilder()
+ .addItem(u"@android:attr/foo", test::buildReference(u"@android:color/black"))
+ .build();
+ EXPECT_NE(valueCast<Style>(style.get()), nullptr);
+ EXPECT_EQ(valueCast<Reference>(style.get()), nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/XliffXmlPullParser.cpp b/tools/aapt2/XliffXmlPullParser.cpp
deleted file mode 100644
index 31115f28f58f..000000000000
--- a/tools/aapt2/XliffXmlPullParser.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "XliffXmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-XliffXmlPullParser::XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
- mParser(parser) {
-}
-
-XmlPullParser::Event XliffXmlPullParser::next() {
- while (XmlPullParser::isGoodEvent(mParser->next())) {
- Event event = mParser->getEvent();
- if (event != Event::kStartElement && event != Event::kEndElement) {
- break;
- }
-
- if (mParser->getElementNamespace() !=
- u"urn:oasis:names:tc:xliff:document:1.2") {
- break;
- }
-
- const std::u16string& name = mParser->getElementName();
- if (name != u"bpt"
- && name != u"ept"
- && name != u"it"
- && name != u"ph"
- && name != u"g"
- && name != u"bx"
- && name != u"ex"
- && name != u"x") {
- break;
- }
-
- // We hit a tag that was ignored, so get the next event.
- }
- return mParser->getEvent();
-}
-
-XmlPullParser::Event XliffXmlPullParser::getEvent() const {
- return mParser->getEvent();
-}
-
-const std::string& XliffXmlPullParser::getLastError() const {
- return mParser->getLastError();
-}
-
-const std::u16string& XliffXmlPullParser::getComment() const {
- return mParser->getComment();
-}
-
-size_t XliffXmlPullParser::getLineNumber() const {
- return mParser->getLineNumber();
-}
-
-size_t XliffXmlPullParser::getDepth() const {
- return mParser->getDepth();
-}
-
-const std::u16string& XliffXmlPullParser::getText() const {
- return mParser->getText();
-}
-
-const std::u16string& XliffXmlPullParser::getNamespacePrefix() const {
- return mParser->getNamespacePrefix();
-}
-
-const std::u16string& XliffXmlPullParser::getNamespaceUri() const {
- return mParser->getNamespaceUri();
-}
-
-bool XliffXmlPullParser::applyPackageAlias(std::u16string* package,
- const std::u16string& defaultPackage) const {
- return mParser->applyPackageAlias(package, defaultPackage);
-}
-
-const std::u16string& XliffXmlPullParser::getElementNamespace() const {
- return mParser->getElementNamespace();
-}
-
-const std::u16string& XliffXmlPullParser::getElementName() const {
- return mParser->getElementName();
-}
-
-size_t XliffXmlPullParser::getAttributeCount() const {
- return mParser->getAttributeCount();
-}
-
-XmlPullParser::const_iterator XliffXmlPullParser::beginAttributes() const {
- return mParser->beginAttributes();
-}
-
-XmlPullParser::const_iterator XliffXmlPullParser::endAttributes() const {
- return mParser->endAttributes();
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/XliffXmlPullParser.h b/tools/aapt2/XliffXmlPullParser.h
deleted file mode 100644
index 77912277b31e..000000000000
--- a/tools/aapt2/XliffXmlPullParser.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_XLIFF_XML_PULL_PARSER_H
-#define AAPT_XLIFF_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <memory>
-#include <string>
-
-namespace aapt {
-
-/**
- * Strips xliff elements and provides the caller with a view of the
- * underlying XML without xliff.
- */
-class XliffXmlPullParser : public XmlPullParser {
-public:
- XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
- XliffXmlPullParser(const XliffXmlPullParser& rhs) = delete;
-
- Event getEvent() const override;
- const std::string& getLastError() const override;
- Event next() override;
-
- const std::u16string& getComment() const override;
- size_t getLineNumber() const override;
- size_t getDepth() const override;
-
- const std::u16string& getText() const override;
-
- const std::u16string& getNamespacePrefix() const override;
- const std::u16string& getNamespaceUri() const override;
- bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
- const override;
-
- const std::u16string& getElementNamespace() const override;
- const std::u16string& getElementName() const override;
-
- const_iterator beginAttributes() const override;
- const_iterator endAttributes() const override;
- size_t getAttributeCount() const override;
-
-private:
- std::shared_ptr<XmlPullParser> mParser;
-};
-
-} // namespace aapt
-
-#endif // AAPT_XLIFF_XML_PULL_PARSER_H
diff --git a/tools/aapt2/XliffXmlPullParser_test.cpp b/tools/aapt2/XliffXmlPullParser_test.cpp
deleted file mode 100644
index f9030724b80b..000000000000
--- a/tools/aapt2/XliffXmlPullParser_test.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "SourceXmlPullParser.h"
-#include "XliffXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-TEST(XliffXmlPullParserTest, IgnoreXliffTags) {
- std::stringstream input;
- input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
- << "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">" << std::endl
- << "<string name=\"foo\">"
- << "Hey <xliff:g><xliff:it>there</xliff:it></xliff:g> world</string>" << std::endl
- << "</resources>" << std::endl;
- std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
- XliffXmlPullParser parser(sourceParser);
- EXPECT_EQ(XmlPullParser::Event::kStartDocument, parser.getEvent());
-
- EXPECT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
- EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
- EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
-
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
- EXPECT_EQ(parser.getElementNamespace(), u"");
- EXPECT_EQ(parser.getElementName(), u"resources");
- EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
-
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
- EXPECT_EQ(parser.getElementNamespace(), u"");
- EXPECT_EQ(parser.getElementName(), u"string");
-
- EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
- EXPECT_EQ(parser.getText(), u"Hey ");
-
- EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
- EXPECT_EQ(parser.getText(), u"there");
-
- EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
- EXPECT_EQ(parser.getText(), u" world");
-
- EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
- EXPECT_EQ(parser.getElementNamespace(), u"");
- EXPECT_EQ(parser.getElementName(), u"string");
- EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
-
- EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
- EXPECT_EQ(parser.getElementNamespace(), u"");
- EXPECT_EQ(parser.getElementName(), u"resources");
-
- EXPECT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
- EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
- EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
-
- EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.next());
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
deleted file mode 100644
index 69318840445d..000000000000
--- a/tools/aapt2/XmlDom.h
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_XML_DOM_H
-#define AAPT_XML_DOM_H
-
-#include "Logger.h"
-#include "StringPiece.h"
-
-#include <istream>
-#include <libexpat/expat.h>
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace xml {
-
-struct Visitor;
-
-/**
- * The type of node. Can be used to downcast to the concrete XML node
- * class.
- */
-enum class NodeType {
- kNamespace,
- kElement,
- kText,
-};
-
-/**
- * Base class for all XML nodes.
- */
-struct Node {
- NodeType type;
- Node* parent;
- size_t lineNumber;
- size_t columnNumber;
- std::u16string comment;
- std::vector<std::unique_ptr<Node>> children;
-
- Node(NodeType type);
- void addChild(std::unique_ptr<Node> child);
- virtual std::unique_ptr<Node> clone() const = 0;
- virtual void accept(Visitor* visitor) = 0;
- virtual ~Node() {}
-};
-
-/**
- * Base class that implements the visitor methods for a
- * subclass of Node.
- */
-template <typename Derived>
-struct BaseNode : public Node {
- BaseNode(NodeType t);
- virtual void accept(Visitor* visitor) override;
-};
-
-/**
- * A Namespace XML node. Can only have one child.
- */
-struct Namespace : public BaseNode<Namespace> {
- std::u16string namespacePrefix;
- std::u16string namespaceUri;
-
- Namespace();
- virtual std::unique_ptr<Node> clone() const override;
-};
-
-/**
- * An XML attribute.
- */
-struct Attribute {
- std::u16string namespaceUri;
- std::u16string name;
- std::u16string value;
-};
-
-/**
- * An Element XML node.
- */
-struct Element : public BaseNode<Element> {
- std::u16string namespaceUri;
- std::u16string name;
- std::vector<Attribute> attributes;
-
- Element();
- virtual std::unique_ptr<Node> clone() const override;
- Attribute* findAttribute(const StringPiece16& ns, const StringPiece16& name);
- xml::Element* findChild(const StringPiece16& ns, const StringPiece16& name);
- xml::Element* findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
- const xml::Attribute* reqAttr);
- std::vector<xml::Element*> getChildElements();
-};
-
-/**
- * A Text (CDATA) XML node. Can not have any children.
- */
-struct Text : public BaseNode<Text> {
- std::u16string text;
-
- Text();
- virtual std::unique_ptr<Node> clone() const override;
-};
-
-/**
- * Inflates an XML DOM from a text stream, logging errors to the logger.
- * Returns the root node on success, or nullptr on failure.
- */
-std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger);
-
-/**
- * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
- * Returns the root node on success, or nullptr on failure.
- */
-std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger);
-
-/**
- * A visitor interface for the different XML Node subtypes.
- */
-struct Visitor {
- virtual void visit(Namespace* node) = 0;
- virtual void visit(Element* node) = 0;
- virtual void visit(Text* text) = 0;
-};
-
-// Implementations
-
-template <typename Derived>
-BaseNode<Derived>::BaseNode(NodeType type) : Node(type) {
-}
-
-template <typename Derived>
-void BaseNode<Derived>::accept(Visitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
-}
-
-} // namespace xml
-} // namespace aapt
-
-#endif // AAPT_XML_DOM_H
diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
deleted file mode 100644
index 56b5613d4264..000000000000
--- a/tools/aapt2/XmlFlattener.cpp
+++ /dev/null
@@ -1,574 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BigBuffer.h"
-#include "Logger.h"
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Resource.h"
-#include "ResourceParser.h"
-#include "ResourceValues.h"
-#include "SdkConstants.h"
-#include "Source.h"
-#include "StringPool.h"
-#include "Util.h"
-#include "XmlFlattener.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <limits>
-#include <map>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace xml {
-
-constexpr uint32_t kLowPriority = 0xffffffffu;
-
-// A vector that maps String refs to their final destination in the out buffer.
-using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>;
-
-struct XmlFlattener : public Visitor {
- XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
- const std::u16string& defaultPackage) :
- mOut(outBuffer), mPool(pool), mStringRefs(stringRefs),
- mDefaultPackage(defaultPackage) {
- }
-
- // No copying.
- XmlFlattener(const XmlFlattener&) = delete;
- XmlFlattener& operator=(const XmlFlattener&) = delete;
-
- void writeNamespace(Namespace* node, uint16_t type) {
- const size_t startIndex = mOut->size();
- android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
- android::ResXMLTree_namespaceExt* flatNs =
- mOut->nextBlock<android::ResXMLTree_namespaceExt>();
- mOut->align4();
-
- flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
- flatNode->lineNumber = node->lineNumber;
- flatNode->comment.index = -1;
- addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
- addString(node->namespaceUri, kLowPriority, &flatNs->uri);
- }
-
- virtual void visit(Namespace* node) override {
- // Extract the package/prefix from this namespace node.
- Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri);
- if (package) {
- mPackageAliases.emplace_back(
- node->namespacePrefix,
- package.value().empty() ? mDefaultPackage : package.value());
- }
-
- writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
- for (const auto& child : node->children) {
- child->accept(this);
- }
- writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
-
- if (package) {
- mPackageAliases.pop_back();
- }
- }
-
- virtual void visit(Text* node) override {
- if (util::trimWhitespace(node->text).empty()) {
- return;
- }
-
- const size_t startIndex = mOut->size();
- android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
- android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>();
- mOut->align4();
-
- const uint16_t type = android::RES_XML_CDATA_TYPE;
- flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
- flatNode->lineNumber = node->lineNumber;
- flatNode->comment.index = -1;
- addString(node->text, kLowPriority, &flatText->data);
- }
-
- virtual void visit(Element* node) override {
- const size_t startIndex = mOut->size();
- android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
- android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>();
-
- const uint16_t type = android::RES_XML_START_ELEMENT_TYPE;
- flatNode->header = { type, sizeof(*flatNode), 0 };
- flatNode->lineNumber = node->lineNumber;
- flatNode->comment.index = -1;
-
- addString(node->namespaceUri, kLowPriority, &flatElem->ns);
- addString(node->name, kLowPriority, &flatElem->name);
- flatElem->attributeStart = sizeof(*flatElem);
- flatElem->attributeSize = sizeof(android::ResXMLTree_attribute);
- flatElem->attributeCount = node->attributes.size();
-
- if (!writeAttributes(mOut, node, flatElem)) {
- mError = true;
- }
-
- mOut->align4();
- flatNode->header.size = (uint32_t)(mOut->size() - startIndex);
-
- for (const auto& child : node->children) {
- child->accept(this);
- }
-
- const size_t startEndIndex = mOut->size();
- android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>();
- android::ResXMLTree_endElementExt* flatEndElem =
- mOut->nextBlock<android::ResXMLTree_endElementExt>();
- mOut->align4();
-
- const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE;
- flatEndNode->header = { endType, sizeof(*flatEndNode),
- (uint32_t)(mOut->size() - startEndIndex) };
- flatEndNode->lineNumber = node->lineNumber;
- flatEndNode->comment.index = -1;
-
- addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
- addString(node->name, kLowPriority, &flatEndElem->name);
- }
-
- bool success() const {
- return !mError;
- }
-
-protected:
- void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
- if (!str.empty()) {
- mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest);
- } else {
- // The device doesn't think a string of size 0 is the same as null.
- dest->index = -1;
- }
- }
-
- void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
- mStringRefs->emplace_back(ref, dest);
- }
-
- Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) {
- const auto endIter = mPackageAliases.rend();
- for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
- if (iter->first == prefix) {
- return iter->second;
- }
- }
- return {};
- }
-
- const std::u16string& getDefaultPackage() const {
- return mDefaultPackage;
- }
-
- /**
- * Subclasses override this to deal with attributes. Attributes can be flattened as
- * raw values or as resources.
- */
- virtual bool writeAttributes(BigBuffer* out, Element* node,
- android::ResXMLTree_attrExt* flatElem) = 0;
-
-private:
- BigBuffer* mOut;
- StringPool* mPool;
- FlatStringRefList* mStringRefs;
- std::u16string mDefaultPackage;
- bool mError = false;
- std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
-};
-
-/**
- * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase.
- */
-struct CompileXmlFlattener : public XmlFlattener {
- CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
- const std::u16string& defaultPackage) :
- XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) {
- }
-
- virtual bool writeAttributes(BigBuffer* out, Element* node,
- android::ResXMLTree_attrExt* flatElem) override {
- flatElem->attributeCount = node->attributes.size();
- if (node->attributes.empty()) {
- return true;
- }
-
- android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>(
- node->attributes.size());
- for (const Attribute& attr : node->attributes) {
- addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns);
- addString(attr.name, kLowPriority, &flatAttrs->name);
- addString(attr.value, kLowPriority, &flatAttrs->rawValue);
- flatAttrs++;
- }
- return true;
- }
-};
-
-struct AttributeToFlatten {
- uint32_t resourceId = 0;
- const Attribute* xmlAttr = nullptr;
- const ::aapt::Attribute* resourceAttr = nullptr;
-};
-
-static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) {
- return a.resourceId < id;
-}
-
-/**
- * Flattens XML, encoding the attributes as resources.
- */
-struct LinkedXmlFlattener : public XmlFlattener {
- LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool,
- std::map<std::u16string, StringPool>* packagePools,
- FlatStringRefList* stringRefs,
- const std::u16string& defaultPackage,
- const std::shared_ptr<IResolver>& resolver,
- SourceLogger* logger,
- const FlattenOptions& options) :
- XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver),
- mLogger(logger), mPackagePools(packagePools), mOptions(options) {
- }
-
- virtual bool writeAttributes(BigBuffer* out, Element* node,
- android::ResXMLTree_attrExt* flatElem) override {
- bool error = false;
- std::vector<AttributeToFlatten> sortedAttributes;
- uint32_t nextAttributeId = 0x80000000u;
-
- // Sort and filter attributes by their resource ID.
- for (const Attribute& attr : node->attributes) {
- AttributeToFlatten attrToFlatten;
- attrToFlatten.xmlAttr = &attr;
-
- Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri);
- if (package) {
- // Find the Attribute object via our Resolver.
- ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name };
- if (attrName.package.empty()) {
- attrName.package = getDefaultPackage();
- }
-
- Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
- if (!result || !result.value().id.isValid() || !result.value().attr) {
- error = true;
- mLogger->error(node->lineNumber)
- << "unresolved attribute '" << attrName << "'."
- << std::endl;
- } else {
- attrToFlatten.resourceId = result.value().id.id;
- attrToFlatten.resourceAttr = result.value().attr;
-
- size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId);
- if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) {
- // We need to filter this attribute out.
- mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk);
- continue;
- }
- }
- }
-
- if (attrToFlatten.resourceId == 0) {
- // Attributes that have no resource ID (because they don't belong to a
- // package) should appear after those that do have resource IDs. Assign
- // them some integer value that will appear after.
- attrToFlatten.resourceId = nextAttributeId++;
- }
-
- // Insert the attribute into the sorted vector.
- auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
- attrToFlatten.resourceId, lessAttributeId);
- sortedAttributes.insert(iter, std::move(attrToFlatten));
- }
-
- flatElem->attributeCount = sortedAttributes.size();
- if (sortedAttributes.empty()) {
- return true;
- }
-
- android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>(
- sortedAttributes.size());
-
- // Now that we have sorted the attributes into their final encoded order, it's time
- // to actually write them out.
- uint16_t attributeIndex = 1;
- for (const AttributeToFlatten& attrToFlatten : sortedAttributes) {
- Maybe<std::u16string> package = util::extractPackageFromNamespace(
- attrToFlatten.xmlAttr->namespaceUri);
-
- // Assign the indices for specific attributes.
- if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") {
- flatElem->idIndex = attributeIndex;
- } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) {
- if (attrToFlatten.xmlAttr->name == u"class") {
- flatElem->classIndex = attributeIndex;
- } else if (attrToFlatten.xmlAttr->name == u"style") {
- flatElem->styleIndex = attributeIndex;
- }
- }
- attributeIndex++;
-
- // Add the namespaceUri and name to the list of StringRefs to encode.
- addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
- flatAttr->rawValue.index = -1;
-
- if (!attrToFlatten.resourceAttr) {
- addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name);
- } else {
- // We've already extracted the package successfully before.
- assert(package);
-
- // Attribute names are stored without packages, but we use
- // their StringPool index to lookup their resource IDs.
- // This will cause collisions, so we can't dedupe
- // attribute names from different packages. We use separate
- // pools that we later combine.
- //
- // Lookup the StringPool for this package and make the reference there.
- StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef(
- attrToFlatten.xmlAttr->name,
- StringPool::Context{ attrToFlatten.resourceId });
-
- // Add it to the list of strings to flatten.
- addString(nameRef, &flatAttr->name);
-
- if (mOptions.keepRawValues) {
- // Keep raw values (this is for static libraries).
- // TODO(with a smarter inflater for binary XML, we can do without this).
- addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue);
- }
- }
-
- error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr,
- flatAttr);
- flatAttr->typedValue.size = sizeof(flatAttr->typedValue);
- flatAttr++;
- }
- return !error;
- }
-
- Maybe<size_t> getSmallestFilteredSdk() const {
- if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) {
- return {};
- }
- return mSmallestFilteredSdk;
- }
-
-private:
- bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr,
- android::ResXMLTree_attribute* flatAttr) {
- std::unique_ptr<Item> item;
- if (!attr) {
- bool create = false;
- item = ResourceParser::tryParseReference(value, &create);
- if (!item) {
- flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
- addString(value, kLowPriority, &flatAttr->rawValue);
- addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
- &flatAttr->typedValue.data));
- return true;
- }
- } else {
- item = ResourceParser::parseItemForAttribute(value, *attr);
- if (!item) {
- if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) {
- mLogger->error(el->lineNumber)
- << "'"
- << value
- << "' is not compatible with attribute '"
- << *attr
- << "'."
- << std::endl;
- return false;
- }
-
- flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
- addString(value, kLowPriority, &flatAttr->rawValue);
- addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
- &flatAttr->typedValue.data));
- return true;
- }
- }
-
- assert(item);
-
- bool error = false;
-
- // If this is a reference, resolve the name into an ID.
- visitFunc<Reference>(*item, [&](Reference& reference) {
- // First see if we can convert the package name from a prefix to a real
- // package name.
- ResourceName realName = reference.name;
- if (!realName.package.empty()) {
- Maybe<std::u16string> package = getPackageAlias(realName.package);
- if (package) {
- realName.package = package.value();
- }
- } else {
- realName.package = getDefaultPackage();
- }
-
- Maybe<ResourceId> result = mResolver->findId(realName);
- if (!result || !result.value().isValid()) {
- std::ostream& out = mLogger->error(el->lineNumber)
- << "unresolved reference '"
- << reference.name
- << "'";
- if (realName != reference.name) {
- out << " (aka '" << realName << "')";
- }
- out << "'." << std::endl;
- error = true;
- } else {
- reference.id = result.value();
- }
- });
-
- if (error) {
- return false;
- }
-
- item->flatten(flatAttr->typedValue);
- return true;
- }
-
- std::shared_ptr<IResolver> mResolver;
- SourceLogger* mLogger;
- std::map<std::u16string, StringPool>* mPackagePools;
- FlattenOptions mOptions;
- size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max();
-};
-
-/**
- * The binary XML file expects the StringPool to appear first, but we haven't collected the
- * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings
- * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and
- * then move the data from the temporary BigBuffer into the given one. This incurs no
- * copies as the given BigBuffer simply takes ownership of the data.
- */
-static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer,
- BigBuffer&& xmlTreeBuffer) {
- // Sort the string pool so that attribute resource IDs show up first.
- pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- return a.context.priority < b.context.priority;
- });
-
- // Now we flatten the string pool references into the correct places.
- for (const auto& refEntry : *stringRefs) {
- refEntry.second->index = refEntry.first.getIndex();
- }
-
- // Write the XML header.
- const size_t beforeXmlTreeIndex = outBuffer->size();
- android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
- header->header.type = android::RES_XML_TYPE;
- header->header.headerSize = sizeof(*header);
-
- // Flatten the StringPool.
- StringPool::flattenUtf16(outBuffer, *pool);
-
- // Write the array of resource IDs, indexed by StringPool order.
- const size_t beforeResIdMapIndex = outBuffer->size();
- android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
- resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
- resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
- for (const auto& str : *pool) {
- ResourceId id { str->context.priority };
- if (id.id == kLowPriority || !id.isValid()) {
- // When we see the first non-resource ID,
- // we're done.
- break;
- }
-
- *outBuffer->nextBlock<uint32_t>() = id.id;
- }
- resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
-
- // Move the temporary BigBuffer into outBuffer.
- outBuffer->appendBuffer(std::move(xmlTreeBuffer));
- header->header.size = outBuffer->size() - beforeXmlTreeIndex;
-}
-
-bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) {
- StringPool pool;
-
- // This will hold the StringRefs and the location in which to write the index.
- // Once we sort the StringPool, we can assign the updated indices
- // to the correct data locations.
- FlatStringRefList stringRefs;
-
- // Since we don't know the size of the final StringPool, we write to this
- // temporary BigBuffer, which we will append to outBuffer later.
- BigBuffer out(1024);
-
- CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage);
- root->accept(&flattener);
-
- if (!flattener.success()) {
- return false;
- }
-
- flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
- return true;
-};
-
-Maybe<size_t> flattenAndLink(const Source& source, Node* root,
- const std::u16string& defaultPackage,
- const std::shared_ptr<IResolver>& resolver,
- const FlattenOptions& options, BigBuffer* outBuffer) {
- SourceLogger logger(source);
- StringPool pool;
-
- // Attribute names are stored without packages, but we use
- // their StringPool index to lookup their resource IDs.
- // This will cause collisions, so we can't dedupe
- // attribute names from different packages. We use separate
- // pools that we later combine.
- std::map<std::u16string, StringPool> packagePools;
-
- FlatStringRefList stringRefs;
-
- // Since we don't know the size of the final StringPool, we write to this
- // temporary BigBuffer, which we will append to outBuffer later.
- BigBuffer out(1024);
-
- LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver,
- &logger, options);
- root->accept(&flattener);
-
- if (!flattener.success()) {
- return {};
- }
-
- // Merge the package pools into the main pool.
- for (auto& packagePoolEntry : packagePools) {
- pool.merge(std::move(packagePoolEntry.second));
- }
-
- flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
-
- if (flattener.getSmallestFilteredSdk()) {
- return flattener.getSmallestFilteredSdk();
- }
- return 0;
-}
-
-} // namespace xml
-} // namespace aapt
diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h
deleted file mode 100644
index 4ece0a37869d..000000000000
--- a/tools/aapt2/XmlFlattener.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_XML_FLATTENER_H
-#define AAPT_XML_FLATTENER_H
-
-#include "BigBuffer.h"
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Source.h"
-#include "XmlDom.h"
-
-#include <string>
-
-namespace aapt {
-namespace xml {
-
-/**
- * Flattens an XML file into a binary representation parseable by
- * the Android resource system.
- */
-bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer);
-
-/**
- * Options for flattenAndLink.
- */
-struct FlattenOptions {
- /**
- * Keep attribute raw string values along with typed values.
- */
- bool keepRawValues = false;
-
- /**
- * If set, any attribute introduced in a later SDK will not be encoded.
- */
- Maybe<size_t> maxSdkAttribute;
-};
-
-/**
- * Like flatten(Node*,BigBuffer*), but references to resources are checked
- * and string values are transformed to typed data where possible.
- *
- * `defaultPackage` is used when a reference has no package or the namespace URI
- * "http://schemas.android.com/apk/res-auto" is used.
- *
- * `resolver` is used to resolve references to resources.
- */
-Maybe<size_t> flattenAndLink(const Source& source, Node* root,
- const std::u16string& defaultPackage,
- const std::shared_ptr<IResolver>& resolver,
- const FlattenOptions& options, BigBuffer* outBuffer);
-
-} // namespace xml
-} // namespace aapt
-
-#endif // AAPT_XML_FLATTENER_H
diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp
deleted file mode 100644
index 8915d2478b64..000000000000
--- a/tools/aapt2/XmlFlattener_test.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "MockResolver.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "Util.h"
-#include "XmlFlattener.h"
-
-#include <androidfw/AssetManager.h>
-#include <androidfw/ResourceTypes.h>
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-using namespace android;
-
-namespace aapt {
-namespace xml {
-
-constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-
-class XmlFlattenerTest : public ::testing::Test {
-public:
- virtual void SetUp() override {
- mResolver = std::make_shared<MockResolver>(
- std::make_shared<ResourceTable>(),
- std::map<ResourceName, ResourceId>({
- { ResourceName{ u"android", ResourceType::kAttr, u"attr" },
- ResourceId{ 0x01010000u } },
- { ResourceName{ u"android", ResourceType::kId, u"id" },
- ResourceId{ 0x01020000u } },
- { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" },
- ResourceId{ 0x01010001u } },
- { ResourceName{ u"com.lib", ResourceType::kId, u"id" },
- ResourceId{ 0x01020001u } }}));
- }
-
- ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) {
- std::stringstream input(kXmlPreamble);
- input << in << std::endl;
-
- SourceLogger logger(Source{ "test.xml" });
- std::unique_ptr<Node> root = inflate(&input, &logger);
- if (!root) {
- return ::testing::AssertionFailure();
- }
-
- BigBuffer outBuffer(1024);
- if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"),
- mResolver, {}, &outBuffer)) {
- return ::testing::AssertionFailure();
- }
-
- std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
- if (outTree->setTo(data.get(), outBuffer.size(), true) != NO_ERROR) {
- return ::testing::AssertionFailure();
- }
- return ::testing::AssertionSuccess();
- }
-
- std::shared_ptr<IResolver> mResolver;
-};
-
-TEST_F(XmlFlattenerTest, ParseSimpleView) {
- std::string input = R"EOF(
- <View xmlns:android="http://schemas.android.com/apk/res/android"
- android:attr="@id/id"
- class="str"
- style="@id/id">
- </View>
- )EOF";
- ResXMLTree tree;
- ASSERT_TRUE(testFlatten(input, &tree));
-
- while (tree.next() != ResXMLTree::START_TAG) {
- ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
- ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
- }
-
- const StringPiece16 androidNs = u"http://schemas.android.com/apk/res/android";
- const StringPiece16 attrName = u"attr";
- ssize_t idx = tree.indexOfAttribute(androidNs.data(), androidNs.size(), attrName.data(),
- attrName.size());
- ASSERT_GE(idx, 0);
- EXPECT_EQ(tree.getAttributeNameResID(idx), 0x01010000u);
- EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
-
- const StringPiece16 class16 = u"class";
- idx = tree.indexOfAttribute(nullptr, 0, class16.data(), class16.size());
- ASSERT_GE(idx, 0);
- EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
- EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_STRING);
- EXPECT_EQ(tree.getAttributeData(idx), tree.getAttributeValueStringID(idx));
-
- const StringPiece16 style16 = u"style";
- idx = tree.indexOfAttribute(nullptr, 0, style16.data(), style16.size());
- ASSERT_GE(idx, 0);
- EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
- EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
- EXPECT_EQ((uint32_t) tree.getAttributeData(idx), 0x01020000u);
- EXPECT_EQ(tree.getAttributeValueStringID(idx), -1);
-
- while (tree.next() != ResXMLTree::END_DOCUMENT) {
- ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
- }
-}
-
-TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) {
- std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n"
- " xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n"
- " ns1:attr=\"@ns2:id/id\">\n"
- "</View>";
- ResXMLTree tree;
- ASSERT_TRUE(testFlatten(input, &tree));
-
- while (tree.next() != ResXMLTree::END_DOCUMENT) {
- ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
- }
-}
-
-::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index,
- ResourceId nameId, ResourceId valueId) {
- if (index >= tree->getAttributeCount()) {
- return ::testing::AssertionFailure() << "index " << index << " is out of bounds ("
- << tree->getAttributeCount() << ")";
- }
-
- if (tree->getAttributeNameResID(index) != nameId.id) {
- return ::testing::AssertionFailure()
- << "attribute at index " << index << " has ID "
- << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) }
- << ". Expected ID " << nameId;
- }
-
- if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) {
- return ::testing::AssertionFailure() << "attribute at index " << index << " has value of "
- << "type " << std::hex
- << tree->getAttributeDataType(index) << std::dec
- << ". Expected reference (" << std::hex
- << Res_value::TYPE_REFERENCE << std::dec << ")";
- }
-
- if ((uint32_t) tree->getAttributeData(index) != valueId.id) {
- return ::testing::AssertionFailure()
- << "attribute at index " << index << " has value " << "with ID "
- << ResourceId{ (uint32_t) tree->getAttributeData(index) }
- << ". Expected ID " << valueId;
- }
- return ::testing::AssertionSuccess();
-}
-
-TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) {
- std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
- " app:attr=\"@app:id/id\">\n"
- " <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n"
- " app:attr=\"@app:id/id\"/>\n"
- "</View>";
- ResXMLTree tree;
- ASSERT_TRUE(testFlatten(input, &tree));
-
- while (tree.next() != ResXMLTree::START_TAG) {
- ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
- ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
- }
-
- ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u },
- ResourceId{ 0x01020000u }));
-
- while (tree.next() != ResXMLTree::START_TAG) {
- ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
- ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
- }
-
- ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
- ResourceId{ 0x01020001u }));
-}
-
-TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) {
- std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n"
- " android:attr=\"@id/id\"/>";
- ResXMLTree tree;
- ASSERT_TRUE(testFlatten(input, &tree));
-
- while (tree.next() != ResXMLTree::START_TAG) {
- ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
- ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
- }
-
- // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace
- // assignment.
- // However, we didn't give '@id/id' a package, so it should use the default package
- // 'android', and not be converted from 'android' to 'com.lib'.
- ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
- ResourceId{ 0x01020000u }));
-}
-
-/*
- * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
- * namespace.
- */
-TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
- std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- " package=\"android\"/>";
-
- ResXMLTree tree;
- ASSERT_TRUE(testFlatten(input, &tree));
-
- while (tree.next() != ResXMLTree::START_TAG) {
- ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
- ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
- }
-
- const StringPiece16 kPackage = u"package";
- EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
-}
-
-} // namespace xml
-} // namespace aapt
diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp
deleted file mode 100644
index 891b4e1e2d3c..000000000000
--- a/tools/aapt2/ZipEntry.cpp
+++ /dev/null
@@ -1,745 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// Access to entries in a Zip archive.
-//
-
-#define LOG_TAG "zip"
-
-#include "ZipEntry.h"
-#include <utils/Log.h>
-
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-namespace aapt {
-
-using namespace android;
-
-/*
- * Initialize a new ZipEntry structure from a FILE* positioned at a
- * CentralDirectoryEntry.
- *
- * On exit, the file pointer will be at the start of the next CDE or
- * at the EOCD.
- */
-status_t ZipEntry::initFromCDE(FILE* fp)
-{
- status_t result;
- long posn;
- bool hasDD;
-
- //ALOGV("initFromCDE ---\n");
-
- /* read the CDE */
- result = mCDE.read(fp);
- if (result != NO_ERROR) {
- ALOGD("mCDE.read failed\n");
- return result;
- }
-
- //mCDE.dump();
-
- /* using the info in the CDE, go load up the LFH */
- posn = ftell(fp);
- if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
- ALOGD("local header seek failed (%ld)\n",
- mCDE.mLocalHeaderRelOffset);
- return UNKNOWN_ERROR;
- }
-
- result = mLFH.read(fp);
- if (result != NO_ERROR) {
- ALOGD("mLFH.read failed\n");
- return result;
- }
-
- if (fseek(fp, posn, SEEK_SET) != 0)
- return UNKNOWN_ERROR;
-
- //mLFH.dump();
-
- /*
- * We *might* need to read the Data Descriptor at this point and
- * integrate it into the LFH. If this bit is set, the CRC-32,
- * compressed size, and uncompressed size will be zero. In practice
- * these seem to be rare.
- */
- hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
- if (hasDD) {
- // do something clever
- //ALOGD("+++ has data descriptor\n");
- }
-
- /*
- * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr"
- * flag is set, because the LFH is incomplete. (Not a problem, since we
- * prefer the CDE values.)
- */
- if (!hasDD && !compareHeaders()) {
- ALOGW("warning: header mismatch\n");
- // keep going?
- }
-
- /*
- * If the mVersionToExtract is greater than 20, we may have an
- * issue unpacking the record -- could be encrypted, compressed
- * with something we don't support, or use Zip64 extensions. We
- * can defer worrying about that to when we're extracting data.
- */
-
- return NO_ERROR;
-}
-
-/*
- * Initialize a new entry. Pass in the file name and an optional comment.
- *
- * Initializes the CDE and the LFH.
- */
-void ZipEntry::initNew(const char* fileName, const char* comment)
-{
- assert(fileName != NULL && *fileName != '\0'); // name required
-
- /* most fields are properly initialized by constructor */
- mCDE.mVersionMadeBy = kDefaultMadeBy;
- mCDE.mVersionToExtract = kDefaultVersion;
- mCDE.mCompressionMethod = kCompressStored;
- mCDE.mFileNameLength = strlen(fileName);
- if (comment != NULL)
- mCDE.mFileCommentLength = strlen(comment);
- mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does
-
- if (mCDE.mFileNameLength > 0) {
- mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
- strcpy((char*) mCDE.mFileName, fileName);
- }
- if (mCDE.mFileCommentLength > 0) {
- /* TODO: stop assuming null-terminated ASCII here? */
- mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
- strcpy((char*) mCDE.mFileComment, comment);
- }
-
- copyCDEtoLFH();
-}
-
-/*
- * Initialize a new entry, starting with the ZipEntry from a different
- * archive.
- *
- * Initializes the CDE and the LFH.
- */
-status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */,
- const ZipEntry* pEntry, const char* storageName)
-{
- mCDE = pEntry->mCDE;
- if (storageName && *storageName != 0) {
- mCDE.mFileNameLength = strlen(storageName);
- mCDE.mFileName = new unsigned char[mCDE.mFileNameLength + 1];
- strcpy((char*) mCDE.mFileName, storageName);
- }
-
- // Check whether we got all the memory needed.
- if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) ||
- (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) ||
- (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) {
- return NO_MEMORY;
- }
-
- /* construct the LFH from the CDE */
- copyCDEtoLFH();
-
- /*
- * The LFH "extra" field is independent of the CDE "extra", so we
- * handle it here.
- */
- assert(mLFH.mExtraField == NULL);
- mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
- if (mLFH.mExtraFieldLength > 0) {
- mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
- if (mLFH.mExtraField == NULL)
- return NO_MEMORY;
- memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
- mLFH.mExtraFieldLength+1);
- }
-
- return NO_ERROR;
-}
-
-/*
- * Insert pad bytes in the LFH by tweaking the "extra" field. This will
- * potentially confuse something that put "extra" data in here earlier,
- * but I can't find an actual problem.
- */
-status_t ZipEntry::addPadding(int padding)
-{
- if (padding <= 0)
- return INVALID_OPERATION;
-
- //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
- // padding, mLFH.mExtraFieldLength, mCDE.mFileName);
-
- if (mLFH.mExtraFieldLength > 0) {
- /* extend existing field */
- unsigned char* newExtra;
-
- newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
- if (newExtra == NULL)
- return NO_MEMORY;
- memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
- memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
-
- delete[] mLFH.mExtraField;
- mLFH.mExtraField = newExtra;
- mLFH.mExtraFieldLength += padding;
- } else {
- /* create new field */
- mLFH.mExtraField = new unsigned char[padding];
- memset(mLFH.mExtraField, 0, padding);
- mLFH.mExtraFieldLength = padding;
- }
-
- return NO_ERROR;
-}
-
-/*
- * Set the fields in the LFH equal to the corresponding fields in the CDE.
- *
- * This does not touch the LFH "extra" field.
- */
-void ZipEntry::copyCDEtoLFH(void)
-{
- mLFH.mVersionToExtract = mCDE.mVersionToExtract;
- mLFH.mGPBitFlag = mCDE.mGPBitFlag;
- mLFH.mCompressionMethod = mCDE.mCompressionMethod;
- mLFH.mLastModFileTime = mCDE.mLastModFileTime;
- mLFH.mLastModFileDate = mCDE.mLastModFileDate;
- mLFH.mCRC32 = mCDE.mCRC32;
- mLFH.mCompressedSize = mCDE.mCompressedSize;
- mLFH.mUncompressedSize = mCDE.mUncompressedSize;
- mLFH.mFileNameLength = mCDE.mFileNameLength;
- // the "extra field" is independent
-
- delete[] mLFH.mFileName;
- if (mLFH.mFileNameLength > 0) {
- mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
- strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
- } else {
- mLFH.mFileName = NULL;
- }
-}
-
-/*
- * Set some information about a file after we add it.
- */
-void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
- int compressionMethod)
-{
- mCDE.mCompressionMethod = compressionMethod;
- mCDE.mCRC32 = crc32;
- mCDE.mCompressedSize = compLen;
- mCDE.mUncompressedSize = uncompLen;
- mCDE.mCompressionMethod = compressionMethod;
- if (compressionMethod == kCompressDeflated) {
- mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used
- }
- copyCDEtoLFH();
-}
-
-/*
- * See if the data in mCDE and mLFH match up. This is mostly useful for
- * debugging these classes, but it can be used to identify damaged
- * archives.
- *
- * Returns "false" if they differ.
- */
-bool ZipEntry::compareHeaders(void) const
-{
- if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
- ALOGV("cmp: VersionToExtract\n");
- return false;
- }
- if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
- ALOGV("cmp: GPBitFlag\n");
- return false;
- }
- if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
- ALOGV("cmp: CompressionMethod\n");
- return false;
- }
- if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
- ALOGV("cmp: LastModFileTime\n");
- return false;
- }
- if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
- ALOGV("cmp: LastModFileDate\n");
- return false;
- }
- if (mCDE.mCRC32 != mLFH.mCRC32) {
- ALOGV("cmp: CRC32\n");
- return false;
- }
- if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
- ALOGV("cmp: CompressedSize\n");
- return false;
- }
- if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
- ALOGV("cmp: UncompressedSize\n");
- return false;
- }
- if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
- ALOGV("cmp: FileNameLength\n");
- return false;
- }
-#if 0 // this seems to be used for padding, not real data
- if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
- ALOGV("cmp: ExtraFieldLength\n");
- return false;
- }
-#endif
- if (mCDE.mFileName != NULL) {
- if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
- ALOGV("cmp: FileName\n");
- return false;
- }
- }
-
- return true;
-}
-
-
-/*
- * Convert the DOS date/time stamp into a UNIX time stamp.
- */
-time_t ZipEntry::getModWhen(void) const
-{
- struct tm parts;
-
- parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
- parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
- parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
- parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
- parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
- parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
- parts.tm_wday = parts.tm_yday = 0;
- parts.tm_isdst = -1; // DST info "not available"
-
- return mktime(&parts);
-}
-
-/*
- * Set the CDE/LFH timestamp from UNIX time.
- */
-void ZipEntry::setModWhen(time_t when)
-{
-#if !defined(_WIN32)
- struct tm tmResult;
-#endif
- time_t even;
- unsigned short zdate, ztime;
-
- struct tm* ptm;
-
- /* round up to an even number of seconds */
- even = (time_t)(((unsigned long)(when) + 1) & (~1));
-
- /* expand */
-#if !defined(_WIN32)
- ptm = localtime_r(&even, &tmResult);
-#else
- ptm = localtime(&even);
-#endif
-
- int year;
- year = ptm->tm_year;
- if (year < 80)
- year = 80;
-
- zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
- ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
-
- mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
- mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
-}
-
-
-/*
- * ===========================================================================
- * ZipEntry::LocalFileHeader
- * ===========================================================================
- */
-
-/*
- * Read a local file header.
- *
- * On entry, "fp" points to the signature at the start of the header.
- * On exit, "fp" points to the start of data.
- */
-status_t ZipEntry::LocalFileHeader::read(FILE* fp)
-{
- status_t result = NO_ERROR;
- unsigned char buf[kLFHLen];
-
- assert(mFileName == NULL);
- assert(mExtraField == NULL);
-
- if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
- ALOGD("whoops: didn't find expected signature\n");
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
- mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
- mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
- mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
- mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
- mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
- mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
- mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
- mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
- mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
-
- // TODO: validate sizes
-
- /* grab filename */
- if (mFileNameLength != 0) {
- mFileName = new unsigned char[mFileNameLength+1];
- if (mFileName == NULL) {
- result = NO_MEMORY;
- goto bail;
- }
- if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
- result = UNKNOWN_ERROR;
- goto bail;
- }
- mFileName[mFileNameLength] = '\0';
- }
-
- /* grab extra field */
- if (mExtraFieldLength != 0) {
- mExtraField = new unsigned char[mExtraFieldLength+1];
- if (mExtraField == NULL) {
- result = NO_MEMORY;
- goto bail;
- }
- if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
- result = UNKNOWN_ERROR;
- goto bail;
- }
- mExtraField[mExtraFieldLength] = '\0';
- }
-
-bail:
- return result;
-}
-
-/*
- * Write a local file header.
- */
-status_t ZipEntry::LocalFileHeader::write(FILE* fp)
-{
- unsigned char buf[kLFHLen];
-
- ZipEntry::putLongLE(&buf[0x00], kSignature);
- ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
- ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
- ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
- ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
- ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
- ZipEntry::putLongLE(&buf[0x0e], mCRC32);
- ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
- ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
- ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
- ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
-
- if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
- return UNKNOWN_ERROR;
-
- /* write filename */
- if (mFileNameLength != 0) {
- if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
- return UNKNOWN_ERROR;
- }
-
- /* write "extra field" */
- if (mExtraFieldLength != 0) {
- if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
- return UNKNOWN_ERROR;
- }
-
- return NO_ERROR;
-}
-
-
-/*
- * Dump the contents of a LocalFileHeader object.
- */
-void ZipEntry::LocalFileHeader::dump(void) const
-{
- ALOGD(" LocalFileHeader contents:\n");
- ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n",
- mVersionToExtract, mGPBitFlag, mCompressionMethod);
- ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
- mLastModFileTime, mLastModFileDate, mCRC32);
- ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
- mCompressedSize, mUncompressedSize);
- ALOGD(" filenameLen=%u extraLen=%u\n",
- mFileNameLength, mExtraFieldLength);
- if (mFileName != NULL)
- ALOGD(" filename: '%s'\n", mFileName);
-}
-
-
-/*
- * ===========================================================================
- * ZipEntry::CentralDirEntry
- * ===========================================================================
- */
-
-/*
- * Read the central dir entry that appears next in the file.
- *
- * On entry, "fp" should be positioned on the signature bytes for the
- * entry. On exit, "fp" will point at the signature word for the next
- * entry or for the EOCD.
- */
-status_t ZipEntry::CentralDirEntry::read(FILE* fp)
-{
- status_t result = NO_ERROR;
- unsigned char buf[kCDELen];
-
- /* no re-use */
- assert(mFileName == NULL);
- assert(mExtraField == NULL);
- assert(mFileComment == NULL);
-
- if (fread(buf, 1, kCDELen, fp) != kCDELen) {
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
- ALOGD("Whoops: didn't find expected signature\n");
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
- mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
- mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
- mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
- mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
- mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
- mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
- mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
- mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
- mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
- mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
- mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
- mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
- mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
- mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
- mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
-
- // TODO: validate sizes and offsets
-
- /* grab filename */
- if (mFileNameLength != 0) {
- mFileName = new unsigned char[mFileNameLength+1];
- if (mFileName == NULL) {
- result = NO_MEMORY;
- goto bail;
- }
- if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
- result = UNKNOWN_ERROR;
- goto bail;
- }
- mFileName[mFileNameLength] = '\0';
- }
-
- /* read "extra field" */
- if (mExtraFieldLength != 0) {
- mExtraField = new unsigned char[mExtraFieldLength+1];
- if (mExtraField == NULL) {
- result = NO_MEMORY;
- goto bail;
- }
- if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
- result = UNKNOWN_ERROR;
- goto bail;
- }
- mExtraField[mExtraFieldLength] = '\0';
- }
-
-
- /* grab comment, if any */
- if (mFileCommentLength != 0) {
- mFileComment = new unsigned char[mFileCommentLength+1];
- if (mFileComment == NULL) {
- result = NO_MEMORY;
- goto bail;
- }
- if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
- {
- result = UNKNOWN_ERROR;
- goto bail;
- }
- mFileComment[mFileCommentLength] = '\0';
- }
-
-bail:
- return result;
-}
-
-/*
- * Write a central dir entry.
- */
-status_t ZipEntry::CentralDirEntry::write(FILE* fp)
-{
- unsigned char buf[kCDELen];
-
- ZipEntry::putLongLE(&buf[0x00], kSignature);
- ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
- ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
- ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
- ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
- ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
- ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
- ZipEntry::putLongLE(&buf[0x10], mCRC32);
- ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
- ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
- ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
- ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
- ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
- ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
- ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
- ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
- ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
-
- if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
- return UNKNOWN_ERROR;
-
- /* write filename */
- if (mFileNameLength != 0) {
- if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
- return UNKNOWN_ERROR;
- }
-
- /* write "extra field" */
- if (mExtraFieldLength != 0) {
- if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
- return UNKNOWN_ERROR;
- }
-
- /* write comment */
- if (mFileCommentLength != 0) {
- if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
- return UNKNOWN_ERROR;
- }
-
- return NO_ERROR;
-}
-
-/*
- * Dump the contents of a CentralDirEntry object.
- */
-void ZipEntry::CentralDirEntry::dump(void) const
-{
- ALOGD(" CentralDirEntry contents:\n");
- ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
- mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
- ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
- mLastModFileTime, mLastModFileDate, mCRC32);
- ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
- mCompressedSize, mUncompressedSize);
- ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n",
- mFileNameLength, mExtraFieldLength, mFileCommentLength);
- ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
- mDiskNumberStart, mInternalAttrs, mExternalAttrs,
- mLocalHeaderRelOffset);
-
- if (mFileName != NULL)
- ALOGD(" filename: '%s'\n", mFileName);
- if (mFileComment != NULL)
- ALOGD(" comment: '%s'\n", mFileComment);
-}
-
-/*
- * Copy-assignment operator for CentralDirEntry.
- */
-ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) {
- if (this == &src) {
- return *this;
- }
-
- // Free up old data.
- delete[] mFileName;
- delete[] mExtraField;
- delete[] mFileComment;
-
- // Copy scalars.
- mVersionMadeBy = src.mVersionMadeBy;
- mVersionToExtract = src.mVersionToExtract;
- mGPBitFlag = src.mGPBitFlag;
- mCompressionMethod = src.mCompressionMethod;
- mLastModFileTime = src.mLastModFileTime;
- mLastModFileDate = src.mLastModFileDate;
- mCRC32 = src.mCRC32;
- mCompressedSize = src.mCompressedSize;
- mUncompressedSize = src.mUncompressedSize;
- mFileNameLength = src.mFileNameLength;
- mExtraFieldLength = src.mExtraFieldLength;
- mFileCommentLength = src.mFileCommentLength;
- mDiskNumberStart = src.mDiskNumberStart;
- mInternalAttrs = src.mInternalAttrs;
- mExternalAttrs = src.mExternalAttrs;
- mLocalHeaderRelOffset = src.mLocalHeaderRelOffset;
-
- // Copy strings, if necessary.
- if (mFileNameLength > 0) {
- mFileName = new unsigned char[mFileNameLength + 1];
- if (mFileName != NULL)
- strcpy((char*)mFileName, (char*)src.mFileName);
- } else {
- mFileName = NULL;
- }
- if (mFileCommentLength > 0) {
- mFileComment = new unsigned char[mFileCommentLength + 1];
- if (mFileComment != NULL)
- strcpy((char*)mFileComment, (char*)src.mFileComment);
- } else {
- mFileComment = NULL;
- }
- if (mExtraFieldLength > 0) {
- /* we null-terminate this, though it may not be a string */
- mExtraField = new unsigned char[mExtraFieldLength + 1];
- if (mExtraField != NULL)
- memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1);
- } else {
- mExtraField = NULL;
- }
-
- return *this;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h
deleted file mode 100644
index 2745a4386278..000000000000
--- a/tools/aapt2/ZipEntry.h
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// Zip archive entries.
-//
-// The ZipEntry class is tightly meshed with the ZipFile class.
-//
-#ifndef __LIBS_ZIPENTRY_H
-#define __LIBS_ZIPENTRY_H
-
-#include <utils/Errors.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-
-namespace aapt {
-
-using android::status_t;
-
-class ZipFile;
-
-/*
- * ZipEntry objects represent a single entry in a Zip archive.
- *
- * You can use one of these to get or set information about an entry, but
- * there are no functions here for accessing the data itself. (We could
- * tuck a pointer to the ZipFile in here for convenience, but that raises
- * the likelihood of using ZipEntry objects after discarding the ZipFile.)
- *
- * File information is stored in two places: next to the file data (the Local
- * File Header, and possibly a Data Descriptor), and at the end of the file
- * (the Central Directory Entry). The two must be kept in sync.
- */
-class ZipEntry {
-public:
- friend class ZipFile;
-
- ZipEntry(void)
- : mDeleted(false), mMarked(false)
- {}
- ~ZipEntry(void) {}
-
- /*
- * Returns "true" if the data is compressed.
- */
- bool isCompressed(void) const {
- return mCDE.mCompressionMethod != kCompressStored;
- }
- int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
-
- /*
- * Return the uncompressed length.
- */
- off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
-
- /*
- * Return the compressed length. For uncompressed data, this returns
- * the same thing as getUncompresesdLen().
- */
- off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
-
- /*
- * Return the offset of the local file header.
- */
- off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
-
- /*
- * Return the absolute file offset of the start of the compressed or
- * uncompressed data.
- */
- off_t getFileOffset(void) const {
- return mCDE.mLocalHeaderRelOffset +
- LocalFileHeader::kLFHLen +
- mLFH.mFileNameLength +
- mLFH.mExtraFieldLength;
- }
-
- /*
- * Return the data CRC.
- */
- unsigned long getCRC32(void) const { return mCDE.mCRC32; }
-
- /*
- * Return file modification time in UNIX seconds-since-epoch.
- */
- time_t getModWhen(void) const;
-
- /*
- * Return the archived file name.
- */
- const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
-
- /*
- * Application-defined "mark". Can be useful when synchronizing the
- * contents of an archive with contents on disk.
- */
- bool getMarked(void) const { return mMarked; }
- void setMarked(bool val) { mMarked = val; }
-
- /*
- * Some basic functions for raw data manipulation. "LE" means
- * Little Endian.
- */
- static inline unsigned short getShortLE(const unsigned char* buf) {
- return buf[0] | (buf[1] << 8);
- }
- static inline unsigned long getLongLE(const unsigned char* buf) {
- return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
- }
- static inline void putShortLE(unsigned char* buf, short val) {
- buf[0] = (unsigned char) val;
- buf[1] = (unsigned char) (val >> 8);
- }
- static inline void putLongLE(unsigned char* buf, long val) {
- buf[0] = (unsigned char) val;
- buf[1] = (unsigned char) (val >> 8);
- buf[2] = (unsigned char) (val >> 16);
- buf[3] = (unsigned char) (val >> 24);
- }
-
- /* defined for Zip archives */
- enum {
- kCompressStored = 0, // no compression
- // shrunk = 1,
- // reduced 1 = 2,
- // reduced 2 = 3,
- // reduced 3 = 4,
- // reduced 4 = 5,
- // imploded = 6,
- // tokenized = 7,
- kCompressDeflated = 8, // standard deflate
- // Deflate64 = 9,
- // lib imploded = 10,
- // reserved = 11,
- // bzip2 = 12,
- };
-
- /*
- * Deletion flag. If set, the entry will be removed on the next
- * call to "flush".
- */
- bool getDeleted(void) const { return mDeleted; }
-
-protected:
- /*
- * Initialize the structure from the file, which is pointing at
- * our Central Directory entry.
- */
- status_t initFromCDE(FILE* fp);
-
- /*
- * Initialize the structure for a new file. We need the filename
- * and comment so that we can properly size the LFH area. The
- * filename is mandatory, the comment is optional.
- */
- void initNew(const char* fileName, const char* comment);
-
- /*
- * Initialize the structure with the contents of a ZipEntry from
- * another file. If fileName is non-NULL, override the name with fileName.
- */
- status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry,
- const char* fileName);
-
- /*
- * Add some pad bytes to the LFH. We do this by adding or resizing
- * the "extra" field.
- */
- status_t addPadding(int padding);
-
- /*
- * Set information about the data for this entry.
- */
- void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
- int compressionMethod);
-
- /*
- * Set the modification date.
- */
- void setModWhen(time_t when);
-
- /*
- * Set the offset of the local file header, relative to the start of
- * the current file.
- */
- void setLFHOffset(off_t offset) {
- mCDE.mLocalHeaderRelOffset = (long) offset;
- }
-
- /* mark for deletion; used by ZipFile::remove() */
- void setDeleted(void) { mDeleted = true; }
-
-private:
- /* these are private and not defined */
- ZipEntry(const ZipEntry& src);
- ZipEntry& operator=(const ZipEntry& src);
-
- /* returns "true" if the CDE and the LFH agree */
- bool compareHeaders(void) const;
- void copyCDEtoLFH(void);
-
- bool mDeleted; // set if entry is pending deletion
- bool mMarked; // app-defined marker
-
- /*
- * Every entry in the Zip archive starts off with one of these.
- */
- class LocalFileHeader {
- public:
- LocalFileHeader(void) :
- mVersionToExtract(0),
- mGPBitFlag(0),
- mCompressionMethod(0),
- mLastModFileTime(0),
- mLastModFileDate(0),
- mCRC32(0),
- mCompressedSize(0),
- mUncompressedSize(0),
- mFileNameLength(0),
- mExtraFieldLength(0),
- mFileName(NULL),
- mExtraField(NULL)
- {}
- virtual ~LocalFileHeader(void) {
- delete[] mFileName;
- delete[] mExtraField;
- }
-
- status_t read(FILE* fp);
- status_t write(FILE* fp);
-
- // unsigned long mSignature;
- unsigned short mVersionToExtract;
- unsigned short mGPBitFlag;
- unsigned short mCompressionMethod;
- unsigned short mLastModFileTime;
- unsigned short mLastModFileDate;
- unsigned long mCRC32;
- unsigned long mCompressedSize;
- unsigned long mUncompressedSize;
- unsigned short mFileNameLength;
- unsigned short mExtraFieldLength;
- unsigned char* mFileName;
- unsigned char* mExtraField;
-
- enum {
- kSignature = 0x04034b50,
- kLFHLen = 30, // LocalFileHdr len, excl. var fields
- };
-
- void dump(void) const;
- };
-
- /*
- * Every entry in the Zip archive has one of these in the "central
- * directory" at the end of the file.
- */
- class CentralDirEntry {
- public:
- CentralDirEntry(void) :
- mVersionMadeBy(0),
- mVersionToExtract(0),
- mGPBitFlag(0),
- mCompressionMethod(0),
- mLastModFileTime(0),
- mLastModFileDate(0),
- mCRC32(0),
- mCompressedSize(0),
- mUncompressedSize(0),
- mFileNameLength(0),
- mExtraFieldLength(0),
- mFileCommentLength(0),
- mDiskNumberStart(0),
- mInternalAttrs(0),
- mExternalAttrs(0),
- mLocalHeaderRelOffset(0),
- mFileName(NULL),
- mExtraField(NULL),
- mFileComment(NULL)
- {}
- virtual ~CentralDirEntry(void) {
- delete[] mFileName;
- delete[] mExtraField;
- delete[] mFileComment;
- }
-
- status_t read(FILE* fp);
- status_t write(FILE* fp);
-
- CentralDirEntry& operator=(const CentralDirEntry& src);
-
- // unsigned long mSignature;
- unsigned short mVersionMadeBy;
- unsigned short mVersionToExtract;
- unsigned short mGPBitFlag;
- unsigned short mCompressionMethod;
- unsigned short mLastModFileTime;
- unsigned short mLastModFileDate;
- unsigned long mCRC32;
- unsigned long mCompressedSize;
- unsigned long mUncompressedSize;
- unsigned short mFileNameLength;
- unsigned short mExtraFieldLength;
- unsigned short mFileCommentLength;
- unsigned short mDiskNumberStart;
- unsigned short mInternalAttrs;
- unsigned long mExternalAttrs;
- unsigned long mLocalHeaderRelOffset;
- unsigned char* mFileName;
- unsigned char* mExtraField;
- unsigned char* mFileComment;
-
- void dump(void) const;
-
- enum {
- kSignature = 0x02014b50,
- kCDELen = 46, // CentralDirEnt len, excl. var fields
- };
- };
-
- enum {
- //kDataDescriptorSignature = 0x08074b50, // currently unused
- kDataDescriptorLen = 16, // four 32-bit fields
-
- kDefaultVersion = 20, // need deflate, nothing much else
- kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3
- kUsesDataDescr = 0x0008, // GPBitFlag bit 3
- };
-
- LocalFileHeader mLFH;
- CentralDirEntry mCDE;
-};
-
-}; // namespace aapt
-
-#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp
deleted file mode 100644
index 268c15efd9b9..000000000000
--- a/tools/aapt2/ZipFile.cpp
+++ /dev/null
@@ -1,1306 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// Access to Zip archives.
-//
-
-#define LOG_TAG "zip"
-
-#include <androidfw/ZipUtils.h>
-#include <utils/Log.h>
-
-#include "ZipFile.h"
-#include "Util.h"
-
-#include <zlib.h>
-#define DEF_MEM_LEVEL 8 // normally in zutil.h?
-
-#include <memory.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <assert.h>
-
-namespace aapt {
-
-using namespace android;
-
-/*
- * Some environments require the "b", some choke on it.
- */
-#define FILE_OPEN_RO "rb"
-#define FILE_OPEN_RW "r+b"
-#define FILE_OPEN_RW_CREATE "w+b"
-
-/* should live somewhere else? */
-static status_t errnoToStatus(int err)
-{
- if (err == ENOENT)
- return NAME_NOT_FOUND;
- else if (err == EACCES)
- return PERMISSION_DENIED;
- else
- return UNKNOWN_ERROR;
-}
-
-/*
- * Open a file and parse its guts.
- */
-status_t ZipFile::open(const char* zipFileName, int flags)
-{
- bool newArchive = false;
-
- assert(mZipFp == NULL); // no reopen
-
- if ((flags & kOpenTruncate))
- flags |= kOpenCreate; // trunc implies create
-
- if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
- return INVALID_OPERATION; // not both
- if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
- return INVALID_OPERATION; // not neither
- if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
- return INVALID_OPERATION; // create requires write
-
- if (flags & kOpenTruncate) {
- newArchive = true;
- } else {
- newArchive = (access(zipFileName, F_OK) != 0);
- if (!(flags & kOpenCreate) && newArchive) {
- /* not creating, must already exist */
- ALOGD("File %s does not exist", zipFileName);
- return NAME_NOT_FOUND;
- }
- }
-
- /* open the file */
- const char* openflags;
- if (flags & kOpenReadWrite) {
- if (newArchive)
- openflags = FILE_OPEN_RW_CREATE;
- else
- openflags = FILE_OPEN_RW;
- } else {
- openflags = FILE_OPEN_RO;
- }
- mZipFp = fopen(zipFileName, openflags);
- if (mZipFp == NULL) {
- int err = errno;
- ALOGD("fopen failed: %d\n", err);
- return errnoToStatus(err);
- }
-
- status_t result;
- if (!newArchive) {
- /*
- * Load the central directory. If that fails, then this probably
- * isn't a Zip archive.
- */
- result = readCentralDir();
- } else {
- /*
- * Newly-created. The EndOfCentralDir constructor actually
- * sets everything to be the way we want it (all zeroes). We
- * set mNeedCDRewrite so that we create *something* if the
- * caller doesn't add any files. (We could also just unlink
- * the file if it's brand new and nothing was added, but that's
- * probably doing more than we really should -- the user might
- * have a need for empty zip files.)
- */
- mNeedCDRewrite = true;
- result = NO_ERROR;
- }
-
- if (flags & kOpenReadOnly)
- mReadOnly = true;
- else
- assert(!mReadOnly);
-
- return result;
-}
-
-/*
- * Return the Nth entry in the archive.
- */
-ZipEntry* ZipFile::getEntryByIndex(int idx) const
-{
- if (idx < 0 || idx >= (int) mEntries.size())
- return NULL;
-
- return mEntries[idx];
-}
-
-/*
- * Find an entry by name.
- */
-ZipEntry* ZipFile::getEntryByName(const char* fileName) const
-{
- /*
- * Do a stupid linear string-compare search.
- *
- * There are various ways to speed this up, especially since it's rare
- * to intermingle changes to the archive with "get by name" calls. We
- * don't want to sort the mEntries vector itself, however, because
- * it's used to recreate the Central Directory.
- *
- * (Hash table works, parallel list of pointers in sorted order is good.)
- */
- int idx;
-
- for (idx = mEntries.size()-1; idx >= 0; idx--) {
- ZipEntry* pEntry = mEntries[idx];
- if (!pEntry->getDeleted() &&
- strcmp(fileName, pEntry->getFileName()) == 0)
- {
- return pEntry;
- }
- }
-
- return NULL;
-}
-
-/*
- * Empty the mEntries vector.
- */
-void ZipFile::discardEntries(void)
-{
- int count = mEntries.size();
-
- while (--count >= 0)
- delete mEntries[count];
-
- mEntries.clear();
-}
-
-
-/*
- * Find the central directory and read the contents.
- *
- * The fun thing about ZIP archives is that they may or may not be
- * readable from start to end. In some cases, notably for archives
- * that were written to stdout, the only length information is in the
- * central directory at the end of the file.
- *
- * Of course, the central directory can be followed by a variable-length
- * comment field, so we have to scan through it backwards. The comment
- * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
- * itself, plus apparently sometimes people throw random junk on the end
- * just for the fun of it.
- *
- * This is all a little wobbly. If the wrong value ends up in the EOCD
- * area, we're hosed. This appears to be the way that everbody handles
- * it though, so we're in pretty good company if this fails.
- */
-status_t ZipFile::readCentralDir(void)
-{
- status_t result = NO_ERROR;
- unsigned char* buf = NULL;
- off_t fileLength, seekStart;
- long readAmount;
- int i;
-
- fseek(mZipFp, 0, SEEK_END);
- fileLength = ftell(mZipFp);
- rewind(mZipFp);
-
- /* too small to be a ZIP archive? */
- if (fileLength < EndOfCentralDir::kEOCDLen) {
- ALOGD("Length is %ld -- too small\n", (long)fileLength);
- result = INVALID_OPERATION;
- goto bail;
- }
-
- buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
- if (buf == NULL) {
- ALOGD("Failure allocating %d bytes for EOCD search",
- EndOfCentralDir::kMaxEOCDSearch);
- result = NO_MEMORY;
- goto bail;
- }
-
- if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
- seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
- readAmount = EndOfCentralDir::kMaxEOCDSearch;
- } else {
- seekStart = 0;
- readAmount = (long) fileLength;
- }
- if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
- ALOGD("Failure seeking to end of zip at %ld", (long) seekStart);
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- /* read the last part of the file into the buffer */
- if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
- ALOGD("short file? wanted %ld\n", readAmount);
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- /* find the end-of-central-dir magic */
- for (i = readAmount - 4; i >= 0; i--) {
- if (buf[i] == 0x50 &&
- ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
- {
- ALOGV("+++ Found EOCD at buf+%d\n", i);
- break;
- }
- }
- if (i < 0) {
- ALOGD("EOCD not found, not Zip\n");
- result = INVALID_OPERATION;
- goto bail;
- }
-
- /* extract eocd values */
- result = mEOCD.readBuf(buf + i, readAmount - i);
- if (result != NO_ERROR) {
- ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
- goto bail;
- }
- //mEOCD.dump();
-
- if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
- mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
- {
- ALOGD("Archive spanning not supported\n");
- result = INVALID_OPERATION;
- goto bail;
- }
-
- /*
- * So far so good. "mCentralDirSize" is the size in bytes of the
- * central directory, so we can just seek back that far to find it.
- * We can also seek forward mCentralDirOffset bytes from the
- * start of the file.
- *
- * We're not guaranteed to have the rest of the central dir in the
- * buffer, nor are we guaranteed that the central dir will have any
- * sort of convenient size. We need to skip to the start of it and
- * read the header, then the other goodies.
- *
- * The only thing we really need right now is the file comment, which
- * we're hoping to preserve.
- */
- if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
- ALOGD("Failure seeking to central dir offset %ld\n",
- mEOCD.mCentralDirOffset);
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- /*
- * Loop through and read the central dir entries.
- */
- ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
- int entry;
- for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
- ZipEntry* pEntry = new ZipEntry;
-
- result = pEntry->initFromCDE(mZipFp);
- if (result != NO_ERROR) {
- ALOGD("initFromCDE failed\n");
- delete pEntry;
- goto bail;
- }
-
- mEntries.push_back(pEntry);
- }
-
-
- /*
- * If all went well, we should now be back at the EOCD.
- */
- {
- unsigned char checkBuf[4];
- if (fread(checkBuf, 1, 4, mZipFp) != 4) {
- ALOGD("EOCD check read failed\n");
- result = INVALID_OPERATION;
- goto bail;
- }
- if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
- ALOGD("EOCD read check failed\n");
- result = UNKNOWN_ERROR;
- goto bail;
- }
- ALOGV("+++ EOCD read check passed\n");
- }
-
-bail:
- delete[] buf;
- return result;
-}
-
-status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod,
- ZipEntry** ppEntry) {
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry);
-}
-
-
-/*
- * Add a new file to the archive.
- *
- * This requires creating and populating a ZipEntry structure, and copying
- * the data into the file at the appropriate position. The "appropriate
- * position" is the current location of the central directory, which we
- * casually overwrite (we can put it back later).
- *
- * If we were concerned about safety, we would want to make all changes
- * in a temp file and then overwrite the original after everything was
- * safely written. Not really a concern for us.
- */
-status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
- const char* storageName, int sourceType, int compressionMethod,
- ZipEntry** ppEntry)
-{
- ZipEntry* pEntry = NULL;
- status_t result = NO_ERROR;
- long lfhPosn, startPosn, endPosn, uncompressedLen;
- FILE* inputFp = NULL;
- unsigned long crc;
- time_t modWhen;
-
- if (mReadOnly)
- return INVALID_OPERATION;
-
- assert(compressionMethod == ZipEntry::kCompressDeflated ||
- compressionMethod == ZipEntry::kCompressStored);
-
- /* make sure we're in a reasonable state */
- assert(mZipFp != NULL);
- assert(mEntries.size() == mEOCD.mTotalNumEntries);
-
- /* make sure it doesn't already exist */
- if (getEntryByName(storageName) != NULL)
- return ALREADY_EXISTS;
-
- if (!data) {
- inputFp = fopen(fileName, FILE_OPEN_RO);
- if (inputFp == NULL)
- return errnoToStatus(errno);
- }
-
- if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- pEntry = new ZipEntry;
- pEntry->initNew(storageName, NULL);
-
- /*
- * From here on out, failures are more interesting.
- */
- mNeedCDRewrite = true;
-
- /*
- * Write the LFH, even though it's still mostly blank. We need it
- * as a place-holder. In theory the LFH isn't necessary, but in
- * practice some utilities demand it.
- */
- lfhPosn = ftell(mZipFp);
- pEntry->mLFH.write(mZipFp);
- startPosn = ftell(mZipFp);
-
- /*
- * Copy the data in, possibly compressing it as we go.
- */
- if (sourceType == ZipEntry::kCompressStored) {
- if (compressionMethod == ZipEntry::kCompressDeflated) {
- bool failed = false;
- result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
- if (result != NO_ERROR) {
- ALOGD("compression failed, storing\n");
- failed = true;
- } else {
- /*
- * Make sure it has compressed "enough". This probably ought
- * to be set through an API call, but I don't expect our
- * criteria to change over time.
- */
- long src = inputFp ? ftell(inputFp) : size;
- long dst = ftell(mZipFp) - startPosn;
- if (dst + (dst / 10) > src) {
- ALOGD("insufficient compression (src=%ld dst=%ld), storing\n",
- src, dst);
- failed = true;
- }
- }
-
- if (failed) {
- compressionMethod = ZipEntry::kCompressStored;
- if (inputFp) rewind(inputFp);
- fseek(mZipFp, startPosn, SEEK_SET);
- /* fall through to kCompressStored case */
- }
- }
- /* handle "no compression" request, or failed compression from above */
- if (compressionMethod == ZipEntry::kCompressStored) {
- if (inputFp) {
- result = copyFpToFp(mZipFp, inputFp, &crc);
- } else {
- result = copyDataToFp(mZipFp, data, size, &crc);
- }
- if (result != NO_ERROR) {
- // don't need to truncate; happens in CDE rewrite
- ALOGD("failed copying data in\n");
- goto bail;
- }
- }
-
- // currently seeked to end of file
- uncompressedLen = inputFp ? ftell(inputFp) : size;
- } else if (sourceType == ZipEntry::kCompressDeflated) {
- /* we should support uncompressed-from-compressed, but it's not
- * important right now */
- assert(compressionMethod == ZipEntry::kCompressDeflated);
-
- bool scanResult;
- int method;
- long compressedLen;
-
- scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
- &compressedLen, &crc);
- if (!scanResult || method != ZipEntry::kCompressDeflated) {
- ALOGD("this isn't a deflated gzip file?");
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
- if (result != NO_ERROR) {
- ALOGD("failed copying gzip data in\n");
- goto bail;
- }
- } else {
- assert(false);
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- /*
- * We could write the "Data Descriptor", but there doesn't seem to
- * be any point since we're going to go back and write the LFH.
- *
- * Update file offsets.
- */
- endPosn = ftell(mZipFp); // seeked to end of compressed data
-
- /*
- * Success! Fill out new values.
- */
- pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
- compressionMethod);
- modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
- pEntry->setModWhen(modWhen);
- pEntry->setLFHOffset(lfhPosn);
- mEOCD.mNumEntries++;
- mEOCD.mTotalNumEntries++;
- mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
- mEOCD.mCentralDirOffset = endPosn;
-
- /*
- * Go back and write the LFH.
- */
- if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
- result = UNKNOWN_ERROR;
- goto bail;
- }
- pEntry->mLFH.write(mZipFp);
-
- /*
- * Add pEntry to the list.
- */
- mEntries.push_back(pEntry);
- if (ppEntry != NULL)
- *ppEntry = pEntry;
- pEntry = NULL;
-
-bail:
- if (inputFp != NULL)
- fclose(inputFp);
- delete pEntry;
- return result;
-}
-
-/*
- * Add an entry by copying it from another zip file. If "padding" is
- * nonzero, the specified number of bytes will be added to the "extra"
- * field in the header.
- *
- * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
- */
-status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
- const char* storageName, int padding, ZipEntry** ppEntry)
-{
- ZipEntry* pEntry = NULL;
- status_t result;
- long lfhPosn, endPosn;
-
- if (mReadOnly)
- return INVALID_OPERATION;
-
- /* make sure we're in a reasonable state */
- assert(mZipFp != NULL);
- assert(mEntries.size() == mEOCD.mTotalNumEntries);
-
- if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- pEntry = new ZipEntry;
- if (pEntry == NULL) {
- result = NO_MEMORY;
- goto bail;
- }
-
- result = pEntry->initFromExternal(pSourceZip, pSourceEntry, storageName);
- if (result != NO_ERROR) {
- goto bail;
- }
- if (padding != 0) {
- result = pEntry->addPadding(padding);
- if (result != NO_ERROR)
- goto bail;
- }
-
- /*
- * From here on out, failures are more interesting.
- */
- mNeedCDRewrite = true;
-
- /*
- * Write the LFH. Since we're not recompressing the data, we already
- * have all of the fields filled out.
- */
- lfhPosn = ftell(mZipFp);
- pEntry->mLFH.write(mZipFp);
-
- /*
- * Copy the data over.
- *
- * If the "has data descriptor" flag is set, we want to copy the DD
- * fields as well. This is a fixed-size area immediately following
- * the data.
- */
- if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
- {
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- off_t copyLen;
- copyLen = pSourceEntry->getCompressedLen();
- if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
- copyLen += ZipEntry::kDataDescriptorLen;
-
- if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
- != NO_ERROR)
- {
- ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
- result = UNKNOWN_ERROR;
- goto bail;
- }
-
- /*
- * Update file offsets.
- */
- endPosn = ftell(mZipFp);
-
- /*
- * Success! Fill out new values.
- */
- pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset
- mEOCD.mNumEntries++;
- mEOCD.mTotalNumEntries++;
- mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
- mEOCD.mCentralDirOffset = endPosn;
-
- /*
- * Add pEntry to the list.
- */
- mEntries.push_back(pEntry);
- if (ppEntry != NULL)
- *ppEntry = pEntry;
- pEntry = NULL;
-
- result = NO_ERROR;
-
-bail:
- delete pEntry;
- return result;
-}
-
-/*
- * Copy all of the bytes in "src" to "dst".
- *
- * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
- * will be seeked immediately past the data.
- */
-status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
-{
- unsigned char tmpBuf[32768];
- size_t count;
-
- *pCRC32 = crc32(0L, Z_NULL, 0);
-
- while (1) {
- count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
- if (ferror(srcFp) || ferror(dstFp))
- return errnoToStatus(errno);
- if (count == 0)
- break;
-
- *pCRC32 = crc32(*pCRC32, tmpBuf, count);
-
- if (fwrite(tmpBuf, 1, count, dstFp) != count) {
- ALOGD("fwrite %d bytes failed\n", (int) count);
- return UNKNOWN_ERROR;
- }
- }
-
- return NO_ERROR;
-}
-
-/*
- * Copy all of the bytes in "src" to "dst".
- *
- * On exit, "dstFp" will be seeked immediately past the data.
- */
-status_t ZipFile::copyDataToFp(FILE* dstFp,
- const void* data, size_t size, unsigned long* pCRC32)
-{
- *pCRC32 = crc32(0L, Z_NULL, 0);
- if (size > 0) {
- *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
- if (fwrite(data, 1, size, dstFp) != size) {
- ALOGD("fwrite %d bytes failed\n", (int) size);
- return UNKNOWN_ERROR;
- }
- }
-
- return NO_ERROR;
-}
-
-/*
- * Copy some of the bytes in "src" to "dst".
- *
- * If "pCRC32" is NULL, the CRC will not be computed.
- *
- * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
- * will be seeked immediately past the data just written.
- */
-status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
- unsigned long* pCRC32)
-{
- unsigned char tmpBuf[32768];
- size_t count;
-
- if (pCRC32 != NULL)
- *pCRC32 = crc32(0L, Z_NULL, 0);
-
- while (length) {
- long readSize;
-
- readSize = sizeof(tmpBuf);
- if (readSize > length)
- readSize = length;
-
- count = fread(tmpBuf, 1, readSize, srcFp);
- if ((long) count != readSize) { // error or unexpected EOF
- ALOGD("fread %d bytes failed\n", (int) readSize);
- return UNKNOWN_ERROR;
- }
-
- if (pCRC32 != NULL)
- *pCRC32 = crc32(*pCRC32, tmpBuf, count);
-
- if (fwrite(tmpBuf, 1, count, dstFp) != count) {
- ALOGD("fwrite %d bytes failed\n", (int) count);
- return UNKNOWN_ERROR;
- }
-
- length -= readSize;
- }
-
- return NO_ERROR;
-}
-
-/*
- * Compress all of the data in "srcFp" and write it to "dstFp".
- *
- * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
- * will be seeked immediately past the compressed data.
- */
-status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
- const void* data, size_t size, unsigned long* pCRC32)
-{
- status_t result = NO_ERROR;
- const size_t kBufSize = 32768;
- unsigned char* inBuf = NULL;
- unsigned char* outBuf = NULL;
- z_stream zstream;
- bool atEof = false; // no feof() aviailable yet
- unsigned long crc;
- int zerr;
-
- /*
- * Create an input buffer and an output buffer.
- */
- inBuf = new unsigned char[kBufSize];
- outBuf = new unsigned char[kBufSize];
- if (inBuf == NULL || outBuf == NULL) {
- result = NO_MEMORY;
- goto bail;
- }
-
- /*
- * Initialize the zlib stream.
- */
- memset(&zstream, 0, sizeof(zstream));
- zstream.zalloc = Z_NULL;
- zstream.zfree = Z_NULL;
- zstream.opaque = Z_NULL;
- zstream.next_in = NULL;
- zstream.avail_in = 0;
- zstream.next_out = outBuf;
- zstream.avail_out = kBufSize;
- zstream.data_type = Z_UNKNOWN;
-
- zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
- Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
- if (zerr != Z_OK) {
- result = UNKNOWN_ERROR;
- if (zerr == Z_VERSION_ERROR) {
- ALOGE("Installed zlib is not compatible with linked version (%s)\n",
- ZLIB_VERSION);
- } else {
- ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr);
- }
- goto bail;
- }
-
- crc = crc32(0L, Z_NULL, 0);
-
- /*
- * Loop while we have data.
- */
- do {
- size_t getSize;
- int flush;
-
- /* only read if the input buffer is empty */
- if (zstream.avail_in == 0 && !atEof) {
- ALOGV("+++ reading %d bytes\n", (int)kBufSize);
- if (data) {
- getSize = size > kBufSize ? kBufSize : size;
- memcpy(inBuf, data, getSize);
- data = ((const char*)data) + getSize;
- size -= getSize;
- } else {
- getSize = fread(inBuf, 1, kBufSize, srcFp);
- if (ferror(srcFp)) {
- ALOGD("deflate read failed (errno=%d)\n", errno);
- goto z_bail;
- }
- }
- if (getSize < kBufSize) {
- ALOGV("+++ got %d bytes, EOF reached\n",
- (int)getSize);
- atEof = true;
- }
-
- crc = crc32(crc, inBuf, getSize);
-
- zstream.next_in = inBuf;
- zstream.avail_in = getSize;
- }
-
- if (atEof)
- flush = Z_FINISH; /* tell zlib that we're done */
- else
- flush = Z_NO_FLUSH; /* more to come! */
-
- zerr = deflate(&zstream, flush);
- if (zerr != Z_OK && zerr != Z_STREAM_END) {
- ALOGD("zlib deflate call failed (zerr=%d)\n", zerr);
- result = UNKNOWN_ERROR;
- goto z_bail;
- }
-
- /* write when we're full or when we're done */
- if (zstream.avail_out == 0 ||
- (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize))
- {
- ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf));
- if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) !=
- (size_t)(zstream.next_out - outBuf))
- {
- ALOGD("write %d failed in deflate\n",
- (int) (zstream.next_out - outBuf));
- goto z_bail;
- }
-
- zstream.next_out = outBuf;
- zstream.avail_out = kBufSize;
- }
- } while (zerr == Z_OK);
-
- assert(zerr == Z_STREAM_END); /* other errors should've been caught */
-
- *pCRC32 = crc;
-
-z_bail:
- deflateEnd(&zstream); /* free up any allocated structures */
-
-bail:
- delete[] inBuf;
- delete[] outBuf;
-
- return result;
-}
-
-/*
- * Mark an entry as deleted.
- *
- * We will eventually need to crunch the file down, but if several files
- * are being removed (perhaps as part of an "update" process) we can make
- * things considerably faster by deferring the removal to "flush" time.
- */
-status_t ZipFile::remove(ZipEntry* pEntry)
-{
- /*
- * Should verify that pEntry is actually part of this archive, and
- * not some stray ZipEntry from a different file.
- */
-
- /* mark entry as deleted, and mark archive as dirty */
- pEntry->setDeleted();
- mNeedCDRewrite = true;
- return NO_ERROR;
-}
-
-/*
- * Flush any pending writes.
- *
- * In particular, this will crunch out deleted entries, and write the
- * Central Directory and EOCD if we have stomped on them.
- */
-status_t ZipFile::flush(void)
-{
- status_t result = NO_ERROR;
- long eocdPosn;
- int i, count;
-
- if (mReadOnly)
- return INVALID_OPERATION;
- if (!mNeedCDRewrite)
- return NO_ERROR;
-
- assert(mZipFp != NULL);
-
- result = crunchArchive();
- if (result != NO_ERROR)
- return result;
-
- if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
- return UNKNOWN_ERROR;
-
- count = mEntries.size();
- for (i = 0; i < count; i++) {
- ZipEntry* pEntry = mEntries[i];
- pEntry->mCDE.write(mZipFp);
- }
-
- eocdPosn = ftell(mZipFp);
- mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
-
- mEOCD.write(mZipFp);
-
- /*
- * If we had some stuff bloat up during compression and get replaced
- * with plain files, or if we deleted some entries, there's a lot
- * of wasted space at the end of the file. Remove it now.
- */
- if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
- ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
- // not fatal
- }
-
- /* should we clear the "newly added" flag in all entries now? */
-
- mNeedCDRewrite = false;
- return NO_ERROR;
-}
-
-/*
- * Crunch deleted files out of an archive by shifting the later files down.
- *
- * Because we're not using a temp file, we do the operation inside the
- * current file.
- */
-status_t ZipFile::crunchArchive(void)
-{
- status_t result = NO_ERROR;
- int i, count;
- long delCount, adjust;
-
-#if 0
- printf("CONTENTS:\n");
- for (i = 0; i < (int) mEntries.size(); i++) {
- printf(" %d: lfhOff=%ld del=%d\n",
- i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
- }
- printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset);
-#endif
-
- /*
- * Roll through the set of files, shifting them as appropriate. We
- * could probably get a slight performance improvement by sliding
- * multiple files down at once (because we could use larger reads
- * when operating on batches of small files), but it's not that useful.
- */
- count = mEntries.size();
- delCount = adjust = 0;
- for (i = 0; i < count; i++) {
- ZipEntry* pEntry = mEntries[i];
- long span;
-
- if (pEntry->getLFHOffset() != 0) {
- long nextOffset;
-
- /* Get the length of this entry by finding the offset
- * of the next entry. Directory entries don't have
- * file offsets, so we need to find the next non-directory
- * entry.
- */
- nextOffset = 0;
- for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
- nextOffset = mEntries[ii]->getLFHOffset();
- if (nextOffset == 0)
- nextOffset = mEOCD.mCentralDirOffset;
- span = nextOffset - pEntry->getLFHOffset();
-
- assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
- } else {
- /* This is a directory entry. It doesn't have
- * any actual file contents, so there's no need to
- * move anything.
- */
- span = 0;
- }
-
- //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
- // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
-
- if (pEntry->getDeleted()) {
- adjust += span;
- delCount++;
-
- delete pEntry;
- mEntries.erase(mEntries.begin() + i);
-
- /* adjust loop control */
- count--;
- i--;
- } else if (span != 0 && adjust > 0) {
- /* shuffle this entry back */
- //printf("+++ Shuffling '%s' back %ld\n",
- // pEntry->getFileName(), adjust);
- result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
- pEntry->getLFHOffset(), span);
- if (result != NO_ERROR) {
- /* this is why you use a temp file */
- ALOGE("error during crunch - archive is toast\n");
- return result;
- }
-
- pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
- }
- }
-
- /*
- * Fix EOCD info. We have to wait until the end to do some of this
- * because we use mCentralDirOffset to determine "span" for the
- * last entry.
- */
- mEOCD.mCentralDirOffset -= adjust;
- mEOCD.mNumEntries -= delCount;
- mEOCD.mTotalNumEntries -= delCount;
- mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
-
- assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
- assert(mEOCD.mNumEntries == count);
-
- return result;
-}
-
-/*
- * Works like memmove(), but on pieces of a file.
- */
-status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
-{
- if (dst == src || n <= 0)
- return NO_ERROR;
-
- unsigned char readBuf[32768];
-
- if (dst < src) {
- /* shift stuff toward start of file; must read from start */
- while (n != 0) {
- size_t getSize = sizeof(readBuf);
- if (getSize > n)
- getSize = n;
-
- if (fseek(fp, (long) src, SEEK_SET) != 0) {
- ALOGD("filemove src seek %ld failed\n", (long) src);
- return UNKNOWN_ERROR;
- }
-
- if (fread(readBuf, 1, getSize, fp) != getSize) {
- ALOGD("filemove read %ld off=%ld failed\n",
- (long) getSize, (long) src);
- return UNKNOWN_ERROR;
- }
-
- if (fseek(fp, (long) dst, SEEK_SET) != 0) {
- ALOGD("filemove dst seek %ld failed\n", (long) dst);
- return UNKNOWN_ERROR;
- }
-
- if (fwrite(readBuf, 1, getSize, fp) != getSize) {
- ALOGD("filemove write %ld off=%ld failed\n",
- (long) getSize, (long) dst);
- return UNKNOWN_ERROR;
- }
-
- src += getSize;
- dst += getSize;
- n -= getSize;
- }
- } else {
- /* shift stuff toward end of file; must read from end */
- assert(false); // write this someday, maybe
- return UNKNOWN_ERROR;
- }
-
- return NO_ERROR;
-}
-
-
-/*
- * Get the modification time from a file descriptor.
- */
-time_t ZipFile::getModTime(int fd)
-{
- struct stat sb;
-
- if (fstat(fd, &sb) < 0) {
- ALOGD("HEY: fstat on fd %d failed\n", fd);
- return (time_t) -1;
- }
-
- return sb.st_mtime;
-}
-
-
-#if 0 /* this is a bad idea */
-/*
- * Get a copy of the Zip file descriptor.
- *
- * We don't allow this if the file was opened read-write because we tend
- * to leave the file contents in an uncertain state between calls to
- * flush(). The duplicated file descriptor should only be valid for reads.
- */
-int ZipFile::getZipFd(void) const
-{
- if (!mReadOnly)
- return INVALID_OPERATION;
- assert(mZipFp != NULL);
-
- int fd;
- fd = dup(fileno(mZipFp));
- if (fd < 0) {
- ALOGD("didn't work, errno=%d\n", errno);
- }
-
- return fd;
-}
-#endif
-
-
-#if 0
-/*
- * Expand data.
- */
-bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
-{
- return false;
-}
-#endif
-
-// free the memory when you're done
-void* ZipFile::uncompress(const ZipEntry* entry)
-{
- size_t unlen = entry->getUncompressedLen();
- size_t clen = entry->getCompressedLen();
-
- void* buf = malloc(unlen);
- if (buf == NULL) {
- return NULL;
- }
-
- fseek(mZipFp, 0, SEEK_SET);
-
- off_t offset = entry->getFileOffset();
- if (fseek(mZipFp, offset, SEEK_SET) != 0) {
- goto bail;
- }
-
- switch (entry->getCompressionMethod())
- {
- case ZipEntry::kCompressStored: {
- ssize_t amt = fread(buf, 1, unlen, mZipFp);
- if (amt != (ssize_t)unlen) {
- goto bail;
- }
-#if 0
- printf("data...\n");
- const unsigned char* p = (unsigned char*)buf;
- const unsigned char* end = p+unlen;
- for (int i=0; i<32 && p < end; i++) {
- printf("0x%08x ", (int)(offset+(i*0x10)));
- for (int j=0; j<0x10 && p < end; j++) {
- printf(" %02x", *p);
- p++;
- }
- printf("\n");
- }
-#endif
-
- }
- break;
- case ZipEntry::kCompressDeflated: {
- if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
- goto bail;
- }
- }
- break;
- default:
- goto bail;
- }
- return buf;
-
-bail:
- free(buf);
- return NULL;
-}
-
-
-/*
- * ===========================================================================
- * ZipFile::EndOfCentralDir
- * ===========================================================================
- */
-
-/*
- * Read the end-of-central-dir fields.
- *
- * "buf" should be positioned at the EOCD signature, and should contain
- * the entire EOCD area including the comment.
- */
-status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
-{
- /* don't allow re-use */
- assert(mComment == NULL);
-
- if (len < kEOCDLen) {
- /* looks like ZIP file got truncated */
- ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
- kEOCDLen, len);
- return INVALID_OPERATION;
- }
-
- /* this should probably be an assert() */
- if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
- return UNKNOWN_ERROR;
-
- mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
- mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
- mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
- mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
- mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
- mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
- mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
-
- // TODO: validate mCentralDirOffset
-
- if (mCommentLen > 0) {
- if (kEOCDLen + mCommentLen > len) {
- ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
- kEOCDLen, mCommentLen, len);
- return UNKNOWN_ERROR;
- }
- mComment = new unsigned char[mCommentLen];
- memcpy(mComment, buf + kEOCDLen, mCommentLen);
- }
-
- return NO_ERROR;
-}
-
-/*
- * Write an end-of-central-directory section.
- */
-status_t ZipFile::EndOfCentralDir::write(FILE* fp)
-{
- unsigned char buf[kEOCDLen];
-
- ZipEntry::putLongLE(&buf[0x00], kSignature);
- ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
- ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
- ZipEntry::putShortLE(&buf[0x08], mNumEntries);
- ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
- ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
- ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
- ZipEntry::putShortLE(&buf[0x14], mCommentLen);
-
- if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
- return UNKNOWN_ERROR;
- if (mCommentLen > 0) {
- assert(mComment != NULL);
- if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
- return UNKNOWN_ERROR;
- }
-
- return NO_ERROR;
-}
-
-/*
- * Dump the contents of an EndOfCentralDir object.
- */
-void ZipFile::EndOfCentralDir::dump(void) const
-{
- ALOGD(" EndOfCentralDir contents:\n");
- ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
- mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
- ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n",
- mCentralDirSize, mCentralDirOffset, mCommentLen);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h
deleted file mode 100644
index 9de92ddc0872..000000000000
--- a/tools/aapt2/ZipFile.h
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// General-purpose Zip archive access. This class allows both reading and
-// writing to Zip archives, including deletion of existing entries.
-//
-#ifndef __LIBS_ZIPFILE_H
-#define __LIBS_ZIPFILE_H
-
-#include "BigBuffer.h"
-#include "ZipEntry.h"
-
-#include <stdio.h>
-#include <utils/Errors.h>
-#include <vector>
-
-namespace aapt {
-
-using android::status_t;
-
-/*
- * Manipulate a Zip archive.
- *
- * Some changes will not be visible in the until until "flush" is called.
- *
- * The correct way to update a file archive is to make all changes to a
- * copy of the archive in a temporary file, and then unlink/rename over
- * the original after everything completes. Because we're only interested
- * in using this for packaging, we don't worry about such things. Crashing
- * after making changes and before flush() completes could leave us with
- * an unusable Zip archive.
- */
-class ZipFile {
-public:
- ZipFile(void)
- : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
- {}
- ~ZipFile(void) {
- if (!mReadOnly)
- flush();
- if (mZipFp != NULL)
- fclose(mZipFp);
- discardEntries();
- }
-
- /*
- * Open a new or existing archive.
- */
- enum {
- kOpenReadOnly = 0x01,
- kOpenReadWrite = 0x02,
- kOpenCreate = 0x04, // create if it doesn't exist
- kOpenTruncate = 0x08, // if it exists, empty it
- };
- status_t open(const char* zipFileName, int flags);
-
- /*
- * Add a file to the end of the archive. Specify whether you want the
- * library to try to store it compressed.
- *
- * If "storageName" is specified, the archive will use that instead
- * of "fileName".
- *
- * If there is already an entry with the same name, the call fails.
- * Existing entries with the same name must be removed first.
- *
- * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
- */
- status_t add(const char* fileName, int compressionMethod,
- ZipEntry** ppEntry)
- {
- return add(fileName, fileName, compressionMethod, ppEntry);
- }
- status_t add(const char* fileName, const char* storageName,
- int compressionMethod, ZipEntry** ppEntry)
- {
- return addCommon(fileName, NULL, 0, storageName,
- ZipEntry::kCompressStored,
- compressionMethod, ppEntry);
- }
-
- /*
- * Add a file that is already compressed with gzip.
- *
- * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
- */
- status_t addGzip(const char* fileName, const char* storageName,
- ZipEntry** ppEntry)
- {
- return addCommon(fileName, NULL, 0, storageName,
- ZipEntry::kCompressDeflated,
- ZipEntry::kCompressDeflated, ppEntry);
- }
-
- /*
- * Add a file from an in-memory data buffer.
- *
- * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
- */
- status_t add(const void* data, size_t size, const char* storageName,
- int compressionMethod, ZipEntry** ppEntry)
- {
- return addCommon(NULL, data, size, storageName,
- ZipEntry::kCompressStored,
- compressionMethod, ppEntry);
- }
-
- status_t add(const BigBuffer& data, const char* storageName,
- int compressionMethod, ZipEntry** ppEntry);
-
- /*
- * Add an entry by copying it from another zip file. If storageName is
- * non-NULL, the entry will be inserted with the name storageName, otherwise
- * it will have the same name as the source entry. If "padding" is
- * nonzero, the specified number of bytes will be added to the "extra"
- * field in the header.
- *
- * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
- */
- status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
- const char* storageName, int padding, ZipEntry** ppEntry);
-
- /*
- * Mark an entry as having been removed. It is not actually deleted
- * from the archive or our internal data structures until flush() is
- * called.
- */
- status_t remove(ZipEntry* pEntry);
-
- /*
- * Flush changes. If mNeedCDRewrite is set, this writes the central dir.
- */
- status_t flush(void);
-
- /*
- * Expand the data into the buffer provided. The buffer must hold
- * at least <uncompressed len> bytes. Variation expands directly
- * to a file.
- *
- * Returns "false" if an error was encountered in the compressed data.
- */
- //bool uncompress(const ZipEntry* pEntry, void* buf) const;
- //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
- void* uncompress(const ZipEntry* pEntry);
-
- /*
- * Get an entry, by name. Returns NULL if not found.
- *
- * Does not return entries pending deletion.
- */
- ZipEntry* getEntryByName(const char* fileName) const;
-
- /*
- * Get the Nth entry in the archive.
- *
- * This will return an entry that is pending deletion.
- */
- int getNumEntries(void) const { return mEntries.size(); }
- ZipEntry* getEntryByIndex(int idx) const;
-
-private:
- /* these are private and not defined */
- ZipFile(const ZipFile& src);
- ZipFile& operator=(const ZipFile& src);
-
- class EndOfCentralDir {
- public:
- EndOfCentralDir(void) :
- mDiskNumber(0),
- mDiskWithCentralDir(0),
- mNumEntries(0),
- mTotalNumEntries(0),
- mCentralDirSize(0),
- mCentralDirOffset(0),
- mCommentLen(0),
- mComment(NULL)
- {}
- virtual ~EndOfCentralDir(void) {
- delete[] mComment;
- }
-
- status_t readBuf(const unsigned char* buf, int len);
- status_t write(FILE* fp);
-
- //unsigned long mSignature;
- unsigned short mDiskNumber;
- unsigned short mDiskWithCentralDir;
- unsigned short mNumEntries;
- unsigned short mTotalNumEntries;
- unsigned long mCentralDirSize;
- unsigned long mCentralDirOffset; // offset from first disk
- unsigned short mCommentLen;
- unsigned char* mComment;
-
- enum {
- kSignature = 0x06054b50,
- kEOCDLen = 22, // EndOfCentralDir len, excl. comment
-
- kMaxCommentLen = 65535, // longest possible in ushort
- kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
-
- };
-
- void dump(void) const;
- };
-
-
- /* read all entries in the central dir */
- status_t readCentralDir(void);
-
- /* crunch deleted entries out */
- status_t crunchArchive(void);
-
- /* clean up mEntries */
- void discardEntries(void);
-
- /* common handler for all "add" functions */
- status_t addCommon(const char* fileName, const void* data, size_t size,
- const char* storageName, int sourceType, int compressionMethod,
- ZipEntry** ppEntry);
-
- /* copy all of "srcFp" into "dstFp" */
- status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
- /* copy all of "data" into "dstFp" */
- status_t copyDataToFp(FILE* dstFp,
- const void* data, size_t size, unsigned long* pCRC32);
- /* copy some of "srcFp" into "dstFp" */
- status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
- unsigned long* pCRC32);
- /* like memmove(), but on parts of a single file */
- status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
- /* compress all of "srcFp" into "dstFp", using Deflate */
- status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
- const void* data, size_t size, unsigned long* pCRC32);
-
- /* get modification date from a file descriptor */
- time_t getModTime(int fd);
-
- /*
- * We use stdio FILE*, which gives us buffering but makes dealing
- * with files >2GB awkward. Until we support Zip64, we're fine.
- */
- FILE* mZipFp; // Zip file pointer
-
- /* one of these per file */
- EndOfCentralDir mEOCD;
-
- /* did we open this read-only? */
- bool mReadOnly;
-
- /* set this when we trash the central dir */
- bool mNeedCDRewrite;
-
- /*
- * One ZipEntry per entry in the zip file. I'm using pointers instead
- * of objects because it's easier than making operator= work for the
- * classes and sub-classes.
- */
- std::vector<ZipEntry*> mEntries;
-};
-
-}; // namespace aapt
-
-#endif // __LIBS_ZIPFILE_H
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
new file mode 100644
index 000000000000..2452a1d29410
--- /dev/null
+++ b/tools/aapt2/compile/Compile.cpp
@@ -0,0 +1,578 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "Diagnostics.h"
+#include "Flags.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "compile/IdAssigner.h"
+#include "compile/Png.h"
+#include "compile/PseudolocaleGenerator.h"
+#include "compile/XmlIdCollector.h"
+#include "flatten/Archive.h"
+#include "flatten/XmlFlattener.h"
+#include "proto/ProtoSerialize.h"
+#include "util/Files.h"
+#include "util/Maybe.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlPullParser.h"
+
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <google/protobuf/io/coded_stream.h>
+
+#include <dirent.h>
+#include <fstream>
+#include <string>
+
+namespace aapt {
+
+struct ResourcePathData {
+ Source source;
+ std::u16string resourceDir;
+ std::u16string name;
+ std::string extension;
+
+ // Original config str. We keep this because when we parse the config, we may add on
+ // version qualifiers. We want to preserve the original input so the output is easily
+ // computed before hand.
+ std::string configStr;
+ ConfigDescription config;
+};
+
+/**
+ * Resource file paths are expected to look like:
+ * [--/res/]type[-config]/name
+ */
+static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
+ std::string* outError) {
+ std::vector<std::string> parts = util::split(path, file::sDirSep);
+ if (parts.size() < 2) {
+ if (outError) *outError = "bad resource path";
+ return {};
+ }
+
+ std::string& dir = parts[parts.size() - 2];
+ StringPiece dirStr = dir;
+
+ StringPiece configStr;
+ ConfigDescription config;
+ size_t dashPos = dir.find('-');
+ if (dashPos != std::string::npos) {
+ configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
+ if (!ConfigDescription::parse(configStr, &config)) {
+ if (outError) {
+ std::stringstream errStr;
+ errStr << "invalid configuration '" << configStr << "'";
+ *outError = errStr.str();
+ }
+ return {};
+ }
+ dirStr = dirStr.substr(0, dashPos);
+ }
+
+ std::string& filename = parts[parts.size() - 1];
+ StringPiece name = filename;
+ StringPiece extension;
+ size_t dotPos = filename.find('.');
+ if (dotPos != std::string::npos) {
+ extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
+ name = name.substr(0, dotPos);
+ }
+
+ return ResourcePathData{
+ Source(path),
+ util::utf8ToUtf16(dirStr),
+ util::utf8ToUtf16(name),
+ extension.toString(),
+ configStr.toString(),
+ config
+ };
+}
+
+struct CompileOptions {
+ std::string outputPath;
+ Maybe<std::string> resDir;
+ bool pseudolocalize = false;
+ bool legacyMode = false;
+ bool verbose = false;
+};
+
+static std::string buildIntermediateFilename(const ResourcePathData& data) {
+ std::stringstream name;
+ name << data.resourceDir;
+ if (!data.configStr.empty()) {
+ name << "-" << data.configStr;
+ }
+ name << "_" << data.name;
+ if (!data.extension.empty()) {
+ name << "." << data.extension;
+ }
+ name << ".flat";
+ return name.str();
+}
+
+static bool isHidden(const StringPiece& filename) {
+ return util::stringStartsWith<char>(filename, ".");
+}
+
+/**
+ * Walks the res directory structure, looking for resource files.
+ */
+static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
+ std::vector<ResourcePathData>* outPathData) {
+ const std::string& rootDir = options.resDir.value();
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
+ if (!d) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* entry = readdir(d.get())) {
+ if (isHidden(entry->d_name)) {
+ continue;
+ }
+
+ std::string prefixPath = rootDir;
+ file::appendPath(&prefixPath, entry->d_name);
+
+ if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
+ continue;
+ }
+
+ std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
+ if (!subDir) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* leafEntry = readdir(subDir.get())) {
+ if (isHidden(leafEntry->d_name)) {
+ continue;
+ }
+
+ std::string fullPath = prefixPath;
+ file::appendPath(&fullPath, leafEntry->d_name);
+
+ std::string errStr;
+ Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
+ if (!pathData) {
+ context->getDiagnostics()->error(DiagMessage() << errStr);
+ return false;
+ }
+
+ outPathData->push_back(std::move(pathData.value()));
+ }
+ }
+ return true;
+}
+
+static bool compileTable(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
+ ResourceTable table;
+ {
+ std::ifstream fin(pathData.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+ return false;
+ }
+
+
+ // Parse the values file from XML.
+ xml::XmlPullParser xmlParser(fin);
+
+ ResourceParserOptions parserOptions;
+ parserOptions.errorOnPositionalArguments = !options.legacyMode;
+
+ // If the filename includes donottranslate, then the default translatable is false.
+ parserOptions.translatable = pathData.name.find(u"donottranslate") == std::string::npos;
+
+ ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
+ pathData.config, parserOptions);
+ if (!resParser.parse(&xmlParser)) {
+ return false;
+ }
+
+ fin.close();
+ }
+
+ if (options.pseudolocalize) {
+ // Generate pseudo-localized strings (en-XA and ar-XB).
+ // These are created as weak symbols, and are only generated from default configuration
+ // strings and plurals.
+ PseudolocaleGenerator pseudolocaleGenerator;
+ if (!pseudolocaleGenerator.consume(context, &table)) {
+ return false;
+ }
+ }
+
+ // Ensure we have the compilation package at least.
+ table.createPackage(context->getCompilationPackage());
+
+ // Assign an ID to any package that has resources.
+ for (auto& pkg : table.packages) {
+ if (!pkg->id) {
+ // If no package ID was set while parsing (public identifiers), auto assign an ID.
+ pkg->id = context->getPackageId();
+ }
+ }
+
+ // Create the file/zip entry.
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
+ return false;
+ }
+
+ std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
+
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
+ {
+ google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+
+ if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
+ }
+ }
+
+ if (!writer->finishEntry()) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry");
+ return false;
+ }
+ return true;
+}
+
+static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file,
+ const BigBuffer& buffer, IArchiveWriter* writer,
+ IDiagnostics* diag) {
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ diag->error(DiagMessage(outputPath) << "failed to open file");
+ return false;
+ }
+
+ // Create the header.
+ std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file);
+
+ {
+ // The stream must be destroyed before we finish the entry, or else
+ // some data won't be flushed.
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
+ // interface.
+ google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+ CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get());
+ for (const BigBuffer::Block& block : buffer) {
+ if (!outputStream.Write(block.buffer.get(), block.size)) {
+ diag->error(DiagMessage(outputPath) << "failed to write data");
+ return false;
+ }
+ }
+ }
+
+ if (!writer->finishEntry()) {
+ diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+ return false;
+ }
+ return true;
+}
+
+static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file,
+ const android::FileMap& map, IArchiveWriter* writer,
+ IDiagnostics* diag) {
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ diag->error(DiagMessage(outputPath) << "failed to open file");
+ return false;
+ }
+
+ // Create the header.
+ std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file);
+
+ {
+ // The stream must be destroyed before we finish the entry, or else
+ // some data won't be flushed.
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
+ // interface.
+ google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+ CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get());
+ if (!outputStream.Write(map.getDataPtr(), map.getDataLength())) {
+ diag->error(DiagMessage(outputPath) << "failed to write data");
+ return false;
+ }
+ }
+
+ if (!writer->finishEntry()) {
+ diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+ return false;
+ }
+ return true;
+}
+
+static bool compileXml(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
+
+ std::unique_ptr<xml::XmlResource> xmlRes;
+ {
+ std::ifstream fin(pathData.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+ return false;
+ }
+
+ xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
+
+ fin.close();
+ }
+
+ if (!xmlRes) {
+ return false;
+ }
+
+ // Collect IDs that are defined here.
+ XmlIdCollector collector;
+ if (!collector.consume(context, xmlRes.get())) {
+ return false;
+ }
+
+ xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+ xmlRes->file.config = pathData.config;
+ xmlRes->file.source = pathData.source;
+
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions xmlFlattenerOptions;
+ xmlFlattenerOptions.keepRawValues = true;
+ XmlFlattener flattener(&buffer, xmlFlattenerOptions);
+ if (!flattener.consume(context, xmlRes.get())) {
+ return false;
+ }
+
+ if (!writeHeaderAndBufferToWriter(outputPath, xmlRes->file, buffer, writer,
+ context->getDiagnostics())) {
+ return false;
+ }
+ return true;
+}
+
+static bool compilePng(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
+ BigBuffer buffer(4096);
+ ResourceFile resFile;
+ resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+ resFile.config = pathData.config;
+ resFile.source = pathData.source;
+
+ {
+ std::ifstream fin(pathData.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+ return false;
+ }
+
+ Png png(context->getDiagnostics());
+ if (!png.process(pathData.source, &fin, &buffer, {})) {
+ return false;
+ }
+ }
+
+ if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
+ context->getDiagnostics())) {
+ return false;
+ }
+ return true;
+}
+
+static bool compileFile(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
+ BigBuffer buffer(256);
+ ResourceFile resFile;
+ resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+ resFile.config = pathData.config;
+ resFile.source = pathData.source;
+
+ std::string errorStr;
+ Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
+ if (!f) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
+ return false;
+ }
+
+ if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
+ context->getDiagnostics())) {
+ return false;
+ }
+ return true;
+}
+
+class CompileContext : public IAaptContext {
+public:
+ void setVerbose(bool val) {
+ mVerbose = val;
+ }
+
+ bool verbose() override {
+ return mVerbose;
+ }
+
+ IDiagnostics* getDiagnostics() override {
+ return &mDiagnostics;
+ }
+
+ NameMangler* getNameMangler() override {
+ abort();
+ return nullptr;
+ }
+
+ const std::u16string& getCompilationPackage() override {
+ static std::u16string empty;
+ return empty;
+ }
+
+ uint8_t getPackageId() override {
+ return 0x0;
+ }
+
+ SymbolTable* getExternalSymbols() override {
+ abort();
+ return nullptr;
+ }
+
+private:
+ StdErrDiagnostics mDiagnostics;
+ bool mVerbose = false;
+
+};
+
+/**
+ * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
+ */
+int compile(const std::vector<StringPiece>& args) {
+ CompileContext context;
+ CompileOptions options;
+
+ bool verbose = false;
+ Flags flags = Flags()
+ .requiredFlag("-o", "Output path", &options.outputPath)
+ .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
+ .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
+ "(en-XA and ar-XB)", &options.pseudolocalize)
+ .optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
+ &options.legacyMode)
+ .optionalSwitch("-v", "Enables verbose logging", &verbose);
+ if (!flags.parse("aapt2 compile", args, &std::cerr)) {
+ return 1;
+ }
+
+ context.setVerbose(verbose);
+
+ std::unique_ptr<IArchiveWriter> archiveWriter;
+
+ std::vector<ResourcePathData> inputData;
+ if (options.resDir) {
+ if (!flags.getArgs().empty()) {
+ // Can't have both files and a resource directory.
+ context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
+ flags.usage("aapt2 compile", &std::cerr);
+ return 1;
+ }
+
+ if (!loadInputFilesFromDir(&context, options, &inputData)) {
+ return 1;
+ }
+
+ archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
+
+ } else {
+ inputData.reserve(flags.getArgs().size());
+
+ // Collect data from the path for each input file.
+ for (const std::string& arg : flags.getArgs()) {
+ std::string errorStr;
+ if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
+ inputData.push_back(std::move(pathData.value()));
+ } else {
+ context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+ return 1;
+ }
+ }
+
+ archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
+ }
+
+ if (!archiveWriter) {
+ return false;
+ }
+
+ bool error = false;
+ for (ResourcePathData& pathData : inputData) {
+ if (options.verbose) {
+ context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
+ }
+
+ if (pathData.resourceDir == u"values") {
+ // Overwrite the extension.
+ pathData.extension = "arsc";
+
+ const std::string outputFilename = buildIntermediateFilename(pathData);
+ if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
+ error = true;
+ }
+
+ } else {
+ const std::string outputFilename = buildIntermediateFilename(pathData);
+ if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
+ if (*type != ResourceType::kRaw) {
+ if (pathData.extension == "xml") {
+ if (!compileXml(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
+ } else if (pathData.extension == "png" || pathData.extension == "9.png") {
+ if (!compilePng(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
+ } else {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
+ }
+ } else {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
+ }
+ } else {
+ context.getDiagnostics()->error(
+ DiagMessage() << "invalid file path '" << pathData.source << "'");
+ error = true;
+ }
+ }
+ }
+
+ if (error) {
+ return 1;
+ }
+ return 0;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
new file mode 100644
index 000000000000..aa4a5803b8df
--- /dev/null
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+
+#include "compile/IdAssigner.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Util.h"
+
+#include <bitset>
+#include <cassert>
+#include <set>
+
+namespace aapt {
+
+bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
+ std::bitset<256> usedTypeIds;
+ std::set<uint16_t> usedEntryIds;
+
+ for (auto& package : table->packages) {
+ assert(package->id && "packages must have manually assigned IDs");
+
+ usedTypeIds.reset();
+
+ // Type ID 0 is invalid, reserve it.
+ usedTypeIds.set(0);
+
+ // Collect used type IDs.
+ for (auto& type : package->types) {
+ if (type->id) {
+ usedEntryIds.clear();
+
+ if (usedTypeIds[type->id.value()]) {
+ // This ID is already taken!
+ context->getDiagnostics()->error(DiagMessage()
+ << "type '" << type->type << "' in "
+ << "package '" << package->name << "' has "
+ << "duplicate ID "
+ << std::hex << (int) type->id.value()
+ << std::dec);
+ return false;
+ }
+
+ // Mark the type ID as taken.
+ usedTypeIds.set(type->id.value());
+ }
+
+ // Collect used entry IDs.
+ for (auto& entry : type->entries) {
+ if (entry->id) {
+ // Mark entry ID as taken.
+ if (!usedEntryIds.insert(entry->id.value()).second) {
+ // This ID existed before!
+ ResourceNameRef nameRef(package->name, type->type, entry->name);
+ context->getDiagnostics()->error(DiagMessage()
+ << "resource '" << nameRef << "' "
+ << "has duplicate entry ID "
+ << std::hex << (int) entry->id.value()
+ << std::dec);
+ return false;
+ }
+ }
+ }
+
+ // Assign unused entry IDs.
+ const auto endUsedEntryIter = usedEntryIds.end();
+ auto nextUsedEntryIter = usedEntryIds.begin();
+ uint16_t nextId = 0;
+ for (auto& entry : type->entries) {
+ if (!entry->id) {
+ // Assign the next available entryID.
+ while (nextUsedEntryIter != endUsedEntryIter &&
+ nextId == *nextUsedEntryIter) {
+ nextId++;
+ ++nextUsedEntryIter;
+ }
+ entry->id = nextId++;
+ }
+ }
+ }
+
+ // Assign unused type IDs.
+ size_t nextTypeId = 0;
+ for (auto& type : package->types) {
+ if (!type->id) {
+ while (nextTypeId < usedTypeIds.size() && usedTypeIds[nextTypeId]) {
+ nextTypeId++;
+ }
+ type->id = static_cast<uint8_t>(nextTypeId);
+ nextTypeId++;
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h
new file mode 100644
index 000000000000..514df3ad3861
--- /dev/null
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_COMPILE_IDASSIGNER_H
+#define AAPT_COMPILE_IDASSIGNER_H
+
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+/**
+ * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps
+ * in between fixed ID assignments.
+ */
+struct IdAssigner : public IResourceTableConsumer {
+ bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_COMPILE_IDASSIGNER_H */
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
new file mode 100644
index 000000000000..e25a17ab125e
--- /dev/null
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compile/IdAssigner.h"
+
+#include "test/Context.h"
+#include "test/Builders.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+::testing::AssertionResult verifyIds(ResourceTable* table);
+
+TEST(IdAssignerTest, AssignIds) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple(u"@android:attr/foo")
+ .addSimple(u"@android:attr/bar")
+ .addSimple(u"@android:id/foo")
+ .setPackageId(u"android", 0x01)
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ IdAssigner assigner;
+
+ ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+ ASSERT_TRUE(verifyIds(table.get()));
+}
+
+TEST(IdAssignerTest, AssignIdsWithReservedIds) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple(u"@android:attr/foo", ResourceId(0x01040006))
+ .addSimple(u"@android:attr/bar")
+ .addSimple(u"@android:id/foo")
+ .addSimple(u"@app:id/biz")
+ .setPackageId(u"android", 0x01)
+ .setPackageId(u"app", 0x7f)
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ IdAssigner assigner;
+
+ ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+ ASSERT_TRUE(verifyIds(table.get()));
+}
+
+TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple(u"@android:attr/foo", ResourceId(0x01040006))
+ .addSimple(u"@android:attr/bar", ResourceId(0x01040006))
+ .setPackageId(u"android", 0x01)
+ .setPackageId(u"app", 0x7f)
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ IdAssigner assigner;
+
+ ASSERT_FALSE(assigner.consume(context.get(), table.get()));
+}
+
+::testing::AssertionResult verifyIds(ResourceTable* table) {
+ std::set<uint8_t> packageIds;
+ for (auto& package : table->packages) {
+ if (!package->id) {
+ return ::testing::AssertionFailure() << "package " << package->name << " has no ID";
+ }
+
+ if (!packageIds.insert(package->id.value()).second) {
+ return ::testing::AssertionFailure() << "package " << package->name
+ << " has non-unique ID " << std::hex << (int) package->id.value() << std::dec;
+ }
+ }
+
+ for (auto& package : table->packages) {
+ std::set<uint8_t> typeIds;
+ for (auto& type : package->types) {
+ if (!type->id) {
+ return ::testing::AssertionFailure() << "type " << type->type << " of package "
+ << package->name << " has no ID";
+ }
+
+ if (!typeIds.insert(type->id.value()).second) {
+ return ::testing::AssertionFailure() << "type " << type->type
+ << " of package " << package->name << " has non-unique ID "
+ << std::hex << (int) type->id.value() << std::dec;
+ }
+ }
+
+
+ for (auto& type : package->types) {
+ std::set<uint16_t> entryIds;
+ for (auto& entry : type->entries) {
+ if (!entry->id) {
+ return ::testing::AssertionFailure() << "entry " << entry->name << " of type "
+ << type->type << " of package " << package->name << " has no ID";
+ }
+
+ if (!entryIds.insert(entry->id.value()).second) {
+ return ::testing::AssertionFailure() << "entry " << entry->name
+ << " of type " << type->type << " of package " << package->name
+ << " has non-unique ID "
+ << std::hex << (int) entry->id.value() << std::dec;
+ }
+ }
+ }
+ }
+ return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/compile/Png.cpp
index 4e9b68e8c95f..bbf7f411d07e 100644
--- a/tools/aapt2/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-#include "BigBuffer.h"
-#include "Logger.h"
+#include "util/BigBuffer.h"
#include "Png.h"
#include "Source.h"
-#include "Util.h"
+#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
#include <iostream>
@@ -95,15 +94,14 @@ static void flushDataToStream(png_structp /*writePtr*/) {
}
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
- SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr));
- logger->warn() << warningMessage << "." << std::endl;
+ IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
+ diag->warn(DiagMessage() << warningMessage);
}
-static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo,
- std::string* outError) {
+static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
if (setjmp(png_jmpbuf(readPtr))) {
- *outError = "failed reading png";
+ diag->error(DiagMessage() << "failed reading png");
return false;
}
@@ -229,7 +227,7 @@ static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void*
#define MAX(a,b) ((a)>(b)?(a):(b))
#define ABS(a) ((a)<0?-(a):(a))
-static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance,
+static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
png_colorp rgbPalette, png_bytep alphaPalette,
int *paletteEntries, bool *hasTransparency, int *colorType,
png_bytepp outRows) {
@@ -363,9 +361,9 @@ static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int gr
*colorType = PNG_COLOR_TYPE_PALETTE;
} else {
if (maxGrayDeviation <= grayscaleTolerance) {
- logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation
- << ")."
- << std::endl;
+ diag->note(DiagMessage()
+ << "forcing image to gray (max deviation = "
+ << maxGrayDeviation << ")");
*colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
} else {
*colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
@@ -411,10 +409,10 @@ static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int gr
}
}
-static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
- int grayScaleTolerance, SourceLogger* logger, std::string* outError) {
+static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
+ int grayScaleTolerance) {
if (setjmp(png_jmpbuf(writePtr))) {
- *outError = "failed to write png";
+ diag->error(DiagMessage() << "failed to write png");
return false;
}
@@ -442,9 +440,9 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
if (kDebug) {
- logger->note() << "writing image: w = " << info->width
- << ", h = " << info->height
- << std::endl;
+ diag->note(DiagMessage()
+ << "writing image: w = " << info->width
+ << ", h = " << info->height);
}
png_color rgbPalette[256];
@@ -452,7 +450,7 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
bool hasTransparency;
int paletteEntries;
- analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+ analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
&paletteEntries, &hasTransparency, &colorType, outRows);
// If the image is a 9-patch, we need to preserve it as a ARGB file to make
@@ -465,22 +463,22 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
if (kDebug) {
switch (colorType) {
case PNG_COLOR_TYPE_PALETTE:
- logger->note() << "has " << paletteEntries
- << " colors" << (hasTransparency ? " (with alpha)" : "")
- << ", using PNG_COLOR_TYPE_PALLETTE."
- << std::endl;
+ diag->note(DiagMessage()
+ << "has " << paletteEntries
+ << " colors" << (hasTransparency ? " (with alpha)" : "")
+ << ", using PNG_COLOR_TYPE_PALLETTE");
break;
case PNG_COLOR_TYPE_GRAY:
- logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl;
+ diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
- logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl;
+ diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
break;
case PNG_COLOR_TYPE_RGB:
- logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl;
+ diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
- logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl;
+ diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
break;
}
}
@@ -511,7 +509,7 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
// base 9 patch data
if (kDebug) {
- logger->note() << "adding 9-patch info..." << std::endl;
+ diag->note(DiagMessage() << "adding 9-patch info..");
}
strcpy((char*)unknowns[pIndex].name, "npTc");
unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
@@ -587,10 +585,10 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
&compressionType, nullptr);
if (kDebug) {
- logger->note() << "image written: w = " << width << ", h = " << height
- << ", d = " << bitDepth << ", colors = " << colorType
- << ", inter = " << interlaceType << ", comp = " << compressionType
- << std::endl;
+ diag->note(DiagMessage()
+ << "image written: w = " << width << ", h = " << height
+ << ", d = " << bitDepth << ", colors = " << colorType
+ << ", inter = " << interlaceType << ", comp = " << compressionType);
}
return true;
}
@@ -1177,7 +1175,7 @@ getout:
if (errorMsg) {
std::stringstream err;
err << "9-patch malformed: " << errorMsg;
- if (!errorEdge) {
+ if (errorEdge) {
err << "." << std::endl;
if (errorPixel >= 0) {
err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
@@ -1192,23 +1190,22 @@ getout:
}
-bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer,
- const Options& options, std::string* outError) {
+bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+ const PngOptions& options) {
png_byte signature[kPngSignatureSize];
// Read the PNG signature first.
- if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
- *outError = strerror(errno);
+ if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+ mDiag->error(DiagMessage() << strerror(errno));
return false;
}
// If the PNG signature doesn't match, bail early.
if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- *outError = "not a valid png file";
+ mDiag->error(DiagMessage() << "not a valid png file");
return false;
}
- SourceLogger logger(source);
bool result = false;
png_structp readPtr = nullptr;
png_infop infoPtr = nullptr;
@@ -1218,40 +1215,42 @@ bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffe
readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
if (!readPtr) {
- *outError = "failed to allocate read ptr";
+ mDiag->error(DiagMessage() << "failed to allocate read ptr");
goto bail;
}
infoPtr = png_create_info_struct(readPtr);
if (!infoPtr) {
- *outError = "failed to allocate info ptr";
+ mDiag->error(DiagMessage() << "failed to allocate info ptr");
goto bail;
}
- png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning);
+ png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
// Set the read function to read from std::istream.
- png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream);
+ png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream);
- if (!readPng(readPtr, infoPtr, &pngInfo, outError)) {
+ if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
goto bail;
}
if (util::stringEndsWith<char>(source.path, ".9.png")) {
- if (!do9Patch(&pngInfo, outError)) {
+ std::string errorMsg;
+ if (!do9Patch(&pngInfo, &errorMsg)) {
+ mDiag->error(DiagMessage() << errorMsg);
goto bail;
}
}
writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
if (!writePtr) {
- *outError = "failed to allocate write ptr";
+ mDiag->error(DiagMessage() << "failed to allocate write ptr");
goto bail;
}
writeInfoPtr = png_create_info_struct(writePtr);
if (!writeInfoPtr) {
- *outError = "failed to allocate write info ptr";
+ mDiag->error(DiagMessage() << "failed to allocate write info ptr");
goto bail;
}
@@ -1260,8 +1259,7 @@ bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffe
// Set the write function to write to std::ostream.
png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
- if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
- outError)) {
+ if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) {
goto bail;
}
diff --git a/tools/aapt2/Png.h b/tools/aapt2/compile/Png.h
index 4577ab89d2d9..345ff6c56870 100644
--- a/tools/aapt2/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -17,7 +17,8 @@
#ifndef AAPT_PNG_H
#define AAPT_PNG_H
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
+#include "Diagnostics.h"
#include "Source.h"
#include <iostream>
@@ -25,13 +26,20 @@
namespace aapt {
-struct Png {
- struct Options {
- int grayScaleTolerance = 0;
- };
+struct PngOptions {
+ int grayScaleTolerance = 0;
+};
+
+class Png {
+public:
+ Png(IDiagnostics* diag) : mDiag(diag) {
+ }
+
+ bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+ const PngOptions& options);
- bool process(const Source& source, std::istream& input, BigBuffer* outBuffer,
- const Options& options, std::string* outError);
+private:
+ IDiagnostics* mDiag;
};
} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
new file mode 100644
index 000000000000..99c20778c816
--- /dev/null
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "compile/PseudolocaleGenerator.h"
+#include "compile/Pseudolocalizer.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
+ Pseudolocalizer::Method method,
+ StringPool* pool) {
+ Pseudolocalizer localizer(method);
+
+ const StringPiece16 originalText = *string->value->str;
+
+ StyleString localized;
+
+ // Copy the spans. We will update their offsets when we localize.
+ localized.spans.reserve(string->value->spans.size());
+ for (const StringPool::Span& span : string->value->spans) {
+ localized.spans.push_back(Span{ *span.name, span.firstChar, span.lastChar });
+ }
+
+ // The ranges are all represented with a single value. This is the start of one range and
+ // end of another.
+ struct Range {
+ size_t start;
+
+ // Once the new string is localized, these are the pointers to the spans to adjust.
+ // Since this struct represents the start of one range and end of another, we have
+ // the two pointers respectively.
+ uint32_t* updateStart;
+ uint32_t* updateEnd;
+ };
+
+ auto cmp = [](const Range& r, size_t index) -> bool {
+ return r.start < index;
+ };
+
+ // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
+ // The ranges are the spaces in between. In this example, with a total string length of 9,
+ // the vector represents: (0,1], (2,4], (5,6], (7,9]
+ //
+ std::vector<Range> ranges;
+ ranges.push_back(Range{ 0 });
+ ranges.push_back(Range{ originalText.size() - 1 });
+ for (size_t i = 0; i < string->value->spans.size(); i++) {
+ const StringPool::Span& span = string->value->spans[i];
+
+ // Insert or update the Range marker for the start of this span.
+ auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp);
+ if (iter != ranges.end() && iter->start == span.firstChar) {
+ iter->updateStart = &localized.spans[i].firstChar;
+ } else {
+ ranges.insert(iter,
+ Range{ span.firstChar, &localized.spans[i].firstChar, nullptr });
+ }
+
+ // Insert or update the Range marker for the end of this span.
+ iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp);
+ if (iter != ranges.end() && iter->start == span.lastChar) {
+ iter->updateEnd = &localized.spans[i].lastChar;
+ } else {
+ ranges.insert(iter,
+ Range{ span.lastChar, nullptr, &localized.spans[i].lastChar });
+ }
+ }
+
+ localized.str += localizer.start();
+
+ // Iterate over the ranges and localize each section.
+ for (size_t i = 0; i < ranges.size(); i++) {
+ const size_t start = ranges[i].start;
+ size_t len = originalText.size() - start;
+ if (i + 1 < ranges.size()) {
+ len = ranges[i + 1].start - start;
+ }
+
+ if (ranges[i].updateStart) {
+ *ranges[i].updateStart = localized.str.size();
+ }
+
+ if (ranges[i].updateEnd) {
+ *ranges[i].updateEnd = localized.str.size();
+ }
+
+ localized.str += localizer.text(originalText.substr(start, len));
+ }
+
+ localized.str += localizer.end();
+
+ std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>(
+ pool->makeRef(localized));
+ localizedString->setSource(string->getSource());
+ return localizedString;
+}
+
+namespace {
+
+struct Visitor : public RawValueVisitor {
+ StringPool* mPool;
+ Pseudolocalizer::Method mMethod;
+ Pseudolocalizer mLocalizer;
+
+ // Either value or item will be populated upon visiting the value.
+ std::unique_ptr<Value> mValue;
+ std::unique_ptr<Item> mItem;
+
+ Visitor(StringPool* pool, Pseudolocalizer::Method method) :
+ mPool(pool), mMethod(method), mLocalizer(method) {
+ }
+
+ void visit(Array* array) override {
+ std::unique_ptr<Array> localized = util::make_unique<Array>();
+ localized->items.resize(array->items.size());
+ for (size_t i = 0; i < array->items.size(); i++) {
+ Visitor subVisitor(mPool, mMethod);
+ array->items[i]->accept(&subVisitor);
+ if (subVisitor.mItem) {
+ localized->items[i] = std::move(subVisitor.mItem);
+ } else {
+ localized->items[i] = std::unique_ptr<Item>(array->items[i]->clone(mPool));
+ }
+ }
+ localized->setSource(array->getSource());
+ localized->setWeak(true);
+ mValue = std::move(localized);
+ }
+
+ void visit(Plural* plural) override {
+ std::unique_ptr<Plural> localized = util::make_unique<Plural>();
+ for (size_t i = 0; i < plural->values.size(); i++) {
+ Visitor subVisitor(mPool, mMethod);
+ if (plural->values[i]) {
+ plural->values[i]->accept(&subVisitor);
+ if (subVisitor.mValue) {
+ localized->values[i] = std::move(subVisitor.mItem);
+ } else {
+ localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool));
+ }
+ }
+ }
+ localized->setSource(plural->getSource());
+ localized->setWeak(true);
+ mValue = std::move(localized);
+ }
+
+ void visit(String* string) override {
+ if (!string->isTranslateable()) {
+ return;
+ }
+
+ std::u16string result = mLocalizer.start() + mLocalizer.text(*string->value) +
+ mLocalizer.end();
+ std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result));
+ localized->setSource(string->getSource());
+ localized->setWeak(true);
+ mItem = std::move(localized);
+ }
+
+ void visit(StyledString* string) override {
+ if (!string->isTranslateable()) {
+ return;
+ }
+
+ mItem = pseudolocalizeStyledString(string, mMethod, mPool);
+ mItem->setWeak(true);
+ }
+};
+
+ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
+ Pseudolocalizer::Method m) {
+ ConfigDescription modified = base;
+ switch (m) {
+ case Pseudolocalizer::Method::kAccent:
+ modified.language[0] = 'e';
+ modified.language[1] = 'n';
+ modified.country[0] = 'X';
+ modified.country[1] = 'A';
+ break;
+
+ case Pseudolocalizer::Method::kBidi:
+ modified.language[0] = 'a';
+ modified.language[1] = 'r';
+ modified.country[0] = 'X';
+ modified.country[1] = 'B';
+ break;
+ default:
+ break;
+ }
+ return modified;
+}
+
+void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
+ ResourceConfigValue* originalValue,
+ StringPool* pool,
+ ResourceEntry* entry) {
+ Visitor visitor(pool, method);
+ originalValue->value->accept(&visitor);
+
+ std::unique_ptr<Value> localizedValue;
+ if (visitor.mValue) {
+ localizedValue = std::move(visitor.mValue);
+ } else if (visitor.mItem) {
+ localizedValue = std::move(visitor.mItem);
+ }
+
+ if (!localizedValue) {
+ return;
+ }
+
+ ConfigDescription configWithAccent = modifyConfigForPseudoLocale(
+ originalValue->config, method);
+
+ ResourceConfigValue* newConfigValue = entry->findOrCreateValue(
+ configWithAccent, originalValue->product);
+ if (!newConfigValue->value) {
+ // Only use auto-generated pseudo-localization if none is defined.
+ newConfigValue->value = std::move(localizedValue);
+ }
+}
+
+} // namespace
+
+bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) {
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ std::vector<ResourceConfigValue*> values = entry->findAllValues(
+ ConfigDescription::defaultConfig());
+ for (ResourceConfigValue* value : values) {
+ pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
+ &table->stringPool, entry.get());
+ pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
+ &table->stringPool, entry.get());
+ }
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h
new file mode 100644
index 000000000000..4fbc51607595
--- /dev/null
+++ b/tools/aapt2/compile/PseudolocaleGenerator.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H
+#define AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H
+
+#include "StringPool.h"
+#include "compile/Pseudolocalizer.h"
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
+ Pseudolocalizer::Method method,
+ StringPool* pool);
+
+struct PseudolocaleGenerator : public IResourceTableConsumer {
+ bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H */
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
new file mode 100644
index 000000000000..4cb6ea2db565
--- /dev/null
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compile/PseudolocaleGenerator.h"
+#include "test/Builders.h"
+#include "test/Common.h"
+#include "test/Context.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) {
+ StringPool pool;
+ StyleString originalStyle;
+ originalStyle.str = u"Hello world!";
+ originalStyle.spans = { Span{ u"b", 2, 3 }, Span{ u"b", 6, 7 }, Span{ u"i", 1, 10 } };
+
+ std::unique_ptr<StyledString> newString = pseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
+ Pseudolocalizer::Method::kNone, &pool);
+
+ EXPECT_EQ(originalStyle.str, *newString->value->str);
+ ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
+
+ EXPECT_EQ(2u, newString->value->spans[0].firstChar);
+ EXPECT_EQ(3u, newString->value->spans[0].lastChar);
+ EXPECT_EQ(std::u16string(u"b"), *newString->value->spans[0].name);
+
+ EXPECT_EQ(6u, newString->value->spans[1].firstChar);
+ EXPECT_EQ(7u, newString->value->spans[1].lastChar);
+ EXPECT_EQ(std::u16string(u"b"), *newString->value->spans[1].name);
+
+ EXPECT_EQ(1u, newString->value->spans[2].firstChar);
+ EXPECT_EQ(10u, newString->value->spans[2].lastChar);
+ EXPECT_EQ(std::u16string(u"i"), *newString->value->spans[2].name);
+
+ originalStyle.spans.push_back(Span{ u"em", 0, 11u });
+
+ newString = pseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
+ Pseudolocalizer::Method::kAccent, &pool);
+
+ EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str);
+ ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
+
+ EXPECT_EQ(3u, newString->value->spans[0].firstChar);
+ EXPECT_EQ(4u, newString->value->spans[0].lastChar);
+
+ EXPECT_EQ(7u, newString->value->spans[1].firstChar);
+ EXPECT_EQ(8u, newString->value->spans[1].lastChar);
+
+ EXPECT_EQ(2u, newString->value->spans[2].firstChar);
+ EXPECT_EQ(11u, newString->value->spans[2].lastChar);
+
+ EXPECT_EQ(1u, newString->value->spans[3].firstChar);
+ EXPECT_EQ(12u, newString->value->spans[3].lastChar);
+}
+
+TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addString(u"@android:string/one", u"one")
+ .addString(u"@android:string/two", ResourceId{}, test::parseConfigOrDie("en"), u"two")
+ .addString(u"@android:string/three", u"three")
+ .addString(u"@android:string/three", ResourceId{}, test::parseConfigOrDie("en-rXA"),
+ u"three")
+ .addString(u"@android:string/four", u"four")
+ .build();
+
+ String* val = test::getValue<String>(table.get(), u"@android:string/four");
+ val->setTranslateable(false);
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ PseudolocaleGenerator generator;
+ ASSERT_TRUE(generator.consume(context.get(), table.get()));
+
+ // Normal pseudolocalization should take place.
+ ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/one",
+ test::parseConfigOrDie("en-rXA")));
+ ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/one",
+ test::parseConfigOrDie("ar-rXB")));
+
+ // No default config for android:string/two, so no pseudlocales should exist.
+ ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/two",
+ test::parseConfigOrDie("en-rXA")));
+ ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/two",
+ test::parseConfigOrDie("ar-rXB")));
+
+
+ // Check that we didn't override manual pseudolocalization.
+ val = test::getValueForConfig<String>(table.get(), u"@android:string/three",
+ test::parseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, val);
+ EXPECT_EQ(std::u16string(u"three"), *val->value);
+
+ ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/three",
+ test::parseConfigOrDie("ar-rXB")));
+
+ // Check that four's translateable marker was honored.
+ ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/four",
+ test::parseConfigOrDie("en-rXA")));
+ ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/four",
+ test::parseConfigOrDie("ar-rXB")));
+
+}
+
+} // namespace aapt
+
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
new file mode 100644
index 000000000000..eae52d778744
--- /dev/null
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compile/Pseudolocalizer.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+// String basis to generate expansion
+static const std::u16string k_expansion_string = u"one two three "
+ "four five six seven eight nine ten eleven twelve thirteen "
+ "fourteen fiveteen sixteen seventeen nineteen twenty";
+
+// Special unicode characters to override directionality of the words
+static const std::u16string k_rlm = u"\u200f";
+static const std::u16string k_rlo = u"\u202e";
+static const std::u16string k_pdf = u"\u202c";
+
+// Placeholder marks
+static const std::u16string k_placeholder_open = u"\u00bb";
+static const std::u16string k_placeholder_close = u"\u00ab";
+
+static const char16_t k_arg_start = u'{';
+static const char16_t k_arg_end = u'}';
+
+class PseudoMethodNone : public PseudoMethodImpl {
+public:
+ std::u16string text(const StringPiece16& text) override { return text.toString(); }
+ std::u16string placeholder(const StringPiece16& text) override { return text.toString(); }
+};
+
+class PseudoMethodBidi : public PseudoMethodImpl {
+public:
+ std::u16string text(const StringPiece16& text) override;
+ std::u16string placeholder(const StringPiece16& text) override;
+};
+
+class PseudoMethodAccent : public PseudoMethodImpl {
+public:
+ PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {}
+ std::u16string start() override;
+ std::u16string end() override;
+ std::u16string text(const StringPiece16& text) override;
+ std::u16string placeholder(const StringPiece16& text) override;
+private:
+ size_t mDepth;
+ size_t mWordCount;
+ size_t mLength;
+};
+
+Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) {
+ setMethod(method);
+}
+
+void Pseudolocalizer::setMethod(Method method) {
+ switch (method) {
+ case Method::kNone:
+ mImpl = util::make_unique<PseudoMethodNone>();
+ break;
+ case Method::kAccent:
+ mImpl = util::make_unique<PseudoMethodAccent>();
+ break;
+ case Method::kBidi:
+ mImpl = util::make_unique<PseudoMethodBidi>();
+ break;
+ }
+}
+
+std::u16string Pseudolocalizer::text(const StringPiece16& text) {
+ std::u16string out;
+ size_t depth = mLastDepth;
+ size_t lastpos, pos;
+ const size_t length = text.size();
+ const char16_t* str = text.data();
+ bool escaped = false;
+ for (lastpos = pos = 0; pos < length; pos++) {
+ char16_t c = str[pos];
+ if (escaped) {
+ escaped = false;
+ continue;
+ }
+ if (c == '\'') {
+ escaped = true;
+ continue;
+ }
+
+ if (c == k_arg_start) {
+ depth++;
+ } else if (c == k_arg_end && depth) {
+ depth--;
+ }
+
+ if (mLastDepth != depth || pos == length - 1) {
+ bool pseudo = ((mLastDepth % 2) == 0);
+ size_t nextpos = pos;
+ if (!pseudo || depth == mLastDepth) {
+ nextpos++;
+ }
+ size_t size = nextpos - lastpos;
+ if (size) {
+ std::u16string chunk = text.substr(lastpos, size).toString();
+ if (pseudo) {
+ chunk = mImpl->text(chunk);
+ } else if (str[lastpos] == k_arg_start && str[nextpos - 1] == k_arg_end) {
+ chunk = mImpl->placeholder(chunk);
+ }
+ out.append(chunk);
+ }
+ if (pseudo && depth < mLastDepth) { // End of message
+ out.append(mImpl->end());
+ } else if (!pseudo && depth > mLastDepth) { // Start of message
+ out.append(mImpl->start());
+ }
+ lastpos = nextpos;
+ mLastDepth = depth;
+ }
+ }
+ return out;
+}
+
+static const char16_t* pseudolocalizeChar(const char16_t c) {
+ switch (c) {
+ case 'a': return u"\u00e5";
+ case 'b': return u"\u0253";
+ case 'c': return u"\u00e7";
+ case 'd': return u"\u00f0";
+ case 'e': return u"\u00e9";
+ case 'f': return u"\u0192";
+ case 'g': return u"\u011d";
+ case 'h': return u"\u0125";
+ case 'i': return u"\u00ee";
+ case 'j': return u"\u0135";
+ case 'k': return u"\u0137";
+ case 'l': return u"\u013c";
+ case 'm': return u"\u1e3f";
+ case 'n': return u"\u00f1";
+ case 'o': return u"\u00f6";
+ case 'p': return u"\u00fe";
+ case 'q': return u"\u0051";
+ case 'r': return u"\u0155";
+ case 's': return u"\u0161";
+ case 't': return u"\u0163";
+ case 'u': return u"\u00fb";
+ case 'v': return u"\u0056";
+ case 'w': return u"\u0175";
+ case 'x': return u"\u0445";
+ case 'y': return u"\u00fd";
+ case 'z': return u"\u017e";
+ case 'A': return u"\u00c5";
+ case 'B': return u"\u03b2";
+ case 'C': return u"\u00c7";
+ case 'D': return u"\u00d0";
+ case 'E': return u"\u00c9";
+ case 'G': return u"\u011c";
+ case 'H': return u"\u0124";
+ case 'I': return u"\u00ce";
+ case 'J': return u"\u0134";
+ case 'K': return u"\u0136";
+ case 'L': return u"\u013b";
+ case 'M': return u"\u1e3e";
+ case 'N': return u"\u00d1";
+ case 'O': return u"\u00d6";
+ case 'P': return u"\u00de";
+ case 'Q': return u"\u0071";
+ case 'R': return u"\u0154";
+ case 'S': return u"\u0160";
+ case 'T': return u"\u0162";
+ case 'U': return u"\u00db";
+ case 'V': return u"\u03bd";
+ case 'W': return u"\u0174";
+ case 'X': return u"\u00d7";
+ case 'Y': return u"\u00dd";
+ case 'Z': return u"\u017d";
+ case '!': return u"\u00a1";
+ case '?': return u"\u00bf";
+ case '$': return u"\u20ac";
+ default: return NULL;
+ }
+}
+
+static bool isPossibleNormalPlaceholderEnd(const char16_t c) {
+ switch (c) {
+ case 's': return true;
+ case 'S': return true;
+ case 'c': return true;
+ case 'C': return true;
+ case 'd': return true;
+ case 'o': return true;
+ case 'x': return true;
+ case 'X': return true;
+ case 'f': return true;
+ case 'e': return true;
+ case 'E': return true;
+ case 'g': return true;
+ case 'G': return true;
+ case 'a': return true;
+ case 'A': return true;
+ case 'b': return true;
+ case 'B': return true;
+ case 'h': return true;
+ case 'H': return true;
+ case '%': return true;
+ case 'n': return true;
+ default: return false;
+ }
+}
+
+static std::u16string pseudoGenerateExpansion(const unsigned int length) {
+ std::u16string result = k_expansion_string;
+ const char16_t* s = result.data();
+ if (result.size() < length) {
+ result += u" ";
+ result += pseudoGenerateExpansion(length - result.size());
+ } else {
+ int ext = 0;
+ // Should contain only whole words, so looking for a space
+ for (unsigned int i = length + 1; i < result.size(); ++i) {
+ ++ext;
+ if (s[i] == ' ') {
+ break;
+ }
+ }
+ result = result.substr(0, length + ext);
+ }
+ return result;
+}
+
+std::u16string PseudoMethodAccent::start() {
+ std::u16string result;
+ if (mDepth == 0) {
+ result = u"[";
+ }
+ mWordCount = mLength = 0;
+ mDepth++;
+ return result;
+}
+
+std::u16string PseudoMethodAccent::end() {
+ std::u16string result;
+ if (mLength) {
+ result += u" ";
+ result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2);
+ }
+ mWordCount = mLength = 0;
+ mDepth--;
+ if (mDepth == 0) {
+ result += u"]";
+ }
+ return result;
+}
+
+/**
+ * Converts characters so they look like they've been localized.
+ *
+ * Note: This leaves placeholder syntax untouched.
+ */
+std::u16string PseudoMethodAccent::text(const StringPiece16& source)
+{
+ const char16_t* s = source.data();
+ std::u16string result;
+ const size_t I = source.size();
+ bool lastspace = true;
+ for (size_t i = 0; i < I; i++) {
+ char16_t c = s[i];
+ if (c == '%') {
+ // Placeholder syntax, no need to pseudolocalize
+ std::u16string chunk;
+ bool end = false;
+ chunk.append(&c, 1);
+ while (!end && i < I) {
+ ++i;
+ c = s[i];
+ chunk.append(&c, 1);
+ if (isPossibleNormalPlaceholderEnd(c)) {
+ end = true;
+ } else if (c == 't') {
+ ++i;
+ c = s[i];
+ chunk.append(&c, 1);
+ end = true;
+ }
+ }
+ // Treat chunk as a placeholder unless it ends with %.
+ result += ((c == '%') ? chunk : placeholder(chunk));
+ } else if (c == '<' || c == '&') {
+ // html syntax, no need to pseudolocalize
+ bool tag_closed = false;
+ while (!tag_closed && i < I) {
+ if (c == '&') {
+ std::u16string escapeText;
+ escapeText.append(&c, 1);
+ bool end = false;
+ size_t htmlCodePos = i;
+ while (!end && htmlCodePos < I) {
+ ++htmlCodePos;
+ c = s[htmlCodePos];
+ escapeText.append(&c, 1);
+ // Valid html code
+ if (c == ';') {
+ end = true;
+ i = htmlCodePos;
+ }
+ // Wrong html code
+ else if (!((c == '#' ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9')))) {
+ end = true;
+ }
+ }
+ result += escapeText;
+ if (escapeText != u"&lt;") {
+ tag_closed = true;
+ }
+ continue;
+ }
+ if (c == '>') {
+ tag_closed = true;
+ result.append(&c, 1);
+ continue;
+ }
+ result.append(&c, 1);
+ i++;
+ c = s[i];
+ }
+ } else {
+ // This is a pure text that should be pseudolocalized
+ const char16_t* p = pseudolocalizeChar(c);
+ if (p != nullptr) {
+ result += p;
+ } else {
+ bool space = util::isspace16(c);
+ if (lastspace && !space) {
+ mWordCount++;
+ }
+ lastspace = space;
+ result.append(&c, 1);
+ }
+ // Count only pseudolocalizable chars and delimiters
+ mLength++;
+ }
+ }
+ return result;
+}
+
+std::u16string PseudoMethodAccent::placeholder(const StringPiece16& source) {
+ // Surround a placeholder with brackets
+ return k_placeholder_open + source.toString() + k_placeholder_close;
+}
+
+std::u16string PseudoMethodBidi::text(const StringPiece16& source) {
+ const char16_t* s = source.data();
+ std::u16string result;
+ bool lastspace = true;
+ bool space = true;
+ for (size_t i = 0; i < source.size(); i++) {
+ char16_t c = s[i];
+ space = util::isspace16(c);
+ if (lastspace && !space) {
+ // Word start
+ result += k_rlm + k_rlo;
+ } else if (!lastspace && space) {
+ // Word end
+ result += k_pdf + k_rlm;
+ }
+ lastspace = space;
+ result.append(&c, 1);
+ }
+ if (!lastspace) {
+ // End of last word
+ result += k_pdf + k_rlm;
+ }
+ return result;
+}
+
+std::u16string PseudoMethodBidi::placeholder(const StringPiece16& source) {
+ // Surround a placeholder with directionality change sequence
+ return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
new file mode 100644
index 000000000000..8818c1725617
--- /dev/null
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_COMPILE_PSEUDOLOCALIZE_H
+#define AAPT_COMPILE_PSEUDOLOCALIZE_H
+
+#include "ResourceValues.h"
+#include "StringPool.h"
+#include "util/StringPiece.h"
+
+#include <android-base/macros.h>
+#include <memory>
+
+namespace aapt {
+
+class PseudoMethodImpl {
+public:
+ virtual ~PseudoMethodImpl() {}
+ virtual std::u16string start() { return {}; }
+ virtual std::u16string end() { return {}; }
+ virtual std::u16string text(const StringPiece16& text) = 0;
+ virtual std::u16string placeholder(const StringPiece16& text) = 0;
+};
+
+class Pseudolocalizer {
+public:
+ enum class Method {
+ kNone,
+ kAccent,
+ kBidi,
+ };
+
+ Pseudolocalizer(Method method);
+ void setMethod(Method method);
+ std::u16string start() { return mImpl->start(); }
+ std::u16string end() { return mImpl->end(); }
+ std::u16string text(const StringPiece16& text);
+private:
+ std::unique_ptr<PseudoMethodImpl> mImpl;
+ size_t mLastDepth;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_COMPILE_PSEUDOLOCALIZE_H */
diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp
new file mode 100644
index 000000000000..b0bc2c10fbe0
--- /dev/null
+++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compile/Pseudolocalizer.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+// In this context, 'Axis' represents a particular field in the configuration,
+// such as language or density.
+
+static ::testing::AssertionResult simpleHelper(const char* input, const char* expected,
+ Pseudolocalizer::Method method) {
+ Pseudolocalizer pseudo(method);
+ std::string result = util::utf16ToUtf8(
+ pseudo.start() + pseudo.text(util::utf8ToUtf16(input)) + pseudo.end());
+ if (StringPiece(expected) != result) {
+ return ::testing::AssertionFailure() << expected << " != " << result;
+ }
+ return ::testing::AssertionSuccess();
+}
+
+static ::testing::AssertionResult compoundHelper(const char* in1, const char* in2, const char *in3,
+ const char* expected,
+ Pseudolocalizer::Method method) {
+ Pseudolocalizer pseudo(method);
+ std::string result = util::utf16ToUtf8(pseudo.start() +
+ pseudo.text(util::utf8ToUtf16(in1)) +
+ pseudo.text(util::utf8ToUtf16(in2)) +
+ pseudo.text(util::utf8ToUtf16(in3)) +
+ pseudo.end());
+ if (StringPiece(expected) != result) {
+ return ::testing::AssertionFailure() << expected << " != " << result;
+ }
+ return ::testing::AssertionSuccess();
+}
+
+TEST(PseudolocalizerTest, NoPseudolocalization) {
+ EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone));
+ EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world", Pseudolocalizer::Method::kNone));
+
+ EXPECT_TRUE(compoundHelper("Hello,", " world", "",
+ "Hello, world", Pseudolocalizer::Method::kNone));
+}
+
+TEST(PseudolocalizerTest, PlaintextAccent) {
+ EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Hello, world",
+ "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(simpleHelper("Hello, %1d",
+ "[Ĥéļļö, »%1d« one two]", Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(simpleHelper("Battery %1d%%",
+ "[βåţţéŕý »%1d«%% one two]", Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(compoundHelper("Hello,", " world", "",
+ "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent));
+}
+
+TEST(PseudolocalizerTest, PlaintextBidi) {
+ EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper("word",
+ "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(" word ",
+ " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(" word ",
+ " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper("hello\n world\n",
+ "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \
+ " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(compoundHelper("hello", "\n ", " world\n",
+ "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \
+ " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
+ Pseudolocalizer::Method::kBidi));
+}
+
+TEST(PseudolocalizerTest, SimpleICU) {
+ // Single-fragment messages
+ EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("{USER} is offline",
+ "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}",
+ "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}",
+ "[Ţöðåý îš »{1,date}« »{1,time}« one two]",
+ Pseudolocalizer::Method::kAccent));
+
+ // Multi-fragment messages
+ EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline",
+ "[»{USER}« îš öƒƒļîñé one two]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}",
+ "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
+ Pseudolocalizer::Method::kAccent));
+}
+
+TEST(PseudolocalizerTest, ICUBidi) {
+ // Single-fragment messages
+ EXPECT_TRUE(simpleHelper("{placeholder}",
+ "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(
+ "{COUNT, plural, one {one} other {other}}",
+ "{COUNT, plural, " \
+ "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} " \
+ "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}",
+ Pseudolocalizer::Method::kBidi));
+}
+
+TEST(PseudolocalizerTest, Escaping) {
+ // Single-fragment messages
+ EXPECT_TRUE(simpleHelper("'{USER'} is offline",
+ "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]",
+ Pseudolocalizer::Method::kAccent));
+
+ // Multi-fragment messages
+ EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline",
+ "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]",
+ Pseudolocalizer::Method::kAccent));
+}
+
+TEST(PseudolocalizerTest, PluralsAndSelects) {
+ EXPECT_TRUE(simpleHelper(
+ "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}",
+ "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \
+ "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
+ Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(simpleHelper(
+ "Distance is {COUNT, plural, one {# mile} other {# miles}}",
+ "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} " \
+ "other {# ḿîļéš one two}}]",
+ Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(simpleHelper(
+ "{1, select, female {{1} added you} " \
+ "male {{1} added you} other {{1} added you}}",
+ "[{1, select, female {»{1}« åððéð ýöû one two} " \
+ "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]",
+ Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(compoundHelper(
+ "{COUNT, plural, one {Delete a file} " \
+ "other {Delete ", "{COUNT}", " files}}",
+ "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \
+ "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
+ Pseudolocalizer::Method::kAccent));
+}
+
+TEST(PseudolocalizerTest, NestedICU) {
+ EXPECT_TRUE(simpleHelper(
+ "{person, select, " \
+ "female {" \
+ "{num_circles, plural," \
+ "=0{{person} didn't add you to any of her circles.}" \
+ "=1{{person} added you to one of her circles.}" \
+ "other{{person} added you to her # circles.}}}" \
+ "male {" \
+ "{num_circles, plural," \
+ "=0{{person} didn't add you to any of his circles.}" \
+ "=1{{person} added you to one of his circles.}" \
+ "other{{person} added you to his # circles.}}}" \
+ "other {" \
+ "{num_circles, plural," \
+ "=0{{person} didn't add you to any of their circles.}" \
+ "=1{{person} added you to one of their circles.}" \
+ "other{{person} added you to their # circles.}}}}",
+ "[{person, select, " \
+ "female {" \
+ "{num_circles, plural," \
+ "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš." \
+ " one two three four five}" \
+ "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš." \
+ " one two three four}" \
+ "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš." \
+ " one two three four}}}" \
+ "male {" \
+ "{num_circles, plural," \
+ "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš." \
+ " one two three four five}" \
+ "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš." \
+ " one two three four}" \
+ "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš." \
+ " one two three four}}}" \
+ "other {{num_circles, plural," \
+ "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš." \
+ " one two three four five}" \
+ "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš." \
+ " one two three four}" \
+ "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš." \
+ " one two three four}}}}]",
+ Pseudolocalizer::Method::kAccent));
+}
+
+TEST(PseudolocalizerTest, RedefineMethod) {
+ Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent);
+ std::u16string result = pseudo.text(u"Hello, ");
+ pseudo.setMethod(Pseudolocalizer::Method::kNone);
+ result += pseudo.text(u"world!");
+ ASSERT_EQ(StringPiece("Ĥéļļö, world!"), util::utf16ToUtf8(result));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
new file mode 100644
index 000000000000..f40689eaeb47
--- /dev/null
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "compile/XmlIdCollector.h"
+#include "xml/XmlDom.h"
+
+#include <algorithm>
+#include <vector>
+
+namespace aapt {
+
+namespace {
+
+static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
+ return a.name < b;
+}
+
+struct IdCollector : public xml::Visitor {
+ using xml::Visitor::visit;
+
+ std::vector<SourcedResourceName>* mOutSymbols;
+
+ IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) {
+ }
+
+ void visit(xml::Element* element) override {
+ for (xml::Attribute& attr : element->attributes) {
+ ResourceNameRef name;
+ bool create = false;
+ if (ResourceUtils::tryParseReference(attr.value, &name, &create, nullptr)) {
+ if (create && name.type == ResourceType::kId) {
+ auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
+ name, cmpName);
+ if (iter == mOutSymbols->end() || iter->name != name) {
+ mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(),
+ element->lineNumber });
+ }
+ }
+ }
+ }
+
+ xml::Visitor::visit(element);
+ }
+};
+
+} // namespace
+
+bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
+ xmlRes->file.exportedSymbols.clear();
+ IdCollector collector(&xmlRes->file.exportedSymbols);
+ xmlRes->root->accept(&collector);
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h
new file mode 100644
index 000000000000..1b149449de2c
--- /dev/null
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XMLIDCOLLECTOR_H
+#define AAPT_XMLIDCOLLECTOR_H
+
+#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+
+struct XmlIdCollector : public IXmlResourceConsumer {
+ bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_XMLIDCOLLECTOR_H */
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
new file mode 100644
index 000000000000..a37ea86c317f
--- /dev/null
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compile/XmlIdCollector.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <algorithm>
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(XmlIdCollectorTest, CollectsIds) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/foo"
+ text="@+id/bar">
+ <SubView android:id="@+id/car"
+ class="@+id/bar"/>
+ </View>)EOF");
+
+ XmlIdCollector collector;
+ ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+
+ EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+ SourcedResourceName{ test::parseNameOrDie(u"@id/foo"), 3u }));
+
+ EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+ SourcedResourceName{ test::parseNameOrDie(u"@id/bar"), 3u }));
+
+ EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+ SourcedResourceName{ test::parseNameOrDie(u"@id/car"), 6u }));
+}
+
+TEST(XmlIdCollectorTest, DontCollectNonIds) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+
+ XmlIdCollector collector;
+ ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+
+ EXPECT_TRUE(doc->file.exportedSymbols.empty());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml
deleted file mode 100644
index 8533c28c24bb..000000000000
--- a/tools/aapt2/data/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.app">
- <application
- android:name=".Activity">
- </application>
-</manifest>
diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile
deleted file mode 100644
index 91ff5fee6477..000000000000
--- a/tools/aapt2/data/Makefile
+++ /dev/null
@@ -1,83 +0,0 @@
-##
-# Environment dependent variables
-##
-
-AAPT := aapt2
-ZIPALIGN := zipalign -f 4
-FRAMEWORK := ../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk
-
-##
-# Project depenedent variables
-##
-
-LOCAL_PACKAGE := com.android.app
-LOCAL_RESOURCE_DIR := res
-LOCAL_LIBS := lib/out/package.apk
-LOCAL_OUT := out
-LOCAL_GEN := out/gen
-LOCAL_PROGUARD := out/proguard.rule
-
-##
-# AAPT2 custom rules.
-##
-
-PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
-PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
-
-# Eg: framework.apk, etc.
-PRIVATE_INCLUDES := $(FRAMEWORK)
-$(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES))
-
-# Eg: gen/com/android/app/R.java
-PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
-$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA))
-
-# Eg: res/drawable/icon.png, res/values/styles.xml
-PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f)
-$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES))
-
-# Eg: drawable, values, layouts
-PRIVATE_RESOURCE_TYPES := \
- $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
-$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
-
-# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
-PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
-$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
-
-# Generates rules for collect phase.
-# $1: Resource type (values-v4)
-# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
-define make-collect-rule
-$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
- $(AAPT) compile -o $$@ $$^
-endef
-
-# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
-$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
-
-# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
-$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml
- $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v
-
-# R.java: gen/com/android/app/R.java <- out/resources.arsc
-# No action since R.java is generated when out/resources.arsc is.
-$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
-
-# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
-$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
- $(ZIPALIGN) $< $@
-
-# Create the out directory if needed.
-dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
-
-.PHONY: java
-java: $(PRIVATE_R_JAVA)
-
-.PHONY: assemble
-assemble: $(PRIVATE_APK_ALIGNED)
-
-.PHONY: all
-all: assemble java
-
-.DEFAULT_GOAL := all
diff --git a/tools/aapt2/data/lib/AndroidManifest.xml b/tools/aapt2/data/lib/AndroidManifest.xml
deleted file mode 100644
index 08b468ee6229..000000000000
--- a/tools/aapt2/data/lib/AndroidManifest.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.appcompat">
-
- <uses-feature android:name="bloooop" />
-</manifest>
diff --git a/tools/aapt2/data/lib/Makefile b/tools/aapt2/data/lib/Makefile
deleted file mode 100644
index 741be9a0e75a..000000000000
--- a/tools/aapt2/data/lib/Makefile
+++ /dev/null
@@ -1,81 +0,0 @@
-##
-# Environment dependent variables
-##
-
-AAPT := aapt2
-ZIPALIGN := zipalign -f 4
-FRAMEWORK := ../../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk
-
-##
-# Project depenedent variables
-##
-
-LOCAL_PACKAGE := android.appcompat
-LOCAL_RESOURCE_DIR := res
-LOCAL_OUT := out
-LOCAL_GEN := out/gen
-
-##
-# AAPT2 custom rules.
-##
-
-PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
-PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
-
-# Eg: framework.apk, etc.
-PRIVATE_LIBS := $(FRAMEWORK)
-$(info PRIVATE_LIBS = $(PRIVATE_LIBS))
-
-# Eg: gen/com/android/app/R.java
-PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
-$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA))
-
-# Eg: res/drawable/icon.png, res/values/styles.xml
-PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f)
-$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES))
-
-# Eg: drawable, values, layouts
-PRIVATE_RESOURCE_TYPES := \
- $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
-$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
-
-# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
-PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
-$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
-
-# Generates rules for collect phase.
-# $1: Resource type (values-v4)
-# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
-define make-collect-rule
-$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
- $(AAPT) compile -o $$@ $$^
-endef
-
-# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
-$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
-
-# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
-$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) AndroidManifest.xml
- $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) --static-lib
-
-# R.java: gen/com/android/app/R.java <- out/resources.arsc
-# No action since R.java is generated when out/resources.arsc is.
-$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
-
-# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
-$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
- $(ZIPALIGN) $< $@
-
-# Create the out directory if needed.
-dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
-
-.PHONY: java
-java: $(PRIVATE_R_JAVA)
-
-.PHONY: assemble
-assemble: $(PRIVATE_APK_ALIGNED)
-
-.PHONY: all
-all: assemble java
-
-.DEFAULT_GOAL := all
diff --git a/tools/aapt2/data/lib/res/layout/main.xml b/tools/aapt2/data/lib/res/layout/main.xml
deleted file mode 100644
index 187ed2d75da3..000000000000
--- a/tools/aapt2/data/lib/res/layout/main.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?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"/>
diff --git a/tools/aapt2/data/lib/res/raw/hello.txt b/tools/aapt2/data/lib/res/raw/hello.txt
deleted file mode 100644
index 44fc22bebb39..000000000000
--- a/tools/aapt2/data/lib/res/raw/hello.txt
+++ /dev/null
@@ -1 +0,0 @@
-Oh howdy there
diff --git a/tools/aapt2/data/lib/res/values/styles.xml b/tools/aapt2/data/lib/res/values/styles.xml
deleted file mode 100644
index 4ce633394063..000000000000
--- a/tools/aapt2/data/lib/res/values/styles.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <style name="Platform.AppCompat" parent="@android:style/Theme">
- <item name="android:windowNoTitle">true</item>
- </style>
-
- <bool name="allow">true</bool>
-</resources>
diff --git a/tools/aapt2/data/res/drawable/image.xml b/tools/aapt2/data/res/drawable/image.xml
deleted file mode 100644
index 9b387390ea82..000000000000
--- a/tools/aapt2/data/res/drawable/image.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector />
diff --git a/tools/aapt2/data/res/values-v4/styles.xml b/tools/aapt2/data/res/values-v4/styles.xml
deleted file mode 100644
index 979a82a06df9..000000000000
--- a/tools/aapt2/data/res/values-v4/styles.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <style name="App" parent="android:Theme.Material">
- <item name="android:colorAccent">@color/accent</item>
- <item name="android:text">Hey</item>
- </style>
-</resources>
diff --git a/tools/aapt2/data/res/values/colors.xml b/tools/aapt2/data/res/values/colors.xml
deleted file mode 100644
index 89db5fb3ec45..000000000000
--- a/tools/aapt2/data/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <color name="primary">#f44336</color>
- <color name="primary_dark">#b71c1c</color>
- <color name="accent">#fdd835</color>
-</resources>
diff --git a/tools/aapt2/data/res/values/test.xml b/tools/aapt2/data/res/values/test.xml
deleted file mode 100644
index d3ead34d043c..000000000000
--- a/tools/aapt2/data/res/values/test.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string>
- <public name="hooha" type="string" id="0x7f020001"/>
- <string name="wow">@android:string/ok</string>
- <public name="image" type="drawable" id="0x7f060000" />
- <attr name="layout_width" format="boolean" />
- <attr name="flags">
- <flag name="complex" value="1" />
- <flag name="pub" value="2" />
- <flag name="weak" value="4" />
- </attr>
-</resources>
diff --git a/tools/aapt2/data/resources.arsc b/tools/aapt2/data/resources.arsc
deleted file mode 100644
index 6a416df0a96f..000000000000
--- a/tools/aapt2/data/resources.arsc
+++ /dev/null
Binary files differ
diff --git a/tools/aapt2/data/resources_base.arsc b/tools/aapt2/data/resources_base.arsc
deleted file mode 100644
index f9d06107b0e0..000000000000
--- a/tools/aapt2/data/resources_base.arsc
+++ /dev/null
Binary files differ
diff --git a/tools/aapt2/data/resources_hdpi.arsc b/tools/aapt2/data/resources_hdpi.arsc
deleted file mode 100644
index 97232a3317ba..000000000000
--- a/tools/aapt2/data/resources_hdpi.arsc
+++ /dev/null
Binary files differ
diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp
new file mode 100644
index 000000000000..56b9f9a3e081
--- /dev/null
+++ b/tools/aapt2/dump/Dump.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Debug.h"
+#include "Diagnostics.h"
+#include "Flags.h"
+#include "io/ZipArchive.h"
+#include "process/IResourceTableConsumer.h"
+#include "proto/ProtoSerialize.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <vector>
+
+namespace aapt {
+
+//struct DumpOptions {
+//
+//};
+
+void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data, size_t len,
+ const Source& source, IAaptContext* context) {
+ std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(pbFile, source,
+ context->getDiagnostics());
+ if (!file) {
+ return;
+ }
+
+ std::cout << "Resource: " << file->name << "\n"
+ << "Config: " << file->config << "\n"
+ << "Source: " << file->source << "\n";
+}
+
+void dumpCompiledTable(const pb::ResourceTable& pbTable, const Source& source,
+ IAaptContext* context) {
+ std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
+ context->getDiagnostics());
+ if (!table) {
+ return;
+ }
+
+ Debug::printTable(table.get());
+}
+
+void tryDumpFile(IAaptContext* context, const std::string& filePath) {
+ std::string err;
+ std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::create(filePath, &err);
+ if (zip) {
+ io::IFile* file = zip->findFile("resources.arsc.flat");
+ if (file) {
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ context->getDiagnostics()->error(DiagMessage(filePath)
+ << "failed to open resources.arsc.flat");
+ return;
+ }
+
+ pb::ResourceTable pbTable;
+ if (!pbTable.ParseFromArray(data->data(), data->size())) {
+ context->getDiagnostics()->error(DiagMessage(filePath)
+ << "invalid resources.arsc.flat");
+ return;
+ }
+
+ std::unique_ptr<ResourceTable> table = deserializeTableFromPb(
+ pbTable, Source(filePath), context->getDiagnostics());
+ if (table) {
+ DebugPrintTableOptions debugPrintTableOptions;
+ debugPrintTableOptions.showSources = true;
+ Debug::printTable(table.get(), debugPrintTableOptions);
+ }
+ }
+ return;
+ }
+
+ Maybe<android::FileMap> file = file::mmapPath(filePath, &err);
+ if (!file) {
+ context->getDiagnostics()->error(DiagMessage(filePath) << err);
+ return;
+ }
+
+ android::FileMap* fileMap = &file.value();
+
+ // Try as a compiled table.
+ pb::ResourceTable pbTable;
+ if (pbTable.ParseFromArray(fileMap->getDataPtr(), fileMap->getDataLength())) {
+ dumpCompiledTable(pbTable, Source(filePath), context);
+ return;
+ }
+
+ // Try as a compiled file.
+ CompiledFileInputStream input(fileMap->getDataPtr(), fileMap->getDataLength());
+ if (const pb::CompiledFile* pbFile = input.CompiledFile()) {
+ dumpCompiledFile(*pbFile, input.data(), input.size(), Source(filePath), context);
+ return;
+ }
+}
+
+class DumpContext : public IAaptContext {
+public:
+ IDiagnostics* getDiagnostics() override {
+ return &mDiagnostics;
+ }
+
+ NameMangler* getNameMangler() override {
+ abort();
+ return nullptr;
+ }
+
+ const std::u16string& getCompilationPackage() override {
+ static std::u16string empty;
+ return empty;
+ }
+
+ uint8_t getPackageId() override {
+ return 0;
+ }
+
+ SymbolTable* getExternalSymbols() override {
+ abort();
+ return nullptr;
+ }
+
+ bool verbose() override {
+ return mVerbose;
+ }
+
+ void setVerbose(bool val) {
+ mVerbose = val;
+ }
+
+private:
+ StdErrDiagnostics mDiagnostics;
+ bool mVerbose = false;
+};
+
+/**
+ * Entry point for dump command.
+ */
+int dump(const std::vector<StringPiece>& args) {
+ bool verbose = false;
+ Flags flags = Flags()
+ .optionalSwitch("-v", "increase verbosity of output", &verbose);
+ if (!flags.parse("aapt2 dump", args, &std::cerr)) {
+ return 1;
+ }
+
+ DumpContext context;
+ context.setVerbose(verbose);
+
+ for (const std::string& arg : flags.getArgs()) {
+ tryDumpFile(&context, arg);
+ }
+ return 0;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp
new file mode 100644
index 000000000000..68a017d247f6
--- /dev/null
+++ b/tools/aapt2/filter/ConfigFilter.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "filter/ConfigFilter.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+void AxisConfigFilter::addConfig(ConfigDescription config) {
+ uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
+
+ // Ignore the version
+ diffMask &= ~android::ResTable_config::CONFIG_VERSION;
+
+ // Ignore any densities. Those are best handled in --preferred-density
+ if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) {
+ config.density = 0;
+ diffMask &= ~android::ResTable_config::CONFIG_DENSITY;
+ }
+
+ mConfigs.insert(std::make_pair(config, diffMask));
+ mConfigMask |= diffMask;
+}
+
+bool AxisConfigFilter::match(const ConfigDescription& config) const {
+ const uint32_t mask = ConfigDescription::defaultConfig().diff(config);
+ if ((mConfigMask & mask) == 0) {
+ // The two configurations don't have any common axis.
+ return true;
+ }
+
+ uint32_t matchedAxis = 0;
+ for (const auto& entry : mConfigs) {
+ const ConfigDescription& target = entry.first;
+ const uint32_t diffMask = entry.second;
+ uint32_t diff = target.diff(config);
+ if ((diff & diffMask) == 0) {
+ // Mark the axis that was matched.
+ matchedAxis |= diffMask;
+ } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) {
+ // If the locales differ, but the languages are the same and
+ // the locale we are matching only has a language specified,
+ // we match.
+ if (config.language[0] &&
+ memcmp(config.language, target.language, sizeof(config.language)) == 0) {
+ if (config.country[0] == 0) {
+ matchedAxis |= android::ResTable_config::CONFIG_LOCALE;
+ }
+ }
+ } else if ((diff & diffMask) == android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
+ // Special case if the smallest screen width doesn't match. We check that the
+ // config being matched has a smaller screen width than the filter specified.
+ if (config.smallestScreenWidthDp != 0 &&
+ config.smallestScreenWidthDp < target.smallestScreenWidthDp) {
+ matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
+ }
+ }
+ }
+ return matchedAxis == (mConfigMask & mask);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h
new file mode 100644
index 000000000000..36e9c44255e4
--- /dev/null
+++ b/tools/aapt2/filter/ConfigFilter.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FILTER_CONFIGFILTER_H
+#define AAPT_FILTER_CONFIGFILTER_H
+
+#include "ConfigDescription.h"
+
+#include <set>
+#include <utility>
+
+namespace aapt {
+
+/**
+ * Matches ConfigDescriptions based on some pattern.
+ */
+class IConfigFilter {
+public:
+ virtual ~IConfigFilter() = default;
+
+ /**
+ * Returns true if the filter matches the configuration, false otherwise.
+ */
+ virtual bool match(const ConfigDescription& config) const = 0;
+};
+
+/**
+ * Implements config axis matching. An axis is one component of a configuration, like screen
+ * density or locale. If an axis is specified in the filter, and the axis is specified in
+ * the configuration to match, they must be compatible. Otherwise the configuration to match is
+ * accepted.
+ *
+ * Used when handling "-c" options.
+ */
+class AxisConfigFilter : public IConfigFilter {
+public:
+ void addConfig(ConfigDescription config);
+
+ bool match(const ConfigDescription& config) const override;
+
+private:
+ std::set<std::pair<ConfigDescription, uint32_t>> mConfigs;
+ uint32_t mConfigMask = 0;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FILTER_CONFIGFILTER_H */
diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp
new file mode 100644
index 000000000000..f6b49557306d
--- /dev/null
+++ b/tools/aapt2/filter/ConfigFilter_test.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "filter/ConfigFilter.h"
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ConfigFilterTest, EmptyFilterMatchesAnything) {
+ AxisConfigFilter filter;
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithOneMatchingAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr-rFR"));
+ filter.addConfig(test::parseConfigOrDie("sw360dp"));
+ filter.addConfig(test::parseConfigOrDie("normal"));
+ filter.addConfig(test::parseConfigOrDie("en-rUS"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("en")));
+}
+
+TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
+
+ EXPECT_FALSE(filter.match(test::parseConfigOrDie("de")));
+}
+
+TEST(ConfigFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr-rFR"));
+ filter.addConfig(test::parseConfigOrDie("en-rUS"));
+ filter.addConfig(test::parseConfigOrDie("normal"));
+ filter.addConfig(test::parseConfigOrDie("large"));
+ filter.addConfig(test::parseConfigOrDie("xxhdpi"));
+ filter.addConfig(test::parseConfigOrDie("sw320dp"));
+
+ EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13")));
+}
+
+TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("sw600dp"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("de-rDE"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("de")));
+}
+
+TEST(ConfigFilterTest, IgnoresVersion) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("normal-v4"));
+
+ // The configs don't match on any axis besides version, which should be ignored.
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithRegion) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("kok"));
+ filter.addConfig(test::parseConfigOrDie("kok-rIN"));
+ filter.addConfig(test::parseConfigOrDie("kok-v419"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
new file mode 100644
index 000000000000..3a244c05efec
--- /dev/null
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flatten/Archive.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <vector>
+#include <ziparchive/zip_writer.h>
+
+namespace aapt {
+
+namespace {
+
+struct DirectoryWriter : public IArchiveWriter {
+ std::string mOutDir;
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
+
+ bool open(IDiagnostics* diag, const StringPiece& outDir) {
+ mOutDir = outDir.toString();
+ file::FileType type = file::getFileType(mOutDir);
+ if (type == file::FileType::kNonexistant) {
+ diag->error(DiagMessage() << "directory " << mOutDir << " does not exist");
+ return false;
+ } else if (type != file::FileType::kDirectory) {
+ diag->error(DiagMessage() << mOutDir << " is not a directory");
+ return false;
+ }
+ return true;
+ }
+
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
+ if (mFile) {
+ return false;
+ }
+
+ std::string fullPath = mOutDir;
+ file::appendPath(&fullPath, path);
+ file::mkdirs(file::getStem(fullPath));
+
+ mFile = { fopen(fullPath.data(), "wb"), fclose };
+ if (!mFile) {
+ return false;
+ }
+ return true;
+ }
+
+ bool writeEntry(const BigBuffer& buffer) override {
+ if (!mFile) {
+ return false;
+ }
+
+ for (const BigBuffer::Block& b : buffer) {
+ if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
+ mFile.reset(nullptr);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool writeEntry(const void* data, size_t len) override {
+ if (fwrite(data, 1, len, mFile.get()) != len) {
+ mFile.reset(nullptr);
+ return false;
+ }
+ return true;
+ }
+
+ bool finishEntry() override {
+ if (!mFile) {
+ return false;
+ }
+ mFile.reset(nullptr);
+ return true;
+ }
+};
+
+struct ZipFileWriter : public IArchiveWriter {
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
+ std::unique_ptr<ZipWriter> mWriter;
+
+ bool open(IDiagnostics* diag, const StringPiece& path) {
+ mFile = { fopen(path.data(), "w+b"), fclose };
+ if (!mFile) {
+ diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno));
+ return false;
+ }
+ mWriter = util::make_unique<ZipWriter>(mFile.get());
+ return true;
+ }
+
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
+ if (!mWriter) {
+ return false;
+ }
+
+ size_t zipFlags = 0;
+ if (flags & ArchiveEntry::kCompress) {
+ zipFlags |= ZipWriter::kCompress;
+ }
+
+ if (flags & ArchiveEntry::kAlign) {
+ zipFlags |= ZipWriter::kAlign32;
+ }
+
+ int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+ if (result != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ bool writeEntry(const void* data, size_t len) override {
+ int32_t result = mWriter->WriteBytes(data, len);
+ if (result != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ bool writeEntry(const BigBuffer& buffer) override {
+ for (const BigBuffer::Block& b : buffer) {
+ int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
+ if (result != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool finishEntry() override {
+ int32_t result = mWriter->FinishEntry();
+ if (result != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ virtual ~ZipFileWriter() {
+ if (mWriter) {
+ mWriter->Finish();
+ }
+ }
+};
+
+} // namespace
+
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+
+ std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
+}
+
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+ std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
new file mode 100644
index 000000000000..34c10ad40365
--- /dev/null
+++ b/tools/aapt2/flatten/Archive.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_ARCHIVE_H
+#define AAPT_FLATTEN_ARCHIVE_H
+
+#include "Diagnostics.h"
+#include "util/BigBuffer.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+struct ArchiveEntry {
+ enum : uint32_t {
+ kCompress = 0x01,
+ kAlign = 0x02,
+ };
+
+ std::string path;
+ uint32_t flags;
+ size_t uncompressedSize;
+};
+
+struct IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
+ virtual ~IArchiveWriter() = default;
+
+ virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
+ virtual bool writeEntry(const BigBuffer& buffer) = 0;
+ virtual bool writeEntry(const void* data, size_t len) = 0;
+ virtual bool finishEntry() = 0;
+
+ // CopyingOutputStream implementations.
+ bool Write(const void* buffer, int size) override {
+ return writeEntry(buffer, size);
+ }
+};
+
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path);
+
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path);
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_ARCHIVE_H */
diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h
new file mode 100644
index 000000000000..de1d87a57e6d
--- /dev/null
+++ b/tools/aapt2/flatten/ChunkWriter.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_CHUNKWRITER_H
+#define AAPT_FLATTEN_CHUNKWRITER_H
+
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+class ChunkWriter {
+private:
+ BigBuffer* mBuffer;
+ size_t mStartSize = 0;
+ android::ResChunk_header* mHeader = nullptr;
+
+public:
+ explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {
+ }
+
+ ChunkWriter(const ChunkWriter&) = delete;
+ ChunkWriter& operator=(const ChunkWriter&) = delete;
+ ChunkWriter(ChunkWriter&&) = default;
+ ChunkWriter& operator=(ChunkWriter&&) = default;
+
+ template <typename T>
+ inline T* startChunk(uint16_t type) {
+ mStartSize = mBuffer->size();
+ T* chunk = mBuffer->nextBlock<T>();
+ mHeader = &chunk->header;
+ mHeader->type = util::hostToDevice16(type);
+ mHeader->headerSize = util::hostToDevice16(sizeof(T));
+ return chunk;
+ }
+
+ template <typename T>
+ inline T* nextBlock(size_t count = 1) {
+ return mBuffer->nextBlock<T>(count);
+ }
+
+ inline BigBuffer* getBuffer() {
+ return mBuffer;
+ }
+
+ inline android::ResChunk_header* getChunkHeader() {
+ return mHeader;
+ }
+
+ inline size_t size() {
+ return mBuffer->size() - mStartSize;
+ }
+
+ inline android::ResChunk_header* finish() {
+ mBuffer->align4();
+ mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
+ return mHeader;
+ }
+};
+
+template <>
+inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) {
+ mStartSize = mBuffer->size();
+ mHeader = mBuffer->nextBlock<android::ResChunk_header>();
+ mHeader->type = util::hostToDevice16(type);
+ mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header));
+ return mHeader;
+}
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_CHUNKWRITER_H */
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
new file mode 100644
index 000000000000..3e20ad643eb6
--- /dev/null
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H
+#define AAPT_RESOURCE_TYPE_EXTENSIONS_H
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+/**
+ * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout
+ * struct.
+ */
+struct ResTable_entry_ext {
+ android::ResTable_entry entry;
+ android::ResTable_ref parent;
+ uint32_t count;
+};
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
new file mode 100644
index 000000000000..28a792820de3
--- /dev/null
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "flatten/ChunkWriter.h"
+#include "flatten/ResourceTypeExtensions.h"
+#include "flatten/TableFlattener.h"
+#include "util/BigBuffer.h"
+
+#include <android-base/macros.h>
+#include <algorithm>
+#include <type_traits>
+#include <numeric>
+
+using namespace android;
+
+namespace aapt {
+
+namespace {
+
+template <typename T>
+static bool cmpIds(const T* a, const T* b) {
+ return a->id.value() < b->id.value();
+}
+
+static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
+ if (len == 0) {
+ return;
+ }
+
+ size_t i;
+ const char16_t* srcData = src.data();
+ for (i = 0; i < len - 1 && i < src.size(); i++) {
+ dst[i] = util::hostToDevice16((uint16_t) srcData[i]);
+ }
+ dst[i] = 0;
+}
+
+static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
+ if (a.key.id) {
+ if (b.key.id) {
+ return a.key.id.value() < b.key.id.value();
+ }
+ return true;
+ } else if (!b.key.id) {
+ return a.key.name.value() < b.key.name.value();
+ }
+ return false;
+}
+
+struct FlatEntry {
+ ResourceEntry* entry;
+ Value* value;
+
+ // The entry string pool index to the entry's name.
+ uint32_t entryKey;
+};
+
+class MapFlattenVisitor : public RawValueVisitor {
+public:
+ using RawValueVisitor::visit;
+
+ MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) :
+ mOutEntry(outEntry), mBuffer(buffer) {
+ }
+
+ void visit(Attribute* attr) override {
+ {
+ Reference key = Reference(ResTable_map::ATTR_TYPE);
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
+ flattenEntry(&key, &val);
+ }
+
+ if (attr->minInt != std::numeric_limits<int32_t>::min()) {
+ Reference key = Reference(ResTable_map::ATTR_MIN);
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt));
+ flattenEntry(&key, &val);
+ }
+
+ if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
+ Reference key = Reference(ResTable_map::ATTR_MAX);
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt));
+ flattenEntry(&key, &val);
+ }
+
+ for (Attribute::Symbol& s : attr->symbols) {
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
+ flattenEntry(&s.symbol, &val);
+ }
+ }
+
+ void visit(Style* style) override {
+ if (style->parent) {
+ const Reference& parentRef = style->parent.value();
+ assert(parentRef.id && "parent has no ID");
+ mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id);
+ }
+
+ // Sort the style.
+ std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
+
+ for (Style::Entry& entry : style->entries) {
+ flattenEntry(&entry.key, entry.value.get());
+ }
+ }
+
+ void visit(Styleable* styleable) override {
+ for (auto& attrRef : styleable->entries) {
+ BinaryPrimitive val(Res_value{});
+ flattenEntry(&attrRef, &val);
+ }
+
+ }
+
+ void visit(Array* array) override {
+ for (auto& item : array->items) {
+ ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+ flattenValue(item.get(), outEntry);
+ outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+ mEntryCount++;
+ }
+ }
+
+ void visit(Plural* plural) override {
+ const size_t count = plural->values.size();
+ for (size_t i = 0; i < count; i++) {
+ if (!plural->values[i]) {
+ continue;
+ }
+
+ ResourceId q;
+ switch (i) {
+ case Plural::Zero:
+ q.id = android::ResTable_map::ATTR_ZERO;
+ break;
+
+ case Plural::One:
+ q.id = android::ResTable_map::ATTR_ONE;
+ break;
+
+ case Plural::Two:
+ q.id = android::ResTable_map::ATTR_TWO;
+ break;
+
+ case Plural::Few:
+ q.id = android::ResTable_map::ATTR_FEW;
+ break;
+
+ case Plural::Many:
+ q.id = android::ResTable_map::ATTR_MANY;
+ break;
+
+ case Plural::Other:
+ q.id = android::ResTable_map::ATTR_OTHER;
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ Reference key(q);
+ flattenEntry(&key, plural->values[i].get());
+ }
+ }
+
+ /**
+ * Call this after visiting a Value. This will finish any work that
+ * needs to be done to prepare the entry.
+ */
+ void finish() {
+ mOutEntry->count = util::hostToDevice32(mEntryCount);
+ }
+
+private:
+ void flattenKey(Reference* key, ResTable_map* outEntry) {
+ assert(key->id && "key has no ID");
+ outEntry->name.ident = util::hostToDevice32(key->id.value().id);
+ }
+
+ void flattenValue(Item* value, ResTable_map* outEntry) {
+ bool result = value->flatten(&outEntry->value);
+ assert(result && "flatten failed");
+ }
+
+ void flattenEntry(Reference* key, Item* value) {
+ ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+ flattenKey(key, outEntry);
+ flattenValue(value, outEntry);
+ outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+ mEntryCount++;
+ }
+
+ ResTable_entry_ext* mOutEntry;
+ BigBuffer* mBuffer;
+ size_t mEntryCount = 0;
+};
+
+class PackageFlattener {
+public:
+ PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) :
+ mDiag(diag), mPackage(package) {
+ }
+
+ bool flattenPackage(BigBuffer* buffer) {
+ ChunkWriter pkgWriter(buffer);
+ ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>(
+ RES_TABLE_PACKAGE_TYPE);
+ pkgHeader->id = util::hostToDevice32(mPackage->id.value());
+
+ if (mPackage->name.size() >= arraysize(pkgHeader->name)) {
+ mDiag->error(DiagMessage() <<
+ "package name '" << mPackage->name << "' is too long");
+ return false;
+ }
+
+ // Copy the package name in device endianness.
+ strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name), mPackage->name);
+
+ // Serialize the types. We do this now so that our type and key strings
+ // are populated. We write those first.
+ BigBuffer typeBuffer(1024);
+ flattenTypes(&typeBuffer);
+
+ pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
+ StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
+
+ pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
+ StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
+
+ // Append the types.
+ buffer->appendBuffer(std::move(typeBuffer));
+
+ pkgWriter.finish();
+ return true;
+ }
+
+private:
+ IDiagnostics* mDiag;
+ ResourceTablePackage* mPackage;
+ StringPool mTypePool;
+ StringPool mKeyPool;
+
+ template <typename T, bool IsItem>
+ T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
+ static_assert(std::is_same<ResTable_entry, T>::value ||
+ std::is_same<ResTable_entry_ext, T>::value,
+ "T must be ResTable_entry or ResTable_entry_ext");
+
+ T* result = buffer->nextBlock<T>();
+ ResTable_entry* outEntry = (ResTable_entry*)(result);
+ if (entry->entry->symbolStatus.state == SymbolState::kPublic) {
+ outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
+ }
+
+ if (entry->value->isWeak()) {
+ outEntry->flags |= ResTable_entry::FLAG_WEAK;
+ }
+
+ if (!IsItem) {
+ outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
+ }
+
+ outEntry->flags = util::hostToDevice16(outEntry->flags);
+ outEntry->key.index = util::hostToDevice32(entry->entryKey);
+ outEntry->size = util::hostToDevice16(sizeof(T));
+ return result;
+ }
+
+ bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
+ if (Item* item = valueCast<Item>(entry->value)) {
+ writeEntry<ResTable_entry, true>(entry, buffer);
+ Res_value* outValue = buffer->nextBlock<Res_value>();
+ bool result = item->flatten(outValue);
+ assert(result && "flatten failed");
+ outValue->size = util::hostToDevice16(sizeof(*outValue));
+ } else {
+ ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
+ MapFlattenVisitor visitor(outEntry, buffer);
+ entry->value->accept(&visitor);
+ visitor.finish();
+ }
+ return true;
+ }
+
+ bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config,
+ std::vector<FlatEntry>* entries, BigBuffer* buffer) {
+ ChunkWriter typeWriter(buffer);
+ ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
+ typeHeader->id = type->id.value();
+ typeHeader->config = config;
+ typeHeader->config.swapHtoD();
+
+ auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
+ return std::max(max, (uint32_t) a->id.value());
+ };
+
+ // Find the largest entry ID. That is how many entries we will have.
+ const uint32_t entryCount =
+ std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1;
+
+ typeHeader->entryCount = util::hostToDevice32(entryCount);
+ uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
+
+ assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1);
+ memset(indices, 0xff, entryCount * sizeof(uint32_t));
+
+ typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
+
+ const size_t entryStart = typeWriter.getBuffer()->size();
+ for (FlatEntry& flatEntry : *entries) {
+ assert(flatEntry.entry->id.value() < entryCount);
+ indices[flatEntry.entry->id.value()] = util::hostToDevice32(
+ typeWriter.getBuffer()->size() - entryStart);
+ if (!flattenValue(&flatEntry, typeWriter.getBuffer())) {
+ mDiag->error(DiagMessage()
+ << "failed to flatten resource '"
+ << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name)
+ << "' for configuration '" << config << "'");
+ return false;
+ }
+ }
+ typeWriter.finish();
+ return true;
+ }
+
+ std::vector<ResourceTableType*> collectAndSortTypes() {
+ std::vector<ResourceTableType*> sortedTypes;
+ for (auto& type : mPackage->types) {
+ if (type->type == ResourceType::kStyleable) {
+ // Styleables aren't real Resource Types, they are represented in the R.java
+ // file.
+ continue;
+ }
+
+ assert(type->id && "type must have an ID set");
+
+ sortedTypes.push_back(type.get());
+ }
+ std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>);
+ return sortedTypes;
+ }
+
+ std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
+ // Sort the entries by entry ID.
+ std::vector<ResourceEntry*> sortedEntries;
+ for (auto& entry : type->entries) {
+ assert(entry->id && "entry must have an ID set");
+ sortedEntries.push_back(entry.get());
+ }
+ std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>);
+ return sortedEntries;
+ }
+
+ bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
+ BigBuffer* buffer) {
+ ChunkWriter typeSpecWriter(buffer);
+ ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>(
+ RES_TABLE_TYPE_SPEC_TYPE);
+ specHeader->id = type->id.value();
+
+ if (sortedEntries->empty()) {
+ typeSpecWriter.finish();
+ return true;
+ }
+
+ // We can't just take the size of the vector. There may be holes in the entry ID space.
+ // Since the entries are sorted by ID, the last one will be the biggest.
+ const size_t numEntries = sortedEntries->back()->id.value() + 1;
+
+ specHeader->entryCount = util::hostToDevice32(numEntries);
+
+ // Reserve space for the masks of each resource in this type. These
+ // show for which configuration axis the resource changes.
+ uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries);
+
+ const size_t actualNumEntries = sortedEntries->size();
+ for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
+ ResourceEntry* entry = sortedEntries->at(entryIndex);
+
+ // Populate the config masks for this entry.
+
+ if (entry->symbolStatus.state == SymbolState::kPublic) {
+ configMasks[entry->id.value()] |=
+ util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+ }
+
+ const size_t configCount = entry->values.size();
+ for (size_t i = 0; i < configCount; i++) {
+ const ConfigDescription& config = entry->values[i]->config;
+ for (size_t j = i + 1; j < configCount; j++) {
+ configMasks[entry->id.value()] |= util::hostToDevice32(
+ config.diff(entry->values[j]->config));
+ }
+ }
+ }
+ typeSpecWriter.finish();
+ return true;
+ }
+
+ bool flattenTypes(BigBuffer* buffer) {
+ // Sort the types by their IDs. They will be inserted into the StringPool in this order.
+ std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
+
+ size_t expectedTypeId = 1;
+ for (ResourceTableType* type : sortedTypes) {
+ // If there is a gap in the type IDs, fill in the StringPool
+ // with empty values until we reach the ID we expect.
+ while (type->id.value() > expectedTypeId) {
+ std::u16string typeName(u"?");
+ typeName += expectedTypeId;
+ mTypePool.makeRef(typeName);
+ expectedTypeId++;
+ }
+ expectedTypeId++;
+ mTypePool.makeRef(toString(type->type));
+
+ std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
+
+ if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
+ return false;
+ }
+
+ // The binary resource table lists resource entries for each configuration.
+ // We store them inverted, where a resource entry lists the values for each
+ // configuration available. Here we reverse this to match the binary table.
+ std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
+ for (ResourceEntry* entry : sortedEntries) {
+ const uint32_t keyIndex = (uint32_t) mKeyPool.makeRef(entry->name).getIndex();
+
+ // Group values by configuration.
+ for (auto& configValue : entry->values) {
+ configToEntryListMap[configValue->config].push_back(FlatEntry{
+ entry, configValue->value.get(), keyIndex });
+ }
+ }
+
+ // Flatten a configuration value.
+ for (auto& entry : configToEntryListMap) {
+ if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+};
+
+} // namespace
+
+bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
+ // We must do this before writing the resources, since the string pool IDs may change.
+ table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ int diff = a.context.priority - b.context.priority;
+ if (diff < 0) return true;
+ if (diff > 0) return false;
+ diff = a.context.config.compare(b.context.config);
+ if (diff < 0) return true;
+ if (diff > 0) return false;
+ return a.value < b.value;
+ });
+ table->stringPool.prune();
+
+ // Write the ResTable header.
+ ChunkWriter tableWriter(mBuffer);
+ ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
+ tableHeader->packageCount = util::hostToDevice32(table->packages.size());
+
+ // Flatten the values string pool.
+ StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
+
+ BigBuffer packageBuffer(1024);
+
+ // Flatten each package.
+ for (auto& package : table->packages) {
+ PackageFlattener flattener(context->getDiagnostics(), package.get());
+ if (!flattener.flattenPackage(&packageBuffer)) {
+ return false;
+ }
+ }
+
+ // Finally merge all the packages into the main buffer.
+ tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
+ tableWriter.finish();
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ManifestParser.h b/tools/aapt2/flatten/TableFlattener.h
index f2e43d4bb695..0ab01974044b 100644
--- a/tools/aapt2/ManifestParser.h
+++ b/tools/aapt2/flatten/TableFlattener.h
@@ -14,32 +14,27 @@
* limitations under the License.
*/
-#ifndef AAPT_MANIFEST_PARSER_H
-#define AAPT_MANIFEST_PARSER_H
+#ifndef AAPT_FLATTEN_TABLEFLATTENER_H
+#define AAPT_FLATTEN_TABLEFLATTENER_H
-#include "AppInfo.h"
-#include "Logger.h"
-#include "Source.h"
-#include "XmlPullParser.h"
+#include "process/IResourceTableConsumer.h"
namespace aapt {
-/*
- * Parses an AndroidManifest.xml file and fills in an AppInfo structure with
- * app data.
- */
-class ManifestParser {
+class BigBuffer;
+class ResourceTable;
+
+class TableFlattener : public IResourceTableConsumer {
public:
- ManifestParser() = default;
- ManifestParser(const ManifestParser&) = delete;
+ TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {
+ }
- bool parse(const Source& source, std::shared_ptr<XmlPullParser> parser, AppInfo* outInfo);
+ bool consume(IAaptContext* context, ResourceTable* table) override;
private:
- bool parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
- AppInfo* outInfo);
+ BigBuffer* mBuffer;
};
} // namespace aapt
-#endif // AAPT_MANIFEST_PARSER_H
+#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
new file mode 100644
index 000000000000..39c4fd318508
--- /dev/null
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flatten/TableFlattener.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "util/Util.h"
+
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace aapt {
+
+class TableFlattenerTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ mContext = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .build();
+ }
+
+ ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.consume(mContext.get(), table)) {
+ return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+ }
+
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) {
+ return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ ::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.consume(mContext.get(), table)) {
+ return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+ }
+
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), buffer.size());
+ if (!parser.parse()) {
+ return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ ::testing::AssertionResult exists(ResTable* table,
+ const StringPiece16& expectedName,
+ const ResourceId expectedId,
+ const ConfigDescription& expectedConfig,
+ const uint8_t expectedDataType, const uint32_t expectedData,
+ const uint32_t expectedSpecFlags) {
+ const ResourceName expectedResName = test::parseNameOrDie(expectedName);
+
+ table->setParameters(&expectedConfig);
+
+ ResTable_config config;
+ Res_value val;
+ uint32_t specFlags;
+ if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < 0) {
+ return ::testing::AssertionFailure() << "could not find resource with";
+ }
+
+ if (expectedDataType != val.dataType) {
+ return ::testing::AssertionFailure()
+ << "expected data type "
+ << std::hex << (int) expectedDataType << " but got data type "
+ << (int) val.dataType << std::dec << " instead";
+ }
+
+ if (expectedData != val.data) {
+ return ::testing::AssertionFailure()
+ << "expected data "
+ << std::hex << expectedData << " but got data "
+ << val.data << std::dec << " instead";
+ }
+
+ if (expectedSpecFlags != specFlags) {
+ return ::testing::AssertionFailure()
+ << "expected specFlags "
+ << std::hex << expectedSpecFlags << " but got specFlags "
+ << specFlags << std::dec << " instead";
+ }
+
+ ResTable::resource_name actualName;
+ if (!table->getResourceName(expectedId.id, false, &actualName)) {
+ return ::testing::AssertionFailure() << "failed to find resource name";
+ }
+
+ StringPiece16 package16(actualName.package, actualName.packageLen);
+ if (package16 != expectedResName.package) {
+ return ::testing::AssertionFailure()
+ << "expected package '" << expectedResName.package << "' but got '"
+ << package16 << "'";
+ }
+
+ StringPiece16 type16(actualName.type, actualName.typeLen);
+ if (type16 != toString(expectedResName.type)) {
+ return ::testing::AssertionFailure()
+ << "expected type '" << expectedResName.type
+ << "' but got '" << type16 << "'";
+ }
+
+ StringPiece16 name16(actualName.name, actualName.nameLen);
+ if (name16 != expectedResName.entry) {
+ return ::testing::AssertionFailure()
+ << "expected name '" << expectedResName.entry
+ << "' but got '" << name16 << "'";
+ }
+
+ if (expectedConfig != config) {
+ return ::testing::AssertionFailure()
+ << "expected config '" << expectedConfig << "' but got '"
+ << ConfigDescription(config) << "'";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+private:
+ std::unique_ptr<IAaptContext> mContext;
+};
+
+TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020000))
+ .addSimple(u"@com.app.test:id/two", ResourceId(0x7f020001))
+ .addValue(u"@com.app.test:id/three", ResourceId(0x7f020002),
+ test::buildReference(u"@com.app.test:id/one", ResourceId(0x7f020000)))
+ .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000),
+ util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
+ .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000),
+ test::parseConfigOrDie("v1"),
+ util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
+ .addString(u"@com.app.test:string/test", ResourceId(0x7f040000), u"foo")
+ .addString(u"@com.app.test:layout/bar", ResourceId(0x7f050000), u"res/layout/bar.xml")
+ .build();
+
+ ResTable resTable;
+ ASSERT_TRUE(flatten(table.get(), &resTable));
+
+ EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020000), {},
+ Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+ EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/two", ResourceId(0x7f020001), {},
+ Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+ EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020002), {},
+ Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+
+ EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000),
+ {}, Res_value::TYPE_INT_DEC, 1u,
+ ResTable_config::CONFIG_VERSION));
+
+ EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000),
+ test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
+ ResTable_config::CONFIG_VERSION));
+
+ StringPiece16 fooStr = u"foo";
+ ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(exists(&resTable, u"@com.app.test:string/test", ResourceId(0x7f040000),
+ {}, Res_value::TYPE_STRING, (uint32_t) idx, 0u));
+
+ StringPiece16 barPath = u"res/layout/bar.xml";
+ idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(exists(&resTable, u"@com.app.test:layout/bar", ResourceId(0x7f050000), {},
+ Res_value::TYPE_STRING, (uint32_t) idx, 0u));
+}
+
+TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020001))
+ .addSimple(u"@com.app.test:id/three", ResourceId(0x7f020003))
+ .build();
+
+ ResTable resTable;
+ ASSERT_TRUE(flatten(table.get(), &resTable));
+
+ EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020001), {},
+ Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+ EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020003), {},
+ Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+}
+
+TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
+ Attribute attr(false);
+ attr.typeMask = android::ResTable_map::TYPE_INTEGER;
+ attr.minInt = 10;
+ attr.maxInt = 23;
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+ util::make_unique<Attribute>(attr))
+ .build();
+
+ ResourceTable result;
+ ASSERT_TRUE(flatten(table.get(), &result));
+
+ Attribute* actualAttr = test::getValue<Attribute>(&result, u"@android:attr/foo");
+ ASSERT_NE(nullptr, actualAttr);
+ EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
+ EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
+ EXPECT_EQ(attr.minInt, actualAttr->minInt);
+ EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
new file mode 100644
index 000000000000..570cd9635de3
--- /dev/null
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SdkConstants.h"
+#include "flatten/ChunkWriter.h"
+#include "flatten/ResourceTypeExtensions.h"
+#include "flatten/XmlFlattener.h"
+#include "xml/XmlDom.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <algorithm>
+#include <utils/misc.h>
+#include <vector>
+
+using namespace android;
+
+namespace aapt {
+
+namespace {
+
+constexpr uint32_t kLowPriority = 0xffffffffu;
+
+struct XmlFlattenerVisitor : public xml::Visitor {
+ using xml::Visitor::visit;
+
+ BigBuffer* mBuffer;
+ XmlFlattenerOptions mOptions;
+ StringPool mPool;
+ std::map<uint8_t, StringPool> mPackagePools;
+
+ struct StringFlattenDest {
+ StringPool::Ref ref;
+ ResStringPool_ref* dest;
+ };
+ std::vector<StringFlattenDest> mStringRefs;
+
+ // Scratch vector to filter attributes. We avoid allocations
+ // making this a member.
+ std::vector<xml::Attribute*> mFilteredAttrs;
+
+
+ XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) :
+ mBuffer(buffer), mOptions(options) {
+ }
+
+ void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
+ if (!str.empty()) {
+ mStringRefs.push_back(StringFlattenDest{
+ mPool.makeRef(str, StringPool::Context{ priority }),
+ dest });
+ } else {
+ // The device doesn't think a string of size 0 is the same as null.
+ dest->index = util::deviceToHost32(-1);
+ }
+ }
+
+ void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
+ mStringRefs.push_back(StringFlattenDest{ ref, dest });
+ }
+
+ void writeNamespace(xml::Namespace* node, uint16_t type) {
+ ChunkWriter writer(mBuffer);
+
+ ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
+ flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+ flatNode->comment.index = util::hostToDevice32(-1);
+
+ ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>();
+ addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
+ addString(node->namespaceUri, kLowPriority, &flatNs->uri);
+
+ writer.finish();
+ }
+
+ void visit(xml::Namespace* node) override {
+ writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
+ xml::Visitor::visit(node);
+ writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
+ }
+
+ void visit(xml::Text* node) override {
+ if (util::trimWhitespace(node->text).empty()) {
+ // Skip whitespace only text nodes.
+ return;
+ }
+
+ ChunkWriter writer(mBuffer);
+ ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
+ flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+ flatNode->comment.index = util::hostToDevice32(-1);
+
+ ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
+ addString(node->text, kLowPriority, &flatText->data);
+
+ writer.finish();
+ }
+
+ void visit(xml::Element* node) override {
+ {
+ ChunkWriter startWriter(mBuffer);
+ ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>(
+ RES_XML_START_ELEMENT_TYPE);
+ flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+ flatNode->comment.index = util::hostToDevice32(-1);
+
+ ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>();
+ addString(node->namespaceUri, kLowPriority, &flatElem->ns);
+ addString(node->name, kLowPriority, &flatElem->name);
+ flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
+ flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute));
+
+ writeAttributes(node, flatElem, &startWriter);
+
+ startWriter.finish();
+ }
+
+ xml::Visitor::visit(node);
+
+ {
+ ChunkWriter endWriter(mBuffer);
+ ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>(
+ RES_XML_END_ELEMENT_TYPE);
+ flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
+ flatEndNode->comment.index = util::hostToDevice32(-1);
+
+ ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
+ addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
+ addString(node->name, kLowPriority, &flatEndElem->name);
+
+ endWriter.finish();
+ }
+ }
+
+ static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) {
+ if (a->compiledAttribute && a->compiledAttribute.value().id) {
+ if (b->compiledAttribute && b->compiledAttribute.value().id) {
+ return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value();
+ }
+ return true;
+ } else if (!b->compiledAttribute) {
+ int diff = a->namespaceUri.compare(b->namespaceUri);
+ if (diff < 0) {
+ return true;
+ } else if (diff > 0) {
+ return false;
+ }
+ return a->name < b->name;
+ }
+ return false;
+ }
+
+ void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) {
+ mFilteredAttrs.clear();
+ mFilteredAttrs.reserve(node->attributes.size());
+
+ // Filter the attributes.
+ for (xml::Attribute& attr : node->attributes) {
+ if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) {
+ size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
+ if (sdkLevel > mOptions.maxSdkLevel.value()) {
+ continue;
+ }
+ }
+ mFilteredAttrs.push_back(&attr);
+ }
+
+ if (mFilteredAttrs.empty()) {
+ return;
+ }
+
+ const ResourceId kIdAttr(0x010100d0);
+
+ std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById);
+
+ flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
+
+ ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>(
+ mFilteredAttrs.size());
+ uint16_t attributeIndex = 1;
+ for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
+ // Assign the indices for specific attributes.
+ if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
+ xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
+ flatElem->idIndex = util::hostToDevice16(attributeIndex);
+ } else if (xmlAttr->namespaceUri.empty()) {
+ if (xmlAttr->name == u"class") {
+ flatElem->classIndex = util::hostToDevice16(attributeIndex);
+ } else if (xmlAttr->name == u"style") {
+ flatElem->styleIndex = util::hostToDevice16(attributeIndex);
+ }
+ }
+ attributeIndex++;
+
+ // Add the namespaceUri to the list of StringRefs to encode.
+ addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
+
+ flatAttr->rawValue.index = util::hostToDevice32(-1);
+
+ if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) {
+ // The attribute has no associated ResourceID, so the string order doesn't matter.
+ addString(xmlAttr->name, kLowPriority, &flatAttr->name);
+ } else {
+ // Attribute names are stored without packages, but we use
+ // their StringPool index to lookup their resource IDs.
+ // This will cause collisions, so we can't dedupe
+ // attribute names from different packages. We use separate
+ // pools that we later combine.
+ //
+ // Lookup the StringPool for this package and make the reference there.
+ const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
+
+ StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef(
+ xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id });
+
+ // Add it to the list of strings to flatten.
+ addString(nameRef, &flatAttr->name);
+ }
+
+ if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
+ // Keep raw values if the value is not compiled or
+ // if we're building a static library (need symbols).
+ addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
+ }
+
+ if (xmlAttr->compiledValue) {
+ bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
+ assert(result);
+ } else {
+ // Flatten as a regular string type.
+ flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ addString(xmlAttr->value, kLowPriority,
+ (ResStringPool_ref*) &flatAttr->typedValue.data);
+ }
+
+ flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue));
+ flatAttr++;
+ }
+ }
+};
+
+} // namespace
+
+bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
+ BigBuffer nodeBuffer(1024);
+ XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
+ node->accept(&visitor);
+
+ // Merge the package pools into the main pool.
+ for (auto& packagePoolEntry : visitor.mPackagePools) {
+ visitor.mPool.merge(std::move(packagePoolEntry.second));
+ }
+
+ // Sort the string pool so that attribute resource IDs show up first.
+ visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ return a.context.priority < b.context.priority;
+ });
+
+ // Now we flatten the string pool references into the correct places.
+ for (const auto& refEntry : visitor.mStringRefs) {
+ refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
+ }
+
+ // Write the XML header.
+ ChunkWriter xmlHeaderWriter(mBuffer);
+ xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
+
+ // Flatten the StringPool.
+ StringPool::flattenUtf16(mBuffer, visitor.mPool);
+
+ {
+ // Write the array of resource IDs, indexed by StringPool order.
+ ChunkWriter resIdMapWriter(mBuffer);
+ resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
+ for (const auto& str : visitor.mPool) {
+ ResourceId id = { str->context.priority };
+ if (id.id == kLowPriority || !id.isValid()) {
+ // When we see the first non-resource ID,
+ // we're done.
+ break;
+ }
+
+ *resIdMapWriter.nextBlock<uint32_t>() = id.id;
+ }
+ resIdMapWriter.finish();
+ }
+
+ // Move the nodeBuffer and append it to the out buffer.
+ mBuffer->appendBuffer(std::move(nodeBuffer));
+
+ // Finish the xml header.
+ xmlHeaderWriter.finish();
+ return true;
+}
+
+bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
+ if (!resource->root) {
+ return false;
+ }
+ return flatten(context, resource->root.get());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
new file mode 100644
index 000000000000..a688ac965b0d
--- /dev/null
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_XMLFLATTENER_H
+#define AAPT_FLATTEN_XMLFLATTENER_H
+
+#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+
+struct XmlFlattenerOptions {
+ /**
+ * Keep attribute raw string values along with typed values.
+ */
+ bool keepRawValues = false;
+
+ /**
+ * If set, the max SDK level of attribute to flatten. All others are ignored.
+ */
+ Maybe<size_t> maxSdkLevel;
+};
+
+class XmlFlattener : public IXmlResourceConsumer {
+public:
+ XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) :
+ mBuffer(buffer), mOptions(options) {
+ }
+
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+
+private:
+ BigBuffer* mBuffer;
+ XmlFlattenerOptions mOptions;
+
+ bool flatten(IAaptContext* context, xml::Node* node);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_XMLFLATTENER_H */
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
new file mode 100644
index 000000000000..4e6eb811e572
--- /dev/null
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flatten/XmlFlattener.h"
+#include "link/Linkers.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+class XmlFlattenerTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ mContext = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+ .addSymbolSource(test::StaticSymbolSourceBuilder()
+ .addSymbol(u"@android:attr/id", ResourceId(0x010100d0),
+ test::AttributeBuilder().build())
+ .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f020000))
+ .addSymbol(u"@android:attr/paddingStart", ResourceId(0x010103b3),
+ test::AttributeBuilder().build())
+ .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+ test::AttributeBuilder().build())
+ .build())
+ .build();
+ }
+
+ ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree,
+ XmlFlattenerOptions options = {}) {
+ using namespace android; // For NO_ERROR on windows because it is a macro.
+
+ BigBuffer buffer(1024);
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.consume(mContext.get(), doc)) {
+ return ::testing::AssertionFailure() << "failed to flatten XML Tree";
+ }
+
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
+ return ::testing::AssertionFailure() << "flattened XML is corrupt";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+protected:
+ std::unique_ptr<IAaptContext> mContext;
+};
+
+TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ <View xmlns:test="http://com.test"
+ attr="hey">
+ <Layout test:hello="hi" />
+ <Layout>Some text</Layout>
+ </View>)EOF");
+
+
+ android::ResXMLTree tree;
+ ASSERT_TRUE(flatten(doc.get(), &tree));
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
+
+ size_t len;
+ const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+
+ const char16_t* namespaceUri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ const char16_t* tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"View");
+
+ ASSERT_EQ(1u, tree.getAttributeCount());
+ ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr);
+ const char16_t* attrName = tree.getAttributeName(0, &len);
+ EXPECT_EQ(StringPiece16(attrName, len), u"attr");
+
+ EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size()));
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+
+ ASSERT_EQ(1u, tree.getAttributeCount());
+ const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len);
+ EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test");
+
+ attrName = tree.getAttributeName(0, &len);
+ EXPECT_EQ(StringPiece16(attrName, len), u"hello");
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+ ASSERT_EQ(0u, tree.getAttributeCount());
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
+ const char16_t* text = tree.getText(&len);
+ EXPECT_EQ(StringPiece16(text, len), u"Some text");
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"View");
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
+ namespacePrefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+
+ namespaceUri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
+}
+
+TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingStart="1dp"
+ android:colorAccent="#ffffff"/>)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ ASSERT_TRUE(linker.getSdkLevels().count(17) == 1);
+ ASSERT_TRUE(linker.getSdkLevels().count(21) == 1);
+
+ android::ResXMLTree tree;
+ XmlFlattenerOptions options;
+ options.maxSdkLevel = 17;
+ ASSERT_TRUE(flatten(doc.get(), &tree, options));
+
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+ }
+
+ ASSERT_EQ(1u, tree.getAttributeCount());
+ EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
+}
+
+TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@id/id"
+ class="str"
+ style="@id/id"/>)EOF");
+
+ android::ResXMLTree tree;
+ ASSERT_TRUE(flatten(doc.get(), &tree));
+
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+ }
+
+ EXPECT_EQ(tree.indexOfClass(), 0);
+ EXPECT_EQ(tree.indexOfStyle(), 1);
+}
+
+/*
+ * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
+ * namespace.
+ */
+TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
+
+ android::ResXMLTree tree;
+ ASSERT_TRUE(flatten(doc.get(), &tree));
+
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+ }
+
+ const StringPiece16 kPackage = u"package";
+ EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/integration-tests/Android.mk b/tools/aapt2/integration-tests/Android.mk
new file mode 100644
index 000000000000..6361f9b8ae7d
--- /dev/null
+++ b/tools/aapt2/integration-tests/Android.mk
@@ -0,0 +1,2 @@
+LOCAL_PATH := $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/AppOne/Android.mk
new file mode 100644
index 000000000000..bc40a6269382
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/Android.mk
@@ -0,0 +1,28 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_PACKAGE_NAME := AaptTestAppOne
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ AaptTestStaticLibOne \
+ AaptTestStaticLibTwo
+LOCAL_AAPT_FLAGS := --no-version-vectors
+include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml
new file mode 100644
index 000000000000..b6d8f2d1b748
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 package="com.android.aapt.app.one" />
diff --git a/tools/aapt2/data/res/drawable/icon.png b/tools/aapt2/integration-tests/AppOne/res/drawable/icon.png
index 4bff9b900643..4bff9b900643 100644
--- a/tools/aapt2/data/res/drawable/icon.png
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/icon.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml b/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml
new file mode 100644
index 000000000000..6132a75d85d0
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector />
diff --git a/tools/aapt2/data/res/drawable/test.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/test.9.png
index 33daa117ea9d..33daa117ea9d 100644
--- a/tools/aapt2/data/res/drawable/test.9.png
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/test.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml b/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml
new file mode 100644
index 000000000000..9f5a4a85cbcf
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:support="http://schemas.android.com/apk/res/android.appcompat"
+ android:id="@+id/view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+</LinearLayout>
diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/integration-tests/AppOne/res/layout/main.xml
index 50a51d99ad0a..ab1a251a7d56 100644
--- a/tools/aapt2/data/res/layout/main.xml
+++ b/tools/aapt2/integration-tests/AppOne/res/layout/main.xml
@@ -1,4 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:support="http://schemas.android.com/apk/res/android.appcompat"
android:id="@+id/view"
@@ -14,8 +29,8 @@
android:layout_width="1dp"
android:onClick="doClick"
android:text="@{user.name}"
+ android:background="#ffffff"
android:layout_height="match_parent"
- app:layout_width="@support:bool/allow"
app:flags="complex|weak"
android:colorAccent="#ffffff"/>
</LinearLayout>
diff --git a/tools/aapt2/integration-tests/AppOne/res/raw/test.txt b/tools/aapt2/integration-tests/AppOne/res/raw/test.txt
new file mode 100644
index 000000000000..b14df6442ea5
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/raw/test.txt
@@ -0,0 +1 @@
+Hi
diff --git a/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml b/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml
new file mode 100644
index 000000000000..d8c11e210eda
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <style name="App" parent="android:Theme.Material">
+ <item name="android:colorAccent">@color/accent</item>
+ <item name="android:text">Hey</item>
+ </style>
+</resources>
diff --git a/tools/aapt2/integration-tests/AppOne/res/values/colors.xml b/tools/aapt2/integration-tests/AppOne/res/values/colors.xml
new file mode 100644
index 000000000000..4df5077051d2
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <color name="primary">#f44336</color>
+ <color name="primary_dark">#b71c1c</color>
+ <color name="accent">#fdd835</color>
+</resources>
diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/integration-tests/AppOne/res/values/styles.xml
index d0b19a3a881b..f05845cfab28 100644
--- a/tools/aapt2/data/res/values/styles.xml
+++ b/tools/aapt2/integration-tests/AppOne/res/values/styles.xml
@@ -1,6 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
<resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat">
- <style name="App" parent="android.appcompat:Platform.AppCompat">
+ <style name="App">
<item name="android:background">@color/primary</item>
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorPrimaryDark">@color/primary_dark</item>
@@ -8,8 +23,8 @@
</style>
<attr name="custom" format="reference" />
<style name="Pop">
- <item name="custom">@drawable/image</item>
- <item name="android:focusable">@lib:bool/allow</item>
+ <item name="custom">@android:drawable/btn_default</item>
+ <item name="android:focusable">true</item>
</style>
<string name="yo">@string/wow</string>
diff --git a/tools/aapt2/integration-tests/AppOne/res/values/test.xml b/tools/aapt2/integration-tests/AppOne/res/values/test.xml
new file mode 100644
index 000000000000..f4b7471aefae
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/values/test.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Reference the two static libraries -->
+ <string name="AppFooBar">@string/FooBar</string>
+ <string name="AppFoo">@string/Foo</string>
+
+ <string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string>
+ <public name="hooha" type="string" id="0x7f020001"/>
+ <string name="wow">@android:string/ok</string>
+ <public name="layout_width" type="attr" />
+ <attr name="layout_width" format="boolean" />
+ <attr name="flags">
+ <flag name="complex" value="1" />
+ <flag name="pub" value="2" />
+ <flag name="weak" value="4" />
+ </attr>
+</resources>
diff --git a/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java b/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java
new file mode 100644
index 000000000000..472b35a781fe
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.aapt.app.one;
+
+public class AppOne {
+ // IDs from StaticLibOne
+ public static int FooId = com.android.aapt.staticlib.one.R.string.Foo;
+ public static int LayoutId = com.android.aapt.staticlib.one.R.layout.layout;
+
+ // IDs from StaticLibTwo
+ public static int FooBarId = com.android.aapt.staticlib.two.R.string.FooBar;
+}
+
diff --git a/tools/aapt2/integration-tests/StaticLibOne/Android.mk b/tools/aapt2/integration-tests/StaticLibOne/Android.mk
new file mode 100644
index 000000000000..0b7129aa0d38
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibOne/Android.mk
@@ -0,0 +1,28 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := AaptTestStaticLibOne
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+# We need this to compile the Java sources of AaptTestStaticLibTwo using javac.
+LOCAL_JAR_EXCLUDE_FILES := none
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml
new file mode 100644
index 000000000000..705047e71300
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 package="com.android.aapt.staticlib.one" />
diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml b/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml
new file mode 100644
index 000000000000..683c91cd9cf5
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:text="@string/Foo" />
diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml
new file mode 100644
index 000000000000..d09a4851f7b4
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <!-- An attribute from StaticLibOne -->
+ <attr name="StaticLibOne_attr" format="string" />
+
+ <string name="Foo">Foo</string>
+ <string name="Foo" product="tablet">Bar</string>
+
+ <declare-styleable name="Widget">
+ <attr name="StaticLibOne_attr" />
+ </declare-styleable>
+</resources>
diff --git a/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java b/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java
new file mode 100644
index 000000000000..cf48f67056cf
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.aapt.staticlib.one;
+
+public class StaticLibOne {
+ // IDs from StaticLibOne
+ public static int FooId = com.android.aapt.staticlib.one.R.string.Foo;
+ public static int LayoutId = com.android.aapt.staticlib.one.R.layout.layout;
+}
diff --git a/tools/aapt2/integration-tests/StaticLibTwo/Android.mk b/tools/aapt2/integration-tests/StaticLibTwo/Android.mk
new file mode 100644
index 000000000000..8b6eb41b08cd
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibTwo/Android.mk
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := AaptTestStaticLibTwo
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := AaptTestStaticLibOne
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
diff --git a/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml
new file mode 100644
index 000000000000..28f069932452
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 package="com.android.aapt.staticlib.two" />
diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml b/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml
new file mode 100644
index 000000000000..dd5979f7e838
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="1123"/>
diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml b/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml
new file mode 100644
index 000000000000..ba9830708eb0
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<View xmlns:custom="http://schemas.android.com/apk/res-auto"
+ custom:StaticLibOne_attr="@string/FooBar" />
diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml
new file mode 100644
index 000000000000..97bb2a53d9f6
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="FooBar">@string/Foo</string>
+</resources>
diff --git a/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java b/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java
new file mode 100644
index 000000000000..7110dcdd017a
--- /dev/null
+++ b/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.aapt.staticlib.two;
+
+public class StaticLibTwo {
+ // IDs from StaticLibOne
+ public static int FooId = com.android.aapt.staticlib.one.R.string.Foo;
+ public static int LayoutId = com.android.aapt.staticlib.one.R.layout.layout;
+
+ // IDs from StaticLibTwo
+ public static int FooBarId = com.android.aapt.staticlib.two.R.string.FooBar;
+}
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
new file mode 100644
index 000000000000..467e60464a68
--- /dev/null
+++ b/tools/aapt2/io/Data.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_IO_DATA_H
+#define AAPT_IO_DATA_H
+
+#include <utils/FileMap.h>
+
+#include <memory>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a block of contiguous memory. An instance of this interface owns the data.
+ */
+class IData {
+public:
+ virtual ~IData() = default;
+
+ virtual const void* data() const = 0;
+ virtual size_t size() const = 0;
+};
+
+/**
+ * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this
+ * object.
+ */
+class MmappedData : public IData {
+public:
+ explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) {
+ }
+
+ const void* data() const override {
+ return mMap.getDataPtr();
+ }
+
+ size_t size() const override {
+ return mMap.getDataLength();
+ }
+
+private:
+ android::FileMap mMap;
+};
+
+/**
+ * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The
+ * memory is owned by this object.
+ */
+class MallocData : public IData {
+public:
+ MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) :
+ mData(std::move(data)), mSize(size) {
+ }
+
+ const void* data() const override {
+ return mData.get();
+ }
+
+ size_t size() const override {
+ return mSize;
+ }
+
+private:
+ std::unique_ptr<const uint8_t[]> mData;
+ size_t mSize;
+};
+
+/**
+ * When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0.
+ */
+class EmptyData : public IData {
+public:
+ const void* data() const override {
+ static const uint8_t d = 0;
+ return &d;
+ }
+
+ size_t size() const override {
+ return 0u;
+ }
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_DATA_H */
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
new file mode 100644
index 000000000000..b4d49719aa3e
--- /dev/null
+++ b/tools/aapt2/io/File.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_IO_FILE_H
+#define AAPT_IO_FILE_H
+
+#include "Source.h"
+#include "io/Data.h"
+
+#include <memory>
+#include <vector>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a file, which could be a real file on the file system, or a file inside
+ * a ZIP archive.
+ */
+class IFile {
+public:
+ virtual ~IFile() = default;
+
+ /**
+ * Open the file and return it as a block of contiguous memory. How this occurs is
+ * implementation dependent. For example, if this is a file on the file system, it may
+ * simply mmap the contents. If this file represents a compressed file in a ZIP archive,
+ * it may need to inflate it to memory, incurring a copy.
+ *
+ * Returns nullptr on failure.
+ */
+ virtual std::unique_ptr<IData> openAsData() = 0;
+
+ /**
+ * Returns the source of this file. This is for presentation to the user and may not be a
+ * valid file system path (for example, it may contain a '@' sign to separate the files within
+ * a ZIP archive from the path to the containing ZIP archive.
+ */
+ virtual const Source& getSource() const = 0;
+};
+
+class IFileCollectionIterator {
+public:
+ virtual ~IFileCollectionIterator() = default;
+
+ virtual bool hasNext() = 0;
+ virtual IFile* next() = 0;
+};
+
+/**
+ * Interface for a collection of files, all of which share a common source. That source may
+ * simply be the filesystem, or a ZIP archive.
+ */
+class IFileCollection {
+public:
+ virtual ~IFileCollection() = default;
+
+ virtual IFile* findFile(const StringPiece& path) = 0;
+ virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_FILE_H */
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
new file mode 100644
index 000000000000..e758d8a421e1
--- /dev/null
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Source.h"
+#include "io/FileSystem.h"
+#include "util/Files.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <utils/FileMap.h>
+
+namespace aapt {
+namespace io {
+
+RegularFile::RegularFile(const Source& source) : mSource(source) {
+}
+
+std::unique_ptr<IData> RegularFile::openAsData() {
+ android::FileMap map;
+ if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) {
+ if (map.value().getDataPtr() && map.value().getDataLength() > 0) {
+ return util::make_unique<MmappedData>(std::move(map.value()));
+ }
+ return util::make_unique<EmptyData>();
+ }
+ return {};
+}
+
+const Source& RegularFile::getSource() const {
+ return mSource;
+}
+
+FileCollectionIterator::FileCollectionIterator(FileCollection* collection) :
+ mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) {
+}
+
+bool FileCollectionIterator::hasNext() {
+ return mCurrent != mEnd;
+}
+
+IFile* FileCollectionIterator::next() {
+ IFile* result = mCurrent->second.get();
+ ++mCurrent;
+ return result;
+}
+
+IFile* FileCollection::insertFile(const StringPiece& path) {
+ return (mFiles[path.toString()] = util::make_unique<RegularFile>(Source(path))).get();
+}
+
+IFile* FileCollection::findFile(const StringPiece& path) {
+ auto iter = mFiles.find(path.toString());
+ if (iter != mFiles.end()) {
+ return iter->second.get();
+ }
+ return nullptr;
+}
+
+std::unique_ptr<IFileCollectionIterator> FileCollection::iterator() {
+ return util::make_unique<FileCollectionIterator>(this);
+}
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
new file mode 100644
index 000000000000..f0559c03a8b8
--- /dev/null
+++ b/tools/aapt2/io/FileSystem.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_IO_FILESYSTEM_H
+#define AAPT_IO_FILESYSTEM_H
+
+#include "io/File.h"
+
+#include <map>
+
+namespace aapt {
+namespace io {
+
+/**
+ * A regular file from the file system. Uses mmap to open the data.
+ */
+class RegularFile : public IFile {
+public:
+ RegularFile(const Source& source);
+
+ std::unique_ptr<IData> openAsData() override;
+ const Source& getSource() const override;
+
+private:
+ Source mSource;
+};
+
+class FileCollection;
+
+class FileCollectionIterator : public IFileCollectionIterator {
+public:
+ FileCollectionIterator(FileCollection* collection);
+
+ bool hasNext() override;
+ io::IFile* next() override;
+
+private:
+ std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
+};
+
+/**
+ * An IFileCollection representing the file system.
+ */
+class FileCollection : public IFileCollection {
+public:
+ /**
+ * Adds a file located at path. Returns the IFile representation of that file.
+ */
+ IFile* insertFile(const StringPiece& path);
+ IFile* findFile(const StringPiece& path) override;
+ std::unique_ptr<IFileCollectionIterator> iterator() override;
+
+private:
+ friend class FileCollectionIterator;
+ std::map<std::string, std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_FILESYSTEM_H
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
new file mode 100644
index 000000000000..b3e7a02102e4
--- /dev/null
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Source.h"
+#include "io/ZipArchive.h"
+#include "util/Util.h"
+
+#include <utils/FileMap.h>
+#include <ziparchive/zip_archive.h>
+
+namespace aapt {
+namespace io {
+
+ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) :
+ mZipHandle(handle), mZipEntry(entry), mSource(source) {
+}
+
+std::unique_ptr<IData> ZipFile::openAsData() {
+ if (mZipEntry.method == kCompressStored) {
+ int fd = GetFileDescriptor(mZipHandle);
+
+ android::FileMap fileMap;
+ bool result = fileMap.create(nullptr, fd, mZipEntry.offset,
+ mZipEntry.uncompressed_length, true);
+ if (!result) {
+ return {};
+ }
+ return util::make_unique<MmappedData>(std::move(fileMap));
+
+ } else {
+ std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(
+ new uint8_t[mZipEntry.uncompressed_length]);
+ int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(),
+ static_cast<uint32_t>(mZipEntry.uncompressed_length));
+ if (result != 0) {
+ return {};
+ }
+ return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length);
+ }
+}
+
+const Source& ZipFile::getSource() const {
+ return mSource;
+}
+
+ZipFileCollectionIterator::ZipFileCollectionIterator(ZipFileCollection* collection) :
+ mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) {
+}
+
+bool ZipFileCollectionIterator::hasNext() {
+ return mCurrent != mEnd;
+}
+
+IFile* ZipFileCollectionIterator::next() {
+ IFile* result = mCurrent->second.get();
+ ++mCurrent;
+ return result;
+}
+
+ZipFileCollection::ZipFileCollection() : mHandle(nullptr) {
+}
+
+std::unique_ptr<ZipFileCollection> ZipFileCollection::create(const StringPiece& path,
+ std::string* outError) {
+ constexpr static const int32_t kEmptyArchive = -6;
+
+ std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>(
+ new ZipFileCollection());
+
+ int32_t result = OpenArchive(path.data(), &collection->mHandle);
+ if (result != 0) {
+ // If a zip is empty, result will be an error code. This is fine and we should
+ // return an empty ZipFileCollection.
+ if (result == kEmptyArchive) {
+ return collection;
+ }
+
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+
+ void* cookie = nullptr;
+ result = StartIteration(collection->mHandle, &cookie, nullptr, nullptr);
+ if (result != 0) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+
+ using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
+ IterationEnder iterationEnder(cookie, EndIteration);
+
+ ZipString zipEntryName;
+ ZipEntry zipData;
+ while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) {
+ std::string zipEntryPath = std::string(reinterpret_cast<const char*>(zipEntryName.name),
+ zipEntryName.name_length);
+ std::string nestedPath = path.toString() + "@" + zipEntryPath;
+ collection->mFiles[zipEntryPath] = util::make_unique<ZipFile>(collection->mHandle,
+ zipData,
+ Source(nestedPath));
+ }
+
+ if (result != -1) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+ return collection;
+}
+
+IFile* ZipFileCollection::findFile(const StringPiece& path) {
+ auto iter = mFiles.find(path.toString());
+ if (iter != mFiles.end()) {
+ return iter->second.get();
+ }
+ return nullptr;
+}
+
+std::unique_ptr<IFileCollectionIterator> ZipFileCollection::iterator() {
+ return util::make_unique<ZipFileCollectionIterator>(this);
+}
+
+ZipFileCollection::~ZipFileCollection() {
+ if (mHandle) {
+ CloseArchive(mHandle);
+ }
+}
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
new file mode 100644
index 000000000000..5ad119d1d6d4
--- /dev/null
+++ b/tools/aapt2/io/ZipArchive.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_IO_ZIPARCHIVE_H
+#define AAPT_IO_ZIPARCHIVE_H
+
+#include "io/File.h"
+#include "util/StringPiece.h"
+
+#include <map>
+#include <ziparchive/zip_archive.h>
+
+namespace aapt {
+namespace io {
+
+/**
+ * An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
+ * and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
+ */
+class ZipFile : public IFile {
+public:
+ ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source);
+
+ std::unique_ptr<IData> openAsData() override;
+ const Source& getSource() const override;
+
+private:
+ ZipArchiveHandle mZipHandle;
+ ZipEntry mZipEntry;
+ Source mSource;
+};
+
+class ZipFileCollection;
+
+class ZipFileCollectionIterator : public IFileCollectionIterator {
+public:
+ ZipFileCollectionIterator(ZipFileCollection* collection);
+
+ bool hasNext() override;
+ io::IFile* next() override;
+
+private:
+ std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
+};
+
+/**
+ * An IFileCollection that represents a ZIP archive and the entries within it.
+ */
+class ZipFileCollection : public IFileCollection {
+public:
+ static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
+ std::string* outError);
+
+ io::IFile* findFile(const StringPiece& path) override;
+ std::unique_ptr<IFileCollectionIterator> iterator() override;
+
+ ~ZipFileCollection() override;
+
+private:
+ friend class ZipFileCollectionIterator;
+ ZipFileCollection();
+
+ ZipArchiveHandle mHandle;
+ std::map<std::string, std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_ZIPARCHIVE_H */
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
new file mode 100644
index 000000000000..b7e7f903a2b1
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "java/AnnotationProcessor.h"
+#include "util/Util.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+void AnnotationProcessor::appendCommentLine(std::string& comment) {
+ static const std::string sDeprecated = "@deprecated";
+ static const std::string sSystemApi = "@SystemApi";
+
+ if (comment.find(sDeprecated) != std::string::npos) {
+ mAnnotationBitMask |= kDeprecated;
+ }
+
+ std::string::size_type idx = comment.find(sSystemApi);
+ if (idx != std::string::npos) {
+ mAnnotationBitMask |= kSystemApi;
+ comment.erase(comment.begin() + idx, comment.begin() + idx + sSystemApi.size());
+ }
+
+ if (util::trimWhitespace(comment).empty()) {
+ return;
+ }
+
+ if (!mHasComments) {
+ mHasComments = true;
+ mComment << "/**";
+ }
+
+ mComment << "\n * " << std::move(comment);
+}
+
+void AnnotationProcessor::appendComment(const StringPiece16& comment) {
+ // We need to process line by line to clean-up whitespace and append prefixes.
+ for (StringPiece16 line : util::tokenize(comment, u'\n')) {
+ line = util::trimWhitespace(line);
+ if (!line.empty()) {
+ std::string utf8Line = util::utf16ToUtf8(line);
+ appendCommentLine(utf8Line);
+ }
+ }
+}
+
+void AnnotationProcessor::appendComment(const StringPiece& comment) {
+ for (StringPiece line : util::tokenize(comment, '\n')) {
+ line = util::trimWhitespace(line);
+ if (!line.empty()) {
+ std::string utf8Line = line.toString();
+ appendCommentLine(utf8Line);
+ }
+ }
+}
+
+void AnnotationProcessor::appendNewLine() {
+ mComment << "\n *";
+}
+
+void AnnotationProcessor::writeToStream(std::ostream* out, const StringPiece& prefix) const {
+ if (mHasComments) {
+ std::string result = mComment.str();
+ for (StringPiece line : util::tokenize<char>(result, '\n')) {
+ *out << prefix << line << "\n";
+ }
+ *out << prefix << " */" << "\n";
+ }
+
+ if (mAnnotationBitMask & kDeprecated) {
+ *out << prefix << "@Deprecated\n";
+ }
+
+ if (mAnnotationBitMask & kSystemApi) {
+ *out << prefix << "@android.annotation.SystemApi\n";
+ }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
new file mode 100644
index 000000000000..8309dd978175
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_JAVA_ANNOTATIONPROCESSOR_H
+#define AAPT_JAVA_ANNOTATIONPROCESSOR_H
+
+#include "util/StringPiece.h"
+
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+/**
+ * Builds a JavaDoc comment from a set of XML comments.
+ * This will also look for instances of @SystemApi and convert them to
+ * actual Java annotations.
+ *
+ * Example:
+ *
+ * Input XML:
+ *
+ * <!-- This is meant to be hidden because
+ * It is system api. Also it is @deprecated
+ * @SystemApi
+ * -->
+ *
+ * Output JavaDoc:
+ *
+ * /\*
+ * * This is meant to be hidden because
+ * * It is system api. Also it is @deprecated
+ * *\/
+ *
+ * Output Annotations:
+ *
+ * @Deprecated
+ * @android.annotation.SystemApi
+ *
+ */
+class AnnotationProcessor {
+public:
+ /**
+ * Adds more comments. Since resources can have various values with different configurations,
+ * we need to collect all the comments.
+ */
+ void appendComment(const StringPiece16& comment);
+ void appendComment(const StringPiece& comment);
+
+ void appendNewLine();
+
+ /**
+ * Writes the comments and annotations to the stream, with the given prefix before each line.
+ */
+ void writeToStream(std::ostream* out, const StringPiece& prefix) const;
+
+private:
+ enum : uint32_t {
+ kDeprecated = 0x01,
+ kSystemApi = 0x02,
+ };
+
+ std::stringstream mComment;
+ std::stringstream mAnnotations;
+ bool mHasComments = false;
+ uint32_t mAnnotationBitMask = 0;
+
+ void appendCommentLine(std::string& line);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
new file mode 100644
index 000000000000..5a39add48fbd
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "java/AnnotationProcessor.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+TEST(AnnotationProcessorTest, EmitsDeprecated) {
+ const char* comment = "Some comment, and it should contain a marker word, "
+ "something that marks this resource as nor needed. "
+ "{@deprecated That's the marker! }";
+
+ AnnotationProcessor processor;
+ processor.appendComment(comment);
+
+ std::stringstream result;
+ processor.writeToStream(&result, "");
+ std::string annotations = result.str();
+
+ EXPECT_NE(std::string::npos, annotations.find("@Deprecated"));
+}
+
+TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) {
+ AnnotationProcessor processor;
+ processor.appendComment("@SystemApi This is a system API");
+
+ std::stringstream result;
+ processor.writeToStream(&result, "");
+ std::string annotations = result.str();
+
+ EXPECT_NE(std::string::npos, annotations.find("@android.annotation.SystemApi"));
+ EXPECT_EQ(std::string::npos, annotations.find("@SystemApi"));
+ EXPECT_NE(std::string::npos, annotations.find("This is a system API"));
+}
+
+} // namespace aapt
+
+
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
new file mode 100644
index 000000000000..08f2c8b9805c
--- /dev/null
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "java/ClassDefinition.h"
+#include "util/StringPiece.h"
+
+#include <ostream>
+
+namespace aapt {
+
+bool ClassDefinition::empty() const {
+ for (const std::unique_ptr<ClassMember>& member : mMembers) {
+ if (!member->empty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void ClassDefinition::writeToStream(const StringPiece& prefix, bool final,
+ std::ostream* out) const {
+ if (mMembers.empty() && !mCreateIfEmpty) {
+ return;
+ }
+
+ ClassMember::writeToStream(prefix, final, out);
+
+ *out << prefix << "public ";
+ if (mQualifier == ClassQualifier::Static) {
+ *out << "static ";
+ }
+ *out << "final class " << mName << " {\n";
+
+ std::string newPrefix = prefix.toString();
+ newPrefix.append(kIndent);
+
+ for (const std::unique_ptr<ClassMember>& member : mMembers) {
+ member->writeToStream(newPrefix, final, out);
+ *out << "\n";
+ }
+
+ *out << prefix << "}";
+}
+
+constexpr static const char* sWarningHeader =
+ "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
+ " *\n"
+ " * This class was automatically generated by the\n"
+ " * aapt tool from the resource data it found. It\n"
+ " * should not be modified by hand.\n"
+ " */\n\n";
+
+bool ClassDefinition::writeJavaFile(const ClassDefinition* def,
+ const StringPiece& package,
+ bool final,
+ std::ostream* out) {
+ *out << sWarningHeader << "package " << package << ";\n\n";
+ def->writeToStream("", final, out);
+ return bool(*out);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
new file mode 100644
index 000000000000..d45328fedba2
--- /dev/null
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_JAVA_CLASSDEFINITION_H
+#define AAPT_JAVA_CLASSDEFINITION_H
+
+#include "Resource.h"
+#include "java/AnnotationProcessor.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <android-base/macros.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+// The number of attributes to emit per line in a Styleable array.
+constexpr static size_t kAttribsPerLine = 4;
+constexpr static const char* kIndent = " ";
+
+class ClassMember {
+public:
+ virtual ~ClassMember() = default;
+
+ AnnotationProcessor* getCommentBuilder() {
+ return &mProcessor;
+ }
+
+ virtual bool empty() const = 0;
+
+ virtual void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const {
+ mProcessor.writeToStream(out, prefix);
+ }
+
+private:
+ AnnotationProcessor mProcessor;
+};
+
+template <typename T>
+class PrimitiveMember : public ClassMember {
+public:
+ PrimitiveMember(const StringPiece& name, const T& val) :
+ mName(name.toString()), mVal(val) {
+ }
+
+ bool empty() const override {
+ return false;
+ }
+
+ void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
+ ClassMember::writeToStream(prefix, final, out);
+
+ *out << prefix << "public static " << (final ? "final " : "")
+ << "int " << mName << "=" << mVal << ";";
+ }
+
+private:
+ std::string mName;
+ T mVal;
+
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+};
+
+/**
+ * Specialization for strings so they get the right type and are quoted with "".
+ */
+template <>
+class PrimitiveMember<std::string> : public ClassMember {
+public:
+ PrimitiveMember(const StringPiece& name, const std::string& val) :
+ mName(name.toString()), mVal(val) {
+ }
+
+ bool empty() const override {
+ return false;
+ }
+
+ void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
+ ClassMember::writeToStream(prefix, final, out);
+
+ *out << prefix << "public static " << (final ? "final " : "")
+ << "String " << mName << "=\"" << mVal << "\";";
+ }
+
+private:
+ std::string mName;
+ std::string mVal;
+
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+};
+
+using IntMember = PrimitiveMember<uint32_t>;
+using ResourceMember = PrimitiveMember<ResourceId>;
+using StringMember = PrimitiveMember<std::string>;
+
+template <typename T>
+class PrimitiveArrayMember : public ClassMember {
+public:
+ PrimitiveArrayMember(const StringPiece& name) :
+ mName(name.toString()) {
+ }
+
+ void addElement(const T& val) {
+ mElements.push_back(val);
+ }
+
+ bool empty() const override {
+ return false;
+ }
+
+ void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
+ ClassMember::writeToStream(prefix, final, out);
+
+ *out << prefix << "public static final int[] " << mName << "={";
+
+ const auto begin = mElements.begin();
+ const auto end = mElements.end();
+ for (auto current = begin; current != end; ++current) {
+ if (std::distance(begin, current) % kAttribsPerLine == 0) {
+ *out << "\n" << prefix << kIndent << kIndent;
+ }
+
+ *out << *current;
+ if (std::distance(current, end) > 1) {
+ *out << ", ";
+ }
+ }
+ *out << "\n" << prefix << kIndent <<"};";
+ }
+
+private:
+ std::string mName;
+ std::vector<T> mElements;
+
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
+};
+
+using ResourceArrayMember = PrimitiveArrayMember<ResourceId>;
+
+enum class ClassQualifier {
+ None,
+ Static
+};
+
+class ClassDefinition : public ClassMember {
+public:
+ static bool writeJavaFile(const ClassDefinition* def,
+ const StringPiece& package,
+ bool final,
+ std::ostream* out);
+
+ ClassDefinition(const StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) :
+ mName(name.toString()), mQualifier(qualifier), mCreateIfEmpty(createIfEmpty) {
+ }
+
+ void addMember(std::unique_ptr<ClassMember> member) {
+ mMembers.push_back(std::move(member));
+ }
+
+ bool empty() const override;
+ void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override;
+
+private:
+ std::string mName;
+ ClassQualifier mQualifier;
+ bool mCreateIfEmpty;
+ std::vector<std::unique_ptr<ClassMember>> mMembers;
+
+ DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_CLASSDEFINITION_H */
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
new file mode 100644
index 000000000000..84df0b429fc5
--- /dev/null
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NameMangler.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "java/AnnotationProcessor.h"
+#include "java/ClassDefinition.h"
+#include "java/JavaClassGenerator.h"
+#include "process/SymbolTable.h"
+#include "util/StringPiece.h"
+
+#include <algorithm>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <tuple>
+
+namespace aapt {
+
+JavaClassGenerator::JavaClassGenerator(IAaptContext* context, ResourceTable* table,
+ const JavaClassGeneratorOptions& options) :
+ mContext(context), mTable(table), mOptions(options) {
+}
+
+static const std::set<StringPiece16> sJavaIdentifiers = {
+ u"abstract", u"assert", u"boolean", u"break", u"byte",
+ u"case", u"catch", u"char", u"class", u"const", u"continue",
+ u"default", u"do", u"double", u"else", u"enum", u"extends",
+ u"final", u"finally", u"float", u"for", u"goto", u"if",
+ u"implements", u"import", u"instanceof", u"int", u"interface",
+ u"long", u"native", u"new", u"package", u"private", u"protected",
+ u"public", u"return", u"short", u"static", u"strictfp", u"super",
+ u"switch", u"synchronized", u"this", u"throw", u"throws",
+ u"transient", u"try", u"void", u"volatile", u"while", u"true",
+ u"false", u"null"
+};
+
+static bool isValidSymbol(const StringPiece16& symbol) {
+ return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
+}
+
+/*
+ * Java symbols can not contain . or -, but those are valid in a resource name.
+ * Replace those with '_'.
+ */
+static std::string transform(const StringPiece16& symbol) {
+ std::string output = util::utf16ToUtf8(symbol);
+ for (char& c : output) {
+ if (c == '.' || c == '-') {
+ c = '_';
+ }
+ }
+ return output;
+}
+
+/**
+ * Transforms an attribute in a styleable to the Java field name:
+ *
+ * <declare-styleable name="Foo">
+ * <attr name="android:bar" />
+ * <attr name="bar" />
+ * </declare-styleable>
+ *
+ * Foo_android_bar
+ * Foo_bar
+ */
+static std::string transformNestedAttr(const ResourceNameRef& attrName,
+ const std::string& styleableClassName,
+ const StringPiece16& packageNameToGenerate) {
+ std::string output = styleableClassName;
+
+ // We may reference IDs from other packages, so prefix the entry name with
+ // the package.
+ if (!attrName.package.empty() && packageNameToGenerate != attrName.package) {
+ output += "_" + transform(attrName.package);
+ }
+ output += "_" + transform(attrName.entry);
+ return output;
+}
+
+static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
+ const uint32_t typeMask = attr->typeMask;
+ if (typeMask & android::ResTable_map::TYPE_REFERENCE) {
+ processor->appendComment(
+ "<p>May be a reference to another resource, in the form\n"
+ "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n"
+ "attribute in the form\n"
+ "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_STRING) {
+ processor->appendComment(
+ "<p>May be a string value, using '\\\\;' to escape characters such as\n"
+ "'\\\\n' or '\\\\uxxxx' for a unicode character;");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+ processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ processor->appendComment(
+ "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
+ "\"<code>false</code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_COLOR) {
+ processor->appendComment(
+ "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n"
+ "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
+ "\"<code>#<i>aarrggbb</i></code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_FLOAT) {
+ processor->appendComment(
+ "<p>May be a floating point value, such as \"<code>1.2</code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_DIMENSION) {
+ processor->appendComment(
+ "<p>May be a dimension value, which is a floating point number appended with a\n"
+ "unit such as \"<code>14.5sp</code>\".\n"
+ "Available units are: px (pixels), dp (density-independent pixels),\n"
+ "sp (scaled pixels based on preferred font size), in (inches), and\n"
+ "mm (millimeters).");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_FRACTION) {
+ processor->appendComment(
+ "<p>May be a fractional value, which is a floating point number appended with\n"
+ "either % or %p, such as \"<code>14.5%</code>\".\n"
+ "The % suffix always means a percentage of the base size;\n"
+ "the optional %p suffix provides a size relative to some parent container.");
+ }
+
+ if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
+ if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+ processor->appendComment(
+ "<p>Must be one or more (separated by '|') of the following "
+ "constant values.</p>");
+ } else {
+ processor->appendComment("<p>Must be one of the following constant values.</p>");
+ }
+
+ processor->appendComment("<table>\n<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
+ for (const Attribute::Symbol& symbol : attr->symbols) {
+ std::stringstream line;
+ line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
+ << "<td>" << std::hex << symbol.value << std::dec << "</td>"
+ << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>";
+ processor->appendComment(line.str());
+ }
+ processor->appendComment("</table>");
+ }
+}
+
+bool JavaClassGenerator::skipSymbol(SymbolState state) {
+ switch (mOptions.types) {
+ case JavaClassGeneratorOptions::SymbolTypes::kAll:
+ return false;
+ case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
+ return state == SymbolState::kUndefined;
+ case JavaClassGeneratorOptions::SymbolTypes::kPublic:
+ return state != SymbolState::kPublic;
+ }
+ return true;
+}
+
+struct StyleableAttr {
+ const Reference* attrRef;
+ std::string fieldName;
+ std::unique_ptr<SymbolTable::Symbol> symbol;
+};
+
+static bool lessStyleableAttr(const StyleableAttr& lhs, const StyleableAttr& rhs) {
+ const ResourceId lhsId = lhs.attrRef->id ? lhs.attrRef->id.value() : ResourceId(0);
+ const ResourceId rhsId = rhs.attrRef->id ? rhs.attrRef->id.value() : ResourceId(0);
+ if (lhsId < rhsId) {
+ return true;
+ } else if (lhsId > rhsId) {
+ return false;
+ } else {
+ return lhs.attrRef->name.value() < rhs.attrRef->name.value();
+ }
+}
+
+void JavaClassGenerator::addMembersToStyleableClass(const StringPiece16& packageNameToGenerate,
+ const std::u16string& entryName,
+ const Styleable* styleable,
+ ClassDefinition* outStyleableClassDef) {
+ const std::string className = transform(entryName);
+
+ std::unique_ptr<ResourceArrayMember> styleableArrayDef =
+ util::make_unique<ResourceArrayMember>(className);
+
+ // This must be sorted by resource ID.
+ std::vector<StyleableAttr> sortedAttributes;
+ sortedAttributes.reserve(styleable->entries.size());
+ for (const auto& attr : styleable->entries) {
+ // If we are not encoding final attributes, the styleable entry may have no ID
+ // if we are building a static library.
+ assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry");
+ assert(attr.name && "no name set for Styleable entry");
+
+ // We will need the unmangled, transformed name in the comments and the field,
+ // so create it once and cache it in this StyleableAttr data structure.
+ StyleableAttr styleableAttr = {};
+ styleableAttr.attrRef = &attr;
+ styleableAttr.fieldName = transformNestedAttr(attr.name.value(), className,
+ packageNameToGenerate);
+
+ Reference mangledReference;
+ mangledReference.id = attr.id;
+ mangledReference.name = attr.name;
+ if (mangledReference.name.value().package.empty()) {
+ mangledReference.name.value().package = mContext->getCompilationPackage();
+ }
+
+ if (Maybe<ResourceName> mangledName =
+ mContext->getNameMangler()->mangleName(mangledReference.name.value())) {
+ mangledReference.name = mangledName;
+ }
+
+ // Look up the symbol so that we can write out in the comments what are possible
+ // legal values for this attribute.
+ const SymbolTable::Symbol* symbol = mContext->getExternalSymbols()->findByReference(
+ mangledReference);
+ if (symbol && symbol->attribute) {
+ // Copy the symbol data structure because the returned instance can be destroyed.
+ styleableAttr.symbol = util::make_unique<SymbolTable::Symbol>(*symbol);
+ }
+ sortedAttributes.push_back(std::move(styleableAttr));
+ }
+
+ // Sort the attributes by ID.
+ std::sort(sortedAttributes.begin(), sortedAttributes.end(), lessStyleableAttr);
+
+ const size_t attrCount = sortedAttributes.size();
+ if (attrCount > 0) {
+ // Build the comment string for the Styleable. It includes details about the
+ // child attributes.
+ std::stringstream styleableComment;
+ if (!styleable->getComment().empty()) {
+ styleableComment << styleable->getComment() << "\n";
+ } else {
+ styleableComment << "Attributes that can be used with a " << className << ".\n";
+ }
+
+ styleableComment <<
+ "<p>Includes the following attributes:</p>\n"
+ "<table>\n"
+ "<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<tr><th>Attribute</th><th>Description</th></tr>\n";
+
+ for (const StyleableAttr& entry : sortedAttributes) {
+ if (!entry.symbol) {
+ continue;
+ }
+
+ if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+ !entry.symbol->isPublic) {
+ // Don't write entries for non-public attributes.
+ continue;
+ }
+
+ StringPiece16 attrCommentLine = entry.symbol->attribute->getComment();
+ if (attrCommentLine.contains(StringPiece16(u"@removed"))) {
+ // Removed attributes are public but hidden from the documentation, so don't emit
+ // them as part of the class documentation.
+ continue;
+ }
+
+ const ResourceName& attrName = entry.attrRef->name.value();
+ styleableComment << "<tr><td>";
+ styleableComment << "<code>{@link #"
+ << entry.fieldName << " "
+ << (!attrName.package.empty()
+ ? attrName.package : mContext->getCompilationPackage())
+ << ":" << attrName.entry
+ << "}</code>";
+ styleableComment << "</td>";
+
+ styleableComment << "<td>";
+
+ // Only use the comment up until the first '.'. This is to stay compatible with
+ // the way old AAPT did it (presumably to keep it short and to avoid including
+ // annotations like @hide which would affect this Styleable).
+ auto iter = std::find(attrCommentLine.begin(), attrCommentLine.end(), u'.');
+ if (iter != attrCommentLine.end()) {
+ attrCommentLine = attrCommentLine.substr(
+ 0, (iter - attrCommentLine.begin()) + 1);
+ }
+ styleableComment << attrCommentLine << "</td></tr>\n";
+ }
+ styleableComment << "</table>\n";
+
+ for (const StyleableAttr& entry : sortedAttributes) {
+ if (!entry.symbol) {
+ continue;
+ }
+
+ if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+ !entry.symbol->isPublic) {
+ // Don't write entries for non-public attributes.
+ continue;
+ }
+ styleableComment << "@see #" << entry.fieldName << "\n";
+ }
+
+ styleableArrayDef->getCommentBuilder()->appendComment(styleableComment.str());
+ }
+
+ // Add the ResourceIds to the array member.
+ for (const StyleableAttr& styleableAttr : sortedAttributes) {
+ styleableArrayDef->addElement(
+ styleableAttr.attrRef->id ? styleableAttr.attrRef->id.value() : ResourceId(0));
+ }
+
+ // Add the Styleable array to the Styleable class.
+ outStyleableClassDef->addMember(std::move(styleableArrayDef));
+
+ // Now we emit the indices into the array.
+ for (size_t i = 0; i < attrCount; i++) {
+ const StyleableAttr& styleableAttr = sortedAttributes[i];
+
+ if (!styleableAttr.symbol) {
+ continue;
+ }
+
+ if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+ !styleableAttr.symbol->isPublic) {
+ // Don't write entries for non-public attributes.
+ continue;
+ }
+
+ StringPiece16 comment = styleableAttr.attrRef->getComment();
+ if (styleableAttr.symbol->attribute && comment.empty()) {
+ comment = styleableAttr.symbol->attribute->getComment();
+ }
+
+ if (comment.contains(StringPiece16(u"@removed"))) {
+ // Removed attributes are public but hidden from the documentation, so don't emit them
+ // as part of the class documentation.
+ continue;
+ }
+
+ const ResourceName& attrName = styleableAttr.attrRef->name.value();
+
+ StringPiece16 packageName = attrName.package;
+ if (packageName.empty()) {
+ packageName = mContext->getCompilationPackage();
+ }
+
+ std::unique_ptr<IntMember> indexMember = util::make_unique<IntMember>(
+ sortedAttributes[i].fieldName, static_cast<uint32_t>(i));
+
+ AnnotationProcessor* attrProcessor = indexMember->getCommentBuilder();
+
+ if (!comment.empty()) {
+ attrProcessor->appendComment("<p>\n@attr description");
+ attrProcessor->appendComment(comment);
+ } else {
+ std::stringstream defaultComment;
+ defaultComment
+ << "<p>This symbol is the offset where the "
+ << "{@link " << packageName << ".R.attr#" << transform(attrName.entry) << "}\n"
+ << "attribute's value can be found in the "
+ << "{@link #" << className << "} array.";
+ attrProcessor->appendComment(defaultComment.str());
+ }
+
+ attrProcessor->appendNewLine();
+
+ addAttributeFormatDoc(attrProcessor, styleableAttr.symbol->attribute.get());
+ attrProcessor->appendNewLine();
+
+ std::stringstream doclavaName;
+ doclavaName << "@attr name " << packageName << ":" << attrName.entry;;
+ attrProcessor->appendComment(doclavaName.str());
+
+ outStyleableClassDef->addMember(std::move(indexMember));
+ }
+}
+
+bool JavaClassGenerator::addMembersToTypeClass(const StringPiece16& packageNameToGenerate,
+ const ResourceTablePackage* package,
+ const ResourceTableType* type,
+ ClassDefinition* outTypeClassDef) {
+
+ for (const auto& entry : type->entries) {
+ if (skipSymbol(entry->symbolStatus.state)) {
+ continue;
+ }
+
+ ResourceId id;
+ if (package->id && type->id && entry->id) {
+ id = ResourceId(package->id.value(), type->id.value(), entry->id.value());
+ }
+
+ std::u16string unmangledPackage;
+ std::u16string unmangledName = entry->name;
+ if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
+ // The entry name was mangled, and we successfully unmangled it.
+ // Check that we want to emit this symbol.
+ if (package->name != unmangledPackage) {
+ // Skip the entry if it doesn't belong to the package we're writing.
+ continue;
+ }
+ } else if (packageNameToGenerate != package->name) {
+ // We are processing a mangled package name,
+ // but this is a non-mangled resource.
+ continue;
+ }
+
+ if (!isValidSymbol(unmangledName)) {
+ ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName);
+ std::stringstream err;
+ err << "invalid symbol name '" << resourceName << "'";
+ mError = err.str();
+ return false;
+ }
+
+ if (type->type == ResourceType::kStyleable) {
+ assert(!entry->values.empty());
+
+ const Styleable* styleable = static_cast<const Styleable*>(
+ entry->values.front()->value.get());
+
+ // Comments are handled within this method.
+ addMembersToStyleableClass(packageNameToGenerate, unmangledName, styleable,
+ outTypeClassDef);
+ } else {
+ std::unique_ptr<ResourceMember> resourceMember =
+ util::make_unique<ResourceMember>(transform(unmangledName), id);
+
+ // Build the comments and annotations for this entry.
+ AnnotationProcessor* processor = resourceMember->getCommentBuilder();
+
+ // Add the comments from any <public> tags.
+ if (entry->symbolStatus.state != SymbolState::kUndefined) {
+ processor->appendComment(entry->symbolStatus.comment);
+ }
+
+ // Add the comments from all configurations of this entry.
+ for (const auto& configValue : entry->values) {
+ processor->appendComment(configValue->value->getComment());
+ }
+
+ // If this is an Attribute, append the format Javadoc.
+ if (!entry->values.empty()) {
+ if (Attribute* attr = valueCast<Attribute>(entry->values.front()->value.get())) {
+ // We list out the available values for the given attribute.
+ addAttributeFormatDoc(processor, attr);
+ }
+ }
+
+ outTypeClassDef->addMember(std::move(resourceMember));
+ }
+ }
+ return true;
+}
+
+bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
+ return generate(packageNameToGenerate, packageNameToGenerate, out);
+}
+
+static void appendJavaDocAnnotations(const std::vector<std::string>& annotations,
+ AnnotationProcessor* processor) {
+ for (const std::string& annotation : annotations) {
+ std::string properAnnotation = "@";
+ properAnnotation += annotation;
+ processor->appendComment(properAnnotation);
+ }
+}
+
+bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate,
+ const StringPiece16& outPackageName, std::ostream* out) {
+
+ ClassDefinition rClass("R", ClassQualifier::None, true);
+
+ for (const auto& package : mTable->packages) {
+ for (const auto& type : package->types) {
+ if (type->type == ResourceType::kAttrPrivate) {
+ continue;
+ }
+
+ const bool forceCreationIfEmpty =
+ (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
+
+ std::unique_ptr<ClassDefinition> classDef = util::make_unique<ClassDefinition>(
+ util::utf16ToUtf8(toString(type->type)), ClassQualifier::Static,
+ forceCreationIfEmpty);
+
+ bool result = addMembersToTypeClass(packageNameToGenerate, package.get(), type.get(),
+ classDef.get());
+ if (!result) {
+ return false;
+ }
+
+ if (type->type == ResourceType::kAttr) {
+ // Also include private attributes in this same class.
+ ResourceTableType* privType = package->findType(ResourceType::kAttrPrivate);
+ if (privType) {
+ result = addMembersToTypeClass(packageNameToGenerate, package.get(), privType,
+ classDef.get());
+ if (!result) {
+ return false;
+ }
+ }
+ }
+
+ if (type->type == ResourceType::kStyleable &&
+ mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
+ // When generating a public R class, we don't want Styleable to be part of the API.
+ // It is only emitted for documentation purposes.
+ classDef->getCommentBuilder()->appendComment("@doconly");
+ }
+
+ appendJavaDocAnnotations(mOptions.javadocAnnotations, classDef->getCommentBuilder());
+
+ rClass.addMember(std::move(classDef));
+ }
+ }
+
+ appendJavaDocAnnotations(mOptions.javadocAnnotations, rClass.getCommentBuilder());
+
+ if (!ClassDefinition::writeJavaFile(&rClass, util::utf16ToUtf8(outPackageName),
+ mOptions.useFinal, out)) {
+ return false;
+ }
+
+ out->flush();
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
new file mode 100644
index 000000000000..77e0ed76143a
--- /dev/null
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_JAVA_CLASS_GENERATOR_H
+#define AAPT_JAVA_CLASS_GENERATOR_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/StringPiece.h"
+
+#include <ostream>
+#include <string>
+
+namespace aapt {
+
+class AnnotationProcessor;
+class ClassDefinition;
+
+struct JavaClassGeneratorOptions {
+ /*
+ * Specifies whether to use the 'final' modifier
+ * on resource entries. Default is true.
+ */
+ bool useFinal = true;
+
+ enum class SymbolTypes {
+ kAll,
+ kPublicPrivate,
+ kPublic,
+ };
+
+ SymbolTypes types = SymbolTypes::kAll;
+
+ /**
+ * A list of JavaDoc annotations to add to the comments of all generated classes.
+ */
+ std::vector<std::string> javadocAnnotations;
+};
+
+/*
+ * Generates the R.java file for a resource table.
+ */
+class JavaClassGenerator {
+public:
+ JavaClassGenerator(IAaptContext* context, ResourceTable* table,
+ const JavaClassGeneratorOptions& options);
+
+ /*
+ * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
+ * All symbols technically belong to a single package, but linked libraries will
+ * have their names mangled, denoting that they came from a different package.
+ * We need to generate these symbols in a separate file.
+ * Returns true on success.
+ */
+ bool generate(const StringPiece16& packageNameToGenerate, std::ostream* out);
+
+ bool generate(const StringPiece16& packageNameToGenerate,
+ const StringPiece16& outputPackageName,
+ std::ostream* out);
+
+ const std::string& getError() const;
+
+private:
+ bool addMembersToTypeClass(const StringPiece16& packageNameToGenerate,
+ const ResourceTablePackage* package,
+ const ResourceTableType* type,
+ ClassDefinition* outTypeClassDef);
+
+ void addMembersToStyleableClass(const StringPiece16& packageNameToGenerate,
+ const std::u16string& entryName,
+ const Styleable* styleable,
+ ClassDefinition* outStyleableClassDef);
+
+ bool skipSymbol(SymbolState state);
+
+ IAaptContext* mContext;
+ ResourceTable* mTable;
+ JavaClassGeneratorOptions mOptions;
+ std::string mError;
+};
+
+inline const std::string& JavaClassGenerator::getError() const {
+ return mError;
+}
+
+} // namespace aapt
+
+#endif // AAPT_JAVA_CLASS_GENERATOR_H
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
new file mode 100644
index 000000000000..46266b3f3e89
--- /dev/null
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "java/JavaClassGenerator.h"
+#include "test/Test.h"
+#include "util/Util.h"
+
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/class", ResourceId(0x01020000))
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ std::stringstream out;
+ EXPECT_FALSE(generator.generate(u"android", &out));
+}
+
+TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/hey-man", ResourceId(0x01020000))
+ .addValue(u"@android:attr/cool.attr", ResourceId(0x01010000),
+ test::AttributeBuilder(false).build())
+ .addValue(u"@android:styleable/hey.dude", ResourceId(0x01030000),
+ test::StyleableBuilder()
+ .addItem(u"@android:attr/cool.attr", ResourceId(0x01010000))
+ .build())
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(u"android", &out));
+
+ std::string output = out.str();
+
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int hey_man=0x01020000;"));
+
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int[] hey_dude={"));
+
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int hey_dude_cool_attr=0;"));
+}
+
+TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/one", ResourceId(0x01020000))
+ .addSimple(u"@android:id/com.foo$two", ResourceId(0x01020001))
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", u"com.android.internal", &out));
+
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("package com.android.internal;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
+ EXPECT_EQ(std::string::npos, output.find("two"));
+ EXPECT_EQ(std::string::npos, output.find("com_foo$two"));
+}
+
+TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:attr/two", ResourceId(0x01010001))
+ .addSimple(u"@android:^attr-private/one", ResourceId(0x01010000))
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("public static final class attr"));
+ EXPECT_EQ(std::string::npos, output.find("public static final class ^attr-private"));
+}
+
+TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
+ StdErrDiagnostics diag;
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/one", ResourceId(0x01020000))
+ .addSimple(u"@android:id/two", ResourceId(0x01020001))
+ .addSimple(u"@android:id/three", ResourceId(0x01020002))
+ .setSymbolState(u"@android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
+ .setSymbolState(u"@android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .build();
+
+ JavaClassGeneratorOptions options;
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+ {
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
+ EXPECT_EQ(std::string::npos, output.find("two"));
+ EXPECT_EQ(std::string::npos, output.find("three"));
+ }
+
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+ {
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int two=0x01020001;"));
+ EXPECT_EQ(std::string::npos, output.find("three"));
+ }
+
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+ {
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int two=0x01020001;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int three=0x01020002;"));
+ }
+}
+
+/*
+ * TODO(adamlesinski): Re-enable this once we get merging working again.
+ * TEST(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
+ ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" },
+ ResourceId{ 0x01, 0x02, 0x0000 }));
+ ResourceTable table;
+ table.setPackage(u"com.lib");
+ ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
+ Source{ "lib.xml", 33 }, util::make_unique<Id>()));
+ ASSERT_TRUE(mTable->merge(std::move(table)));
+
+ Linker linker(mTable,
+ std::make_shared<MockResolver>(mTable, std::map<ResourceName, ResourceId>()),
+ {});
+ ASSERT_TRUE(linker.linkAndValidate());
+
+ JavaClassGenerator generator(mTable, {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int foo ="));
+ EXPECT_EQ(std::string::npos, output.find("int test ="));
+
+ out.str("");
+ EXPECT_TRUE(generator.generate(u"com.lib", out));
+ output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int test ="));
+ EXPECT_EQ(std::string::npos, output.find("int foo ="));
+}*/
+
+TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .setPackageId(u"com.lib", 0x02)
+ .addValue(u"@android:attr/bar", ResourceId(0x01010000),
+ test::AttributeBuilder(false).build())
+ .addValue(u"@com.lib:attr/bar", ResourceId(0x02010000),
+ test::AttributeBuilder(false).build())
+ .addValue(u"@android:styleable/foo", ResourceId(0x01030000),
+ test::StyleableBuilder()
+ .addItem(u"@android:attr/bar", ResourceId(0x01010000))
+ .addItem(u"@com.lib:attr/bar", ResourceId(0x02010000))
+ .build())
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(u"android", &out));
+
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int foo_bar="));
+ EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar="));
+}
+
+TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/foo", ResourceId(0x01010000))
+ .build();
+ test::getValue<Id>(table.get(), u"@android:id/foo")
+ ->setComment(std::u16string(u"This is a comment\n@deprecated"));
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string actual = out.str();
+
+ const char* expectedText =
+R"EOF(/**
+ * This is a comment
+ * @deprecated
+ */
+ @Deprecated
+ public static final int foo=0x01010000;)EOF";
+
+ EXPECT_NE(std::string::npos, actual.find(expectedText));
+}
+
+TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {
+
+}
+
+TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
+ Attribute attr(false);
+ attr.setComment(StringPiece16(u"This is an attribute"));
+
+ Styleable styleable;
+ styleable.entries.push_back(Reference(test::parseNameOrDie(u"@android:attr/one")));
+ styleable.setComment(StringPiece16(u"This is a styleable"));
+
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addValue(u"@android:attr/one", util::make_unique<Attribute>(attr))
+ .addValue(u"@android:styleable/Container",
+ std::unique_ptr<Styleable>(styleable.clone(nullptr)))
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .build();
+ JavaClassGeneratorOptions options;
+ options.useFinal = false;
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string actual = out.str();
+
+ EXPECT_NE(std::string::npos, actual.find("@attr name android:one"));
+ EXPECT_NE(std::string::npos, actual.find("@attr description"));
+ EXPECT_NE(std::string::npos, actual.find(util::utf16ToUtf8(attr.getComment())));
+ EXPECT_NE(std::string::npos, actual.find(util::utf16ToUtf8(styleable.getComment())));
+}
+
+TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) {
+ Attribute attr(false);
+ attr.setComment(StringPiece16(u"@removed"));
+
+
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addValue(u"@android:attr/one", util::make_unique<Attribute>(attr))
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .build();
+ JavaClassGeneratorOptions options;
+ options.useFinal = false;
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string actual = out.str();
+
+ std::cout << actual << std::endl;
+
+ EXPECT_EQ(std::string::npos, actual.find("@attr name android:one"));
+ EXPECT_EQ(std::string::npos, actual.find("@attr description"));
+
+ // We should find @removed only in the attribute javadoc and not anywhere else (i.e. the class
+ // javadoc).
+ const size_t pos = actual.find("@removed");
+ EXPECT_NE(std::string::npos, pos);
+ EXPECT_EQ(std::string::npos, actual.find("@removed", pos + 1));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
new file mode 100644
index 000000000000..be8955ecdf83
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Source.h"
+#include "java/AnnotationProcessor.h"
+#include "java/ClassDefinition.h"
+#include "java/ManifestClassGenerator.h"
+#include "util/Maybe.h"
+#include "xml/XmlDom.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+static Maybe<StringPiece16> extractJavaIdentifier(IDiagnostics* diag, const Source& source,
+ const StringPiece16& value) {
+ const StringPiece16 sep = u".";
+ auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end());
+
+ StringPiece16 result;
+ if (iter != value.end()) {
+ result.assign(iter + sep.size(), value.end() - (iter + sep.size()));
+ } else {
+ result = value;
+ }
+
+ if (result.empty()) {
+ diag->error(DiagMessage(source) << "empty symbol");
+ return {};
+ }
+
+ iter = util::findNonAlphaNumericAndNotInSet(result, u"_");
+ if (iter != result.end()) {
+ diag->error(DiagMessage(source)
+ << "invalid character '" << StringPiece16(iter, 1)
+ << "' in '" << result << "'");
+ return {};
+ }
+
+ if (*result.begin() >= u'0' && *result.begin() <= u'9') {
+ diag->error(DiagMessage(source) << "symbol can not start with a digit");
+ return {};
+ }
+
+ return result;
+}
+
+static bool writeSymbol(const Source& source, IDiagnostics* diag, xml::Element* el,
+ ClassDefinition* classDef) {
+ xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name");
+ if (!attr) {
+ diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'");
+ return false;
+ }
+
+ Maybe<StringPiece16> result = extractJavaIdentifier(diag, source.withLine(el->lineNumber),
+ attr->value);
+ if (!result) {
+ return false;
+ }
+
+ std::unique_ptr<StringMember> stringMember = util::make_unique<StringMember>(
+ util::utf16ToUtf8(result.value()), util::utf16ToUtf8(attr->value));
+ stringMember->getCommentBuilder()->appendComment(el->comment);
+
+ classDef->addMember(std::move(stringMember));
+ return true;
+}
+
+std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag, xml::XmlResource* res) {
+ xml::Element* el = xml::findRootElement(res->root.get());
+ if (!el) {
+ diag->error(DiagMessage(res->file.source) << "no root tag defined");
+ return {};
+ }
+
+ if (el->name != u"manifest" && !el->namespaceUri.empty()) {
+ diag->error(DiagMessage(res->file.source) << "no <manifest> root tag defined");
+ return {};
+ }
+
+ std::unique_ptr<ClassDefinition> permissionClass =
+ util::make_unique<ClassDefinition>("permission", ClassQualifier::Static, false);
+ std::unique_ptr<ClassDefinition> permissionGroupClass =
+ util::make_unique<ClassDefinition>("permission_group", ClassQualifier::Static, false);
+
+ bool error = false;
+
+ std::vector<xml::Element*> children = el->getChildElements();
+ for (xml::Element* childEl : children) {
+ if (childEl->namespaceUri.empty()) {
+ if (childEl->name == u"permission") {
+ error |= !writeSymbol(res->file.source, diag, childEl, permissionClass.get());
+ } else if (childEl->name == u"permission-group") {
+ error |= !writeSymbol(res->file.source, diag, childEl, permissionGroupClass.get());
+ }
+ }
+ }
+
+ if (error) {
+ return {};
+ }
+
+ std::unique_ptr<ClassDefinition> manifestClass =
+ util::make_unique<ClassDefinition>("Manifest", ClassQualifier::None, false);
+ manifestClass->addMember(std::move(permissionClass));
+ manifestClass->addMember(std::move(permissionGroupClass));
+ return manifestClass;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
new file mode 100644
index 000000000000..f565289393fb
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+#define AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+
+#include "Diagnostics.h"
+#include "java/ClassDefinition.h"
+#include "util/StringPiece.h"
+#include "xml/XmlDom.h"
+
+#include <iostream>
+
+namespace aapt {
+
+std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag, xml::XmlResource* res);
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
new file mode 100644
index 000000000000..d3bca7068cb2
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "java/ManifestClassGenerator.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+static ::testing::AssertionResult getManifestClassText(IAaptContext* context, xml::XmlResource* res,
+ std::string* outStr) {
+ std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
+ context->getDiagnostics(), res);
+ if (!manifestClass) {
+ return ::testing::AssertionFailure() << "manifestClass == nullptr";
+ }
+
+ std::stringstream out;
+ if (!manifestClass->writeJavaFile(manifestClass.get(), "android", true, &out)) {
+ return ::testing::AssertionFailure() << "failed to write java file";
+ }
+
+ *outStr = out.str();
+ return ::testing::AssertionSuccess();
+}
+
+TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <permission android:name="android.DO_DANGEROUS_THINGS" />
+ <permission android:name="com.test.sample.permission.HUH" />
+ <permission-group android:name="foo.bar.PERMISSION" />
+ </manifest>)EOF");
+
+ std::string actual;
+ ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual));
+
+ const size_t permissionClassPos = actual.find("public static final class permission {");
+ const size_t permissionGroupClassPos =
+ actual.find("public static final class permission_group {");
+ ASSERT_NE(std::string::npos, permissionClassPos);
+ ASSERT_NE(std::string::npos, permissionGroupClassPos);
+
+ //
+ // Make sure these permissions are in the permission class.
+ //
+
+ size_t pos = actual.find("public static final String ACCESS_INTERNET="
+ "\"android.permission.ACCESS_INTERNET\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ pos = actual.find("public static final String DO_DANGEROUS_THINGS="
+ "\"android.DO_DANGEROUS_THINGS\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ pos = actual.find("public static final String HUH=\"com.test.sample.permission.HUH\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ //
+ // Make sure these permissions are in the permission_group class
+ //
+
+ pos = actual.find("public static final String PERMISSION="
+ "\"foo.bar.PERMISSION\";");
+ EXPECT_GT(pos, permissionGroupClassPos);
+ EXPECT_LT(pos, std::string::npos);
+}
+
+TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Required to access the internet.
+ Added in API 1. -->
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <!-- @deprecated This permission is for playing outside. -->
+ <permission android:name="android.permission.PLAY_OUTSIDE" />
+ <!-- This is a private permission for system only!
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.SECRET" />
+ </manifest>)EOF");
+
+ std::string actual;
+ ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual));
+
+ const char* expectedAccessInternet =
+R"EOF( /**
+ * Required to access the internet.
+ * Added in API 1.
+ */
+ public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF";
+
+ EXPECT_NE(std::string::npos, actual.find(expectedAccessInternet));
+
+ const char* expectedPlayOutside =
+R"EOF( /**
+ * @deprecated This permission is for playing outside.
+ */
+ @Deprecated
+ public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF";
+
+ EXPECT_NE(std::string::npos, actual.find(expectedPlayOutside));
+
+ const char* expectedSecret =
+R"EOF( /**
+ * This is a private permission for system only!
+ * @hide
+ */
+ @android.annotation.SystemApi
+ public static final String SECRET="android.permission.SECRET";)EOF";
+
+ EXPECT_NE(std::string::npos, actual.find(expectedSecret));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index e89fb7c8bd3d..c610bb0f2ff2 100644
--- a/tools/aapt2/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#include "ProguardRules.h"
-#include "Util.h"
-#include "XmlDom.h"
+#include "java/ProguardRules.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
#include <memory>
#include <string>
@@ -24,8 +24,6 @@
namespace aapt {
namespace proguard {
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
class BaseVisitor : public xml::Visitor {
public:
BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
@@ -41,11 +39,11 @@ public:
virtual void visit(xml::Element* node) override {
if (!node->namespaceUri.empty()) {
- Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(
+ Maybe<xml::ExtractedPackage> maybePackage = xml::extractPackageFromNamespace(
node->namespaceUri);
if (maybePackage) {
// This is a custom view, let's figure out the class name from this.
- std::u16string package = maybePackage.value() + u"." + node->name;
+ std::u16string package = maybePackage.value().package + u"." + node->name;
if (util::isJavaClassName(package)) {
addClass(node->lineNumber, package);
}
@@ -61,11 +59,11 @@ public:
protected:
void addClass(size_t lineNumber, const std::u16string& className) {
- mKeepSet->addClass(mSource.line(lineNumber), className);
+ mKeepSet->addClass(Source(mSource.path, lineNumber), className);
}
void addMethod(size_t lineNumber, const std::u16string& methodName) {
- mKeepSet->addMethod(mSource.line(lineNumber), methodName);
+ mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName);
}
private:
@@ -82,7 +80,7 @@ struct LayoutVisitor : public BaseVisitor {
bool checkName = false;
if (node->namespaceUri.empty()) {
checkClass = node->name == u"view" || node->name == u"fragment";
- } else if (node->namespaceUri == kSchemaAndroid) {
+ } else if (node->namespaceUri == xml::kSchemaAndroid) {
checkName = node->name == u"fragment";
}
@@ -90,10 +88,10 @@ struct LayoutVisitor : public BaseVisitor {
if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" &&
util::isJavaClassName(attr.value)) {
addClass(node->lineNumber, attr.value);
- } else if (checkName && attr.namespaceUri == kSchemaAndroid && attr.name == u"name" &&
- util::isJavaClassName(attr.value)) {
+ } else if (checkName && attr.namespaceUri == xml::kSchemaAndroid &&
+ attr.name == u"name" && util::isJavaClassName(attr.value)) {
addClass(node->lineNumber, attr.value);
- } else if (attr.namespaceUri == kSchemaAndroid && attr.name == u"onClick") {
+ } else if (attr.namespaceUri == xml::kSchemaAndroid && attr.name == u"onClick") {
addMethod(node->lineNumber, attr.value);
}
}
@@ -113,7 +111,7 @@ struct XmlResourceVisitor : public BaseVisitor {
}
if (checkFragment) {
- xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment");
+ xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"fragment");
if (attr && util::isJavaClassName(attr->value)) {
addClass(node->lineNumber, attr->value);
}
@@ -155,7 +153,7 @@ struct ManifestVisitor : public BaseVisitor {
}
} else if (node->name == u"application") {
getName = true;
- xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"backupAgent");
+ xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"backupAgent");
if (attr) {
Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
attr->value);
@@ -170,7 +168,7 @@ struct ManifestVisitor : public BaseVisitor {
}
if (getName) {
- xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"name");
+ xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"name");
if (attr) {
Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
attr->value);
@@ -186,30 +184,37 @@ struct ManifestVisitor : public BaseVisitor {
std::u16string mPackage;
};
-bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) {
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res,
+ KeepSet* keepSet) {
ManifestVisitor visitor(source, keepSet);
- node->accept(&visitor);
- return true;
+ if (res->root) {
+ res->root->accept(&visitor);
+ return true;
+ }
+ return false;
}
-bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
- KeepSet* keepSet) {
- switch (type) {
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet) {
+ if (!res->root) {
+ return false;
+ }
+
+ switch (res->file.name.type) {
case ResourceType::kLayout: {
LayoutVisitor visitor(source, keepSet);
- node->accept(&visitor);
+ res->root->accept(&visitor);
break;
}
case ResourceType::kXml: {
XmlResourceVisitor visitor(source, keepSet);
- node->accept(&visitor);
+ res->root->accept(&visitor);
break;
}
case ResourceType::kTransition: {
TransitionVisitor visitor(source, keepSet);
- node->accept(&visitor);
+ res->root->accept(&visitor);
break;
}
@@ -221,15 +226,15 @@ bool collectProguardRules(ResourceType type, const Source& source, xml::Node* no
bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
for (const auto& entry : keepSet.mKeepSet) {
- for (const SourceLine& source : entry.second) {
- *out << "// Referenced at " << source << "\n";
+ for (const Source& source : entry.second) {
+ *out << "# Referenced at " << source << "\n";
}
*out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
}
for (const auto& entry : keepSet.mKeepMethodSet) {
- for (const SourceLine& source : entry.second) {
- *out << "// Referenced at " << source << "\n";
+ for (const Source& source : entry.second) {
+ *out << "# Referenced at " << source << "\n";
}
*out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
}
diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index bbb3e64e6758..aafffd39d84e 100644
--- a/tools/aapt2/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -19,7 +19,7 @@
#include "Resource.h"
#include "Source.h"
-#include "XmlDom.h"
+#include "xml/XmlDom.h"
#include <map>
#include <ostream>
@@ -31,24 +31,23 @@ namespace proguard {
class KeepSet {
public:
- inline void addClass(const SourceLine& source, const std::u16string& className) {
+ inline void addClass(const Source& source, const std::u16string& className) {
mKeepSet[className].insert(source);
}
- inline void addMethod(const SourceLine& source, const std::u16string& methodName) {
+ inline void addMethod(const Source& source, const std::u16string& methodName) {
mKeepMethodSet[methodName].insert(source);
}
private:
friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
- std::map<std::u16string, std::set<SourceLine>> mKeepSet;
- std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet;
+ std::map<std::u16string, std::set<Source>> mKeepSet;
+ std::map<std::u16string, std::set<Source>> mKeepMethodSet;
};
-bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet);
-bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
- KeepSet* keepSet);
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
new file mode 100644
index 000000000000..459c330cfbdc
--- /dev/null
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "SdkConstants.h"
+#include "ValueVisitor.h"
+#include "link/Linkers.h"
+
+#include <algorithm>
+#include <cassert>
+
+namespace aapt {
+
+bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+ const int sdkVersionToGenerate) {
+ assert(sdkVersionToGenerate > config.sdkVersion);
+ const auto endIter = entry->values.end();
+ auto iter = entry->values.begin();
+ for (; iter != endIter; ++iter) {
+ if ((*iter)->config == config) {
+ break;
+ }
+ }
+
+ // The source config came from this list, so it should be here.
+ assert(iter != entry->values.end());
+ ++iter;
+
+ // The next configuration either only varies in sdkVersion, or it is completely different
+ // and therefore incompatible. If it is incompatible, we must generate the versioned resource.
+
+ // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
+ // qualifiers, so we need to iterate through the entire list to be sure there
+ // are no higher sdk level versions of this resource.
+ ConfigDescription tempConfig(config);
+ for (; iter != endIter; ++iter) {
+ tempConfig.sdkVersion = (*iter)->config.sdkVersion;
+ if (tempConfig == (*iter)->config) {
+ // The two configs are the same, check the sdk version.
+ return sdkVersionToGenerate < (*iter)->config.sdkVersion;
+ }
+ }
+
+ // No match was found, so we should generate the versioned resource.
+ return true;
+}
+
+bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ if (type->type != ResourceType::kStyle) {
+ continue;
+ }
+
+ for (auto& entry : type->entries) {
+ for (size_t i = 0; i < entry->values.size(); i++) {
+ ResourceConfigValue* configValue = entry->values[i].get();
+ if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) {
+ // If this configuration is only used on L-MR1 then we don't need
+ // to do anything since we use private attributes since that version.
+ continue;
+ }
+
+ if (Style* style = valueCast<Style>(configValue->value.get())) {
+ Maybe<size_t> minSdkStripped;
+ std::vector<Style::Entry> stripped;
+
+ auto iter = style->entries.begin();
+ while (iter != style->entries.end()) {
+ assert(iter->key.id && "IDs must be assigned and linked");
+
+ // Find the SDK level that is higher than the configuration allows.
+ const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value());
+ if (sdkLevel > std::max<size_t>(configValue->config.sdkVersion, 1)) {
+ // Record that we are about to strip this.
+ stripped.emplace_back(std::move(*iter));
+
+ // We use the smallest SDK level to generate the new style.
+ if (minSdkStripped) {
+ minSdkStripped = std::min(minSdkStripped.value(), sdkLevel);
+ } else {
+ minSdkStripped = sdkLevel;
+ }
+
+ // Erase this from this style.
+ iter = style->entries.erase(iter);
+ continue;
+ }
+ ++iter;
+ }
+
+ if (minSdkStripped && !stripped.empty()) {
+ // We found attributes from a higher SDK level. Check that
+ // there is no other defined resource for the version we want to
+ // generate.
+ if (shouldGenerateVersionedResource(entry.get(),
+ configValue->config,
+ minSdkStripped.value())) {
+ // Let's create a new Style for this versioned resource.
+ ConfigDescription newConfig(configValue->config);
+ newConfig.sdkVersion = minSdkStripped.value();
+
+ std::unique_ptr<Style> newStyle(style->clone(&table->stringPool));
+ newStyle->setComment(style->getComment());
+ newStyle->setSource(style->getSource());
+
+ // Move the previously stripped attributes into this style.
+ newStyle->entries.insert(newStyle->entries.end(),
+ std::make_move_iterator(stripped.begin()),
+ std::make_move_iterator(stripped.end()));
+
+ // Insert the new Resource into the correct place.
+ entry->findOrCreateValue(newConfig, {})->value =
+ std::move(newStyle);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
new file mode 100644
index 000000000000..9b3a87c4eed0
--- /dev/null
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "link/Linkers.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(AutoVersionerTest, GenerateVersionedResources) {
+ const ConfigDescription defaultConfig = {};
+ const ConfigDescription landConfig = test::parseConfigOrDie("land");
+ const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land");
+
+ ResourceEntry entry(u"foo");
+ entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
+ entry.values.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
+ entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
+
+ EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+ EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
+}
+
+TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
+ const ConfigDescription defaultConfig = {};
+ const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13");
+ const ConfigDescription v21Config = test::parseConfigOrDie("v21");
+
+ ResourceEntry entry(u"foo");
+ entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
+ entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpV13Config, ""));
+ entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, ""));
+
+ EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+ EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
+}
+
+TEST(AutoVersionerTest, VersionStylesForTable) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"app", 0x7f)
+ .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v4"),
+ test::StyleBuilder()
+ .addItem(u"@android:attr/onClick", ResourceId(0x0101026f),
+ util::make_unique<Id>())
+ .addItem(u"@android:attr/paddingStart", ResourceId(0x010103b3),
+ util::make_unique<Id>())
+ .addItem(u"@android:attr/requiresSmallestWidthDp",
+ ResourceId(0x01010364), util::make_unique<Id>())
+ .addItem(u"@android:attr/colorAccent", ResourceId(0x01010435),
+ util::make_unique<Id>())
+ .build())
+ .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v21"),
+ test::StyleBuilder()
+ .addItem(u"@android:attr/paddingEnd", ResourceId(0x010103b4),
+ util::make_unique<Id>())
+ .build())
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"app")
+ .setPackageId(0x7f)
+ .build();
+
+ AutoVersioner versioner;
+ ASSERT_TRUE(versioner.consume(context.get(), table.get()));
+
+ Style* style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+ test::parseConfigOrDie("v4"));
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(style->entries.size(), 1u);
+ AAPT_ASSERT_TRUE(style->entries.front().key.name);
+ EXPECT_EQ(style->entries.front().key.name.value(),
+ test::parseNameOrDie(u"@android:attr/onClick"));
+
+ style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+ test::parseConfigOrDie("v13"));
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(style->entries.size(), 2u);
+ AAPT_ASSERT_TRUE(style->entries[0].key.name);
+ EXPECT_EQ(style->entries[0].key.name.value(),
+ test::parseNameOrDie(u"@android:attr/onClick"));
+ AAPT_ASSERT_TRUE(style->entries[1].key.name);
+ EXPECT_EQ(style->entries[1].key.name.value(),
+ test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp"));
+
+ style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+ test::parseConfigOrDie("v17"));
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(style->entries.size(), 3u);
+ AAPT_ASSERT_TRUE(style->entries[0].key.name);
+ EXPECT_EQ(style->entries[0].key.name.value(),
+ test::parseNameOrDie(u"@android:attr/onClick"));
+ AAPT_ASSERT_TRUE(style->entries[1].key.name);
+ EXPECT_EQ(style->entries[1].key.name.value(),
+ test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp"));
+ AAPT_ASSERT_TRUE(style->entries[2].key.name);
+ EXPECT_EQ(style->entries[2].key.name.value(),
+ test::parseNameOrDie(u"@android:attr/paddingStart"));
+
+ style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+ test::parseConfigOrDie("v21"));
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(style->entries.size(), 1u);
+ AAPT_ASSERT_TRUE(style->entries.front().key.name);
+ EXPECT_EQ(style->entries.front().key.name.value(),
+ test::parseNameOrDie(u"@android:attr/paddingEnd"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
new file mode 100644
index 000000000000..49971201fb3c
--- /dev/null
+++ b/tools/aapt2/link/Link.cpp
@@ -0,0 +1,1589 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AppInfo.h"
+#include "Debug.h"
+#include "Flags.h"
+#include "Locale.h"
+#include "NameMangler.h"
+#include "ResourceUtils.h"
+#include "compile/IdAssigner.h"
+#include "filter/ConfigFilter.h"
+#include "flatten/Archive.h"
+#include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
+#include "io/FileSystem.h"
+#include "io/ZipArchive.h"
+#include "java/JavaClassGenerator.h"
+#include "java/ManifestClassGenerator.h"
+#include "java/ProguardRules.h"
+#include "link/Linkers.h"
+#include "link/ProductFilter.h"
+#include "link/ReferenceLinker.h"
+#include "link/ManifestFixer.h"
+#include "link/TableMerger.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "proto/ProtoSerialize.h"
+#include "split/TableSplitter.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+#include "xml/XmlDom.h"
+
+#include <google/protobuf/io/coded_stream.h>
+
+#include <fstream>
+#include <sys/stat.h>
+#include <vector>
+
+namespace aapt {
+
+struct LinkOptions {
+ std::string outputPath;
+ std::string manifestPath;
+ std::vector<std::string> includePaths;
+ std::vector<std::string> overlayFiles;
+ Maybe<std::string> generateJavaClassPath;
+ Maybe<std::u16string> customJavaPackage;
+ std::set<std::u16string> extraJavaPackages;
+ Maybe<std::string> generateProguardRulesPath;
+ bool noAutoVersion = false;
+ bool noVersionVectors = false;
+ bool staticLib = false;
+ bool noStaticLibPackages = false;
+ bool generateNonFinalIds = false;
+ std::vector<std::string> javadocAnnotations;
+ bool outputToDirectory = false;
+ bool autoAddOverlay = false;
+ bool doNotCompressAnything = false;
+ std::vector<std::string> extensionsToNotCompress;
+ Maybe<std::u16string> privateSymbols;
+ ManifestFixerOptions manifestFixerOptions;
+ std::unordered_set<std::string> products;
+ TableSplitterOptions tableSplitterOptions;
+};
+
+class LinkContext : public IAaptContext {
+public:
+ LinkContext() : mNameMangler({}) {
+ }
+
+ IDiagnostics* getDiagnostics() override {
+ return &mDiagnostics;
+ }
+
+ NameMangler* getNameMangler() override {
+ return &mNameMangler;
+ }
+
+ void setNameManglerPolicy(const NameManglerPolicy& policy) {
+ mNameMangler = NameMangler(policy);
+ }
+
+ const std::u16string& getCompilationPackage() override {
+ return mCompilationPackage;
+ }
+
+ void setCompilationPackage(const StringPiece16& packageName) {
+ mCompilationPackage = packageName.toString();
+ }
+
+ uint8_t getPackageId() override {
+ return mPackageId;
+ }
+
+ void setPackageId(uint8_t id) {
+ mPackageId = id;
+ }
+
+ SymbolTable* getExternalSymbols() override {
+ return &mSymbols;
+ }
+
+ bool verbose() override {
+ return mVerbose;
+ }
+
+ void setVerbose(bool val) {
+ mVerbose = val;
+ }
+
+private:
+ StdErrDiagnostics mDiagnostics;
+ NameMangler mNameMangler;
+ std::u16string mCompilationPackage;
+ uint8_t mPackageId = 0x0;
+ SymbolTable mSymbols;
+ bool mVerbose = false;
+};
+
+static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
+ uint32_t compressionFlags,
+ IArchiveWriter* writer, IAaptContext* context) {
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ context->getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
+ return false;
+ }
+
+ const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
+ size_t bufferSize = data->size();
+
+ // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
+ if (util::stringEndsWith<char>(file->getSource().path, ".flat")) {
+ CompiledFileInputStream inputStream(data->data(), data->size());
+ if (!inputStream.CompiledFile()) {
+ context->getDiagnostics()->error(DiagMessage(file->getSource())
+ << "invalid compiled file header");
+ return false;
+ }
+ buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
+ bufferSize = inputStream.size();
+ }
+
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
+ }
+
+ if (writer->startEntry(outPath, compressionFlags)) {
+ if (writer->writeEntry(buffer, bufferSize)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
+ }
+
+ context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
+ return false;
+}
+
+static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+ bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions options = {};
+ options.keepRawValues = keepRawValues;
+ options.maxSdkLevel = maxSdkLevel;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.consume(context, xmlRes)) {
+ return false;
+ }
+
+ if (context->verbose()) {
+ DiagMessage msg;
+ msg << "writing " << path << " to archive";
+ if (maxSdkLevel) {
+ msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
+ }
+ context->getDiagnostics()->note(msg);
+ }
+
+ if (writer->startEntry(path, ArchiveEntry::kCompress)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
+ }
+ context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
+ return false;
+}
+
+/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
+ IDiagnostics* diag) {
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+ BinaryResourceParser parser(diag, table.get(), source, data, len);
+ if (!parser.parse()) {
+ return {};
+ }
+ return table;
+}*/
+
+static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
+ pb::ResourceTable pbTable;
+ if (!pbTable.ParseFromArray(data, len)) {
+ diag->error(DiagMessage(source) << "invalid compiled table");
+ return {};
+ }
+
+ std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
+ if (!table) {
+ return {};
+ }
+ return table;
+}
+
+/**
+ * Inflates an XML file from the source path.
+ */
+static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
+ std::ifstream fin(path, std::ifstream::binary);
+ if (!fin) {
+ diag->error(DiagMessage(path) << strerror(errno));
+ return {};
+ }
+ return xml::inflate(&fin, diag, Source(path));
+}
+
+static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
+ CompiledFileInputStream inputStream(data, len);
+ if (!inputStream.CompiledFile()) {
+ diag->error(DiagMessage(source) << "invalid compiled file header");
+ return {};
+ }
+
+ const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
+ const size_t xmlDataLen = inputStream.size();
+
+ std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
+ if (!xmlRes) {
+ return {};
+ }
+ return xmlRes;
+}
+
+static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
+ CompiledFileInputStream inputStream(data, len);
+ const pb::CompiledFile* pbFile = inputStream.CompiledFile();
+ if (!pbFile) {
+ diag->error(DiagMessage(source) << "invalid compiled file header");
+ return {};
+ }
+
+ std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
+ if (!resFile) {
+ return {};
+ }
+ return resFile;
+}
+
+struct ResourceFileFlattenerOptions {
+ bool noAutoVersion = false;
+ bool noVersionVectors = false;
+ bool keepRawValues = false;
+ bool doNotCompressAnything = false;
+ std::vector<std::string> extensionsToNotCompress;
+};
+
+class ResourceFileFlattener {
+public:
+ ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
+ IAaptContext* context, proguard::KeepSet* keepSet) :
+ mOptions(options), mContext(context), mKeepSet(keepSet) {
+ }
+
+ bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
+
+private:
+ struct FileOperation {
+ io::IFile* fileToCopy;
+ std::unique_ptr<xml::XmlResource> xmlToFlatten;
+ std::string dstPath;
+ bool skipVersion = false;
+ };
+
+ uint32_t getCompressionFlags(const StringPiece& str);
+
+ bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
+ io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
+
+ ResourceFileFlattenerOptions mOptions;
+ IAaptContext* mContext;
+ proguard::KeepSet* mKeepSet;
+};
+
+uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
+ if (mOptions.doNotCompressAnything) {
+ return 0;
+ }
+
+ for (const std::string& extension : mOptions.extensionsToNotCompress) {
+ if (util::stringEndsWith<char>(str, extension)) {
+ return 0;
+ }
+ }
+ return ArchiveEntry::kCompress;
+}
+
+bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
+ const ResourceFile& fileDesc,
+ io::IFile* file,
+ ResourceTable* table,
+ FileOperation* outFileOp) {
+ const StringPiece srcPath = file->getSource().path;
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
+ }
+
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
+ return false;
+ }
+
+ if (util::stringEndsWith<char>(srcPath, ".flat")) {
+ outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
+ data->data(), data->size(),
+ mContext->getDiagnostics());
+ } else {
+ outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
+ mContext->getDiagnostics(),
+ file->getSource());
+ }
+
+ if (!outFileOp->xmlToFlatten) {
+ return false;
+ }
+
+ // Copy the the file description header.
+ outFileOp->xmlToFlatten->file = fileDesc;
+
+ XmlReferenceLinker xmlLinker;
+ if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
+ return false;
+ }
+
+ if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source,
+ outFileOp->xmlToFlatten.get(), mKeepSet)) {
+ return false;
+ }
+
+ if (!mOptions.noAutoVersion) {
+ if (mOptions.noVersionVectors) {
+ // Skip this if it is a vector or animated-vector.
+ xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
+ if (el && el->namespaceUri.empty()) {
+ if (el->name == u"vector" || el->name == u"animated-vector") {
+ // We are NOT going to version this file.
+ outFileOp->skipVersion = true;
+ return true;
+ }
+ }
+ }
+
+ // Find the first SDK level used that is higher than this defined config and
+ // not superseded by a lower or equal SDK level resource.
+ for (int sdkLevel : xmlLinker.getSdkLevels()) {
+ if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
+ if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
+ sdkLevel)) {
+ // If we shouldn't generate a versioned resource, stop checking.
+ break;
+ }
+
+ ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
+ versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
+
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
+ << "auto-versioning resource from config '"
+ << outFileOp->xmlToFlatten->file.config
+ << "' -> '"
+ << versionedFileDesc.config << "'");
+ }
+
+ std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
+ versionedFileDesc, mContext->getNameMangler()));
+
+ bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
+ versionedFileDesc.config,
+ versionedFileDesc.source,
+ genPath,
+ file,
+ mContext->getDiagnostics());
+ if (!added) {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+/**
+ * Do not insert or remove any resources while executing in this function. It will
+ * corrupt the iteration order.
+ */
+bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
+ bool error = false;
+ std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;
+
+ for (auto& pkg : table->packages) {
+ for (auto& type : pkg->types) {
+ // Sort by config and name, so that we get better locality in the zip file.
+ configSortedFiles.clear();
+ for (auto& entry : type->entries) {
+ // Iterate via indices because auto generated values can be inserted ahead of
+ // the value being processed.
+ for (size_t i = 0; i < entry->values.size(); i++) {
+ ResourceConfigValue* configValue = entry->values[i].get();
+
+ FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
+ if (!fileRef) {
+ continue;
+ }
+
+ io::IFile* file = fileRef->file;
+ if (!file) {
+ mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
+ << "file not found");
+ return false;
+ }
+
+ FileOperation fileOp;
+ fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);
+
+ const StringPiece srcPath = file->getSource().path;
+ if (type->type != ResourceType::kRaw &&
+ (util::stringEndsWith<char>(srcPath, ".xml.flat") ||
+ util::stringEndsWith<char>(srcPath, ".xml"))) {
+ ResourceFile fileDesc;
+ fileDesc.config = configValue->config;
+ fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
+ fileDesc.source = fileRef->getSource();
+ if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
+ error = true;
+ continue;
+ }
+
+ } else {
+ fileOp.fileToCopy = file;
+ }
+
+ // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
+ // we end up copying the string in the std::make_pair() method, then creating
+ // a StringPiece16 from the copy, which would cause us to end up referencing
+ // garbage in the map.
+ const StringPiece16 entryName(entry->name);
+ configSortedFiles[std::make_pair(configValue->config, entryName)] =
+ std::move(fileOp);
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+
+ // Now flatten the sorted values.
+ for (auto& mapEntry : configSortedFiles) {
+ const ConfigDescription& config = mapEntry.first.first;
+ const FileOperation& fileOp = mapEntry.second;
+
+ if (fileOp.xmlToFlatten) {
+ Maybe<size_t> maxSdkLevel;
+ if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
+ maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
+ }
+
+ bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
+ mOptions.keepRawValues,
+ archiveWriter, mContext);
+ if (!result) {
+ error = true;
+ }
+ } else {
+ bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
+ getCompressionFlags(fileOp.dstPath),
+ archiveWriter, mContext);
+ if (!result) {
+ error = true;
+ }
+ }
+ }
+ }
+ }
+ return !error;
+}
+
+class LinkCommand {
+public:
+ LinkCommand(LinkContext* context, const LinkOptions& options) :
+ mOptions(options), mContext(context), mFinalTable(),
+ mFileCollection(util::make_unique<io::FileCollection>()) {
+ }
+
+ /**
+ * Creates a SymbolTable that loads symbols from the various APKs and caches the
+ * results for faster lookup.
+ */
+ bool loadSymbolsFromIncludePaths() {
+ std::unique_ptr<AssetManagerSymbolSource> assetSource =
+ util::make_unique<AssetManagerSymbolSource>();
+ for (const std::string& path : mOptions.includePaths) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
+ }
+
+ // First try to load the file as a static lib.
+ std::string errorStr;
+ std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
+ if (staticInclude) {
+ if (!mOptions.staticLib) {
+ // Can't include static libraries when not building a static library.
+ mContext->getDiagnostics()->error(
+ DiagMessage(path) << "can't include static library when building app");
+ return false;
+ }
+
+ // If we are using --no-static-lib-packages, we need to rename the package of this
+ // table to our compilation package.
+ if (mOptions.noStaticLibPackages) {
+ if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
+ pkg->name = mContext->getCompilationPackage();
+ }
+ }
+
+ mContext->getExternalSymbols()->appendSource(
+ util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
+
+ mStaticTableIncludes.push_back(std::move(staticInclude));
+
+ } else if (!errorStr.empty()) {
+ // We had an error with reading, so fail.
+ mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
+ return false;
+ }
+
+ if (!assetSource->addAssetPath(path)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(path) << "failed to load include path");
+ return false;
+ }
+ }
+
+ mContext->getExternalSymbols()->appendSource(std::move(assetSource));
+ return true;
+ }
+
+ Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
+ // Make sure the first element is <manifest> with package attribute.
+ if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
+ if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
+ if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
+ return AppInfo{ packageAttr->value };
+ }
+ }
+ }
+ return {};
+ }
+
+ /**
+ * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
+ * Postcondition: ResourceTable has only one package left. All others are stripped, or there
+ * is an error and false is returned.
+ */
+ bool verifyNoExternalPackages() {
+ auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
+ return mContext->getCompilationPackage() != pkg->name ||
+ !pkg->id ||
+ pkg->id.value() != mContext->getPackageId();
+ };
+
+ bool error = false;
+ for (const auto& package : mFinalTable.packages) {
+ if (isExtPackageFunc(package)) {
+ // We have a package that is not related to the one we're building!
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ ResourceNameRef resName(package->name, type->type, entry->name);
+
+ for (const auto& configValue : entry->values) {
+ // Special case the occurrence of an ID that is being generated for the
+ // 'android' package. This is due to legacy reasons.
+ if (valueCast<Id>(configValue->value.get()) &&
+ package->name == u"android") {
+ mContext->getDiagnostics()->warn(
+ DiagMessage(configValue->value->getSource())
+ << "generated id '" << resName
+ << "' for external package '" << package->name
+ << "'");
+ } else {
+ mContext->getDiagnostics()->error(
+ DiagMessage(configValue->value->getSource())
+ << "defined resource '" << resName
+ << "' for external package '" << package->name
+ << "'");
+ error = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
+ isExtPackageFunc);
+ mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
+ return !error;
+ }
+
+ /**
+ * Returns true if no IDs have been set, false otherwise.
+ */
+ bool verifyNoIdsSet() {
+ for (const auto& package : mFinalTable.packages) {
+ for (const auto& type : package->types) {
+ if (type->id) {
+ mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
+ << " has ID " << std::hex
+ << (int) type->id.value()
+ << std::dec << " assigned");
+ return false;
+ }
+
+ for (const auto& entry : type->entries) {
+ if (entry->id) {
+ ResourceNameRef resName(package->name, type->type, entry->name);
+ mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
+ << " has ID " << std::hex
+ << (int) entry->id.value()
+ << std::dec << " assigned");
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
+ if (mOptions.outputToDirectory) {
+ return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
+ } else {
+ return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
+ }
+ }
+
+ bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.consume(mContext, table)) {
+ return false;
+ }
+
+ if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
+ }
+
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed to write resources.arsc to archive");
+ return false;
+ }
+
+ bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
+ // Create the file/zip entry.
+ if (!writer->startEntry("resources.arsc.flat", 0)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
+ return false;
+ }
+
+ std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
+
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
+ // interface.
+ {
+ google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+
+ if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
+ return false;
+ }
+ }
+
+ if (!writer->finishEntry()) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
+ return false;
+ }
+ return true;
+ }
+
+ bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
+ const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
+ if (!mOptions.generateJavaClassPath) {
+ return true;
+ }
+
+ std::string outPath = mOptions.generateJavaClassPath.value();
+ file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
+ if (!file::mkdirs(outPath)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed to create directory '" << outPath << "'");
+ return false;
+ }
+
+ file::appendPath(&outPath, "R.java");
+
+ std::ofstream fout(outPath, std::ofstream::binary);
+ if (!fout) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
+ return false;
+ }
+
+ JavaClassGenerator generator(mContext, table, javaOptions);
+ if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
+ mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
+ return false;
+ }
+
+ if (!fout) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
+ }
+ return true;
+ }
+
+ bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
+ if (!mOptions.generateJavaClassPath) {
+ return true;
+ }
+
+ std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
+ mContext->getDiagnostics(), manifestXml);
+
+ if (!manifestClass) {
+ // Something bad happened, but we already logged it, so exit.
+ return false;
+ }
+
+ if (manifestClass->empty()) {
+ // Empty Manifest class, no need to generate it.
+ return true;
+ }
+
+ // Add any JavaDoc annotations to the generated class.
+ for (const std::string& annotation : mOptions.javadocAnnotations) {
+ std::string properAnnotation = "@";
+ properAnnotation += annotation;
+ manifestClass->getCommentBuilder()->appendComment(properAnnotation);
+ }
+
+ const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage());
+
+ std::string outPath = mOptions.generateJavaClassPath.value();
+ file::appendPath(&outPath, file::packageToPath(packageUtf8));
+
+ if (!file::mkdirs(outPath)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed to create directory '" << outPath << "'");
+ return false;
+ }
+
+ file::appendPath(&outPath, "Manifest.java");
+
+ std::ofstream fout(outPath, std::ofstream::binary);
+ if (!fout) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
+ return false;
+ }
+
+ if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
+ return false;
+ }
+ return true;
+ }
+
+ bool writeProguardFile(const proguard::KeepSet& keepSet) {
+ if (!mOptions.generateProguardRulesPath) {
+ return true;
+ }
+
+ const std::string& outPath = mOptions.generateProguardRulesPath.value();
+ std::ofstream fout(outPath, std::ofstream::binary);
+ if (!fout) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
+ return false;
+ }
+
+ proguard::writeKeepSet(&fout, keepSet);
+ if (!fout) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
+ return false;
+ }
+ return true;
+ }
+
+ std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
+ std::string* outError) {
+ std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
+ input, outError);
+ if (!collection) {
+ return {};
+ }
+ return loadTablePbFromCollection(collection.get());
+ }
+
+ std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
+ io::IFile* file = collection->findFile("resources.arsc.flat");
+ if (!file) {
+ return {};
+ }
+
+ std::unique_ptr<io::IData> data = file->openAsData();
+ return loadTableFromPb(file->getSource(), data->data(), data->size(),
+ mContext->getDiagnostics());
+ }
+
+ bool mergeStaticLibrary(const std::string& input, bool override) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
+ }
+
+ std::string errorStr;
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::create(input, &errorStr);
+ if (!collection) {
+ mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
+ return false;
+ }
+
+ std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
+ if (!table) {
+ mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
+ return false;
+ }
+
+ ResourceTablePackage* pkg = table->findPackageById(0x7f);
+ if (!pkg) {
+ mContext->getDiagnostics()->error(DiagMessage(input)
+ << "static library has no package");
+ return false;
+ }
+
+ bool result;
+ if (mOptions.noStaticLibPackages) {
+ // Merge all resources as if they were in the compilation package. This is the old
+ // behaviour of aapt.
+
+ // Add the package to the set of --extra-packages so we emit an R.java for each
+ // library package.
+ if (!pkg->name.empty()) {
+ mOptions.extraJavaPackages.insert(pkg->name);
+ }
+
+ pkg->name = u"";
+ if (override) {
+ result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
+ } else {
+ result = mTableMerger->merge(Source(input), table.get(), collection.get());
+ }
+
+ } else {
+ // This is the proper way to merge libraries, where the package name is preserved
+ // and resource names are mangled.
+ result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
+ collection.get());
+ }
+
+ if (!result) {
+ return false;
+ }
+
+ // Make sure to move the collection into the set of IFileCollections.
+ mCollections.push_back(std::move(collection));
+ return true;
+ }
+
+ bool mergeResourceTable(io::IFile* file, bool override) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
+ << file->getSource());
+ }
+
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
+ return false;
+ }
+
+ std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
+ data->data(), data->size(),
+ mContext->getDiagnostics());
+ if (!table) {
+ return false;
+ }
+
+ bool result = false;
+ if (override) {
+ result = mTableMerger->mergeOverlay(file->getSource(), table.get());
+ } else {
+ result = mTableMerger->merge(file->getSource(), table.get());
+ }
+ return result;
+ }
+
+ bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
+ << file->getSource());
+ }
+
+ bool result = false;
+ if (override) {
+ result = mTableMerger->mergeFileOverlay(*fileDesc, file);
+ } else {
+ result = mTableMerger->mergeFile(*fileDesc, file);
+ }
+
+ if (!result) {
+ return false;
+ }
+
+ // Add the exports of this file to the table.
+ for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
+ if (exportedSymbol.name.package.empty()) {
+ exportedSymbol.name.package = mContext->getCompilationPackage();
+ }
+
+ ResourceNameRef resName = exportedSymbol.name;
+
+ Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
+ exportedSymbol.name);
+ if (mangledName) {
+ resName = mangledName.value();
+ }
+
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->setSource(fileDesc->source.withLine(exportedSymbol.line));
+ bool result = mFinalTable.addResourceAllowMangled(
+ resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
+ mContext->getDiagnostics());
+ if (!result) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
+ * If override is true, conflicting resources are allowed to override each other, in order of
+ * last seen.
+ *
+ * An io::IFileCollection is created from the ZIP file and added to the set of
+ * io::IFileCollections that are open.
+ */
+ bool mergeArchive(const std::string& input, bool override) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
+ }
+
+ std::string errorStr;
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::create(input, &errorStr);
+ if (!collection) {
+ mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
+ return false;
+ }
+
+ bool error = false;
+ for (auto iter = collection->iterator(); iter->hasNext(); ) {
+ if (!mergeFile(iter->next(), override)) {
+ error = true;
+ }
+ }
+
+ // Make sure to move the collection into the set of IFileCollections.
+ mCollections.push_back(std::move(collection));
+ return !error;
+ }
+
+ /**
+ * Takes a path to load and merge into the master ResourceTable. If override is true,
+ * conflicting resources are allowed to override each other, in order of last seen.
+ *
+ * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
+ * and the files within are merged individually.
+ *
+ * Otherwise the files is processed on its own.
+ */
+ bool mergePath(const std::string& path, bool override) {
+ if (util::stringEndsWith<char>(path, ".flata") ||
+ util::stringEndsWith<char>(path, ".jar") ||
+ util::stringEndsWith<char>(path, ".jack") ||
+ util::stringEndsWith<char>(path, ".zip")) {
+ return mergeArchive(path, override);
+ } else if (util::stringEndsWith<char>(path, ".apk")) {
+ return mergeStaticLibrary(path, override);
+ }
+
+ io::IFile* file = mFileCollection->insertFile(path);
+ return mergeFile(file, override);
+ }
+
+ /**
+ * Takes a file to load and merge into the master ResourceTable. If override is true,
+ * conflicting resources are allowed to override each other, in order of last seen.
+ *
+ * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
+ * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
+ * and the header data is read and merged into the final ResourceTable.
+ *
+ * All other file types are ignored. This is because these files could be coming from a zip,
+ * where we could have other files like classes.dex.
+ */
+ bool mergeFile(io::IFile* file, bool override) {
+ const Source& src = file->getSource();
+ if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
+ return mergeResourceTable(file, override);
+
+ } else if (util::stringEndsWith<char>(src.path, ".flat")){
+ // Try opening the file and looking for an Export header.
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
+ return false;
+ }
+
+ std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
+ src, data->data(), data->size(), mContext->getDiagnostics());
+ if (resourceFile) {
+ return mergeCompiledFile(file, resourceFile.get(), override);
+ }
+ return false;
+ }
+
+ // Ignore non .flat files. This could be classes.dex or something else that happens
+ // to be in an archive.
+ return true;
+ }
+
+ int run(const std::vector<std::string>& inputFiles) {
+ // Load the AndroidManifest.xml
+ std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
+ mContext->getDiagnostics());
+ if (!manifestXml) {
+ return 1;
+ }
+
+ if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
+ mContext->setCompilationPackage(maybeAppInfo.value().package);
+ } else {
+ mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+ << "no package specified in <manifest> tag");
+ return 1;
+ }
+
+ if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
+ mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+ << "invalid package name '"
+ << mContext->getCompilationPackage()
+ << "'");
+ return 1;
+ }
+
+ mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
+
+ if (mContext->getCompilationPackage() == u"android") {
+ mContext->setPackageId(0x01);
+ } else {
+ mContext->setPackageId(0x7f);
+ }
+
+ if (!loadSymbolsFromIncludePaths()) {
+ return 1;
+ }
+
+ TableMergerOptions tableMergerOptions;
+ tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
+ mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
+
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(
+ DiagMessage() << "linking package '" << mContext->getCompilationPackage()
+ << "' with package ID " << std::hex
+ << (int) mContext->getPackageId());
+ }
+
+
+ for (const std::string& input : inputFiles) {
+ if (!mergePath(input, false)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
+ return 1;
+ }
+ }
+
+ for (const std::string& input : mOptions.overlayFiles) {
+ if (!mergePath(input, true)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
+ return 1;
+ }
+ }
+
+ if (!verifyNoExternalPackages()) {
+ return 1;
+ }
+
+ if (!mOptions.staticLib) {
+ PrivateAttributeMover mover;
+ if (!mover.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed moving private attributes");
+ return 1;
+ }
+ }
+
+ if (!mOptions.staticLib) {
+ // Assign IDs if we are building a regular app.
+ IdAssigner idAssigner;
+ if (!idAssigner.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
+ return 1;
+ }
+ } else {
+ // Static libs are merged with other apps, and ID collisions are bad, so verify that
+ // no IDs have been set.
+ if (!verifyNoIdsSet()) {
+ return 1;
+ }
+ }
+
+ // Add the names to mangle based on our source merge earlier.
+ mContext->setNameManglerPolicy(NameManglerPolicy{
+ mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
+
+ // Add our table to the symbol table.
+ mContext->getExternalSymbols()->prependSource(
+ util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
+
+ {
+ ReferenceLinker linker;
+ if (!linker.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
+ return 1;
+ }
+
+ if (mOptions.staticLib) {
+ if (!mOptions.products.empty()) {
+ mContext->getDiagnostics()->warn(
+ DiagMessage() << "can't select products when building static library");
+ }
+
+ if (mOptions.tableSplitterOptions.configFilter != nullptr ||
+ mOptions.tableSplitterOptions.preferredDensity) {
+ mContext->getDiagnostics()->warn(
+ DiagMessage() << "can't strip resources when building static library");
+ }
+ } else {
+ ProductFilter productFilter(mOptions.products);
+ if (!productFilter.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
+ return 1;
+ }
+
+ // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
+ // level.
+ TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
+ if (!tableSplitter.verifySplitConstraints(mContext)) {
+ return 1;
+ }
+ tableSplitter.splitTable(&mFinalTable);
+ }
+ }
+
+ proguard::KeepSet proguardKeepSet;
+
+ std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
+ if (!archiveWriter) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
+ return 1;
+ }
+
+ bool error = false;
+ {
+ ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
+ if (!manifestFixer.consume(mContext, manifestXml.get())) {
+ error = true;
+ }
+
+ // AndroidManifest.xml has no resource name, but the CallSite is built from the name
+ // (aka, which package the AndroidManifest.xml is coming from).
+ // So we give it a package name so it can see local resources.
+ manifestXml->file.name.package = mContext->getCompilationPackage();
+
+ XmlReferenceLinker manifestLinker;
+ if (manifestLinker.consume(mContext, manifestXml.get())) {
+ if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
+ manifestXml.get(),
+ &proguardKeepSet)) {
+ error = true;
+ }
+
+ if (mOptions.generateJavaClassPath) {
+ if (!writeManifestJavaFile(manifestXml.get())) {
+ error = true;
+ }
+ }
+
+ const bool keepRawValues = mOptions.staticLib;
+ bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
+ keepRawValues, archiveWriter.get(), mContext);
+ if (!result) {
+ error = true;
+ }
+ } else {
+ error = true;
+ }
+ }
+
+ if (error) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
+ return 1;
+ }
+
+ ResourceFileFlattenerOptions fileFlattenerOptions;
+ fileFlattenerOptions.keepRawValues = mOptions.staticLib;
+ fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
+ fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
+ fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
+ fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
+ ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
+
+ if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
+ return 1;
+ }
+
+ if (!mOptions.noAutoVersion) {
+ AutoVersioner versioner;
+ if (!versioner.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
+ return 1;
+ }
+ }
+
+ if (mOptions.staticLib) {
+ if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed to write resources.arsc.flat");
+ return 1;
+ }
+ } else {
+ if (!flattenTable(&mFinalTable, archiveWriter.get())) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed to write resources.arsc");
+ return 1;
+ }
+ }
+
+ if (mOptions.generateJavaClassPath) {
+ JavaClassGeneratorOptions options;
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+ options.javadocAnnotations = mOptions.javadocAnnotations;
+
+ if (mOptions.staticLib || mOptions.generateNonFinalIds) {
+ options.useFinal = false;
+ }
+
+ const StringPiece16 actualPackage = mContext->getCompilationPackage();
+ StringPiece16 outputPackage = mContext->getCompilationPackage();
+ if (mOptions.customJavaPackage) {
+ // Override the output java package to the custom one.
+ outputPackage = mOptions.customJavaPackage.value();
+ }
+
+ if (mOptions.privateSymbols) {
+ // If we defined a private symbols package, we only emit Public symbols
+ // to the original package, and private and public symbols to the private package.
+
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+ if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
+ outputPackage, options)) {
+ return 1;
+ }
+
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+ outputPackage = mOptions.privateSymbols.value();
+ }
+
+ if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
+ return 1;
+ }
+
+ for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
+ if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
+ return 1;
+ }
+ }
+ }
+
+ if (mOptions.generateProguardRulesPath) {
+ if (!writeProguardFile(proguardKeepSet)) {
+ return 1;
+ }
+ }
+
+ if (mContext->verbose()) {
+ DebugPrintTableOptions debugPrintTableOptions;
+ debugPrintTableOptions.showSources = true;
+ Debug::printTable(&mFinalTable, debugPrintTableOptions);
+ }
+ return 0;
+ }
+
+private:
+ LinkOptions mOptions;
+ LinkContext* mContext;
+ ResourceTable mFinalTable;
+
+ std::unique_ptr<TableMerger> mTableMerger;
+
+ // A pointer to the FileCollection representing the filesystem (not archives).
+ std::unique_ptr<io::FileCollection> mFileCollection;
+
+ // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
+ std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
+
+ // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
+ // can use these.
+ std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
+};
+
+int link(const std::vector<StringPiece>& args) {
+ LinkContext context;
+ LinkOptions options;
+ Maybe<std::string> privateSymbolsPackage;
+ Maybe<std::string> minSdkVersion, targetSdkVersion;
+ Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage;
+ Maybe<std::string> versionCode, versionName;
+ Maybe<std::string> customJavaPackage;
+ std::vector<std::string> extraJavaPackages;
+ Maybe<std::string> configs;
+ Maybe<std::string> preferredDensity;
+ Maybe<std::string> productList;
+ bool legacyXFlag = false;
+ bool requireLocalization = false;
+ bool verbose = false;
+ Flags flags = Flags()
+ .requiredFlag("-o", "Output path", &options.outputPath)
+ .requiredFlag("--manifest", "Path to the Android manifest to build",
+ &options.manifestPath)
+ .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
+ .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
+ "The last conflicting resource given takes precedence.",
+ &options.overlayFiles)
+ .optionalFlag("--java", "Directory in which to generate R.java",
+ &options.generateJavaClassPath)
+ .optionalFlag("--proguard", "Output file for generated Proguard rules",
+ &options.generateProguardRulesPath)
+ .optionalSwitch("--no-auto-version",
+ "Disables automatic style and layout SDK versioning",
+ &options.noAutoVersion)
+ .optionalSwitch("--no-version-vectors",
+ "Disables automatic versioning of vector drawables. Use this only\n"
+ "when building with vector drawable support library",
+ &options.noVersionVectors)
+ .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
+ &legacyXFlag)
+ .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
+ &requireLocalization)
+ .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
+ "is all configurations", &configs)
+ .optionalFlag("--preferred-density",
+ "Selects the closest matching density and strips out all others.",
+ &preferredDensity)
+ .optionalFlag("--product", "Comma separated list of product names to keep",
+ &productList)
+ .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
+ "by -o",
+ &options.outputToDirectory)
+ .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
+ "AndroidManifest.xml", &minSdkVersion)
+ .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
+ "AndroidManifest.xml", &targetSdkVersion)
+ .optionalFlag("--version-code", "Version code (integer) to inject into the "
+ "AndroidManifest.xml if none is present", &versionCode)
+ .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
+ "if none is present", &versionName)
+ .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
+ .optionalSwitch("--no-static-lib-packages",
+ "Merge all library resources under the app's package",
+ &options.noStaticLibPackages)
+ .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
+ "This is implied when --static-lib is specified.",
+ &options.generateNonFinalIds)
+ .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
+ "private symbols.\n"
+ "If not specified, public and private symbols will use the application's "
+ "package name", &privateSymbolsPackage)
+ .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
+ &customJavaPackage)
+ .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
+ "package names", &extraJavaPackages)
+ .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
+ "generated Java classes", &options.javadocAnnotations)
+ .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
+ "overlays without <add-resource> tags", &options.autoAddOverlay)
+ .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
+ &renameManifestPackage)
+ .optionalFlag("--rename-instrumentation-target-package",
+ "Changes the name of the target package for instrumentation. Most useful "
+ "when used\nin conjunction with --rename-manifest-package",
+ &renameInstrumentationTargetPackage)
+ .optionalFlagList("-0", "File extensions not to compress",
+ &options.extensionsToNotCompress)
+ .optionalSwitch("-v", "Enables verbose logging", &verbose);
+
+ if (!flags.parse("aapt2 link", args, &std::cerr)) {
+ return 1;
+ }
+
+ // Expand all argument-files passed into the command line. These start with '@'.
+ std::vector<std::string> argList;
+ for (const std::string& arg : flags.getArgs()) {
+ if (util::stringStartsWith<char>(arg, "@")) {
+ const std::string path = arg.substr(1, arg.size() - 1);
+ std::string error;
+ if (!file::appendArgsFromFile(path, &argList, &error)) {
+ context.getDiagnostics()->error(DiagMessage(path) << error);
+ return 1;
+ }
+ } else {
+ argList.push_back(arg);
+ }
+ }
+
+ if (verbose) {
+ context.setVerbose(verbose);
+ }
+
+ if (privateSymbolsPackage) {
+ options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
+ }
+
+ if (minSdkVersion) {
+ options.manifestFixerOptions.minSdkVersionDefault =
+ util::utf8ToUtf16(minSdkVersion.value());
+ }
+
+ if (targetSdkVersion) {
+ options.manifestFixerOptions.targetSdkVersionDefault =
+ util::utf8ToUtf16(targetSdkVersion.value());
+ }
+
+ if (renameManifestPackage) {
+ options.manifestFixerOptions.renameManifestPackage =
+ util::utf8ToUtf16(renameManifestPackage.value());
+ }
+
+ if (renameInstrumentationTargetPackage) {
+ options.manifestFixerOptions.renameInstrumentationTargetPackage =
+ util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
+ }
+
+ if (versionCode) {
+ options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
+ }
+
+ if (versionName) {
+ options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
+ }
+
+ if (customJavaPackage) {
+ options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
+ }
+
+ // Populate the set of extra packages for which to generate R.java.
+ for (std::string& extraPackage : extraJavaPackages) {
+ // A given package can actually be a colon separated list of packages.
+ for (StringPiece package : util::split(extraPackage, ':')) {
+ options.extraJavaPackages.insert(util::utf8ToUtf16(package));
+ }
+ }
+
+ if (productList) {
+ for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
+ if (product != "" && product != "default") {
+ options.products.insert(product.toString());
+ }
+ }
+ }
+
+ AxisConfigFilter filter;
+ if (configs) {
+ for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
+ ConfigDescription config;
+ LocaleValue lv;
+ if (lv.initFromFilterString(configStr)) {
+ lv.writeTo(&config);
+ } else if (!ConfigDescription::parse(configStr, &config)) {
+ context.getDiagnostics()->error(
+ DiagMessage() << "invalid config '" << configStr << "' for -c option");
+ return 1;
+ }
+
+ if (config.density != 0) {
+ context.getDiagnostics()->warn(
+ DiagMessage() << "ignoring density '" << config << "' for -c option");
+ } else {
+ filter.addConfig(config);
+ }
+ }
+
+ options.tableSplitterOptions.configFilter = &filter;
+ }
+
+ if (preferredDensity) {
+ ConfigDescription preferredDensityConfig;
+ if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
+ context.getDiagnostics()->error(DiagMessage() << "invalid density '"
+ << preferredDensity.value()
+ << "' for --preferred-density option");
+ return 1;
+ }
+
+ // Clear the version that can be automatically added.
+ preferredDensityConfig.sdkVersion = 0;
+
+ if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
+ != ConfigDescription::CONFIG_DENSITY) {
+ context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
+ << preferredDensity.value() << "'. "
+ << "Preferred density must only be a density value");
+ return 1;
+ }
+ options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
+ }
+
+ // Turn off auto versioning for static-libs.
+ if (options.staticLib) {
+ options.noAutoVersion = true;
+ options.noVersionVectors = true;
+ }
+
+ LinkCommand cmd(&context, options);
+ return cmd.run(argList);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
new file mode 100644
index 000000000000..ec532aba465f
--- /dev/null
+++ b/tools/aapt2/link/Linkers.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINKER_LINKERS_H
+#define AAPT_LINKER_LINKERS_H
+
+#include "Resource.h"
+#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
+
+#include <set>
+
+namespace aapt {
+
+class ResourceTable;
+class ResourceEntry;
+struct ConfigDescription;
+
+/**
+ * Defines the location in which a value exists. This determines visibility of other
+ * package's private symbols.
+ */
+struct CallSite {
+ ResourceNameRef resource;
+};
+
+/**
+ * Determines whether a versioned resource should be created. If a versioned resource already
+ * exists, it takes precedence.
+ */
+bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+ const int sdkVersionToGenerate);
+
+struct AutoVersioner : public IResourceTableConsumer {
+ bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+struct XmlAutoVersioner : public IXmlResourceConsumer {
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+};
+
+/**
+ * If any attribute resource values are defined as public, this consumer will move all private
+ * attribute resource values to a private ^private-attr type, avoiding backwards compatibility
+ * issues with new apps running on old platforms.
+ *
+ * The Android platform ignores resource attributes it doesn't recognize, so an app developer can
+ * use new attributes in their layout XML files without worrying about versioning. This assumption
+ * actually breaks on older platforms. OEMs may add private attributes that are used internally.
+ * AAPT originally assigned all private attributes IDs immediately proceeding the public attributes'
+ * IDs.
+ *
+ * This means that on a newer Android platform, an ID previously assigned to a private attribute
+ * may end up assigned to a public attribute.
+ *
+ * App developers assume using the newer attribute is safe on older platforms because it will
+ * be ignored. Instead, the platform thinks the new attribute is an older, private attribute and
+ * will interpret it as such. This leads to unintended styling and exceptions thrown due to
+ * unexpected types.
+ *
+ * By moving the private attributes to a completely different type, this ID conflict will never
+ * occur.
+ */
+struct PrivateAttributeMover : public IResourceTableConsumer {
+ bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+/**
+ * Resolves attributes in the XmlResource and compiles string values to resource values.
+ * Once an XmlResource is processed by this linker, it is ready to be flattened.
+ */
+class XmlReferenceLinker : public IXmlResourceConsumer {
+private:
+ std::set<int> mSdkLevelsFound;
+
+public:
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+
+ /**
+ * Once the XmlResource has been consumed, this returns the various SDK levels in which
+ * framework attributes used within the XML document were defined.
+ */
+ inline const std::set<int>& getSdkLevels() const {
+ return mSdkLevelsFound;
+ }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_LINKERS_H */
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
new file mode 100644
index 000000000000..953e87e104be
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceUtils.h"
+#include "link/ManifestFixer.h"
+#include "util/Util.h"
+#include "xml/XmlActionExecutor.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+
+/**
+ * This is how PackageManager builds class names from AndroidManifest.xml entries.
+ */
+static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
+ SourcePathDiagnostics* diag) {
+ std::u16string className = attr->value;
+ if (className.find(u'.') == std::u16string::npos) {
+ // There is no '.', so add one to the beginning.
+ className = u".";
+ className += attr->value;
+ }
+
+ // We allow unqualified class names (ie: .HelloActivity)
+ // Since we don't know the package name, we can just make a fake one here and
+ // the test will be identical as long as the real package name is valid too.
+ Maybe<std::u16string> fullyQualifiedClassName =
+ util::getFullyQualifiedClassName(u"a", className);
+
+ StringPiece16 qualifiedClassName = fullyQualifiedClassName
+ ? fullyQualifiedClassName.value() : className;
+ if (!util::isJavaClassName(qualifiedClassName)) {
+ diag->error(DiagMessage(el->lineNumber)
+ << "attribute 'android:name' in <"
+ << el->name << "> tag must be a valid Java class name");
+ return false;
+ }
+ return true;
+}
+
+static bool optionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
+ if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) {
+ return nameIsJavaClassName(el, attr, diag);
+ }
+ return true;
+}
+
+static bool requiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
+ if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) {
+ return nameIsJavaClassName(el, attr, diag);
+ }
+ diag->error(DiagMessage(el->lineNumber)
+ << "<" << el->name << "> is missing attribute 'android:name'");
+ return false;
+}
+
+static bool verifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
+ xml::Attribute* attr = el->findAttribute({}, u"package");
+ if (!attr) {
+ diag->error(DiagMessage(el->lineNumber) << "<manifest> tag is missing 'package' attribute");
+ return false;
+ } else if (ResourceUtils::isReference(attr->value)) {
+ diag->error(DiagMessage(el->lineNumber)
+ << "attribute 'package' in <manifest> tag must not be a reference");
+ return false;
+ } else if (!util::isJavaPackageName(attr->value)) {
+ diag->error(DiagMessage(el->lineNumber)
+ << "attribute 'package' in <manifest> tag is not a valid Java package name: '"
+ << attr->value << "'");
+ return false;
+ }
+ return true;
+}
+
+bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) {
+ // First verify some options.
+ if (mOptions.renameManifestPackage) {
+ if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) {
+ diag->error(DiagMessage() << "invalid manifest package override '"
+ << mOptions.renameManifestPackage.value() << "'");
+ return false;
+ }
+ }
+
+ if (mOptions.renameInstrumentationTargetPackage) {
+ if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) {
+ diag->error(DiagMessage() << "invalid instrumentation target package override '"
+ << mOptions.renameInstrumentationTargetPackage.value() << "'");
+ return false;
+ }
+ }
+
+ // Common intent-filter actions.
+ xml::XmlNodeAction intentFilterAction;
+ intentFilterAction[u"action"];
+ intentFilterAction[u"category"];
+ intentFilterAction[u"data"];
+
+ // Common meta-data actions.
+ xml::XmlNodeAction metaDataAction;
+
+ // Manifest actions.
+ xml::XmlNodeAction& manifestAction = (*executor)[u"manifest"];
+ manifestAction.action(verifyManifest);
+ manifestAction.action([&](xml::Element* el) -> bool {
+ if (mOptions.versionNameDefault) {
+ if (el->findAttribute(xml::kSchemaAndroid, u"versionName") == nullptr) {
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid,
+ u"versionName",
+ mOptions.versionNameDefault.value() });
+ }
+ }
+
+ if (mOptions.versionCodeDefault) {
+ if (el->findAttribute(xml::kSchemaAndroid, u"versionCode") == nullptr) {
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid,
+ u"versionCode",
+ mOptions.versionCodeDefault.value() });
+ }
+ }
+ return true;
+ });
+
+ // Meta tags.
+ manifestAction[u"eat-comment"];
+
+ // Uses-sdk actions.
+ manifestAction[u"uses-sdk"].action([&](xml::Element* el) -> bool {
+ if (mOptions.minSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) {
+ // There was no minSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"minSdkVersion",
+ mOptions.minSdkVersionDefault.value() });
+ }
+
+ if (mOptions.targetSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) {
+ // There was no targetSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"targetSdkVersion",
+ mOptions.targetSdkVersionDefault.value() });
+ }
+ return true;
+ });
+
+ // Instrumentation actions.
+ manifestAction[u"instrumentation"].action([&](xml::Element* el) -> bool {
+ if (!mOptions.renameInstrumentationTargetPackage) {
+ return true;
+ }
+
+ if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"targetPackage")) {
+ attr->value = mOptions.renameInstrumentationTargetPackage.value();
+ }
+ return true;
+ });
+
+ manifestAction[u"original-package"];
+ manifestAction[u"protected-broadcast"];
+ manifestAction[u"uses-permission"];
+ manifestAction[u"permission"];
+ manifestAction[u"permission-tree"];
+ manifestAction[u"permission-group"];
+
+ manifestAction[u"uses-configuration"];
+ manifestAction[u"uses-feature"];
+ manifestAction[u"uses-library"];
+ manifestAction[u"supports-screens"];
+ manifestAction[u"compatible-screens"];
+ manifestAction[u"supports-gl-texture"];
+
+ // Application actions.
+ xml::XmlNodeAction& applicationAction = (*executor)[u"manifest"][u"application"];
+ applicationAction.action(optionalNameIsJavaClassName);
+
+ // Activity actions.
+ applicationAction[u"activity"].action(requiredNameIsJavaClassName);
+ applicationAction[u"activity"][u"intent-filter"] = intentFilterAction;
+ applicationAction[u"activity"][u"meta-data"] = metaDataAction;
+
+ // Activity alias actions.
+ applicationAction[u"activity-alias"][u"intent-filter"] = intentFilterAction;
+ applicationAction[u"activity-alias"][u"meta-data"] = metaDataAction;
+
+ // Service actions.
+ applicationAction[u"service"].action(requiredNameIsJavaClassName);
+ applicationAction[u"service"][u"intent-filter"] = intentFilterAction;
+ applicationAction[u"service"][u"meta-data"] = metaDataAction;
+
+ // Receiver actions.
+ applicationAction[u"receiver"].action(requiredNameIsJavaClassName);
+ applicationAction[u"receiver"][u"intent-filter"] = intentFilterAction;
+ applicationAction[u"receiver"][u"meta-data"] = metaDataAction;
+
+ // Provider actions.
+ applicationAction[u"provider"].action(requiredNameIsJavaClassName);
+ applicationAction[u"provider"][u"grant-uri-permissions"];
+ applicationAction[u"provider"][u"meta-data"] = metaDataAction;
+ applicationAction[u"provider"][u"path-permissions"];
+ return true;
+}
+
+class FullyQualifiedClassNameVisitor : public xml::Visitor {
+public:
+ using xml::Visitor::visit;
+
+ FullyQualifiedClassNameVisitor(const StringPiece16& package) : mPackage(package) {
+ }
+
+ void visit(xml::Element* el) override {
+ for (xml::Attribute& attr : el->attributes) {
+ if (Maybe<std::u16string> newValue =
+ util::getFullyQualifiedClassName(mPackage, attr.value)) {
+ attr.value = std::move(newValue.value());
+ }
+ }
+
+ // Super implementation to iterate over the children.
+ xml::Visitor::visit(el);
+ }
+
+private:
+ StringPiece16 mPackage;
+};
+
+static bool renameManifestPackage(const StringPiece16& packageOverride, xml::Element* manifestEl) {
+ xml::Attribute* attr = manifestEl->findAttribute({}, u"package");
+
+ // We've already verified that the manifest element is present, with a package name specified.
+ assert(attr);
+
+ std::u16string originalPackage = std::move(attr->value);
+ attr->value = packageOverride.toString();
+
+ FullyQualifiedClassNameVisitor visitor(originalPackage);
+ manifestEl->accept(&visitor);
+ return true;
+}
+
+bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) {
+ xml::Element* root = xml::findRootElement(doc->root.get());
+ if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
+ context->getDiagnostics()->error(DiagMessage(doc->file.source)
+ << "root tag must be <manifest>");
+ return false;
+ }
+
+ if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)
+ && root->findChild({}, u"uses-sdk") == nullptr) {
+ // Auto insert a <uses-sdk> element.
+ std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>();
+ usesSdk->name = u"uses-sdk";
+ root->addChild(std::move(usesSdk));
+ }
+
+ xml::XmlActionExecutor executor;
+ if (!buildRules(&executor, context->getDiagnostics())) {
+ return false;
+ }
+
+ if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(),
+ doc)) {
+ return false;
+ }
+
+ if (mOptions.renameManifestPackage) {
+ // Rename manifest package outside of the XmlActionExecutor.
+ // We need to extract the old package name and FullyQualify all class names.
+ if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
new file mode 100644
index 000000000000..4d9356a933c2
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINK_MANIFESTFIXER_H
+#define AAPT_LINK_MANIFESTFIXER_H
+
+#include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "xml/XmlActionExecutor.h"
+#include "xml/XmlDom.h"
+
+#include <string>
+
+namespace aapt {
+
+struct ManifestFixerOptions {
+ Maybe<std::u16string> minSdkVersionDefault;
+ Maybe<std::u16string> targetSdkVersionDefault;
+ Maybe<std::u16string> renameManifestPackage;
+ Maybe<std::u16string> renameInstrumentationTargetPackage;
+ Maybe<std::u16string> versionNameDefault;
+ Maybe<std::u16string> versionCodeDefault;
+};
+
+/**
+ * Verifies that the manifest is correctly formed and inserts defaults
+ * where specified with ManifestFixerOptions.
+ */
+class ManifestFixer : public IXmlResourceConsumer {
+public:
+ ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
+ }
+
+ bool consume(IAaptContext* context, xml::XmlResource* doc) override;
+
+private:
+ bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag);
+
+ ManifestFixerOptions mOptions;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINK_MANIFESTFIXER_H */
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
new file mode 100644
index 000000000000..f993720b9566
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/ManifestFixer.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+struct ManifestFixerTest : public ::testing::Test {
+ std::unique_ptr<IAaptContext> mContext;
+
+ void SetUp() override {
+ mContext = test::ContextBuilder()
+ .setCompilationPackage(u"android")
+ .setPackageId(0x01)
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .addSymbolSource(test::StaticSymbolSourceBuilder()
+ .addSymbol(u"@android:attr/package", ResourceId(0x01010000),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING)
+ .build())
+ .addSymbol(u"@android:attr/minSdkVersion", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .build())
+ .addSymbol(u"@android:attr/targetSdkVersion", ResourceId(0x01010002),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .build())
+ .addSymbol(u"@android:string/str", ResourceId(0x01060000))
+ .build())
+ .build();
+ }
+
+ std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) {
+ return verifyWithOptions(str, {});
+ }
+
+ std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str,
+ const ManifestFixerOptions& options) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str);
+ ManifestFixer fixer(options);
+ if (fixer.consume(mContext.get(), doc.get())) {
+ return doc;
+ }
+ return {};
+ }
+};
+
+TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) {
+ EXPECT_EQ(nullptr, verify("<other-tag />"));
+ EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>"));
+}
+
+TEST_F(ManifestFixerTest, EnsureManifestHasPackage) {
+ EXPECT_NE(nullptr, verify("<manifest package=\"android\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />"));
+ EXPECT_EQ(nullptr, verify("<manifest package=\"com.android.google.Class$1\" />"));
+ EXPECT_EQ(nullptr,
+ verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
+ "android:package=\"com.android\" />"));
+ EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />"));
+}
+
+TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
+ ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
+
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* el;
+ xml::Attribute* attr;
+
+ el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"7", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"21", attr->value);
+
+ doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-sdk android:targetSdkVersion="21" />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"21", attr->value);
+
+ doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-sdk />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"22", attr->value);
+
+ doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android" />)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"22", attr->value);
+}
+
+TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) {
+ ManifestFixerOptions options;
+ options.renameManifestPackage = std::u16string(u"com.android");
+
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application android:name=".MainApplication" text="hello">
+ <activity android:name=".activity.Start" />
+ <receiver android:name="com.google.android.Receiver" />
+ </application>
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* manifestEl = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
+
+ xml::Attribute* attr = nullptr;
+
+ attr = manifestEl->findAttribute({}, u"package");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"com.android"), attr->value);
+
+ xml::Element* applicationEl = manifestEl->findChild({}, u"application");
+ ASSERT_NE(nullptr, applicationEl);
+
+ attr = applicationEl->findAttribute(xml::kSchemaAndroid, u"name");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"android.MainApplication"), attr->value);
+
+ attr = applicationEl->findAttribute({}, u"text");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"hello"), attr->value);
+
+ xml::Element* el;
+ el = applicationEl->findChild({}, u"activity");
+ ASSERT_NE(nullptr, el);
+
+ attr = el->findAttribute(xml::kSchemaAndroid, u"name");
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ(std::u16string(u"android.activity.Start"), attr->value);
+
+ el = applicationEl->findChild({}, u"receiver");
+ ASSERT_NE(nullptr, el);
+
+ attr = el->findAttribute(xml::kSchemaAndroid, u"name");
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ(std::u16string(u"com.google.android.Receiver"), attr->value);
+}
+
+TEST_F(ManifestFixerTest, RenameManifestInstrumentationPackageAndFullyQualifyTarget) {
+ ManifestFixerOptions options;
+ options.renameInstrumentationTargetPackage = std::u16string(u"com.android");
+
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <instrumentation android:targetPackage="android" />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* manifestEl = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
+
+ xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation");
+ ASSERT_NE(nullptr, instrumentationEl);
+
+ xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"com.android"), attr->value);
+}
+
+TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
+ ManifestFixerOptions options;
+ options.versionNameDefault = std::u16string(u"Beta");
+ options.versionCodeDefault = std::u16string(u"0x10000000");
+
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android" />)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* manifestEl = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
+
+ xml::Attribute* attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"Beta"), attr->value);
+
+ attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"0x10000000"), attr->value);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
new file mode 100644
index 000000000000..3c8af4f81ffe
--- /dev/null
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "link/Linkers.h"
+
+#include <algorithm>
+#include <iterator>
+
+namespace aapt {
+
+template <typename InputContainer, typename OutputIterator, typename Predicate>
+OutputIterator moveIf(InputContainer& inputContainer, OutputIterator result,
+ Predicate pred) {
+ const auto last = inputContainer.end();
+ auto newEnd = std::find_if(inputContainer.begin(), inputContainer.end(), pred);
+ if (newEnd == last) {
+ return result;
+ }
+
+ *result = std::move(*newEnd);
+
+ auto first = newEnd;
+ ++first;
+
+ for (; first != last; ++first) {
+ if (bool(pred(*first))) {
+ // We want to move this guy
+ *result = std::move(*first);
+ ++result;
+ } else {
+ // We want to keep this guy, but we will need to move it up the list to replace
+ // missing items.
+ *newEnd = std::move(*first);
+ ++newEnd;
+ }
+ }
+
+ inputContainer.erase(newEnd, last);
+ return result;
+}
+
+bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) {
+ for (auto& package : table->packages) {
+ ResourceTableType* type = package->findType(ResourceType::kAttr);
+ if (!type) {
+ continue;
+ }
+
+ if (type->symbolStatus.state != SymbolState::kPublic) {
+ // No public attributes, so we can safely leave these private attributes where they are.
+ return true;
+ }
+
+ ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate);
+ assert(privAttrType->entries.empty());
+
+ moveIf(type->entries, std::back_inserter(privAttrType->entries),
+ [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+ return entry->symbolStatus.state != SymbolState::kPublic;
+ });
+ break;
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
new file mode 100644
index 000000000000..dbe0c92253c1
--- /dev/null
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/Linkers.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(PrivateAttributeMoverTest, MovePrivateAttributes) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple(u"@android:attr/publicA")
+ .addSimple(u"@android:attr/privateA")
+ .addSimple(u"@android:attr/publicB")
+ .addSimple(u"@android:attr/privateB")
+ .setSymbolState(u"@android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic)
+ .setSymbolState(u"@android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic)
+ .build();
+
+ PrivateAttributeMover mover;
+ ASSERT_TRUE(mover.consume(context.get(), table.get()));
+
+ ResourceTablePackage* package = table->findPackage(u"android");
+ ASSERT_NE(package, nullptr);
+
+ ResourceTableType* type = package->findType(ResourceType::kAttr);
+ ASSERT_NE(type, nullptr);
+ ASSERT_EQ(type->entries.size(), 2u);
+ EXPECT_NE(type->findEntry(u"publicA"), nullptr);
+ EXPECT_NE(type->findEntry(u"publicB"), nullptr);
+
+ type = package->findType(ResourceType::kAttrPrivate);
+ ASSERT_NE(type, nullptr);
+ ASSERT_EQ(type->entries.size(), 2u);
+ EXPECT_NE(type->findEntry(u"privateA"), nullptr);
+ EXPECT_NE(type->findEntry(u"privateB"), nullptr);
+}
+
+TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple(u"@android:attr/privateA")
+ .addSimple(u"@android:attr/privateB")
+ .build();
+
+ PrivateAttributeMover mover;
+ ASSERT_TRUE(mover.consume(context.get(), table.get()));
+
+ ResourceTablePackage* package = table->findPackage(u"android");
+ ASSERT_NE(package, nullptr);
+
+ ResourceTableType* type = package->findType(ResourceType::kAttr);
+ ASSERT_NE(type, nullptr);
+ ASSERT_EQ(type->entries.size(), 2u);
+
+ type = package->findType(ResourceType::kAttrPrivate);
+ ASSERT_EQ(type, nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/link/ProductFilter.cpp
new file mode 100644
index 000000000000..8784e891b293
--- /dev/null
+++ b/tools/aapt2/link/ProductFilter.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/ProductFilter.h"
+
+namespace aapt {
+
+ProductFilter::ResourceConfigValueIter
+ProductFilter::selectProductToKeep(const ResourceNameRef& name,
+ const ResourceConfigValueIter begin,
+ const ResourceConfigValueIter end,
+ IDiagnostics* diag) {
+ ResourceConfigValueIter defaultProductIter = end;
+ ResourceConfigValueIter selectedProductIter = end;
+
+ for (ResourceConfigValueIter iter = begin; iter != end; ++iter) {
+ ResourceConfigValue* configValue = iter->get();
+ if (mProducts.find(configValue->product) != mProducts.end()) {
+ if (selectedProductIter != end) {
+ // We have two possible values for this product!
+ diag->error(DiagMessage(configValue->value->getSource())
+ << "selection of product '" << configValue->product
+ << "' for resource " << name << " is ambiguous");
+
+ ResourceConfigValue* previouslySelectedConfigValue = selectedProductIter->get();
+ diag->note(DiagMessage(previouslySelectedConfigValue->value->getSource())
+ << "product '" << previouslySelectedConfigValue->product
+ << "' is also a candidate");
+ return end;
+ }
+
+ // Select this product.
+ selectedProductIter = iter;
+ }
+
+ if (configValue->product.empty() || configValue->product == "default") {
+ if (defaultProductIter != end) {
+ // We have two possible default values.
+ diag->error(DiagMessage(configValue->value->getSource())
+ << "multiple default products defined for resource " << name);
+
+ ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get();
+ diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource())
+ << "default product also defined here");
+ return end;
+ }
+
+ // Mark the default.
+ defaultProductIter = iter;
+ }
+ }
+
+ if (defaultProductIter == end) {
+ diag->error(DiagMessage() << "no default product defined for resource " << name);
+ return end;
+ }
+
+ if (selectedProductIter == end) {
+ selectedProductIter = defaultProductIter;
+ }
+ return selectedProductIter;
+}
+
+bool ProductFilter::consume(IAaptContext* context, ResourceTable* table) {
+ bool error = false;
+ for (auto& pkg : table->packages) {
+ for (auto& type : pkg->types) {
+ for (auto& entry : type->entries) {
+ std::vector<std::unique_ptr<ResourceConfigValue>> newValues;
+
+ ResourceConfigValueIter iter = entry->values.begin();
+ ResourceConfigValueIter startRangeIter = iter;
+ while (iter != entry->values.end()) {
+ ++iter;
+ if (iter == entry->values.end() ||
+ (*iter)->config != (*startRangeIter)->config) {
+
+ // End of the array, or we saw a different config,
+ // so this must be the end of a range of products.
+ // Select the product to keep from the set of products defined.
+ ResourceNameRef name(pkg->name, type->type, entry->name);
+ auto valueToKeep = selectProductToKeep(name, startRangeIter, iter,
+ context->getDiagnostics());
+ if (valueToKeep == iter) {
+ // An error occurred, we could not pick a product.
+ error = true;
+ } else {
+ // We selected a product to keep. Move it to the new array.
+ newValues.push_back(std::move(*valueToKeep));
+ }
+
+ // Start the next range of products.
+ startRangeIter = iter;
+ }
+ }
+
+ // Now move the new values in to place.
+ entry->values = std::move(newValues);
+ }
+ }
+ }
+ return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h
new file mode 100644
index 000000000000..d2edd38289dc
--- /dev/null
+++ b/tools/aapt2/link/ProductFilter.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINK_PRODUCTFILTER_H
+#define AAPT_LINK_PRODUCTFILTER_H
+
+#include "ResourceTable.h"
+#include "process/IResourceTableConsumer.h"
+
+#include <android-base/macros.h>
+#include <unordered_set>
+
+namespace aapt {
+
+class ProductFilter {
+public:
+ using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
+
+ ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { }
+
+ ResourceConfigValueIter selectProductToKeep(const ResourceNameRef& name,
+ const ResourceConfigValueIter begin,
+ const ResourceConfigValueIter end,
+ IDiagnostics* diag);
+
+ bool consume(IAaptContext* context, ResourceTable* table);
+
+private:
+ std::unordered_set<std::string> mProducts;
+
+ DISALLOW_COPY_AND_ASSIGN(ProductFilter);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINK_PRODUCTFILTER_H */
diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp
new file mode 100644
index 000000000000..f4f756ae4519
--- /dev/null
+++ b/tools/aapt2/link/ProductFilter_test.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/ProductFilter.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ProductFilterTest, SelectTwoProducts) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ const ConfigDescription land = test::parseConfigOrDie("land");
+ const ConfigDescription port = test::parseConfigOrDie("port");
+
+ ResourceTable table;
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ land, "",
+ test::ValueBuilder<Id>()
+ .setSource(Source("land/default.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ land, "tablet",
+ test::ValueBuilder<Id>()
+ .setSource(Source("land/tablet.xml")).build(),
+ context->getDiagnostics()));
+
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ port, "",
+ test::ValueBuilder<Id>()
+ .setSource(Source("port/default.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ port, "tablet",
+ test::ValueBuilder<Id>()
+ .setSource(Source("port/tablet.xml")).build(),
+ context->getDiagnostics()));
+
+ ProductFilter filter({ "tablet" });
+ ASSERT_TRUE(filter.consume(context.get(), &table));
+
+ EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+ land, ""));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+ land, "tablet"));
+ EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+ port, ""));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+ port, "tablet"));
+}
+
+TEST(ProductFilterTest, SelectDefaultProduct) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ ResourceTable table;
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ ConfigDescription::defaultConfig(), "",
+ test::ValueBuilder<Id>()
+ .setSource(Source("default.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ ConfigDescription::defaultConfig(), "tablet",
+ test::ValueBuilder<Id>()
+ .setSource(Source("tablet.xml")).build(),
+ context->getDiagnostics()));
+
+ ProductFilter filter({});
+ ASSERT_TRUE(filter.consume(context.get(), &table));
+
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+ ConfigDescription::defaultConfig(),
+ ""));
+ EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+ ConfigDescription::defaultConfig(),
+ "tablet"));
+}
+
+TEST(ProductFilterTest, FailOnAmbiguousProduct) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ ResourceTable table;
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ ConfigDescription::defaultConfig(), "",
+ test::ValueBuilder<Id>()
+ .setSource(Source("default.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ ConfigDescription::defaultConfig(), "tablet",
+ test::ValueBuilder<Id>()
+ .setSource(Source("tablet.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ ConfigDescription::defaultConfig(), "no-sdcard",
+ test::ValueBuilder<Id>()
+ .setSource(Source("no-sdcard.xml")).build(),
+ context->getDiagnostics()));
+
+ ProductFilter filter({ "tablet", "no-sdcard" });
+ ASSERT_FALSE(filter.consume(context.get(), &table));
+}
+
+TEST(ProductFilterTest, FailOnMultipleDefaults) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ ResourceTable table;
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ ConfigDescription::defaultConfig(), "",
+ test::ValueBuilder<Id>()
+ .setSource(Source(".xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+ ConfigDescription::defaultConfig(), "default",
+ test::ValueBuilder<Id>()
+ .setSource(Source("default.xml")).build(),
+ context->getDiagnostics()));
+
+ ProductFilter filter({});
+ ASSERT_FALSE(filter.consume(context.get(), &table));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
new file mode 100644
index 000000000000..66eb0df048db
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Diagnostics.h"
+#include "ReferenceLinker.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "link/Linkers.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <cassert>
+
+namespace aapt {
+
+namespace {
+
+/**
+ * The ReferenceLinkerVisitor will follow all references and make sure they point
+ * to resources that actually exist, either in the local resource table, or as external
+ * symbols. Once the target resource has been found, the ID of the resource will be assigned
+ * to the reference object.
+ *
+ * NOTE: All of the entries in the ResourceTable must be assigned IDs.
+ */
+class ReferenceLinkerVisitor : public ValueVisitor {
+public:
+ using ValueVisitor::visit;
+
+ ReferenceLinkerVisitor(IAaptContext* context, SymbolTable* symbols, StringPool* stringPool,
+ xml::IPackageDeclStack* decl,CallSite* callSite) :
+ mContext(context), mSymbols(symbols), mPackageDecls(decl), mStringPool(stringPool),
+ mCallSite(callSite) {
+ }
+
+ void visit(Reference* ref) override {
+ if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, mCallSite)) {
+ mError = true;
+ }
+ }
+
+ /**
+ * We visit the Style specially because during this phase, values of attributes are
+ * all RawString values. Now that we are expected to resolve all symbols, we can
+ * lookup the attributes to find out which types are allowed for the attributes' values.
+ */
+ void visit(Style* style) override {
+ if (style->parent) {
+ visit(&style->parent.value());
+ }
+
+ for (Style::Entry& entry : style->entries) {
+ std::string errStr;
+
+ // Transform the attribute reference so that it is using the fully qualified package
+ // name. This will also mark the reference as being able to see private resources if
+ // there was a '*' in the reference or if the package came from the private namespace.
+ Reference transformedReference = entry.key;
+ transformReferenceFromNamespace(mPackageDecls, mContext->getCompilationPackage(),
+ &transformedReference);
+
+ // Find the attribute in the symbol table and check if it is visible from this callsite.
+ const SymbolTable::Symbol* symbol = ReferenceLinker::resolveAttributeCheckVisibility(
+ transformedReference, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
+ if (symbol) {
+ // Assign our style key the correct ID.
+ // The ID may not exist.
+ entry.key.id = symbol->id;
+
+ // Try to convert the value to a more specific, typed value based on the
+ // attribute it is set to.
+ entry.value = parseValueWithAttribute(std::move(entry.value),
+ symbol->attribute.get());
+
+ // Link/resolve the final value (mostly if it's a reference).
+ entry.value->accept(this);
+
+ // Now verify that the type of this item is compatible with the attribute it
+ // is defined for. We pass `nullptr` as the DiagMessage so that this check is
+ // fast and we avoid creating a DiagMessage when the match is successful.
+ if (!symbol->attribute->matches(entry.value.get(), nullptr)) {
+ // The actual type of this item is incompatible with the attribute.
+ DiagMessage msg(entry.key.getSource());
+
+ // Call the matches method again, this time with a DiagMessage so we fill
+ // in the actual error message.
+ symbol->attribute->matches(entry.value.get(), &msg);
+ mContext->getDiagnostics()->error(msg);
+ mError = true;
+ }
+
+ } else {
+ DiagMessage msg(entry.key.getSource());
+ msg << "style attribute '";
+ ReferenceLinker::writeResourceName(&msg, entry.key, transformedReference);
+ msg << "' " << errStr;
+ mContext->getDiagnostics()->error(msg);
+ mError = true;
+ }
+ }
+ }
+
+ bool hasError() {
+ return mError;
+ }
+
+private:
+ IAaptContext* mContext;
+ SymbolTable* mSymbols;
+ xml::IPackageDeclStack* mPackageDecls;
+ StringPool* mStringPool;
+ CallSite* mCallSite;
+ bool mError = false;
+
+ /**
+ * Transform a RawString value into a more specific, appropriate value, based on the
+ * Attribute. If a non RawString value is passed in, this is an identity transform.
+ */
+ std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value,
+ const Attribute* attr) {
+ if (RawString* rawString = valueCast<RawString>(value.get())) {
+ std::unique_ptr<Item> transformed =
+ ResourceUtils::parseItemForAttribute(*rawString->value, attr);
+
+ // If we could not parse as any specific type, try a basic STRING.
+ if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+ util::StringBuilder stringBuilder;
+ stringBuilder.append(*rawString->value);
+ if (stringBuilder) {
+ transformed = util::make_unique<String>(
+ mStringPool->makeRef(stringBuilder.str()));
+ }
+ }
+
+ if (transformed) {
+ return transformed;
+ }
+ };
+ return value;
+ }
+};
+
+} // namespace
+
+/**
+ * The symbol is visible if it is public, or if the reference to it is requesting private access
+ * or if the callsite comes from the same package.
+ */
+bool ReferenceLinker::isSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref,
+ const CallSite& callSite) {
+ if (!symbol.isPublic && !ref.privateReference) {
+ if (ref.name) {
+ return callSite.resource.package == ref.name.value().package;
+ } else if (ref.id && symbol.id) {
+ return ref.id.value().packageId() == symbol.id.value().packageId();
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+const SymbolTable::Symbol* ReferenceLinker::resolveSymbol(const Reference& reference,
+ NameMangler* mangler,
+ SymbolTable* symbols) {
+ if (reference.name) {
+ Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value());
+ return symbols->findByName(mangled ? mangled.value() : reference.name.value());
+ } else if (reference.id) {
+ return symbols->findById(reference.id.value());
+ } else {
+ return nullptr;
+ }
+}
+
+const SymbolTable::Symbol* ReferenceLinker::resolveSymbolCheckVisibility(
+ const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols,
+ CallSite* callSite, std::string* outError) {
+ const SymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+ if (!symbol) {
+ if (outError) *outError = "not found";
+ return nullptr;
+ }
+
+ if (!isSymbolVisible(*symbol, reference, *callSite)) {
+ if (outError) *outError = "is private";
+ return nullptr;
+ }
+ return symbol;
+}
+
+const SymbolTable::Symbol* ReferenceLinker::resolveAttributeCheckVisibility(
+ const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols,
+ CallSite* callSite, std::string* outError) {
+ const SymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(reference, nameMangler,
+ symbols, callSite,
+ outError);
+ if (!symbol) {
+ return nullptr;
+ }
+
+ if (!symbol->attribute) {
+ if (outError) *outError = "is not an attribute";
+ return nullptr;
+ }
+ return symbol;
+}
+
+Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(const Reference& reference,
+ NameMangler* nameMangler,
+ SymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError) {
+ const SymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+ if (!symbol) {
+ return {};
+ }
+
+ if (!symbol->attribute) {
+ if (outError) *outError = "is not an attribute";
+ return {};
+ }
+ return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+}
+
+void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig,
+ const Reference& transformed) {
+ assert(outMsg);
+
+ if (orig.name) {
+ *outMsg << orig.name.value();
+ if (transformed.name.value() != orig.name.value()) {
+ *outMsg << " (aka " << transformed.name.value() << ")";
+ }
+ } else {
+ *outMsg << orig.id.value();
+ }
+}
+
+bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
+ SymbolTable* symbols, xml::IPackageDeclStack* decls,
+ CallSite* callSite) {
+ assert(reference);
+ assert(reference->name || reference->id);
+
+ Reference transformedReference = *reference;
+ transformReferenceFromNamespace(decls, context->getCompilationPackage(),
+ &transformedReference);
+
+ std::string errStr;
+ const SymbolTable::Symbol* s = resolveSymbolCheckVisibility(
+ transformedReference, context->getNameMangler(), symbols, callSite, &errStr);
+ if (s) {
+ // The ID may not exist. This is fine because of the possibility of building against
+ // libraries without assigned IDs.
+ // Ex: Linking against own resources when building a static library.
+ reference->id = s->id;
+ return true;
+ }
+
+ DiagMessage errorMsg(reference->getSource());
+ errorMsg << "resource ";
+ writeResourceName(&errorMsg, *reference, transformedReference);
+ errorMsg << " " << errStr;
+ context->getDiagnostics()->error(errorMsg);
+ return false;
+}
+
+namespace {
+
+struct EmptyDeclStack : public xml::IPackageDeclStack {
+ Maybe<xml::ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const override {
+ if (alias.empty()) {
+ return xml::ExtractedPackage{ localPackage.toString(), true /* private */ };
+ }
+ return {};
+ }
+};
+
+} // namespace
+
+bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) {
+ EmptyDeclStack declStack;
+ bool error = false;
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ // Symbol state information may be lost if there is no value for the resource.
+ if (entry->symbolStatus.state != SymbolState::kUndefined && entry->values.empty()) {
+ context->getDiagnostics()->error(
+ DiagMessage(entry->symbolStatus.source)
+ << "no definition for declared symbol '"
+ << ResourceNameRef(package->name, type->type, entry->name)
+ << "'");
+ error = true;
+ }
+
+ CallSite callSite = { ResourceNameRef(package->name, type->type, entry->name) };
+ ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(),
+ &table->stringPool, &declStack, &callSite);
+
+ for (auto& configValue : entry->values) {
+ configValue->value->accept(&visitor);
+ }
+
+ if (visitor.hasError()) {
+ error = true;
+ }
+ }
+ }
+ }
+ return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
new file mode 100644
index 000000000000..7993aaf39e47
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINKER_REFERENCELINKER_H
+#define AAPT_LINKER_REFERENCELINKER_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "link/Linkers.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "xml/XmlDom.h"
+
+#include <cassert>
+
+namespace aapt {
+
+/**
+ * Resolves all references to resources in the ResourceTable and assigns them IDs.
+ * The ResourceTable must already have IDs assigned to each resource.
+ * Once the ResourceTable is processed by this linker, it is ready to be flattened.
+ */
+struct ReferenceLinker : public IResourceTableConsumer {
+ /**
+ * Returns true if the symbol is visible by the reference and from the callsite.
+ */
+ static bool isSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref,
+ const CallSite& callSite);
+
+ /**
+ * Performs name mangling and looks up the resource in the symbol table. Returns nullptr
+ * if the symbol was not found.
+ */
+ static const SymbolTable::Symbol* resolveSymbol(const Reference& reference,
+ NameMangler* mangler, SymbolTable* symbols);
+
+ /**
+ * Performs name mangling and looks up the resource in the symbol table. If the symbol is
+ * not visible by the reference at the callsite, nullptr is returned. outError holds
+ * the error message.
+ */
+ static const SymbolTable::Symbol* resolveSymbolCheckVisibility(const Reference& reference,
+ NameMangler* nameMangler,
+ SymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError);
+
+ /**
+ * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute.
+ * That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute.
+ */
+ static const SymbolTable::Symbol* resolveAttributeCheckVisibility(const Reference& reference,
+ NameMangler* nameMangler,
+ SymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError);
+
+ /**
+ * Resolves the attribute reference and returns an xml::AaptAttribute if successful.
+ * If resolution fails, outError holds the error message.
+ */
+ static Maybe<xml::AaptAttribute> compileXmlAttribute(const Reference& reference,
+ NameMangler* nameMangler,
+ SymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError);
+
+ /**
+ * Writes the resource name to the DiagMessage, using the "orig_name (aka <transformed_name>)"
+ * syntax.
+ */
+ static void writeResourceName(DiagMessage* outMsg, const Reference& orig,
+ const Reference& transformed);
+
+ /**
+ * Transforms the package name of the reference to the fully qualified package name using
+ * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
+ * to the reference at the callsite, the reference is updated with an ID.
+ * Returns false on failure, and an error message is logged to the IDiagnostics in the context.
+ */
+ static bool linkReference(Reference* reference, IAaptContext* context, SymbolTable* symbols,
+ xml::IPackageDeclStack* decls, CallSite* callSite);
+
+ /**
+ * Links all references in the ResourceTable.
+ */
+ bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_REFERENCELINKER_H */
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
new file mode 100644
index 000000000000..76b23098a35c
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/ReferenceLinker.h"
+#include "test/Test.h"
+
+using android::ResTable_map;
+
+namespace aapt {
+
+TEST(ReferenceLinkerTest, LinkSimpleReferences) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+ u"@com.app.test:string/bar")
+
+ // Test use of local reference (w/o package name).
+ .addReference(u"@com.app.test:string/bar", ResourceId(0x7f020001), u"@string/baz")
+
+ .addReference(u"@com.app.test:string/baz", ResourceId(0x7f020002),
+ u"@android:string/ok")
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .addSymbolSource(test::StaticSymbolSourceBuilder()
+ .addPublicSymbol(u"@android:string/ok", ResourceId(0x01040034))
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+ Reference* ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/foo");
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+
+ ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/bar");
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002));
+
+ ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/baz");
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x01040034));
+}
+
+TEST(ReferenceLinkerTest, LinkStyleAttributes) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addValue(u"@com.app.test:style/Theme", test::StyleBuilder()
+ .setParent(u"@android:style/Theme.Material")
+ .addItem(u"@android:attr/foo", ResourceUtils::tryParseColor(u"#ff00ff"))
+ .addItem(u"@android:attr/bar", {} /* placeholder */)
+ .build())
+ .build();
+
+ {
+ // We need to fill in the value for the attribute android:attr/bar after we build the
+ // table, because we need access to the string pool.
+ Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+ ASSERT_NE(style, nullptr);
+ style->entries.back().value = util::make_unique<RawString>(
+ table->stringPool.makeRef(u"one|two"));
+ }
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+ .addSymbolSource(test::StaticSymbolSourceBuilder()
+ .addPublicSymbol(u"@android:style/Theme.Material",
+ ResourceId(0x01060000))
+ .addPublicSymbol(u"@android:attr/foo", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(ResTable_map::TYPE_COLOR)
+ .build())
+ .addPublicSymbol(u"@android:attr/bar", ResourceId(0x01010002),
+ test::AttributeBuilder()
+ .setTypeMask(ResTable_map::TYPE_FLAGS)
+ .addItem(u"one", 0x01)
+ .addItem(u"two", 0x02)
+ .build())
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+ Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+ ASSERT_NE(style, nullptr);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().id);
+ EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000));
+
+ ASSERT_EQ(2u, style->entries.size());
+
+ AAPT_ASSERT_TRUE(style->entries[0].key.id);
+ EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001));
+ ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr);
+
+ AAPT_ASSERT_TRUE(style->entries[1].key.id);
+ EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002));
+ ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr);
+}
+
+TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
+ .addSymbolSource(test::StaticSymbolSourceBuilder()
+ .addPublicSymbol(u"@com.app.test:attr/com.android.support$foo",
+ ResourceId(0x7f010000),
+ test::AttributeBuilder()
+ .setTypeMask(ResTable_map::TYPE_COLOR)
+ .build())
+ .build())
+ .build();
+
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f020000),
+ test::StyleBuilder().addItem(u"@com.android.support:attr/foo",
+ ResourceUtils::tryParseColor(u"#ff0000"))
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+ Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(1u, style->entries.size());
+ AAPT_ASSERT_TRUE(style->entries.front().key.id);
+ EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+ u"@android:string/hidden")
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .addSymbolSource(test::StaticSymbolSourceBuilder()
+ .addSymbol(u"@android:string/hidden", ResourceId(0x01040034))
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+ u"@com.app.lib:string/hidden")
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.app.lib" } })
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .addSymbolSource(test::StaticSymbolSourceBuilder()
+ .addSymbol(u"@com.app.test:string/com.app.lib$hidden",
+ ResourceId(0x7f040034))
+ .build())
+
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addValue(u"@com.app.test:style/Theme", test::StyleBuilder()
+ .addItem(u"@android:attr/hidden", ResourceUtils::tryParseColor(u"#ff00ff"))
+ .build())
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+ .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .addSymbolSource(test::StaticSymbolSourceBuilder()
+ .addSymbol(u"@android:attr/hidden", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(
+ android::ResTable_map::TYPE_COLOR)
+ .build())
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
new file mode 100644
index 000000000000..7471e15db41a
--- /dev/null
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "link/TableMerger.h"
+#include "util/Util.h"
+
+#include <cassert>
+
+namespace aapt {
+
+TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable,
+ const TableMergerOptions& options) :
+ mContext(context), mMasterTable(outTable), mOptions(options) {
+ // Create the desired package that all tables will be merged into.
+ mMasterPackage = mMasterTable->createPackage(
+ mContext->getCompilationPackage(), mContext->getPackageId());
+ assert(mMasterPackage && "package name or ID already taken");
+}
+
+bool TableMerger::merge(const Source& src, ResourceTable* table,
+ io::IFileCollection* collection) {
+ return mergeImpl(src, table, collection, false /* overlay */, true /* allow new */);
+}
+
+bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table,
+ io::IFileCollection* collection) {
+ return mergeImpl(src, table, collection, true /* overlay */, mOptions.autoAddOverlay);
+}
+
+/**
+ * This will merge packages with the same package name (or no package name).
+ */
+bool TableMerger::mergeImpl(const Source& src, ResourceTable* table,
+ io::IFileCollection* collection,
+ bool overlay, bool allowNew) {
+ const uint8_t desiredPackageId = mContext->getPackageId();
+
+ bool error = false;
+ for (auto& package : table->packages) {
+ // Warn of packages with an unrelated ID.
+ if (package->id && package->id.value() != 0x0 && package->id.value() != desiredPackageId) {
+ mContext->getDiagnostics()->warn(DiagMessage(src)
+ << "ignoring package " << package->name);
+ continue;
+ }
+
+ if (package->name.empty() || mContext->getCompilationPackage() == package->name) {
+ FileMergeCallback callback;
+ if (collection) {
+ callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
+ FileReference* newFile, FileReference* oldFile) -> bool {
+ // The old file's path points inside the APK, so we can use it as is.
+ io::IFile* f = collection->findFile(util::utf16ToUtf8(*oldFile->path));
+ if (!f) {
+ mContext->getDiagnostics()->error(DiagMessage(src) << "file '"
+ << *oldFile->path
+ << "' not found");
+ return false;
+ }
+
+ newFile->file = f;
+ return true;
+ };
+ }
+
+ // Merge here. Once the entries are merged and mangled, any references to
+ // them are still valid. This is because un-mangled references are
+ // mangled, then looked up at resolution time.
+ // Also, when linking, we convert references with no package name to use
+ // the compilation package name.
+ error |= !doMerge(src, table, package.get(),
+ false /* mangle */, overlay, allowNew, callback);
+ }
+ }
+ return !error;
+}
+
+/**
+ * This will merge and mangle resources from a static library.
+ */
+bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& packageName,
+ ResourceTable* table, io::IFileCollection* collection) {
+ bool error = false;
+ for (auto& package : table->packages) {
+ // Warn of packages with an unrelated ID.
+ if (packageName != package->name) {
+ mContext->getDiagnostics()->warn(DiagMessage(src)
+ << "ignoring package " << package->name);
+ continue;
+ }
+
+ bool mangle = packageName != mContext->getCompilationPackage();
+ mMergedPackages.insert(package->name);
+
+ auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
+ FileReference* newFile, FileReference* oldFile) -> bool {
+ // The old file's path points inside the APK, so we can use it as is.
+ io::IFile* f = collection->findFile(util::utf16ToUtf8(*oldFile->path));
+ if (!f) {
+ mContext->getDiagnostics()->error(DiagMessage(src) << "file '" << *oldFile->path
+ << "' not found");
+ return false;
+ }
+
+ newFile->file = f;
+ return true;
+ };
+
+ error |= !doMerge(src, table, package.get(),
+ mangle, false /* overlay */, true /* allow new */, callback);
+ }
+ return !error;
+}
+
+bool TableMerger::doMerge(const Source& src,
+ ResourceTable* srcTable,
+ ResourceTablePackage* srcPackage,
+ const bool manglePackage,
+ const bool overlay,
+ const bool allowNewResources,
+ FileMergeCallback callback) {
+ bool error = false;
+
+ for (auto& srcType : srcPackage->types) {
+ ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
+ if (srcType->symbolStatus.state == SymbolState::kPublic) {
+ if (dstType->symbolStatus.state == SymbolState::kPublic && dstType->id && srcType->id
+ && dstType->id.value() == srcType->id.value()) {
+ // Both types are public and have different IDs.
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "can not merge type '"
+ << srcType->type
+ << "': conflicting public IDs");
+ error = true;
+ continue;
+ }
+
+ dstType->symbolStatus = std::move(srcType->symbolStatus);
+ dstType->id = srcType->id;
+ }
+
+ for (auto& srcEntry : srcType->entries) {
+ ResourceEntry* dstEntry;
+ if (manglePackage) {
+ std::u16string mangledName = NameMangler::mangleEntry(srcPackage->name,
+ srcEntry->name);
+ if (allowNewResources) {
+ dstEntry = dstType->findOrCreateEntry(mangledName);
+ } else {
+ dstEntry = dstType->findEntry(mangledName);
+ }
+ } else {
+ if (allowNewResources) {
+ dstEntry = dstType->findOrCreateEntry(srcEntry->name);
+ } else {
+ dstEntry = dstType->findEntry(srcEntry->name);
+ }
+ }
+
+ if (!dstEntry) {
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "resource "
+ << ResourceNameRef(srcPackage->name,
+ srcType->type,
+ srcEntry->name)
+ << " does not override an existing resource");
+ mContext->getDiagnostics()->note(DiagMessage(src)
+ << "define an <add-resource> tag or use "
+ "--auto-add-overlay");
+ error = true;
+ continue;
+ }
+
+ if (srcEntry->symbolStatus.state != SymbolState::kUndefined) {
+ if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
+ if (dstEntry->symbolStatus.state == SymbolState::kPublic &&
+ dstEntry->id && srcEntry->id &&
+ dstEntry->id.value() != srcEntry->id.value()) {
+ // Both entries are public and have different IDs.
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "can not merge entry '"
+ << srcEntry->name
+ << "': conflicting public IDs");
+ error = true;
+ continue;
+ }
+
+ if (srcEntry->id) {
+ dstEntry->id = srcEntry->id;
+ }
+ }
+
+ if (dstEntry->symbolStatus.state != SymbolState::kPublic &&
+ dstEntry->symbolStatus.state != srcEntry->symbolStatus.state) {
+ dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
+ }
+ }
+
+ ResourceNameRef resName(mMasterPackage->name, dstType->type, dstEntry->name);
+
+ for (auto& srcValue : srcEntry->values) {
+ ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config,
+ srcValue->product);
+ if (dstValue) {
+ const int collisionResult = ResourceTable::resolveValueCollision(
+ dstValue->value.get(), srcValue->value.get());
+ if (collisionResult == 0 && !overlay) {
+ // Error!
+ ResourceNameRef resourceName(srcPackage->name,
+ srcType->type,
+ srcEntry->name);
+
+ mContext->getDiagnostics()->error(DiagMessage(srcValue->value->getSource())
+ << "resource '" << resourceName
+ << "' has a conflicting value for "
+ << "configuration ("
+ << srcValue->config << ")");
+ mContext->getDiagnostics()->note(DiagMessage(dstValue->value->getSource())
+ << "originally defined here");
+ error = true;
+ continue;
+ } else if (collisionResult < 0) {
+ // Keep our existing value.
+ continue;
+ }
+
+ }
+
+ if (!dstValue) {
+ // Force create the entry if we didn't have it.
+ dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product);
+ }
+
+ if (FileReference* f = valueCast<FileReference>(srcValue->value.get())) {
+ std::unique_ptr<FileReference> newFileRef;
+ if (manglePackage) {
+ newFileRef = cloneAndMangleFile(srcPackage->name, *f);
+ } else {
+ newFileRef = std::unique_ptr<FileReference>(f->clone(
+ &mMasterTable->stringPool));
+ }
+
+ if (callback) {
+ if (!callback(resName, srcValue->config, newFileRef.get(), f)) {
+ error = true;
+ continue;
+ }
+ }
+ dstValue->value = std::move(newFileRef);
+
+ } else {
+ dstValue->value = std::unique_ptr<Value>(srcValue->value->clone(
+ &mMasterTable->stringPool));
+ }
+ }
+ }
+ }
+ return !error;
+}
+
+std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(const std::u16string& package,
+ const FileReference& fileRef) {
+
+ StringPiece16 prefix, entry, suffix;
+ if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) {
+ std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
+ std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
+ std::unique_ptr<FileReference> newFileRef = util::make_unique<FileReference>(
+ mMasterTable->stringPool.makeRef(newPath));
+ newFileRef->setComment(fileRef.getComment());
+ newFileRef->setSource(fileRef.getSource());
+ return newFileRef;
+ }
+ return std::unique_ptr<FileReference>(fileRef.clone(&mMasterTable->stringPool));
+}
+
+bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay) {
+ ResourceTable table;
+ std::u16string path = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(fileDesc,
+ nullptr));
+ std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
+ table.stringPool.makeRef(path));
+ fileRef->setSource(fileDesc.source);
+ fileRef->file = file;
+
+ ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
+ pkg->findOrCreateType(fileDesc.name.type)
+ ->findOrCreateEntry(fileDesc.name.entry)
+ ->findOrCreateValue(fileDesc.config, {})
+ ->value = std::move(fileRef);
+
+ return doMerge(file->getSource(), &table, pkg,
+ false /* mangle */, overlay /* overlay */, true /* allow new */, {});
+}
+
+bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
+ return mergeFileImpl(fileDesc, file, false /* overlay */);
+}
+
+bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) {
+ return mergeFileImpl(fileDesc, file, true /* overlay */);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
new file mode 100644
index 000000000000..80c2a5e69b66
--- /dev/null
+++ b/tools/aapt2/link/TableMerger.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TABLEMERGER_H
+#define AAPT_TABLEMERGER_H
+
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "filter/ConfigFilter.h"
+#include "io/File.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Util.h"
+
+#include <functional>
+#include <map>
+
+namespace aapt {
+
+struct TableMergerOptions {
+ /**
+ * If true, resources in overlays can be added without previously having existed.
+ */
+ bool autoAddOverlay = false;
+};
+
+/**
+ * TableMerger takes resource tables and merges all packages within the tables that have the same
+ * package ID.
+ *
+ * If a package has a different name, all the entries in that table have their names mangled
+ * to include the package name. This way there are no collisions. In order to do this correctly,
+ * the TableMerger needs to also mangle any FileReference paths. Once these are mangled,
+ * the original source path of the file, along with the new destination path is recorded in the
+ * queue returned from getFileMergeQueue().
+ *
+ * Once the merging is complete, a separate process can go collect the files from the various
+ * source APKs and either copy or process their XML and put them in the correct location in
+ * the final APK.
+ */
+class TableMerger {
+public:
+ /**
+ * Note: The outTable ResourceTable must live longer than this TableMerger. References
+ * are made to this ResourceTable for efficiency reasons.
+ */
+ TableMerger(IAaptContext* context, ResourceTable* outTable, const TableMergerOptions& options);
+
+ const std::set<std::u16string>& getMergedPackages() const {
+ return mMergedPackages;
+ }
+
+ /**
+ * Merges resources from the same or empty package. This is for local sources.
+ * An io::IFileCollection is optional and used to find the referenced Files and process them.
+ */
+ bool merge(const Source& src, ResourceTable* table,
+ io::IFileCollection* collection = nullptr);
+
+ /**
+ * Merges resources from an overlay ResourceTable.
+ * An io::IFileCollection is optional and used to find the referenced Files and process them.
+ */
+ bool mergeOverlay(const Source& src, ResourceTable* table,
+ io::IFileCollection* collection = nullptr);
+
+ /**
+ * Merges resources from the given package, mangling the name. This is for static libraries.
+ * An io::IFileCollection is needed in order to find the referenced Files and process them.
+ */
+ bool mergeAndMangle(const Source& src, const StringPiece16& package, ResourceTable* table,
+ io::IFileCollection* collection);
+
+ /**
+ * Merges a compiled file that belongs to this same or empty package. This is for local sources.
+ */
+ bool mergeFile(const ResourceFile& fileDesc, io::IFile* file);
+
+ /**
+ * Merges a compiled file from an overlay, overriding an existing definition.
+ */
+ bool mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file);
+
+private:
+ using FileMergeCallback = std::function<bool(const ResourceNameRef&,
+ const ConfigDescription& config,
+ FileReference*, FileReference*)>;
+
+ IAaptContext* mContext;
+ ResourceTable* mMasterTable;
+ TableMergerOptions mOptions;
+ ResourceTablePackage* mMasterPackage;
+
+ std::set<std::u16string> mMergedPackages;
+
+ bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay);
+
+ bool mergeImpl(const Source& src, ResourceTable* srcTable, io::IFileCollection* collection,
+ bool overlay, bool allowNew);
+
+ bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage,
+ const bool manglePackage,
+ const bool overlay,
+ const bool allowNewResources,
+ FileMergeCallback callback);
+
+ std::unique_ptr<FileReference> cloneAndMangleFile(const std::u16string& package,
+ const FileReference& value);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_TABLEMERGER_H */
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
new file mode 100644
index 000000000000..4a80d3f48777
--- /dev/null
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "filter/ConfigFilter.h"
+#include "io/FileSystem.h"
+#include "link/TableMerger.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+struct TableMergerTest : public ::testing::Test {
+ std::unique_ptr<IAaptContext> mContext;
+
+ void SetUp() override {
+ mContext = test::ContextBuilder()
+ // We are compiling this package.
+ .setCompilationPackage(u"com.app.a")
+
+ // Merge all packages that have this package ID.
+ .setPackageId(0x7f)
+
+ // Mangle all packages that do not have this package name.
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.a", { u"com.app.b" } })
+
+ .build();
+ }
+};
+
+TEST_F(TableMergerTest, SimpleMerge) {
+ std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.a", 0x7f)
+ .addReference(u"@com.app.a:id/foo", u"@com.app.a:id/bar")
+ .addReference(u"@com.app.a:id/bar", u"@com.app.b:id/foo")
+ .addValue(u"@com.app.a:styleable/view", test::StyleableBuilder()
+ .addItem(u"@com.app.b:id/foo")
+ .build())
+ .build();
+
+ std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.b", 0x7f)
+ .addSimple(u"@com.app.b:id/foo")
+ .build();
+
+ ResourceTable finalTable;
+ TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+ io::FileCollection collection;
+
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get(), &collection));
+
+ EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0);
+
+ // Entries from com.app.a should not be mangled.
+ AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/foo")));
+ AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/bar")));
+ AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:styleable/view")));
+
+ // The unmangled name should not be present.
+ AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie(u"@com.app.b:id/foo")));
+
+ // Look for the mangled name.
+ AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/com.app.b$foo")));
+}
+
+TEST_F(TableMergerTest, MergeFile) {
+ ResourceTable finalTable;
+ TableMergerOptions options;
+ options.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, options);
+
+ ResourceFile fileDesc;
+ fileDesc.config = test::parseConfigOrDie("hdpi-v4");
+ fileDesc.name = test::parseNameOrDie(u"@layout/main");
+ fileDesc.source = Source("res/layout-hdpi/main.xml");
+ test::TestFile testFile("path/to/res/layout-hdpi/main.xml.flat");
+
+ ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile));
+
+ FileReference* file = test::getValueForConfig<FileReference>(&finalTable,
+ u"@com.app.a:layout/main",
+ test::parseConfigOrDie("hdpi-v4"));
+ ASSERT_NE(nullptr, file);
+ EXPECT_EQ(std::u16string(u"res/layout-hdpi-v4/main.xml"), *file->path);
+}
+
+TEST_F(TableMergerTest, MergeFileOverlay) {
+ ResourceTable finalTable;
+ TableMergerOptions tableMergerOptions;
+ tableMergerOptions.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
+
+ ResourceFile fileDesc;
+ fileDesc.name = test::parseNameOrDie(u"@xml/foo");
+ test::TestFile fileA("path/to/fileA.xml.flat");
+ test::TestFile fileB("path/to/fileB.xml.flat");
+
+ ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA));
+ ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB));
+}
+
+TEST_F(TableMergerTest, MergeFileReferences) {
+ std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.a", 0x7f)
+ .addFileReference(u"@com.app.a:xml/file", u"res/xml/file.xml")
+ .build();
+ std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.b", 0x7f)
+ .addFileReference(u"@com.app.b:xml/file", u"res/xml/file.xml")
+ .build();
+
+ ResourceTable finalTable;
+ TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+ io::FileCollection collection;
+ collection.insertFile("res/xml/file.xml");
+
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get(), &collection));
+
+ FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file");
+ ASSERT_NE(f, nullptr);
+ EXPECT_EQ(std::u16string(u"res/xml/file.xml"), *f->path);
+
+ f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/com.app.b$file");
+ ASSERT_NE(f, nullptr);
+ EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path);
+}
+
+TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
+ std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder()
+ .setPackageId(u"", 0x00)
+ .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true"))
+ .build();
+ std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder()
+ .setPackageId(u"", 0x00)
+ .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"false"))
+ .build();
+
+ ResourceTable finalTable;
+ TableMergerOptions tableMergerOptions;
+ tableMergerOptions.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
+
+ ASSERT_TRUE(merger.merge({}, base.get()));
+ ASSERT_TRUE(merger.mergeOverlay({}, overlay.get()));
+
+ BinaryPrimitive* foo = test::getValue<BinaryPrimitive>(&finalTable, u"@com.app.a:bool/foo");
+ ASSERT_NE(nullptr, foo);
+ EXPECT_EQ(0x0u, foo->value.data);
+}
+
+TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
+ std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+ .setPackageId(u"", 0x7f)
+ .setSymbolState(u"@bool/foo", {}, SymbolState::kUndefined)
+ .build();
+ std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+ .setPackageId(u"", 0x7f)
+ .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true"))
+ .build();
+
+ ResourceTable finalTable;
+ TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+}
+
+TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
+ std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+ .setPackageId(u"", 0x7f)
+ .build();
+ std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+ .setPackageId(u"", 0x7f)
+ .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true"))
+ .build();
+
+ ResourceTable finalTable;
+ TableMergerOptions options;
+ options.autoAddOverlay = true;
+ TableMerger merger(mContext.get(), &finalTable, options);
+
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+}
+
+TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
+ std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+ .setPackageId(u"", 0x7f)
+ .build();
+ std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+ .setPackageId(u"", 0x7f)
+ .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true"))
+ .build();
+
+ ResourceTable finalTable;
+ TableMergerOptions options;
+ options.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, options);
+
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
new file mode 100644
index 000000000000..568bc74895c7
--- /dev/null
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Diagnostics.h"
+#include "ResourceUtils.h"
+#include "SdkConstants.h"
+#include "link/Linkers.h"
+#include "link/ReferenceLinker.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+
+namespace {
+
+/**
+ * Visits all references (including parents of styles, references in styles, arrays, etc) and
+ * links their symbolic name to their Resource ID, performing mangling and package aliasing
+ * as needed.
+ */
+class ReferenceVisitor : public ValueVisitor {
+public:
+ using ValueVisitor::visit;
+
+ ReferenceVisitor(IAaptContext* context, SymbolTable* symbols, xml::IPackageDeclStack* decls,
+ CallSite* callSite) :
+ mContext(context), mSymbols(symbols), mDecls(decls), mCallSite(callSite),
+ mError(false) {
+ }
+
+ void visit(Reference* ref) override {
+ if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) {
+ mError = true;
+ }
+ }
+
+ bool hasError() const {
+ return mError;
+ }
+
+private:
+ IAaptContext* mContext;
+ SymbolTable* mSymbols;
+ xml::IPackageDeclStack* mDecls;
+ CallSite* mCallSite;
+ bool mError;
+};
+
+/**
+ * Visits each xml Element and compiles the attributes within.
+ */
+class XmlVisitor : public xml::PackageAwareVisitor {
+public:
+ using xml::PackageAwareVisitor::visit;
+
+ XmlVisitor(IAaptContext* context, SymbolTable* symbols, const Source& source,
+ std::set<int>* sdkLevelsFound, CallSite* callSite) :
+ mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound),
+ mCallSite(callSite), mReferenceVisitor(context, symbols, this, callSite) {
+ }
+
+ void visit(xml::Element* el) override {
+ const Source source = mSource.withLine(el->lineNumber);
+ for (xml::Attribute& attr : el->attributes) {
+ Maybe<xml::ExtractedPackage> maybePackage =
+ xml::extractPackageFromNamespace(attr.namespaceUri);
+ if (maybePackage) {
+ // There is a valid package name for this attribute. We will look this up.
+ StringPiece16 package = maybePackage.value().package;
+ if (package.empty()) {
+ // Empty package means the 'current' or 'local' package.
+ package = mContext->getCompilationPackage();
+ }
+
+ Reference attrRef(ResourceNameRef(package, ResourceType::kAttr, attr.name));
+ attrRef.privateReference = maybePackage.value().privateNamespace;
+
+ std::string errStr;
+ attr.compiledAttribute = ReferenceLinker::compileXmlAttribute(
+ attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
+
+ // Convert the string value into a compiled Value if this is a valid attribute.
+ if (attr.compiledAttribute) {
+ if (attr.compiledAttribute.value().id) {
+ // Record all SDK levels from which the attributes were defined.
+ const size_t sdkLevel = findAttributeSdkLevel(
+ attr.compiledAttribute.value().id.value());
+ if (sdkLevel > 1) {
+ mSdkLevelsFound->insert(sdkLevel);
+ }
+ }
+
+ const Attribute* attribute = &attr.compiledAttribute.value().attribute;
+ attr.compiledValue = ResourceUtils::parseItemForAttribute(attr.value,
+ attribute);
+ if (!attr.compiledValue &&
+ !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) {
+ // We won't be able to encode this as a string.
+ mContext->getDiagnostics()->error(
+ DiagMessage(source) << "'" << attr.value << "' "
+ << "is incompatible with attribute "
+ << package << ":" << attr.name << " "
+ << *attribute);
+ mError = true;
+ }
+
+ } else {
+ mContext->getDiagnostics()->error(DiagMessage(source)
+ << "attribute '" << package << ":"
+ << attr.name << "' " << errStr);
+ mError = true;
+
+ }
+ } else {
+ // We still encode references.
+ attr.compiledValue = ResourceUtils::tryParseReference(attr.value);
+ }
+
+ if (attr.compiledValue) {
+ // With a compiledValue, we must resolve the reference and assign it an ID.
+ attr.compiledValue->setSource(source);
+ attr.compiledValue->accept(&mReferenceVisitor);
+ }
+ }
+
+ // Call the super implementation.
+ xml::PackageAwareVisitor::visit(el);
+ }
+
+ bool hasError() {
+ return mError || mReferenceVisitor.hasError();
+ }
+
+private:
+ IAaptContext* mContext;
+ SymbolTable* mSymbols;
+ Source mSource;
+ std::set<int>* mSdkLevelsFound;
+ CallSite* mCallSite;
+ ReferenceVisitor mReferenceVisitor;
+ bool mError = false;
+};
+
+} // namespace
+
+bool XmlReferenceLinker::consume(IAaptContext* context, xml::XmlResource* resource) {
+ mSdkLevelsFound.clear();
+ CallSite callSite = { resource->file.name };
+ XmlVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
+ &mSdkLevelsFound, &callSite);
+ if (resource->root) {
+ resource->root->accept(&visitor);
+ return !visitor.hasError();
+ }
+ return false;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
new file mode 100644
index 000000000000..af9098b9e483
--- /dev/null
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <test/Context.h>
+#include "link/Linkers.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+class XmlReferenceLinkerTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ mContext = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setNameManglerPolicy(
+ NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
+ .addSymbolSource(test::StaticSymbolSourceBuilder()
+ .addPublicSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_ENUM |
+ android::ResTable_map::TYPE_DIMENSION)
+ .addItem(u"match_parent", 0xffffffff)
+ .build())
+ .addPublicSymbol(u"@android:attr/background", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+ .addPublicSymbol(u"@android:attr/attr", ResourceId(0x01010002),
+ test::AttributeBuilder().build())
+ .addPublicSymbol(u"@android:attr/text", ResourceId(0x01010003),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING)
+ .build())
+
+ // Add one real symbol that was introduces in v21
+ .addPublicSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+ test::AttributeBuilder().build())
+
+ // Private symbol.
+ .addSymbol(u"@android:color/hidden", ResourceId(0x01020001))
+
+ .addPublicSymbol(u"@android:id/id", ResourceId(0x01030000))
+ .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000))
+ .addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000))
+ .addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001))
+ .addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+ .addPublicSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
+ ResourceId(0x7f010001), test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+ .addPublicSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
+ test::AttributeBuilder().build())
+ .build())
+ .build();
+ }
+
+protected:
+ std::unique_ptr<IAaptContext> mContext;
+};
+
+TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:background="@color/green"
+ android:text="hello"
+ class="hello" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
+
+ xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
+ u"layout_width");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010000));
+ ASSERT_NE(xmlAttr->compiledValue, nullptr);
+ ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+
+ xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"background");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010001));
+ ASSERT_NE(xmlAttr->compiledValue, nullptr);
+ Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->name);
+ EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@color/green")); // Make sure the name
+ // didn't change.
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000));
+
+ xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"text");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ ASSERT_FALSE(xmlAttr->compiledValue); // Strings don't get compiled for memory sake.
+
+ xmlAttr = viewEl->findAttribute(u"", u"class");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute);
+ ASSERT_EQ(xmlAttr->compiledValue, nullptr);
+}
+
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:colorAccent="@android:color/hidden" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(mContext.get(), doc.get()));
+}
+
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:colorAccent="@*android:color/hidden" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+}
+
+TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:colorAccent="#ffffff" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ EXPECT_TRUE(linker.getSdkLevels().count(21) == 1);
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
+ support:colorAccent="#ff0000" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
+
+ xml::Attribute* xmlAttr = viewEl->findAttribute(
+ u"http://schemas.android.com/apk/res/com.android.support", u"colorAccent");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010001));
+ ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:app="http://schemas.android.com/apk/res-auto"
+ app:colorAccent="@app:color/red" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
+
+ xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto",
+ u"colorAccent");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010000));
+ Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->name);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:app="http://schemas.android.com/apk/res/android"
+ app:attr="@app:id/id">
+ <View xmlns:app="http://schemas.android.com/apk/res/com.app.test"
+ app:attr="@app:id/id"/>
+ </View>)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
+
+ // All attributes and references in this element should be referring to "android" (0x01).
+ xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
+ u"attr");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010002));
+ Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x01030000));
+
+ ASSERT_FALSE(viewEl->getChildElements().empty());
+ viewEl = viewEl->getChildElements().front();
+ ASSERT_NE(viewEl, nullptr);
+
+ // All attributes and references in this element should be referring to "com.app.test" (0x7f).
+ xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/com.app.test", u"attr");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010002));
+ ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
+ android:attr="@id/id"/>)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
+
+ // All attributes and references in this element should be referring to "com.app.test" (0x7f).
+ xml::Attribute* xmlAttr = viewEl->findAttribute(
+ u"http://schemas.android.com/apk/res/com.app.test", u"attr");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010002));
+ Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot
deleted file mode 100644
index 4741952d2332..000000000000
--- a/tools/aapt2/process.dot
+++ /dev/null
@@ -1,108 +0,0 @@
-digraph aapt {
- out_package [label="out/default/package.apk"];
- out_fr_package [label="out/fr/package.apk"];
- out_table_aligned [label="out/default/resources-aligned.arsc"];
- out_table_fr_aligned [label="out/fr/resources-aligned.arsc"];
- out_res_layout_main_xml [label="out/res/layout/main.xml"];
- out_res_layout_v21_main_xml [color=red,label="out/res/layout-v21/main.xml"];
- out_res_layout_fr_main_xml [label="out/res/layout-fr/main.xml"];
- out_res_layout_fr_v21_main_xml [color=red,label="out/res/layout-fr-v21/main.xml"];
- out_table [label="out/default/resources.arsc"];
- out_fr_table [label="out/fr/resources.arsc"];
- out_values_table [label="out/values/resources.arsc"];
- out_layout_table [label="out/layout/resources.arsc"];
- out_values_fr_table [label="out/values-fr/resources.arsc"];
- out_layout_fr_table [label="out/layout-fr/resources.arsc"];
- res_values_strings_xml [label="res/values/strings.xml"];
- res_values_attrs_xml [label="res/values/attrs.xml"];
- res_layout_main_xml [label="res/layout/main.xml"];
- res_layout_fr_main_xml [label="res/layout-fr/main.xml"];
- res_values_fr_strings_xml [label="res/values-fr/strings.xml"];
-
- lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green];
- lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green];
- lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green];
- lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green];
- lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green];
- out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"];
-
- out_package -> package_default;
- out_fr_package -> package_fr;
-
- package_default [shape=box,label="Assemble",color=blue];
- package_default -> out_table_aligned;
- package_default -> out_res_layout_main_xml;
- package_default -> out_res_layout_v21_main_xml [color=red];
- package_default -> out_res_layout_lib_main_xml;
-
- package_fr [shape=box,label="Assemble",color=blue];
- package_fr -> out_table_fr_aligned;
- package_fr -> out_res_layout_fr_main_xml;
- package_fr -> out_res_layout_fr_v21_main_xml [color=red];
-
- out_table_aligned -> align_tables;
- out_table_fr_aligned -> align_tables;
-
- align_tables [shape=box,label="Align",color=blue];
- align_tables -> out_table;
- align_tables -> out_fr_table;
-
- out_table -> link_tables;
-
- link_tables [shape=box,label="Link",color=blue];
- link_tables -> out_values_table;
- link_tables -> out_layout_table;
- link_tables -> lib_apk_resources_arsc;
-
- out_values_table -> compile_values;
-
- compile_values [shape=box,label="Collect",color=blue];
- compile_values -> res_values_strings_xml;
- compile_values -> res_values_attrs_xml;
-
- out_layout_table -> collect_xml;
-
- collect_xml [shape=box,label="Collect",color=blue];
- collect_xml -> res_layout_main_xml;
-
- out_fr_table -> link_fr_tables;
-
- link_fr_tables [shape=box,label="Link",color=blue];
- link_fr_tables -> out_values_fr_table;
- link_fr_tables -> out_layout_fr_table;
- link_fr_tables -> lib_apk_resources_arsc;
-
- out_values_fr_table -> compile_values_fr;
-
- compile_values_fr [shape=box,label="Collect",color=blue];
- compile_values_fr -> res_values_fr_strings_xml;
-
- out_layout_fr_table -> collect_xml_fr;
-
- collect_xml_fr [shape=box,label="Collect",color=blue];
- collect_xml_fr -> res_layout_fr_main_xml;
-
- compile_res_layout_main_xml [shape=box,label="Compile",color=blue];
-
- out_res_layout_main_xml -> compile_res_layout_main_xml;
-
- out_res_layout_v21_main_xml -> compile_res_layout_main_xml [color=red];
-
- compile_res_layout_main_xml -> res_layout_main_xml;
- compile_res_layout_main_xml -> out_table_aligned;
-
- compile_res_layout_fr_main_xml [shape=box,label="Compile",color=blue];
-
- out_res_layout_fr_main_xml -> compile_res_layout_fr_main_xml;
-
- out_res_layout_fr_v21_main_xml -> compile_res_layout_fr_main_xml [color=red];
-
- compile_res_layout_fr_main_xml -> res_layout_fr_main_xml;
- compile_res_layout_fr_main_xml -> out_table_fr_aligned;
-
- out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml;
-
- compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue];
- compile_res_layout_lib_main_xml -> out_table_aligned;
- compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml;
-}
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
new file mode 100644
index 000000000000..9affb836340c
--- /dev/null
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PROCESS_IRESOURCETABLECONSUMER_H
+#define AAPT_PROCESS_IRESOURCETABLECONSUMER_H
+
+#include "Diagnostics.h"
+#include "NameMangler.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "Source.h"
+
+#include <iostream>
+#include <list>
+#include <sstream>
+
+namespace aapt {
+
+class ResourceTable;
+class SymbolTable;
+
+struct IAaptContext {
+ virtual ~IAaptContext() = default;
+
+ virtual SymbolTable* getExternalSymbols() = 0;
+ virtual IDiagnostics* getDiagnostics() = 0;
+ virtual const std::u16string& getCompilationPackage() = 0;
+ virtual uint8_t getPackageId() = 0;
+ virtual NameMangler* getNameMangler() = 0;
+ virtual bool verbose() = 0;
+};
+
+struct IResourceTableConsumer {
+ virtual ~IResourceTableConsumer() = default;
+
+ virtual bool consume(IAaptContext* context, ResourceTable* table) = 0;
+};
+
+namespace xml {
+struct XmlResource;
+}
+
+struct IXmlResourceConsumer {
+ virtual ~IXmlResourceConsumer() = default;
+
+ virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_PROCESS_IRESOURCETABLECONSUMER_H */
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
new file mode 100644
index 000000000000..eaaf06f7e530
--- /dev/null
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "Resource.h"
+#include "ValueVisitor.h"
+#include "process/SymbolTable.h"
+#include "util/Util.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+void SymbolTable::appendSource(std::unique_ptr<ISymbolSource> source) {
+ mSources.push_back(std::move(source));
+
+ // We do not clear the cache, because sources earlier in the list take precedent.
+}
+
+void SymbolTable::prependSource(std::unique_ptr<ISymbolSource> source) {
+ mSources.insert(mSources.begin(), std::move(source));
+
+ // We must clear the cache in case we did a lookup before adding this resource.
+ mCache.clear();
+}
+
+const SymbolTable::Symbol* SymbolTable::findByName(const ResourceName& name) {
+ if (const std::shared_ptr<Symbol>& s = mCache.get(name)) {
+ return s.get();
+ }
+
+ // We did not find it in the cache, so look through the sources.
+ for (auto& symbolSource : mSources) {
+ std::unique_ptr<Symbol> symbol = symbolSource->findByName(name);
+ if (symbol) {
+ // Take ownership of the symbol into a shared_ptr. We do this because LruCache
+ // doesn't support unique_ptr.
+ std::shared_ptr<Symbol> sharedSymbol = std::shared_ptr<Symbol>(symbol.release());
+ mCache.put(name, sharedSymbol);
+
+ if (sharedSymbol->id) {
+ // The symbol has an ID, so we can also cache this!
+ mIdCache.put(sharedSymbol->id.value(), sharedSymbol);
+ }
+ return sharedSymbol.get();
+ }
+ }
+ return nullptr;
+}
+
+const SymbolTable::Symbol* SymbolTable::findById(ResourceId id) {
+ if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
+ return s.get();
+ }
+
+ // We did not find it in the cache, so look through the sources.
+ for (auto& symbolSource : mSources) {
+ std::unique_ptr<Symbol> symbol = symbolSource->findById(id);
+ if (symbol) {
+ // Take ownership of the symbol into a shared_ptr. We do this because LruCache
+ // doesn't support unique_ptr.
+ std::shared_ptr<Symbol> sharedSymbol = std::shared_ptr<Symbol>(symbol.release());
+ mIdCache.put(id, sharedSymbol);
+ return sharedSymbol.get();
+ }
+ }
+ return nullptr;
+}
+
+const SymbolTable::Symbol* SymbolTable::findByReference(const Reference& ref) {
+ // First try the ID. This is because when we lookup by ID, we only fill in the ID cache.
+ // Looking up by name fills in the name and ID cache. So a cache miss will cause a failed
+ // ID lookup, then a successfull name lookup. Subsequent look ups will hit immediately
+ // because the ID is cached too.
+ //
+ // If we looked up by name first, a cache miss would mean we failed to lookup by name, then
+ // succeeded to lookup by ID. Subsequent lookups will miss then hit.
+ const SymbolTable::Symbol* symbol = nullptr;
+ if (ref.id) {
+ symbol = findById(ref.id.value());
+ }
+
+ if (ref.name && !symbol) {
+ symbol = findByName(ref.name.value());
+ }
+ return symbol;
+}
+
+std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::findByName(
+ const ResourceName& name) {
+ Maybe<ResourceTable::SearchResult> result = mTable->findResource(name);
+ if (!result) {
+ if (name.type == ResourceType::kAttr) {
+ // Recurse and try looking up a private attribute.
+ return findByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry));
+ }
+ return {};
+ }
+
+ ResourceTable::SearchResult sr = result.value();
+
+ std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>();
+ symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic);
+
+ if (sr.package->id && sr.type->id && sr.entry->id) {
+ symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
+ }
+
+ if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
+ const ConfigDescription kDefaultConfig;
+ ResourceConfigValue* configValue = sr.entry->findValue(kDefaultConfig);
+ if (configValue) {
+ // This resource has an Attribute.
+ if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) {
+ symbol->attribute = std::make_shared<Attribute>(*attr);
+ } else {
+ return {};
+ }
+ }
+ }
+ return symbol;
+}
+
+bool AssetManagerSymbolSource::addAssetPath(const StringPiece& path) {
+ int32_t cookie = 0;
+ return mAssets.addAssetPath(android::String8(path.data(), path.size()), &cookie);
+}
+
+static std::unique_ptr<SymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table,
+ ResourceId id) {
+ // Try as a bag.
+ const android::ResTable::bag_entry* entry;
+ ssize_t count = table.lockBag(id.id, &entry);
+ if (count < 0) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ // We found a resource.
+ std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>();
+ s->id = id;
+
+ // Check to see if it is an attribute.
+ for (size_t i = 0; i < (size_t) count; i++) {
+ if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
+ s->attribute = std::make_shared<Attribute>(false);
+ s->attribute->typeMask = entry[i].map.value.data;
+ break;
+ }
+ }
+
+ if (s->attribute) {
+ for (size_t i = 0; i < (size_t) count; i++) {
+ const android::ResTable_map& mapEntry = entry[i].map;
+ if (Res_INTERNALID(mapEntry.name.ident)) {
+ switch (mapEntry.name.ident) {
+ case android::ResTable_map::ATTR_MIN:
+ s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case android::ResTable_map::ATTR_MAX:
+ s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ }
+ continue;
+ }
+
+ android::ResTable::resource_name entryName;
+ if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ const ResourceType* parsedType = parseResourceType(
+ StringPiece16(entryName.type, entryName.typeLen));
+ if (!parsedType) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ Attribute::Symbol symbol;
+ symbol.symbol.name = ResourceName(
+ StringPiece16(entryName.package, entryName.packageLen),
+ *parsedType,
+ StringPiece16(entryName.name, entryName.nameLen));
+ symbol.symbol.id = ResourceId(mapEntry.name.ident);
+ symbol.value = mapEntry.value.data;
+ s->attribute->symbols.push_back(std::move(symbol));
+ }
+ }
+ table.unlockBag(entry);
+ return s;
+}
+
+std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByName(
+ const ResourceName& name) {
+ const android::ResTable& table = mAssets.getResources(false);
+ StringPiece16 typeStr = toString(name.type);
+ uint32_t typeSpecFlags = 0;
+ ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(),
+ typeStr.data(), typeStr.size(),
+ name.package.data(), name.package.size(),
+ &typeSpecFlags);
+ if (!resId.isValid()) {
+ return {};
+ }
+
+ std::unique_ptr<SymbolTable::Symbol> s;
+ if (name.type == ResourceType::kAttr) {
+ s = lookupAttributeInTable(table, resId);
+ } else {
+ s = util::make_unique<SymbolTable::Symbol>();
+ s->id = resId;
+ }
+
+ if (s) {
+ s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ return s;
+ }
+ return {};
+}
+
+static Maybe<ResourceName> getResourceName(const android::ResTable& table, ResourceId id) {
+ android::ResTable::resource_name resName = {};
+ if (!table.getResourceName(id.id, true, &resName)) {
+ return {};
+ }
+
+ ResourceName name;
+ if (resName.package) {
+ name.package = StringPiece16(resName.package, resName.packageLen).toString();
+ }
+
+ const ResourceType* type;
+ if (resName.type) {
+ type = parseResourceType(StringPiece16(resName.type, resName.typeLen));
+
+ } else if (resName.type8) {
+ type = parseResourceType(util::utf8ToUtf16(StringPiece(resName.type8, resName.typeLen)));
+ } else {
+ return {};
+ }
+
+ if (!type) {
+ return {};
+ }
+
+ name.type = *type;
+
+ if (resName.name) {
+ name.entry = StringPiece16(resName.name, resName.nameLen).toString();
+ } else if (resName.name8) {
+ name.entry = util::utf8ToUtf16(StringPiece(resName.name8, resName.nameLen));
+ } else {
+ return {};
+ }
+
+ return name;
+}
+
+std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findById(ResourceId id) {
+ const android::ResTable& table = mAssets.getResources(false);
+ Maybe<ResourceName> maybeName = getResourceName(table, id);
+ if (!maybeName) {
+ return {};
+ }
+
+ uint32_t typeSpecFlags = 0;
+ table.getResourceFlags(id.id, &typeSpecFlags);
+
+ std::unique_ptr<SymbolTable::Symbol> s;
+ if (maybeName.value().type == ResourceType::kAttr) {
+ s = lookupAttributeInTable(table, id);
+ } else {
+ s = util::make_unique<SymbolTable::Symbol>();
+ s->id = id;
+ }
+
+ if (s) {
+ s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ return s;
+ }
+ return {};
+}
+
+std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByReference(
+ const Reference& ref) {
+ // AssetManager always prefers IDs.
+ if (ref.id) {
+ return findById(ref.id.value());
+ } else if (ref.name) {
+ return findByName(ref.name.value());
+ }
+ return {};
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
new file mode 100644
index 000000000000..e684bb06f1f5
--- /dev/null
+++ b/tools/aapt2/process/SymbolTable.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PROCESS_SYMBOLTABLE_H
+#define AAPT_PROCESS_SYMBOLTABLE_H
+
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+
+#include <utils/JenkinsHash.h>
+#include <utils/LruCache.h>
+
+#include <android-base/macros.h>
+#include <androidfw/AssetManager.h>
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+inline android::hash_t hash_type(const ResourceName& name) {
+ std::hash<std::u16string> strHash;
+ android::hash_t hash = 0;
+ hash = android::JenkinsHashMix(hash, (uint32_t) strHash(name.package));
+ hash = android::JenkinsHashMix(hash, (uint32_t) name.type);
+ hash = android::JenkinsHashMix(hash, (uint32_t) strHash(name.entry));
+ return hash;
+}
+
+inline android::hash_t hash_type(const ResourceId& id) {
+ return android::hash_type(id.id);
+}
+
+class ISymbolSource;
+
+class SymbolTable {
+public:
+ struct Symbol {
+ Symbol() : Symbol(Maybe<ResourceId>{}) {
+ }
+
+ Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) {
+ }
+
+ Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr) :
+ Symbol(i, attr, false) {
+ }
+
+ Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr, bool pub) :
+ id(i), attribute(attr), isPublic(pub) {
+ }
+
+ Symbol(const Symbol&) = default;
+ Symbol(Symbol&&) = default;
+ Symbol& operator=(const Symbol&) = default;
+ Symbol& operator=(Symbol&&) = default;
+
+ Maybe<ResourceId> id;
+ std::shared_ptr<Attribute> attribute;
+ bool isPublic = false;
+ };
+
+ SymbolTable() : mCache(200), mIdCache(200) {
+ }
+
+ void appendSource(std::unique_ptr<ISymbolSource> source);
+ void prependSource(std::unique_ptr<ISymbolSource> source);
+
+ /**
+ * Never hold on to the result between calls to findByName or findById. The results
+ * are typically stored in a cache which may evict entries.
+ */
+ const Symbol* findByName(const ResourceName& name);
+ const Symbol* findById(ResourceId id);
+
+ /**
+ * Let's the ISymbolSource decide whether looking up by name or ID is faster, if both
+ * are available.
+ */
+ const Symbol* findByReference(const Reference& ref);
+
+private:
+ std::vector<std::unique_ptr<ISymbolSource>> mSources;
+
+ // We use shared_ptr because unique_ptr is not supported and
+ // we need automatic deletion.
+ android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache;
+ android::LruCache<ResourceId, std::shared_ptr<Symbol>> mIdCache;
+
+ DISALLOW_COPY_AND_ASSIGN(SymbolTable);
+};
+
+/**
+ * An interface that a symbol source implements in order to surface symbol information
+ * to the symbol table.
+ */
+class ISymbolSource {
+public:
+ virtual ~ISymbolSource() = default;
+
+ virtual std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) = 0;
+ virtual std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) = 0;
+
+ /**
+ * Default implementation tries the name if it exists, else the ID.
+ */
+ virtual std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) {
+ if (ref.name) {
+ return findByName(ref.name.value());
+ } else if (ref.id) {
+ return findById(ref.id.value());
+ }
+ return {};
+ }
+};
+
+/**
+ * Exposes the resources in a ResourceTable as symbols for SymbolTable.
+ * Instances of this class must outlive the encompassed ResourceTable.
+ * Lookups by ID are ignored.
+ */
+class ResourceTableSymbolSource : public ISymbolSource {
+public:
+ explicit ResourceTableSymbolSource(ResourceTable* table) : mTable(table) {
+ }
+
+ std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override;
+
+ std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override {
+ return {};
+ }
+
+private:
+ ResourceTable* mTable;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource);
+};
+
+class AssetManagerSymbolSource : public ISymbolSource {
+public:
+ AssetManagerSymbolSource() = default;
+
+ bool addAssetPath(const StringPiece& path);
+
+ std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override;
+ std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override;
+ std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) override;
+
+private:
+ android::AssetManager mAssets;
+
+ DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_PROCESS_SYMBOLTABLE_H */
diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp
new file mode 100644
index 000000000000..34f31be3d0db
--- /dev/null
+++ b/tools/aapt2/process/SymbolTable_test.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "process/SymbolTable.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+TEST(ResourceTableSymbolSourceTest, FindSymbols) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple(u"@android:id/foo", ResourceId(0x01020000))
+ .addSimple(u"@android:id/bar")
+ .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+ test::AttributeBuilder().build())
+ .build();
+
+ ResourceTableSymbolSource symbolSource(table.get());
+ EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie(u"@android:id/foo")));
+ EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie(u"@android:id/bar")));
+
+ std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName(
+ test::parseNameOrDie(u"@android:attr/foo"));
+ ASSERT_NE(nullptr, s);
+ EXPECT_NE(nullptr, s->attribute);
+}
+
+TEST(ResourceTableSymbolSourceTest, FindPrivateAttrSymbol) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addValue(u"@android:^attr-private/foo", ResourceId(0x01010000),
+ test::AttributeBuilder().build())
+ .build();
+
+ ResourceTableSymbolSource symbolSource(table.get());
+ std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName(
+ test::parseNameOrDie(u"@android:attr/foo"));
+ ASSERT_NE(nullptr, s);
+ EXPECT_NE(nullptr, s->attribute);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp
new file mode 100644
index 000000000000..99981c52e26f
--- /dev/null
+++ b/tools/aapt2/proto/ProtoHelpers.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "proto/ProtoHelpers.h"
+
+namespace aapt {
+
+void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool) {
+ BigBuffer buffer(1024);
+ StringPool::flattenUtf8(&buffer, pool);
+
+ std::string* data = outPbPool->mutable_data();
+ data->reserve(buffer.size());
+
+ size_t offset = 0;
+ for (const BigBuffer::Block& block : buffer) {
+ data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size);
+ offset += block.size;
+ }
+}
+
+void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource) {
+ StringPool::Ref ref = srcPool->makeRef(util::utf8ToUtf16(source.path));
+ outPbSource->set_path_idx(static_cast<uint32_t>(ref.getIndex()));
+ if (source.line) {
+ outPbSource->set_line_no(static_cast<uint32_t>(source.line.value()));
+ }
+}
+
+void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
+ Source* outSource) {
+ if (pbSource.has_path_idx()) {
+ outSource->path = util::getString8(srcPool, pbSource.path_idx()).toString();
+ }
+
+ if (pbSource.has_line_no()) {
+ outSource->line = static_cast<size_t>(pbSource.line_no());
+ }
+}
+
+pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state) {
+ switch (state) {
+ case SymbolState::kPrivate: return pb::SymbolStatus_Visibility_Private;
+ case SymbolState::kPublic: return pb::SymbolStatus_Visibility_Public;
+ default: break;
+ }
+ return pb::SymbolStatus_Visibility_Unknown;
+}
+
+SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility) {
+ switch (pbVisibility) {
+ case pb::SymbolStatus_Visibility_Private: return SymbolState::kPrivate;
+ case pb::SymbolStatus_Visibility_Public: return SymbolState::kPublic;
+ default: break;
+ }
+ return SymbolState::kUndefined;
+}
+
+void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig) {
+ android::ResTable_config flatConfig = config;
+ flatConfig.size = sizeof(flatConfig);
+ flatConfig.swapHtoD();
+ outPbConfig->set_data(&flatConfig, sizeof(flatConfig));
+}
+
+bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
+ ConfigDescription* outConfig) {
+ if (!pbConfig.has_data()) {
+ return false;
+ }
+
+ const android::ResTable_config* config;
+ if (pbConfig.data().size() > sizeof(*config)) {
+ return false;
+ }
+
+ config = reinterpret_cast<const android::ResTable_config*>(pbConfig.data().data());
+ outConfig->copyFromDtoH(*config);
+ return true;
+}
+
+pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type) {
+ switch (type) {
+ case Reference::Type::kResource: return pb::Reference_Type_Ref;
+ case Reference::Type::kAttribute: return pb::Reference_Type_Attr;
+ default: break;
+ }
+ return pb::Reference_Type_Ref;
+}
+
+Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType) {
+ switch (pbType) {
+ case pb::Reference_Type_Ref: return Reference::Type::kResource;
+ case pb::Reference_Type_Attr: return Reference::Type::kAttribute;
+ default: break;
+ }
+ return Reference::Type::kResource;
+}
+
+pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx) {
+ switch (pluralIdx) {
+ case Plural::Zero: return pb::Plural_Arity_Zero;
+ case Plural::One: return pb::Plural_Arity_One;
+ case Plural::Two: return pb::Plural_Arity_Two;
+ case Plural::Few: return pb::Plural_Arity_Few;
+ case Plural::Many: return pb::Plural_Arity_Many;
+ default: break;
+ }
+ return pb::Plural_Arity_Other;
+}
+
+size_t deserializePluralEnumFromPb(pb::Plural_Arity arity) {
+ switch (arity) {
+ case pb::Plural_Arity_Zero: return Plural::Zero;
+ case pb::Plural_Arity_One: return Plural::One;
+ case pb::Plural_Arity_Two: return Plural::Two;
+ case pb::Plural_Arity_Few: return Plural::Few;
+ case pb::Plural_Arity_Many: return Plural::Many;
+ default: break;
+ }
+ return Plural::Other;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h
new file mode 100644
index 000000000000..02e67f17c80c
--- /dev/null
+++ b/tools/aapt2/proto/ProtoHelpers.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PROTO_PROTOHELPERS_H
+#define AAPT_PROTO_PROTOHELPERS_H
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "Source.h"
+#include "StringPool.h"
+
+#include "proto/frameworks/base/tools/aapt2/Format.pb.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool);
+
+void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource);
+void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
+ Source* outSource);
+
+pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state);
+SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility);
+
+void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig);
+bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
+ ConfigDescription* outConfig);
+
+pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type);
+Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType);
+
+pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx);
+size_t deserializePluralEnumFromPb(pb::Plural_Arity arity);
+
+} // namespace aapt
+
+#endif /* AAPT_PROTO_PROTOHELPERS_H */
diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h
new file mode 100644
index 000000000000..6e224ab00af4
--- /dev/null
+++ b/tools/aapt2/proto/ProtoSerialize.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
+#define AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
+
+#include "Diagnostics.h"
+#include "ResourceTable.h"
+#include "Source.h"
+#include "proto/ProtoHelpers.h"
+
+#include <android-base/macros.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+
+namespace aapt {
+
+std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table);
+std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
+ const Source& source,
+ IDiagnostics* diag);
+
+std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file);
+std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile,
+ const Source& source,
+ IDiagnostics* diag);
+
+class CompiledFileOutputStream : public google::protobuf::io::CopyingOutputStream {
+public:
+ CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out,
+ pb::CompiledFile* pbFile);
+ bool Write(const void* data, int size) override;
+ bool Finish();
+
+private:
+ bool ensureFileWritten();
+
+ google::protobuf::io::CodedOutputStream mOut;
+ pb::CompiledFile* mPbFile;
+
+ DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
+};
+
+class CompiledFileInputStream {
+public:
+ CompiledFileInputStream(const void* data, size_t size);
+
+ const pb::CompiledFile* CompiledFile();
+
+ const void* data();
+ size_t size();
+
+private:
+ google::protobuf::io::CodedInputStream mIn;
+ std::unique_ptr<pb::CompiledFile> mPbFile;
+ const uint8_t* mData;
+ size_t mSize;
+
+ DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
new file mode 100644
index 000000000000..82e4fb0146ab
--- /dev/null
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+#include "proto/ProtoHelpers.h"
+#include "proto/ProtoSerialize.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+namespace {
+
+class ReferenceIdToNameVisitor : public ValueVisitor {
+public:
+ using ValueVisitor::visit;
+
+ ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping) :
+ mMapping(mapping) {
+ assert(mMapping);
+ }
+
+ void visit(Reference* reference) override {
+ if (!reference->id || !reference->id.value().isValid()) {
+ return;
+ }
+
+ ResourceId id = reference->id.value();
+ auto cacheIter = mMapping->find(id);
+ if (cacheIter != mMapping->end()) {
+ reference->name = cacheIter->second.toResourceName();
+ }
+ }
+
+private:
+ const std::map<ResourceId, ResourceNameRef>* mMapping;
+};
+
+class PackagePbDeserializer {
+public:
+ PackagePbDeserializer(const android::ResStringPool* valuePool,
+ const android::ResStringPool* sourcePool,
+ const android::ResStringPool* symbolPool,
+ const Source& source, IDiagnostics* diag) :
+ mValuePool(valuePool), mSourcePool(sourcePool), mSymbolPool(symbolPool),
+ mSource(source), mDiag(diag) {
+ }
+
+public:
+ bool deserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) {
+ Maybe<uint8_t> id;
+ if (pbPackage.has_package_id()) {
+ id = static_cast<uint8_t>(pbPackage.package_id());
+ }
+
+ std::map<ResourceId, ResourceNameRef> idIndex;
+
+ ResourceTablePackage* pkg = table->createPackage(
+ util::utf8ToUtf16(pbPackage.package_name()), id);
+ for (const pb::Type& pbType : pbPackage.types()) {
+ const ResourceType* resType = parseResourceType(util::utf8ToUtf16(pbType.name()));
+ if (!resType) {
+ mDiag->error(DiagMessage(mSource) << "unknown type '" << pbType.name() << "'");
+ return {};
+ }
+
+ ResourceTableType* type = pkg->findOrCreateType(*resType);
+
+ for (const pb::Entry& pbEntry : pbType.entries()) {
+ ResourceEntry* entry = type->findOrCreateEntry(util::utf8ToUtf16(pbEntry.name()));
+
+ // Deserialize the symbol status (public/private with source and comments).
+ if (pbEntry.has_symbol_status()) {
+ const pb::SymbolStatus& pbStatus = pbEntry.symbol_status();
+ if (pbStatus.has_source()) {
+ deserializeSourceFromPb(pbStatus.source(), *mSourcePool,
+ &entry->symbolStatus.source);
+ }
+
+ if (pbStatus.has_comment()) {
+ entry->symbolStatus.comment = util::utf8ToUtf16(pbStatus.comment());
+ }
+
+ SymbolState visibility = deserializeVisibilityFromPb(pbStatus.visibility());
+ entry->symbolStatus.state = visibility;
+
+ if (visibility == SymbolState::kPublic) {
+ // This is a public symbol, we must encode the ID now if there is one.
+ if (pbEntry.has_id()) {
+ entry->id = static_cast<uint16_t>(pbEntry.id());
+ }
+
+ if (type->symbolStatus.state != SymbolState::kPublic) {
+ // If the type has not been made public, do so now.
+ type->symbolStatus.state = SymbolState::kPublic;
+ if (pbType.has_id()) {
+ type->id = static_cast<uint8_t>(pbType.id());
+ }
+ }
+ } else if (visibility == SymbolState::kPrivate) {
+ if (type->symbolStatus.state == SymbolState::kUndefined) {
+ type->symbolStatus.state = SymbolState::kPrivate;
+ }
+ }
+ }
+
+ ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id());
+ if (resId.isValid()) {
+ idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name);
+ }
+
+ for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) {
+ const pb::ConfigDescription& pbConfig = pbConfigValue.config();
+
+ ConfigDescription config;
+ if (!deserializeConfigDescriptionFromPb(pbConfig, &config)) {
+ mDiag->error(DiagMessage(mSource) << "invalid configuration");
+ return {};
+ }
+
+ ResourceConfigValue* configValue = entry->findOrCreateValue(config,
+ pbConfig.product());
+ if (configValue->value) {
+ // Duplicate config.
+ mDiag->error(DiagMessage(mSource) << "duplicate configuration");
+ return {};
+ }
+
+ configValue->value = deserializeValueFromPb(pbConfigValue.value(),
+ config, &table->stringPool);
+ if (!configValue->value) {
+ return {};
+ }
+ }
+ }
+ }
+
+ ReferenceIdToNameVisitor visitor(&idIndex);
+ visitAllValuesInPackage(pkg, &visitor);
+ return true;
+ }
+
+private:
+ std::unique_ptr<Item> deserializeItemFromPb(const pb::Item& pbItem,
+ const ConfigDescription& config,
+ StringPool* pool) {
+ if (pbItem.has_ref()) {
+ const pb::Reference& pbRef = pbItem.ref();
+ std::unique_ptr<Reference> ref = util::make_unique<Reference>();
+ if (!deserializeReferenceFromPb(pbRef, ref.get())) {
+ return {};
+ }
+ return std::move(ref);
+
+ } else if (pbItem.has_prim()) {
+ const pb::Primitive& pbPrim = pbItem.prim();
+ android::Res_value prim = {};
+ prim.dataType = static_cast<uint8_t>(pbPrim.type());
+ prim.data = pbPrim.data();
+ return util::make_unique<BinaryPrimitive>(prim);
+
+ } else if (pbItem.has_id()) {
+ return util::make_unique<Id>();
+
+ } else if (pbItem.has_str()) {
+ const uint32_t idx = pbItem.str().idx();
+ StringPiece16 str = util::getString(*mValuePool, idx);
+
+ const android::ResStringPool_span* spans = mValuePool->styleAt(idx);
+ if (spans && spans->name.index != android::ResStringPool_span::END) {
+ StyleString styleStr = { str.toString() };
+ while (spans->name.index != android::ResStringPool_span::END) {
+ styleStr.spans.push_back(Span{
+ util::getString(*mValuePool, spans->name.index).toString(),
+ spans->firstChar,
+ spans->lastChar
+ });
+ spans++;
+ }
+ return util::make_unique<StyledString>(
+ pool->makeRef(styleStr, StringPool::Context{ 1, config }));
+ }
+ return util::make_unique<String>(
+ pool->makeRef(str, StringPool::Context{ 1, config }));
+
+ } else if (pbItem.has_raw_str()) {
+ const uint32_t idx = pbItem.raw_str().idx();
+ StringPiece16 str = util::getString(*mValuePool, idx);
+ return util::make_unique<RawString>(
+ pool->makeRef(str, StringPool::Context{ 1, config }));
+
+ } else if (pbItem.has_file()) {
+ const uint32_t idx = pbItem.file().path_idx();
+ StringPiece16 str = util::getString(*mValuePool, idx);
+ return util::make_unique<FileReference>(
+ pool->makeRef(str, StringPool::Context{ 0, config }));
+
+ } else {
+ mDiag->error(DiagMessage(mSource) << "unknown item");
+ }
+ return {};
+ }
+
+ std::unique_ptr<Value> deserializeValueFromPb(const pb::Value& pbValue,
+ const ConfigDescription& config,
+ StringPool* pool) {
+ const bool isWeak = pbValue.has_weak() ? pbValue.weak() : false;
+
+ std::unique_ptr<Value> value;
+ if (pbValue.has_item()) {
+ value = deserializeItemFromPb(pbValue.item(), config, pool);
+ if (!value) {
+ return {};
+ }
+
+ } else if (pbValue.has_compound_value()) {
+ const pb::CompoundValue pbCompoundValue = pbValue.compound_value();
+ if (pbCompoundValue.has_attr()) {
+ const pb::Attribute& pbAttr = pbCompoundValue.attr();
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
+ attr->typeMask = pbAttr.format_flags();
+ for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) {
+ Attribute::Symbol symbol;
+ deserializeItemCommon(pbSymbol, &symbol.symbol);
+ if (!deserializeReferenceFromPb(pbSymbol.name(), &symbol.symbol)) {
+ return {};
+ }
+ symbol.value = pbSymbol.value();
+ attr->symbols.push_back(std::move(symbol));
+ }
+ value = std::move(attr);
+
+ } else if (pbCompoundValue.has_style()) {
+ const pb::Style& pbStyle = pbCompoundValue.style();
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ if (pbStyle.has_parent()) {
+ style->parent = Reference();
+ if (!deserializeReferenceFromPb(pbStyle.parent(), &style->parent.value())) {
+ return {};
+ }
+
+ if (pbStyle.has_parent_source()) {
+ Source parentSource;
+ deserializeSourceFromPb(pbStyle.parent_source(), *mSourcePool,
+ &parentSource);
+ style->parent.value().setSource(std::move(parentSource));
+ }
+ }
+
+ for (const pb::Style_Entry& pbEntry : pbStyle.entries()) {
+ Style::Entry entry;
+ deserializeItemCommon(pbEntry, &entry.key);
+ if (!deserializeReferenceFromPb(pbEntry.key(), &entry.key)) {
+ return {};
+ }
+
+ entry.value = deserializeItemFromPb(pbEntry.item(), config, pool);
+ if (!entry.value) {
+ return {};
+ }
+
+ deserializeItemCommon(pbEntry, entry.value.get());
+ style->entries.push_back(std::move(entry));
+ }
+ value = std::move(style);
+
+ } else if (pbCompoundValue.has_styleable()) {
+ const pb::Styleable& pbStyleable = pbCompoundValue.styleable();
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ for (const pb::Styleable_Entry& pbEntry : pbStyleable.entries()) {
+ Reference attrRef;
+ deserializeItemCommon(pbEntry, &attrRef);
+ deserializeReferenceFromPb(pbEntry.attr(), &attrRef);
+ styleable->entries.push_back(std::move(attrRef));
+ }
+ value = std::move(styleable);
+
+ } else if (pbCompoundValue.has_array()) {
+ const pb::Array& pbArray = pbCompoundValue.array();
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+ for (const pb::Array_Entry& pbEntry : pbArray.entries()) {
+ std::unique_ptr<Item> item = deserializeItemFromPb(pbEntry.item(), config,
+ pool);
+ if (!item) {
+ return {};
+ }
+
+ deserializeItemCommon(pbEntry, item.get());
+ array->items.push_back(std::move(item));
+ }
+ value = std::move(array);
+
+ } else if (pbCompoundValue.has_plural()) {
+ const pb::Plural& pbPlural = pbCompoundValue.plural();
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ for (const pb::Plural_Entry& pbEntry : pbPlural.entries()) {
+ size_t pluralIdx = deserializePluralEnumFromPb(pbEntry.arity());
+ plural->values[pluralIdx] = deserializeItemFromPb(pbEntry.item(), config,
+ pool);
+ if (!plural->values[pluralIdx]) {
+ return {};
+ }
+
+ deserializeItemCommon(pbEntry, plural->values[pluralIdx].get());
+ }
+ value = std::move(plural);
+
+ } else {
+ mDiag->error(DiagMessage(mSource) << "unknown compound value");
+ return {};
+ }
+ } else {
+ mDiag->error(DiagMessage(mSource) << "unknown value");
+ return {};
+ }
+
+ assert(value && "forgot to set value");
+
+ value->setWeak(isWeak);
+ deserializeItemCommon(pbValue, value.get());
+ return value;
+ }
+
+ bool deserializeReferenceFromPb(const pb::Reference& pbRef, Reference* outRef) {
+ outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type());
+ outRef->privateReference = pbRef.private_();
+
+ if (!pbRef.has_id() && !pbRef.has_symbol_idx()) {
+ return false;
+ }
+
+ if (pbRef.has_id()) {
+ outRef->id = ResourceId(pbRef.id());
+ }
+
+ if (pbRef.has_symbol_idx()) {
+ StringPiece16 strSymbol = util::getString(*mSymbolPool, pbRef.symbol_idx());
+ ResourceNameRef nameRef;
+ if (!ResourceUtils::parseResourceName(strSymbol, &nameRef, nullptr)) {
+ mDiag->error(DiagMessage(mSource) << "invalid reference name '"
+ << strSymbol << "'");
+ return false;
+ }
+
+ outRef->name = nameRef.toResourceName();
+ }
+ return true;
+ }
+
+ template <typename T>
+ void deserializeItemCommon(const T& pbItem, Value* outValue) {
+ if (pbItem.has_source()) {
+ Source source;
+ deserializeSourceFromPb(pbItem.source(), *mSourcePool, &source);
+ outValue->setSource(std::move(source));
+ }
+
+ if (pbItem.has_comment()) {
+ outValue->setComment(util::utf8ToUtf16(pbItem.comment()));
+ }
+ }
+
+private:
+ const android::ResStringPool* mValuePool;
+ const android::ResStringPool* mSourcePool;
+ const android::ResStringPool* mSymbolPool;
+ const Source mSource;
+ IDiagnostics* mDiag;
+};
+
+} // namespace
+
+std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
+ const Source& source,
+ IDiagnostics* diag) {
+ // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
+ // causes errors when qualifying it with android::
+ using namespace android;
+
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+
+ if (!pbTable.has_string_pool()) {
+ diag->error(DiagMessage(source) << "no string pool found");
+ return {};
+ }
+
+ ResStringPool valuePool;
+ status_t result = valuePool.setTo(pbTable.string_pool().data().data(),
+ pbTable.string_pool().data().size());
+ if (result != NO_ERROR) {
+ diag->error(DiagMessage(source) << "invalid string pool");
+ return {};
+ }
+
+ ResStringPool sourcePool;
+ if (pbTable.has_source_pool()) {
+ result = sourcePool.setTo(pbTable.source_pool().data().data(),
+ pbTable.source_pool().data().size());
+ if (result != NO_ERROR) {
+ diag->error(DiagMessage(source) << "invalid source pool");
+ return {};
+ }
+ }
+
+ ResStringPool symbolPool;
+ if (pbTable.has_symbol_pool()) {
+ result = symbolPool.setTo(pbTable.symbol_pool().data().data(),
+ pbTable.symbol_pool().data().size());
+ if (result != NO_ERROR) {
+ diag->error(DiagMessage(source) << "invalid symbol pool");
+ return {};
+ }
+ }
+
+ PackagePbDeserializer packagePbDeserializer(&valuePool, &sourcePool, &symbolPool, source, diag);
+ for (const pb::Package& pbPackage : pbTable.packages()) {
+ if (!packagePbDeserializer.deserializeFromPb(pbPackage, table.get())) {
+ return {};
+ }
+ }
+ return table;
+}
+
+std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile,
+ const Source& source,
+ IDiagnostics* diag) {
+ std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>();
+
+ ResourceNameRef nameRef;
+
+ // Need to create an lvalue here so that nameRef can point to something real.
+ std::u16string utf16Name = util::utf8ToUtf16(pbFile.resource_name());
+ if (!ResourceUtils::parseResourceName(utf16Name, &nameRef)) {
+ diag->error(DiagMessage(source) << "invalid resource name in compiled file header: "
+ << pbFile.resource_name());
+ return {};
+ }
+ file->name = nameRef.toResourceName();
+ file->source.path = pbFile.source_path();
+ deserializeConfigDescriptionFromPb(pbFile.config(), &file->config);
+
+ for (const pb::CompiledFile_Symbol& pbSymbol : pbFile.exported_symbols()) {
+ // Need to create an lvalue here so that nameRef can point to something real.
+ utf16Name = util::utf8ToUtf16(pbSymbol.resource_name());
+ if (!ResourceUtils::parseResourceName(utf16Name, &nameRef)) {
+ diag->error(DiagMessage(source) << "invalid resource name for exported symbol in "
+ "compiled file header: "
+ << pbFile.resource_name());
+ return {};
+ }
+ file->exportedSymbols.push_back(
+ SourcedResourceName{ nameRef.toResourceName(), pbSymbol.line_no() });
+ }
+ return file;
+}
+
+CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) :
+ mIn(static_cast<const uint8_t*>(data), size), mPbFile(),
+ mData(static_cast<const uint8_t*>(data)), mSize(size) {
+}
+
+const pb::CompiledFile* CompiledFileInputStream::CompiledFile() {
+ if (!mPbFile) {
+ std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>();
+ uint64_t pbSize = 0u;
+ if (!mIn.ReadLittleEndian64(&pbSize)) {
+ return nullptr;
+ }
+ mIn.PushLimit(static_cast<int>(pbSize));
+ if (!pbFile->ParsePartialFromCodedStream(&mIn)) {
+ return nullptr;
+ }
+
+ const size_t padding = 4 - (pbSize & 0x03);
+ const size_t offset = sizeof(uint64_t) + pbSize + padding;
+ if (offset > mSize) {
+ return nullptr;
+ }
+
+ mData += offset;
+ mSize -= offset;
+ mPbFile = std::move(pbFile);
+ }
+ return mPbFile.get();
+}
+
+const void* CompiledFileInputStream::data() {
+ if (!mPbFile) {
+ if (!CompiledFile()) {
+ return nullptr;
+ }
+ }
+ return mData;
+}
+
+size_t CompiledFileInputStream::size() {
+ if (!mPbFile) {
+ if (!CompiledFile()) {
+ return 0;
+ }
+ }
+ return mSize;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
new file mode 100644
index 000000000000..5d1b72b0ebbd
--- /dev/null
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "StringPool.h"
+#include "ValueVisitor.h"
+#include "proto/ProtoHelpers.h"
+#include "proto/ProtoSerialize.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+
+namespace {
+
+class PbSerializerVisitor : public RawValueVisitor {
+public:
+ using RawValueVisitor::visit;
+
+ /**
+ * Constructor to use when expecting to serialize any value.
+ */
+ PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Value* outPbValue) :
+ mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(outPbValue),
+ mOutPbItem(nullptr) {
+ }
+
+ /**
+ * Constructor to use when expecting to serialize an Item.
+ */
+ PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Item* outPbItem) :
+ mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(nullptr),
+ mOutPbItem(outPbItem) {
+ }
+
+ void visit(Reference* ref) override {
+ serializeReferenceToPb(*ref, getPbItem()->mutable_ref());
+ }
+
+ void visit(String* str) override {
+ getPbItem()->mutable_str()->set_idx(str->value.getIndex());
+ }
+
+ void visit(StyledString* str) override {
+ getPbItem()->mutable_str()->set_idx(str->value.getIndex());
+ }
+
+ void visit(FileReference* file) override {
+ getPbItem()->mutable_file()->set_path_idx(file->path.getIndex());
+ }
+
+ void visit(Id* id) override {
+ getPbItem()->mutable_id();
+ }
+
+ void visit(RawString* rawStr) override {
+ getPbItem()->mutable_raw_str()->set_idx(rawStr->value.getIndex());
+ }
+
+ void visit(BinaryPrimitive* prim) override {
+ android::Res_value val = {};
+ prim->flatten(&val);
+
+ pb::Primitive* pbPrim = getPbItem()->mutable_prim();
+ pbPrim->set_type(val.dataType);
+ pbPrim->set_data(val.data);
+ }
+
+ void visitItem(Item* item) override {
+ assert(false && "unimplemented item");
+ }
+
+ void visit(Attribute* attr) override {
+ pb::Attribute* pbAttr = getPbCompoundValue()->mutable_attr();
+ pbAttr->set_format_flags(attr->typeMask);
+ pbAttr->set_min_int(attr->minInt);
+ pbAttr->set_max_int(attr->maxInt);
+
+ for (auto& symbol : attr->symbols) {
+ pb::Attribute_Symbol* pbSymbol = pbAttr->add_symbols();
+ serializeItemCommonToPb(symbol.symbol, pbSymbol);
+ serializeReferenceToPb(symbol.symbol, pbSymbol->mutable_name());
+ pbSymbol->set_value(symbol.value);
+ }
+ }
+
+ void visit(Style* style) override {
+ pb::Style* pbStyle = getPbCompoundValue()->mutable_style();
+ if (style->parent) {
+ serializeReferenceToPb(style->parent.value(), pbStyle->mutable_parent());
+ serializeSourceToPb(style->parent.value().getSource(),
+ mSourcePool,
+ pbStyle->mutable_parent_source());
+ }
+
+ for (Style::Entry& entry : style->entries) {
+ pb::Style_Entry* pbEntry = pbStyle->add_entries();
+ serializeReferenceToPb(entry.key, pbEntry->mutable_key());
+
+ pb::Item* pbItem = pbEntry->mutable_item();
+ serializeItemCommonToPb(entry.key, pbEntry);
+ PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbItem);
+ entry.value->accept(&subVisitor);
+ }
+ }
+
+ void visit(Styleable* styleable) override {
+ pb::Styleable* pbStyleable = getPbCompoundValue()->mutable_styleable();
+ for (Reference& entry : styleable->entries) {
+ pb::Styleable_Entry* pbEntry = pbStyleable->add_entries();
+ serializeItemCommonToPb(entry, pbEntry);
+ serializeReferenceToPb(entry, pbEntry->mutable_attr());
+ }
+ }
+
+ void visit(Array* array) override {
+ pb::Array* pbArray = getPbCompoundValue()->mutable_array();
+ for (auto& value : array->items) {
+ pb::Array_Entry* pbEntry = pbArray->add_entries();
+ serializeItemCommonToPb(*value, pbEntry);
+ PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbEntry->mutable_item());
+ value->accept(&subVisitor);
+ }
+ }
+
+ void visit(Plural* plural) override {
+ pb::Plural* pbPlural = getPbCompoundValue()->mutable_plural();
+ const size_t count = plural->values.size();
+ for (size_t i = 0; i < count; i++) {
+ if (!plural->values[i]) {
+ // No plural value set here.
+ continue;
+ }
+
+ pb::Plural_Entry* pbEntry = pbPlural->add_entries();
+ pbEntry->set_arity(serializePluralEnumToPb(i));
+ pb::Item* pbElement = pbEntry->mutable_item();
+ serializeItemCommonToPb(*plural->values[i], pbEntry);
+ PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbElement);
+ plural->values[i]->accept(&subVisitor);
+ }
+ }
+
+private:
+ pb::Item* getPbItem() {
+ if (mOutPbValue) {
+ return mOutPbValue->mutable_item();
+ }
+ return mOutPbItem;
+ }
+
+ pb::CompoundValue* getPbCompoundValue() {
+ assert(mOutPbValue);
+ return mOutPbValue->mutable_compound_value();
+ }
+
+ template <typename T>
+ void serializeItemCommonToPb(const Item& item, T* pbItem) {
+ serializeSourceToPb(item.getSource(), mSourcePool, pbItem->mutable_source());
+ if (!item.getComment().empty()) {
+ pbItem->set_comment(util::utf16ToUtf8(item.getComment()));
+ }
+ }
+
+ void serializeReferenceToPb(const Reference& ref, pb::Reference* pbRef) {
+ if (ref.id) {
+ pbRef->set_id(ref.id.value().id);
+ }
+
+ if (ref.name) {
+ StringPool::Ref symbolRef = mSymbolPool->makeRef(ref.name.value().toString());
+ pbRef->set_symbol_idx(static_cast<uint32_t>(symbolRef.getIndex()));
+ }
+
+ pbRef->set_private_(ref.privateReference);
+ pbRef->set_type(serializeReferenceTypeToPb(ref.referenceType));
+ }
+
+ StringPool* mSourcePool;
+ StringPool* mSymbolPool;
+ pb::Value* mOutPbValue;
+ pb::Item* mOutPbItem;
+};
+
+} // namespace
+
+std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table) {
+ // We must do this before writing the resources, since the string pool IDs may change.
+ table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ int diff = a.context.priority - b.context.priority;
+ if (diff < 0) return true;
+ if (diff > 0) return false;
+ diff = a.context.config.compare(b.context.config);
+ if (diff < 0) return true;
+ if (diff > 0) return false;
+ return a.value < b.value;
+ });
+ table->stringPool.prune();
+
+ std::unique_ptr<pb::ResourceTable> pbTable = util::make_unique<pb::ResourceTable>();
+ serializeStringPoolToPb(table->stringPool, pbTable->mutable_string_pool());
+
+ StringPool sourcePool, symbolPool;
+
+ for (auto& package : table->packages) {
+ pb::Package* pbPackage = pbTable->add_packages();
+ if (package->id) {
+ pbPackage->set_package_id(package->id.value());
+ }
+ pbPackage->set_package_name(util::utf16ToUtf8(package->name));
+
+ for (auto& type : package->types) {
+ pb::Type* pbType = pbPackage->add_types();
+ if (type->id) {
+ pbType->set_id(type->id.value());
+ }
+ pbType->set_name(util::utf16ToUtf8(toString(type->type)));
+
+ for (auto& entry : type->entries) {
+ pb::Entry* pbEntry = pbType->add_entries();
+ if (entry->id) {
+ pbEntry->set_id(entry->id.value());
+ }
+ pbEntry->set_name(util::utf16ToUtf8(entry->name));
+
+ // Write the SymbolStatus struct.
+ pb::SymbolStatus* pbStatus = pbEntry->mutable_symbol_status();
+ pbStatus->set_visibility(serializeVisibilityToPb(entry->symbolStatus.state));
+ serializeSourceToPb(entry->symbolStatus.source, &sourcePool,
+ pbStatus->mutable_source());
+ pbStatus->set_comment(util::utf16ToUtf8(entry->symbolStatus.comment));
+
+ for (auto& configValue : entry->values) {
+ pb::ConfigValue* pbConfigValue = pbEntry->add_config_values();
+ serializeConfig(configValue->config, pbConfigValue->mutable_config());
+ if (!configValue->product.empty()) {
+ pbConfigValue->mutable_config()->set_product(configValue->product);
+ }
+
+ pb::Value* pbValue = pbConfigValue->mutable_value();
+ serializeSourceToPb(configValue->value->getSource(), &sourcePool,
+ pbValue->mutable_source());
+ if (!configValue->value->getComment().empty()) {
+ pbValue->set_comment(util::utf16ToUtf8(configValue->value->getComment()));
+ }
+
+ if (configValue->value->isWeak()) {
+ pbValue->set_weak(true);
+ }
+
+ PbSerializerVisitor visitor(&sourcePool, &symbolPool, pbValue);
+ configValue->value->accept(&visitor);
+ }
+ }
+ }
+ }
+
+ serializeStringPoolToPb(sourcePool, pbTable->mutable_source_pool());
+ serializeStringPoolToPb(symbolPool, pbTable->mutable_symbol_pool());
+ return pbTable;
+}
+
+std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file) {
+ std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>();
+ pbFile->set_resource_name(util::utf16ToUtf8(file.name.toString()));
+ pbFile->set_source_path(file.source.path);
+ serializeConfig(file.config, pbFile->mutable_config());
+
+ for (const SourcedResourceName& exported : file.exportedSymbols) {
+ pb::CompiledFile_Symbol* pbSymbol = pbFile->add_exported_symbols();
+ pbSymbol->set_resource_name(util::utf16ToUtf8(exported.name.toString()));
+ pbSymbol->set_line_no(exported.line);
+ }
+ return pbFile;
+}
+
+CompiledFileOutputStream::CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out,
+ pb::CompiledFile* pbFile) :
+ mOut(out), mPbFile(pbFile) {
+}
+
+bool CompiledFileOutputStream::ensureFileWritten() {
+ if (mPbFile) {
+ const uint64_t pbSize = mPbFile->ByteSize();
+ mOut.WriteLittleEndian64(pbSize);
+ mPbFile->SerializeWithCachedSizes(&mOut);
+ const size_t padding = 4 - (pbSize & 0x03);
+ if (padding > 0) {
+ uint32_t zero = 0u;
+ mOut.WriteRaw(&zero, padding);
+ }
+ mPbFile = nullptr;
+ }
+ return !mOut.HadError();
+}
+
+bool CompiledFileOutputStream::Write(const void* data, int size) {
+ if (!ensureFileWritten()) {
+ return false;
+ }
+ mOut.WriteRaw(data, size);
+ return !mOut.HadError();
+}
+
+bool CompiledFileOutputStream::Finish() {
+ return ensureFileWritten();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
new file mode 100644
index 000000000000..dd995d858d77
--- /dev/null
+++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "proto/ProtoSerialize.h"
+#include "test/Builders.h"
+#include "test/Common.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(TableProtoSerializer, SerializeSinglePackage) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.a", 0x7f)
+ .addFileReference(u"@com.app.a:layout/main", ResourceId(0x7f020000),
+ u"res/layout/main.xml")
+ .addReference(u"@com.app.a:layout/other", ResourceId(0x7f020001),
+ u"@com.app.a:layout/main")
+ .addString(u"@com.app.a:string/text", {}, u"hi")
+ .addValue(u"@com.app.a:id/foo", {}, util::make_unique<Id>())
+ .build();
+
+ Symbol publicSymbol;
+ publicSymbol.state = SymbolState::kPublic;
+ ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@com.app.a:layout/main"),
+ ResourceId(0x7f020000),
+ publicSymbol, context->getDiagnostics()));
+
+ Id* id = test::getValue<Id>(table.get(), u"@com.app.a:id/foo");
+ ASSERT_NE(nullptr, id);
+
+ // Make a plural.
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ plural->values[Plural::One] = util::make_unique<String>(table->stringPool.makeRef(u"one"));
+ ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:plurals/hey"),
+ ConfigDescription{}, std::string(), std::move(plural),
+ context->getDiagnostics()));
+
+ // Make a resource with different products.
+ ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"),
+ test::parseConfigOrDie("land"), std::string(),
+ test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 123u),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"),
+ test::parseConfigOrDie("land"), std::string("tablet"),
+ test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 321u),
+ context->getDiagnostics()));
+
+ // Make a reference with both resource name and resource ID.
+ // The reference should point to a resource outside of this table to test that both
+ // name and id get serialized.
+ Reference expectedRef;
+ expectedRef.name = test::parseNameOrDie(u"@android:layout/main");
+ expectedRef.id = ResourceId(0x01020000);
+ ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:layout/abc"),
+ ConfigDescription::defaultConfig(), std::string(),
+ util::make_unique<Reference>(expectedRef),
+ context->getDiagnostics()));
+
+ std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table.get());
+ ASSERT_NE(nullptr, pbTable);
+
+ std::unique_ptr<ResourceTable> newTable = deserializeTableFromPb(*pbTable,
+ Source{ "test" },
+ context->getDiagnostics());
+ ASSERT_NE(nullptr, newTable);
+
+ Id* newId = test::getValue<Id>(newTable.get(), u"@com.app.a:id/foo");
+ ASSERT_NE(nullptr, newId);
+ EXPECT_EQ(id->isWeak(), newId->isWeak());
+
+ Maybe<ResourceTable::SearchResult> result = newTable->findResource(
+ test::parseNameOrDie(u"@com.app.a:layout/main"));
+ AAPT_ASSERT_TRUE(result);
+ EXPECT_EQ(SymbolState::kPublic, result.value().type->symbolStatus.state);
+ EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
+
+ // Find the product-dependent values
+ BinaryPrimitive* prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
+ newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), "");
+ ASSERT_NE(nullptr, prim);
+ EXPECT_EQ(123u, prim->value.data);
+
+ prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
+ newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), "tablet");
+ ASSERT_NE(nullptr, prim);
+ EXPECT_EQ(321u, prim->value.data);
+
+ Reference* actualRef = test::getValue<Reference>(newTable.get(), u"@com.app.a:layout/abc");
+ ASSERT_NE(nullptr, actualRef);
+ AAPT_ASSERT_TRUE(actualRef->name);
+ AAPT_ASSERT_TRUE(actualRef->id);
+ EXPECT_EQ(expectedRef.name.value(), actualRef->name.value());
+ EXPECT_EQ(expectedRef.id.value(), actualRef->id.value());
+}
+
+TEST(TableProtoSerializer, SerializeFileHeader) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ ResourceFile f;
+ f.config = test::parseConfigOrDie("hdpi-v9");
+ f.name = test::parseNameOrDie(u"@com.app.a:layout/main");
+ f.source.path = "res/layout-hdpi-v9/main.xml";
+ f.exportedSymbols.push_back(SourcedResourceName{ test::parseNameOrDie(u"@+id/unchecked"), 23u });
+
+ const std::string expectedData = "1234";
+
+ std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f);
+
+ std::string outputStr;
+ {
+ google::protobuf::io::StringOutputStream outStream(&outputStr);
+ CompiledFileOutputStream outFileStream(&outStream, pbFile.get());
+
+ ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size()));
+ ASSERT_TRUE(outFileStream.Finish());
+ }
+
+ CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
+ const pb::CompiledFile* newPbFile = inFileStream.CompiledFile();
+ ASSERT_NE(nullptr, newPbFile);
+
+ std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(*newPbFile, Source{ "test" },
+ context->getDiagnostics());
+ ASSERT_NE(nullptr, file);
+
+ std::string actualData((const char*)inFileStream.data(), inFileStream.size());
+ EXPECT_EQ(expectedData, actualData);
+ EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(inFileStream.data()) & 0x03);
+
+ ASSERT_EQ(1u, file->exportedSymbols.size());
+ EXPECT_EQ(test::parseNameOrDie(u"@+id/unchecked"), file->exportedSymbols[0].name);
+}
+
+TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) {
+ ResourceFile f;
+ std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f);
+
+ const std::string expectedData = "1234";
+
+ std::string outputStr;
+ {
+ google::protobuf::io::StringOutputStream outStream(&outputStr);
+ CompiledFileOutputStream outFileStream(&outStream, pbFile.get());
+
+ ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size()));
+ ASSERT_TRUE(outFileStream.Finish());
+ }
+
+ outputStr[0] = 0xff;
+
+ CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
+ EXPECT_EQ(nullptr, inFileStream.CompiledFile());
+ EXPECT_EQ(nullptr, inFileStream.data());
+ EXPECT_EQ(0u, inFileStream.size());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
new file mode 100644
index 000000000000..4bfdb1205e19
--- /dev/null
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "split/TableSplitter.h"
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+namespace aapt {
+
+using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
+using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
+
+static ConfigDescription copyWithoutDensity(const ConfigDescription& config) {
+ ConfigDescription withoutDensity = config;
+ withoutDensity.density = 0;
+ return withoutDensity;
+}
+
+/**
+ * Selects values that match exactly the constraints given.
+ */
+class SplitValueSelector {
+public:
+ SplitValueSelector(const SplitConstraints& constraints) {
+ for (const ConfigDescription& config : constraints.configs) {
+ if (config.density == 0) {
+ mDensityIndependentConfigs.insert(config);
+ } else {
+ mDensityDependentConfigToDensityMap[copyWithoutDensity(config)] = config.density;
+ }
+ }
+ }
+
+ std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups,
+ ConfigClaimedMap* claimedValues) {
+ std::vector<ResourceConfigValue*> selected;
+
+ // Select the regular values.
+ for (auto& entry : *claimedValues) {
+ // Check if the entry has a density.
+ ResourceConfigValue* configValue = entry.first;
+ if (configValue->config.density == 0 && !entry.second) {
+ // This is still available.
+ if (mDensityIndependentConfigs.find(configValue->config) !=
+ mDensityIndependentConfigs.end()) {
+ selected.push_back(configValue);
+
+ // Mark the entry as taken.
+ entry.second = true;
+ }
+ }
+ }
+
+ // Now examine the densities
+ for (auto& entry : densityGroups) {
+ // We do not care if the value is claimed, since density values can be
+ // in multiple splits.
+ const ConfigDescription& config = entry.first;
+ const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
+
+ auto densityValueIter = mDensityDependentConfigToDensityMap.find(config);
+ if (densityValueIter != mDensityDependentConfigToDensityMap.end()) {
+ // Select the best one!
+ ConfigDescription targetDensity = config;
+ targetDensity.density = densityValueIter->second;
+
+ ResourceConfigValue* bestValue = nullptr;
+ for (ResourceConfigValue* thisValue : relatedValues) {
+ if (!bestValue ||
+ thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
+ bestValue = thisValue;
+ }
+
+ // When we select one of these, they are all claimed such that the base
+ // doesn't include any anymore.
+ (*claimedValues)[thisValue] = true;
+ }
+ assert(bestValue);
+ selected.push_back(bestValue);
+ }
+ }
+ return selected;
+ }
+
+private:
+ std::set<ConfigDescription> mDensityIndependentConfigs;
+ std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap;
+};
+
+/**
+ * Marking non-preferred densities as claimed will make sure the base doesn't include them,
+ * leaving only the preferred density behind.
+ */
+static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity,
+ const ConfigDensityGroups& densityGroups,
+ ConfigClaimedMap* configClaimedMap) {
+ for (auto& entry : densityGroups) {
+ const ConfigDescription& config = entry.first;
+ const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
+
+ ConfigDescription targetDensity = config;
+ targetDensity.density = preferredDensity;
+ ResourceConfigValue* bestValue = nullptr;
+ for (ResourceConfigValue* thisValue : relatedValues) {
+ if (!bestValue) {
+ bestValue = thisValue;
+ } else if (thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
+ // Claim the previous value so that it is not included in the base.
+ (*configClaimedMap)[bestValue] = true;
+ bestValue = thisValue;
+ } else {
+ // Claim this value so that it is not included in the base.
+ (*configClaimedMap)[thisValue] = true;
+ }
+ }
+ assert(bestValue);
+ }
+}
+
+bool TableSplitter::verifySplitConstraints(IAaptContext* context) {
+ bool error = false;
+ for (size_t i = 0; i < mSplitConstraints.size(); i++) {
+ for (size_t j = i + 1; j < mSplitConstraints.size(); j++) {
+ for (const ConfigDescription& config : mSplitConstraints[i].configs) {
+ if (mSplitConstraints[j].configs.find(config) !=
+ mSplitConstraints[j].configs.end()) {
+ context->getDiagnostics()->error(DiagMessage() << "config '" << config
+ << "' appears in multiple splits, "
+ << "target split ambiguous");
+ error = true;
+ }
+ }
+ }
+ }
+ return !error;
+}
+
+void TableSplitter::splitTable(ResourceTable* originalTable) {
+ const size_t splitCount = mSplitConstraints.size();
+ for (auto& pkg : originalTable->packages) {
+ // Initialize all packages for splits.
+ for (size_t idx = 0; idx < splitCount; idx++) {
+ ResourceTable* splitTable = mSplits[idx].get();
+ splitTable->createPackage(pkg->name, pkg->id);
+ }
+
+ for (auto& type : pkg->types) {
+ if (type->type == ResourceType::kMipmap) {
+ // Always keep mipmaps.
+ continue;
+ }
+
+ for (auto& entry : type->entries) {
+ if (mConfigFilter) {
+ // First eliminate any resource that we definitely don't want.
+ for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+ if (!mConfigFilter->match(configValue->config)) {
+ // null out the entry. We will clean up and remove nulls at the end
+ // for performance reasons.
+ configValue.reset();
+ }
+ }
+ }
+
+ // Organize the values into two separate buckets. Those that are density-dependent
+ // and those that are density-independent.
+ // One density technically matches all density, it's just that some densities
+ // match better. So we need to be aware of the full set of densities to make this
+ // decision.
+ ConfigDensityGroups densityGroups;
+ ConfigClaimedMap configClaimedMap;
+ for (const std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+ if (configValue) {
+ configClaimedMap[configValue.get()] = false;
+
+ if (configValue->config.density != 0) {
+ // Create a bucket for this density-dependent config.
+ densityGroups[copyWithoutDensity(configValue->config)]
+ .push_back(configValue.get());
+ }
+ }
+ }
+
+ // First we check all the splits. If it doesn't match one of the splits, we
+ // leave it in the base.
+ for (size_t idx = 0; idx < splitCount; idx++) {
+ const SplitConstraints& splitConstraint = mSplitConstraints[idx];
+ ResourceTable* splitTable = mSplits[idx].get();
+
+ // Select the values we want from this entry for this split.
+ SplitValueSelector selector(splitConstraint);
+ std::vector<ResourceConfigValue*> selectedValues =
+ selector.selectValues(densityGroups, &configClaimedMap);
+
+ // No need to do any work if we selected nothing.
+ if (!selectedValues.empty()) {
+ // Create the same resource structure in the split. We do this lazily
+ // because we might not have actual values for each type/entry.
+ ResourceTablePackage* splitPkg = splitTable->findPackage(pkg->name);
+ ResourceTableType* splitType = splitPkg->findOrCreateType(type->type);
+ if (!splitType->id) {
+ splitType->id = type->id;
+ splitType->symbolStatus = type->symbolStatus;
+ }
+
+ ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name);
+ if (!splitEntry->id) {
+ splitEntry->id = entry->id;
+ splitEntry->symbolStatus = entry->symbolStatus;
+ }
+
+ // Copy the selected values into the new Split Entry.
+ for (ResourceConfigValue* configValue : selectedValues) {
+ ResourceConfigValue* newConfigValue = splitEntry->findOrCreateValue(
+ configValue->config, configValue->product);
+ newConfigValue->value = std::unique_ptr<Value>(
+ configValue->value->clone(&splitTable->stringPool));
+ }
+ }
+ }
+
+ if (mPreferredDensity) {
+ markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(),
+ densityGroups,
+ &configClaimedMap);
+ }
+
+ // All splits are handled, now check to see what wasn't claimed and remove
+ // whatever exists in other splits.
+ for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+ if (configValue && configClaimedMap[configValue.get()]) {
+ // Claimed, remove from base.
+ configValue.reset();
+ }
+ }
+
+ // Now erase all nullptrs.
+ entry->values.erase(
+ std::remove(entry->values.begin(), entry->values.end(), nullptr),
+ entry->values.end());
+ }
+ }
+ }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h
new file mode 100644
index 000000000000..15e0764c4259
--- /dev/null
+++ b/tools/aapt2/split/TableSplitter.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_SPLIT_TABLESPLITTER_H
+#define AAPT_SPLIT_TABLESPLITTER_H
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "filter/ConfigFilter.h"
+#include "process/IResourceTableConsumer.h"
+
+#include <android-base/macros.h>
+#include <set>
+#include <vector>
+
+namespace aapt {
+
+struct SplitConstraints {
+ std::set<ConfigDescription> configs;
+};
+
+struct TableSplitterOptions {
+ /**
+ * The preferred density to keep in the table, stripping out all others.
+ */
+ Maybe<uint16_t> preferredDensity;
+
+ /**
+ * Configuration filter that determines which resource configuration values end up in
+ * the final table.
+ */
+ IConfigFilter* configFilter = nullptr;
+};
+
+class TableSplitter {
+public:
+ TableSplitter(const std::vector<SplitConstraints>& splits,
+ const TableSplitterOptions& options) :
+ mSplitConstraints(splits), mPreferredDensity(options.preferredDensity),
+ mConfigFilter(options.configFilter) {
+ for (size_t i = 0; i < mSplitConstraints.size(); i++) {
+ mSplits.push_back(util::make_unique<ResourceTable>());
+ }
+ }
+
+ bool verifySplitConstraints(IAaptContext* context);
+
+ void splitTable(ResourceTable* originalTable);
+
+ const std::vector<std::unique_ptr<ResourceTable>>& getSplits() {
+ return mSplits;
+ }
+
+private:
+ std::vector<SplitConstraints> mSplitConstraints;
+ std::vector<std::unique_ptr<ResourceTable>> mSplits;
+ Maybe<uint16_t> mPreferredDensity;
+ IConfigFilter* mConfigFilter;
+
+ DISALLOW_COPY_AND_ASSIGN(TableSplitter);
+};
+
+}
+
+#endif /* AAPT_SPLIT_TABLESPLITTER_H */
diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp
new file mode 100644
index 000000000000..74ca32e04a30
--- /dev/null
+++ b/tools/aapt2/split/TableSplitter_test.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "split/TableSplitter.h"
+#include "test/Builders.h"
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(TableSplitterTest, NoSplitPreferredDensity) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addFileReference(u"@android:drawable/icon", u"res/drawable-mdpi/icon.png",
+ test::parseConfigOrDie("mdpi"))
+ .addFileReference(u"@android:drawable/icon", u"res/drawable-hdpi/icon.png",
+ test::parseConfigOrDie("hdpi"))
+ .addFileReference(u"@android:drawable/icon", u"res/drawable-xhdpi/icon.png",
+ test::parseConfigOrDie("xhdpi"))
+ .addFileReference(u"@android:drawable/icon", u"res/drawable-xxhdpi/icon.png",
+ test::parseConfigOrDie("xxhdpi"))
+ .addSimple(u"@android:string/one", {})
+ .build();
+
+ TableSplitterOptions options;
+ options.preferredDensity = ConfigDescription::DENSITY_XHIGH;
+ TableSplitter splitter({}, options);
+ splitter.splitTable(table.get());
+
+ EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
+ u"@android:drawable/icon",
+ test::parseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
+ u"@android:drawable/icon",
+ test::parseConfigOrDie("hdpi")));
+ EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(),
+ u"@android:drawable/icon",
+ test::parseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
+ u"@android:drawable/icon",
+ test::parseConfigOrDie("xxhdpi")));
+ EXPECT_NE(nullptr, test::getValue<Id>(table.get(), u"@android:string/one"));
+}
+
+TEST(TableSplitterTest, SplitTableByConfigAndDensity) {
+ ResourceTable table;
+
+ const ResourceName foo = test::parseNameOrDie(u"@android:string/foo");
+ ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-hdpi"), {},
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
+ ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xhdpi"), {},
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
+ ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xxhdpi"), {},
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
+
+ std::vector<SplitConstraints> constraints;
+ constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-mdpi") } });
+ constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-xhdpi") } });
+
+ TableSplitter splitter(constraints, TableSplitterOptions{});
+ splitter.splitTable(&table);
+
+ ASSERT_EQ(2u, splitter.getSplits().size());
+
+ ResourceTable* splitOne = splitter.getSplits()[0].get();
+ ResourceTable* splitTwo = splitter.getSplits()[1].get();
+
+ // Since a split was defined, all densities should be gone from base.
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
+ test::parseConfigOrDie("land-hdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
+ test::parseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
+ test::parseConfigOrDie("land-xxhdpi")));
+
+ EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
+ test::parseConfigOrDie("land-hdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
+ test::parseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
+ test::parseConfigOrDie("land-xxhdpi")));
+
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
+ test::parseConfigOrDie("land-hdpi")));
+ EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
+ test::parseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
+ test::parseConfigOrDie("land-xxhdpi")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
new file mode 100644
index 000000000000..8eb4bc88168d
--- /dev/null
+++ b/tools/aapt2/test/Builders.h
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TEST_BUILDERS_H
+#define AAPT_TEST_BUILDERS_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "test/Common.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+
+#include <memory>
+
+namespace aapt {
+namespace test {
+
+class ResourceTableBuilder {
+private:
+ DummyDiagnosticsImpl mDiagnostics;
+ std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>();
+
+public:
+ ResourceTableBuilder() = default;
+
+ StringPool* getStringPool() {
+ return &mTable->stringPool;
+ }
+
+ ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) {
+ ResourceTablePackage* package = mTable->createPackage(packageName, id);
+ assert(package);
+ return *this;
+ }
+
+ ResourceTableBuilder& addSimple(const StringPiece16& name, const ResourceId id = {}) {
+ return addValue(name, id, util::make_unique<Id>());
+ }
+
+ ResourceTableBuilder& addReference(const StringPiece16& name, const StringPiece16& ref) {
+ return addReference(name, {}, ref);
+ }
+
+ ResourceTableBuilder& addReference(const StringPiece16& name, const ResourceId id,
+ const StringPiece16& ref) {
+ return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref)));
+ }
+
+ ResourceTableBuilder& addString(const StringPiece16& name, const StringPiece16& str) {
+ return addString(name, {}, str);
+ }
+
+ ResourceTableBuilder& addString(const StringPiece16& name, const ResourceId id,
+ const StringPiece16& str) {
+ return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str)));
+ }
+
+ ResourceTableBuilder& addString(const StringPiece16& name, const ResourceId id,
+ const ConfigDescription& config, const StringPiece16& str) {
+ return addValue(name, id, config,
+ util::make_unique<String>(mTable->stringPool.makeRef(str)));
+ }
+
+ ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path) {
+ return addFileReference(name, {}, path);
+ }
+
+ ResourceTableBuilder& addFileReference(const StringPiece16& name, const ResourceId id,
+ const StringPiece16& path) {
+ return addValue(name, id,
+ util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
+ }
+
+ ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path,
+ const ConfigDescription& config) {
+ return addValue(name, {}, config,
+ util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
+ }
+
+ ResourceTableBuilder& addValue(const StringPiece16& name,
+ std::unique_ptr<Value> value) {
+ return addValue(name, {}, std::move(value));
+ }
+
+ ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
+ std::unique_ptr<Value> value) {
+ return addValue(name, id, {}, std::move(value));
+ }
+
+ ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
+ const ConfigDescription& config,
+ std::unique_ptr<Value> value) {
+ ResourceName resName = parseNameOrDie(name);
+ bool result = mTable->addResourceAllowMangled(resName, id, config, std::string(),
+ std::move(value), &mDiagnostics);
+ assert(result);
+ return *this;
+ }
+
+ ResourceTableBuilder& setSymbolState(const StringPiece16& name, ResourceId id,
+ SymbolState state) {
+ ResourceName resName = parseNameOrDie(name);
+ Symbol symbol;
+ symbol.state = state;
+ bool result = mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics);
+ assert(result);
+ return *this;
+ }
+
+ std::unique_ptr<ResourceTable> build() {
+ return std::move(mTable);
+ }
+};
+
+inline std::unique_ptr<Reference> buildReference(const StringPiece16& ref,
+ Maybe<ResourceId> id = {}) {
+ std::unique_ptr<Reference> reference = util::make_unique<Reference>(parseNameOrDie(ref));
+ reference->id = id;
+ return reference;
+}
+
+inline std::unique_ptr<BinaryPrimitive> buildPrimitive(uint8_t type, uint32_t data) {
+ android::Res_value value = {};
+ value.size = sizeof(value);
+ value.dataType = type;
+ value.data = data;
+ return util::make_unique<BinaryPrimitive>(value);
+}
+
+template <typename T>
+class ValueBuilder {
+private:
+ std::unique_ptr<Value> mValue;
+
+public:
+ template <typename... Args>
+ ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) {
+ }
+
+ template <typename... Args>
+ ValueBuilder& setSource(Args&&... args) {
+ mValue->setSource(Source{ std::forward<Args>(args)... });
+ return *this;
+ }
+
+ ValueBuilder& setComment(const StringPiece16& str) {
+ mValue->setComment(str);
+ return *this;
+ }
+
+ std::unique_ptr<Value> build() {
+ return std::move(mValue);
+ }
+};
+
+class AttributeBuilder {
+private:
+ std::unique_ptr<Attribute> mAttr;
+
+public:
+ AttributeBuilder(bool weak = false) : mAttr(util::make_unique<Attribute>(weak)) {
+ mAttr->typeMask = android::ResTable_map::TYPE_ANY;
+ }
+
+ AttributeBuilder& setTypeMask(uint32_t typeMask) {
+ mAttr->typeMask = typeMask;
+ return *this;
+ }
+
+ AttributeBuilder& addItem(const StringPiece16& name, uint32_t value) {
+ mAttr->symbols.push_back(Attribute::Symbol{
+ Reference(ResourceName{ {}, ResourceType::kId, name.toString()}),
+ value});
+ return *this;
+ }
+
+ std::unique_ptr<Attribute> build() {
+ return std::move(mAttr);
+ }
+};
+
+class StyleBuilder {
+private:
+ std::unique_ptr<Style> mStyle = util::make_unique<Style>();
+
+public:
+ StyleBuilder& setParent(const StringPiece16& str) {
+ mStyle->parent = Reference(parseNameOrDie(str));
+ return *this;
+ }
+
+ StyleBuilder& addItem(const StringPiece16& str, std::unique_ptr<Item> value) {
+ mStyle->entries.push_back(Style::Entry{ Reference(parseNameOrDie(str)), std::move(value) });
+ return *this;
+ }
+
+ StyleBuilder& addItem(const StringPiece16& str, ResourceId id, std::unique_ptr<Item> value) {
+ addItem(str, std::move(value));
+ mStyle->entries.back().key.id = id;
+ return *this;
+ }
+
+ std::unique_ptr<Style> build() {
+ return std::move(mStyle);
+ }
+};
+
+class StyleableBuilder {
+private:
+ std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>();
+
+public:
+ StyleableBuilder& addItem(const StringPiece16& str, Maybe<ResourceId> id = {}) {
+ mStyleable->entries.push_back(Reference(parseNameOrDie(str)));
+ mStyleable->entries.back().id = id;
+ return *this;
+ }
+
+ std::unique_ptr<Styleable> build() {
+ return std::move(mStyleable);
+ }
+};
+
+inline std::unique_ptr<xml::XmlResource> buildXmlDom(const StringPiece& str) {
+ std::stringstream in;
+ in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
+ StdErrDiagnostics diag;
+ std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, Source("test.xml"));
+ assert(doc);
+ return doc;
+}
+
+inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(IAaptContext* context,
+ const StringPiece& str) {
+ std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str);
+ doc->file.name.package = context->getCompilationPackage();
+ return doc;
+}
+
+} // namespace test
+} // namespace aapt
+
+#endif /* AAPT_TEST_BUILDERS_H */
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
new file mode 100644
index 000000000000..faccd47783ff
--- /dev/null
+++ b/tools/aapt2/test/Common.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TEST_COMMON_H
+#define AAPT_TEST_COMMON_H
+
+#include "ConfigDescription.h"
+#include "Debug.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+#include "io/File.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/StringPiece.h"
+
+#include <gtest/gtest.h>
+#include <iostream>
+
+//
+// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to fail to compile.
+//
+#define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v))
+#define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v))
+#define AAPT_EXPECT_TRUE(v) EXPECT_TRUE(bool(v))
+#define AAPT_EXPECT_FALSE(v) EXPECT_FALSE(bool(v))
+
+namespace aapt {
+namespace test {
+
+struct DummyDiagnosticsImpl : public IDiagnostics {
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ switch (level) {
+ case Level::Note:
+ return;
+
+ case Level::Warn:
+ std::cerr << actualMsg.source << ": warn: " << actualMsg.message << "." << std::endl;
+ break;
+
+ case Level::Error:
+ std::cerr << actualMsg.source << ": error: " << actualMsg.message << "." << std::endl;
+ break;
+ }
+ }
+};
+
+inline IDiagnostics* getDiagnostics() {
+ static DummyDiagnosticsImpl diag;
+ return &diag;
+}
+
+inline ResourceName parseNameOrDie(const StringPiece16& str) {
+ ResourceNameRef ref;
+ bool result = ResourceUtils::tryParseReference(str, &ref);
+ assert(result && "invalid resource name");
+ return ref.toResourceName();
+}
+
+inline ConfigDescription parseConfigOrDie(const StringPiece& str) {
+ ConfigDescription config;
+ bool result = ConfigDescription::parse(str, &config);
+ assert(result && "invalid configuration");
+ return config;
+}
+
+template <typename T> T* getValueForConfigAndProduct(ResourceTable* table,
+ const StringPiece16& resName,
+ const ConfigDescription& config,
+ const StringPiece& product) {
+ Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName));
+ if (result) {
+ ResourceConfigValue* configValue = result.value().entry->findValue(config, product);
+ if (configValue) {
+ return valueCast<T>(configValue->value.get());
+ }
+ }
+ return nullptr;
+}
+
+template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName,
+ const ConfigDescription& config) {
+ return getValueForConfigAndProduct<T>(table, resName, config, {});
+}
+
+template <typename T> T* getValue(ResourceTable* table, const StringPiece16& resName) {
+ return getValueForConfig<T>(table, resName, {});
+}
+
+class TestFile : public io::IFile {
+private:
+ Source mSource;
+
+public:
+ TestFile(const StringPiece& path) : mSource(path) {}
+
+ std::unique_ptr<io::IData> openAsData() override {
+ return {};
+ }
+
+ const Source& getSource() const override {
+ return mSource;
+ }
+};
+
+} // namespace test
+} // namespace aapt
+
+#endif /* AAPT_TEST_COMMON_H */
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
new file mode 100644
index 000000000000..96752d33dd9a
--- /dev/null
+++ b/tools/aapt2/test/Context.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TEST_CONTEXT_H
+#define AAPT_TEST_CONTEXT_H
+
+#include "NameMangler.h"
+#include "util/Util.h"
+
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "test/Common.h"
+
+#include <cassert>
+#include <list>
+
+namespace aapt {
+namespace test {
+
+class Context : public IAaptContext {
+public:
+ SymbolTable* getExternalSymbols() override {
+ return &mSymbols;
+ }
+
+ IDiagnostics* getDiagnostics() override {
+ return &mDiagnostics;
+ }
+
+ const std::u16string& getCompilationPackage() override {
+ assert(mCompilationPackage && "package name not set");
+ return mCompilationPackage.value();
+ }
+
+ uint8_t getPackageId() override {
+ assert(mPackageId && "package ID not set");
+ return mPackageId.value();
+ }
+
+ NameMangler* getNameMangler() override {
+ return &mNameMangler;
+ }
+
+ bool verbose() override {
+ return false;
+ }
+
+private:
+ friend class ContextBuilder;
+
+ Context() : mNameMangler({}) {
+ }
+
+ Maybe<std::u16string> mCompilationPackage;
+ Maybe<uint8_t> mPackageId;
+ StdErrDiagnostics mDiagnostics;
+ SymbolTable mSymbols;
+ NameMangler mNameMangler;
+};
+
+class ContextBuilder {
+private:
+ std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context());
+
+public:
+ ContextBuilder& setCompilationPackage(const StringPiece16& package) {
+ mContext->mCompilationPackage = package.toString();
+ return *this;
+ }
+
+ ContextBuilder& setPackageId(uint8_t id) {
+ mContext->mPackageId = id;
+ return *this;
+ }
+
+ ContextBuilder& setNameManglerPolicy(NameManglerPolicy policy) {
+ mContext->mNameMangler = NameMangler(policy);
+ return *this;
+ }
+
+ ContextBuilder& addSymbolSource(std::unique_ptr<ISymbolSource> src) {
+ mContext->getExternalSymbols()->appendSource(std::move(src));
+ return *this;
+ }
+
+ std::unique_ptr<Context> build() {
+ return std::move(mContext);
+ }
+};
+
+class StaticSymbolSourceBuilder {
+public:
+ StaticSymbolSourceBuilder& addPublicSymbol(const StringPiece16& name, ResourceId id,
+ std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(
+ id, std::move(attr), true);
+ mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get();
+ mSymbolSource->mIdMap[id] = symbol.get();
+ mSymbolSource->mSymbols.push_back(std::move(symbol));
+ return *this;
+ }
+
+ StaticSymbolSourceBuilder& addSymbol(const StringPiece16& name, ResourceId id,
+ std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(
+ id, std::move(attr), false);
+ mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get();
+ mSymbolSource->mIdMap[id] = symbol.get();
+ mSymbolSource->mSymbols.push_back(std::move(symbol));
+ return *this;
+ }
+
+ std::unique_ptr<ISymbolSource> build() {
+ return std::move(mSymbolSource);
+ }
+
+private:
+ class StaticSymbolSource : public ISymbolSource {
+ public:
+ StaticSymbolSource() = default;
+
+ std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override {
+ auto iter = mNameMap.find(name);
+ if (iter != mNameMap.end()) {
+ return cloneSymbol(iter->second);
+ }
+ return nullptr;
+ }
+
+ std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override {
+ auto iter = mIdMap.find(id);
+ if (iter != mIdMap.end()) {
+ return cloneSymbol(iter->second);
+ }
+ return nullptr;
+ }
+
+ std::list<std::unique_ptr<SymbolTable::Symbol>> mSymbols;
+ std::map<ResourceName, SymbolTable::Symbol*> mNameMap;
+ std::map<ResourceId, SymbolTable::Symbol*> mIdMap;
+
+ private:
+ std::unique_ptr<SymbolTable::Symbol> cloneSymbol(SymbolTable::Symbol* sym) {
+ std::unique_ptr<SymbolTable::Symbol> clone = util::make_unique<SymbolTable::Symbol>();
+ clone->id = sym->id;
+ if (sym->attribute) {
+ clone->attribute = std::unique_ptr<Attribute>(sym->attribute->clone(nullptr));
+ }
+ clone->isPublic = sym->isPublic;
+ return clone;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(StaticSymbolSource);
+ };
+
+ std::unique_ptr<StaticSymbolSource> mSymbolSource = util::make_unique<StaticSymbolSource>();
+};
+
+} // namespace test
+} // namespace aapt
+
+#endif /* AAPT_TEST_CONTEXT_H */
diff --git a/tools/aapt2/Compat_test.cpp b/tools/aapt2/test/Test.h
index 96aee44c6c95..d4845cfc19b7 100644
--- a/tools/aapt2/Compat_test.cpp
+++ b/tools/aapt2/test/Test.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,20 +14,19 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
-
-namespace aapt {
-
-TEST(CompatTest, VersionAttributesInStyle) {
-}
+#ifndef AAPT_TEST_TEST_H
+#define AAPT_TEST_TEST_H
-TEST(CompatTest, VersionAttributesInXML) {
-}
+#include "test/Builders.h"
+#include "test/Common.h"
+#include "test/Context.h"
-TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
-}
+#include <gtest/gtest.h>
-TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
-}
+namespace aapt {
+namespace test {
+} // namespace test
} // namespace aapt
+
+#endif // AAPT_TEST_TEST_H
diff --git a/tools/aapt2/todo.txt b/tools/aapt2/todo.txt
deleted file mode 100644
index acc8bfbcc9e5..000000000000
--- a/tools/aapt2/todo.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-XML Files
-X Collect declared IDs
-X Build StringPool
-X Flatten
-
-Resource Table Operations
-X Build Resource Table (with StringPool) from XML.
-X Modify Resource Table.
-X - Copy and transform resources.
-X - Pre-17/21 attr correction.
-X Perform analysis of types.
-X Flatten.
-X Assign resource IDs.
-X Assign public resource IDs.
-X Merge resource tables
-- Assign private attributes to different typespace.
-- Align resource tables
-
-Splits
-- Collect all resources (ids from layouts).
-- Generate resource table from base resources.
-- Generate resource table from individual resources of the required type.
-- Align resource tables (same type/name = same ID).
-
-Fat Apk
-X Collect all resources (ids from layouts).
-X Generate resource tables for all configurations.
-- Align individual resource tables.
-- Merge resource tables.
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
new file mode 100644
index 000000000000..ec4675167676
--- /dev/null
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "Source.h"
+#include "ValueVisitor.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "unflatten/ResChunkPullParser.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/TypeWrappers.h>
+#include <android-base/macros.h>
+#include <algorithm>
+#include <map>
+#include <string>
+
+namespace aapt {
+
+using namespace android;
+
+namespace {
+
+/*
+ * Visitor that converts a reference's resource ID to a resource name,
+ * given a mapping from resource ID to resource name.
+ */
+class ReferenceIdToNameVisitor : public ValueVisitor {
+private:
+ const std::map<ResourceId, ResourceName>* mMapping;
+
+public:
+ using ValueVisitor::visit;
+
+ ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) :
+ mMapping(mapping) {
+ assert(mMapping);
+ }
+
+ void visit(Reference* reference) override {
+ if (!reference->id || !reference->id.value().isValid()) {
+ return;
+ }
+
+ ResourceId id = reference->id.value();
+ auto cacheIter = mMapping->find(id);
+ if (cacheIter != mMapping->end()) {
+ reference->name = cacheIter->second;
+ reference->id = {};
+ }
+ }
+};
+
+} // namespace
+
+BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+ const Source& source, const void* data, size_t len) :
+ mContext(context), mTable(table), mSource(source), mData(data), mDataLen(len) {
+}
+
+bool BinaryResourceParser::parse() {
+ ResChunkPullParser parser(mData, mDataLen);
+
+ bool error = false;
+ while(ResChunkPullParser::isGoodEvent(parser.next())) {
+ if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
+ mContext->getDiagnostics()->warn(DiagMessage(mSource)
+ << "unknown chunk of type '"
+ << (int) parser.getChunk()->type << "'");
+ continue;
+ }
+
+ if (!parseTable(parser.getChunk())) {
+ error = true;
+ }
+ }
+
+ if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt resource table: "
+ << parser.getLastError());
+ return false;
+ }
+ return !error;
+}
+
+/**
+ * Parses the resource table, which contains all the packages, types, and entries.
+ */
+bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
+ const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
+ if (!tableHeader) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource) << "corrupt ResTable_header chunk");
+ return false;
+ }
+
+ ResChunkPullParser parser(getChunkData(&tableHeader->header),
+ getChunkDataLen(&tableHeader->header));
+ while (ResChunkPullParser::isGoodEvent(parser.next())) {
+ switch (util::deviceToHost16(parser.getChunk()->type)) {
+ case android::RES_STRING_POOL_TYPE:
+ if (mValuePool.getError() == NO_INIT) {
+ status_t err = mValuePool.setTo(parser.getChunk(),
+ util::deviceToHost32(parser.getChunk()->size));
+ if (err != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt string pool in ResTable: "
+ << mValuePool.getError());
+ return false;
+ }
+
+ // Reserve some space for the strings we are going to add.
+ mTable->stringPool.hintWillAdd(mValuePool.size(), mValuePool.styleCount());
+ } else {
+ mContext->getDiagnostics()->warn(DiagMessage(mSource)
+ << "unexpected string pool in ResTable");
+ }
+ break;
+
+ case android::RES_TABLE_PACKAGE_TYPE:
+ if (!parsePackage(parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ default:
+ mContext->getDiagnostics()
+ ->warn(DiagMessage(mSource)
+ << "unexpected chunk type "
+ << (int) util::deviceToHost16(parser.getChunk()->type));
+ break;
+ }
+ }
+
+ if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt resource table: " << parser.getLastError());
+ return false;
+ }
+ return true;
+}
+
+
+bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
+ const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
+ if (!packageHeader) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt ResTable_package chunk");
+ return false;
+ }
+
+ uint32_t packageId = util::deviceToHost32(packageHeader->id);
+ if (packageId > std::numeric_limits<uint8_t>::max()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "package ID is too big (" << packageId << ")");
+ return false;
+ }
+
+ // Extract the package name.
+ size_t len = strnlen16((const char16_t*) packageHeader->name, arraysize(packageHeader->name));
+ std::u16string packageName;
+ packageName.resize(len);
+ for (size_t i = 0; i < len; i++) {
+ packageName[i] = util::deviceToHost16(packageHeader->name[i]);
+ }
+
+ ResourceTablePackage* package = mTable->createPackage(packageName, (uint8_t) packageId);
+ if (!package) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "incompatible package '" << packageName
+ << "' with ID " << packageId);
+ return false;
+ }
+
+ // There can be multiple packages in a table, so
+ // clear the type and key pool in case they were set from a previous package.
+ mTypePool.uninit();
+ mKeyPool.uninit();
+
+ ResChunkPullParser parser(getChunkData(&packageHeader->header),
+ getChunkDataLen(&packageHeader->header));
+ while (ResChunkPullParser::isGoodEvent(parser.next())) {
+ switch (util::deviceToHost16(parser.getChunk()->type)) {
+ case android::RES_STRING_POOL_TYPE:
+ if (mTypePool.getError() == NO_INIT) {
+ status_t err = mTypePool.setTo(parser.getChunk(),
+ util::deviceToHost32(parser.getChunk()->size));
+ if (err != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt type string pool in "
+ << "ResTable_package: "
+ << mTypePool.getError());
+ return false;
+ }
+ } else if (mKeyPool.getError() == NO_INIT) {
+ status_t err = mKeyPool.setTo(parser.getChunk(),
+ util::deviceToHost32(parser.getChunk()->size));
+ if (err != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt key string pool in "
+ << "ResTable_package: "
+ << mKeyPool.getError());
+ return false;
+ }
+ } else {
+ mContext->getDiagnostics()->warn(DiagMessage(mSource) << "unexpected string pool");
+ }
+ break;
+
+ case android::RES_TABLE_TYPE_SPEC_TYPE:
+ if (!parseTypeSpec(parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ case android::RES_TABLE_TYPE_TYPE:
+ if (!parseType(package, parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ default:
+ mContext->getDiagnostics()
+ ->warn(DiagMessage(mSource)
+ << "unexpected chunk type "
+ << (int) util::deviceToHost16(parser.getChunk()->type));
+ break;
+ }
+ }
+
+ if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt ResTable_package: "
+ << parser.getLastError());
+ return false;
+ }
+
+ // Now go through the table and change local resource ID references to
+ // symbolic references.
+ ReferenceIdToNameVisitor visitor(&mIdIndex);
+ visitAllValuesInTable(mTable, &visitor);
+ return true;
+}
+
+bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
+ if (mTypePool.getError() != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "missing type string pool");
+ return false;
+ }
+
+ const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
+ if (!typeSpec) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt ResTable_typeSpec chunk");
+ return false;
+ }
+
+ if (typeSpec->id == 0) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "ResTable_typeSpec has invalid id: " << typeSpec->id);
+ return false;
+ }
+ return true;
+}
+
+bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
+ const ResChunk_header* chunk) {
+ if (mTypePool.getError() != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "missing type string pool");
+ return false;
+ }
+
+ if (mKeyPool.getError() != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "missing key string pool");
+ return false;
+ }
+
+ const ResTable_type* type = convertTo<ResTable_type>(chunk);
+ if (!type) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt ResTable_type chunk");
+ return false;
+ }
+
+ if (type->id == 0) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "ResTable_type has invalid id: " << (int) type->id);
+ return false;
+ }
+
+ ConfigDescription config;
+ config.copyFromDtoH(type->config);
+
+ StringPiece16 typeStr16 = util::getString(mTypePool, type->id - 1);
+
+ const ResourceType* parsedType = parseResourceType(typeStr16);
+ if (!parsedType) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "invalid type name '" << typeStr16
+ << "' for type with ID " << (int) type->id);
+ return false;
+ }
+
+ TypeVariant tv(type);
+ for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
+ const ResTable_entry* entry = *it;
+ if (!entry) {
+ continue;
+ }
+
+ const ResourceName name(package->name, *parsedType,
+ util::getString(mKeyPool,
+ util::deviceToHost32(entry->key.index)).toString());
+
+ const ResourceId resId(package->id.value(), type->id, static_cast<uint16_t>(it.index()));
+
+ std::unique_ptr<Value> resourceValue;
+ if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
+ const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
+
+ // TODO(adamlesinski): Check that the entry count is valid.
+ resourceValue = parseMapEntry(name, config, mapEntry);
+ } else {
+ const Res_value* value = (const Res_value*)(
+ (const uint8_t*) entry + util::deviceToHost32(entry->size));
+ resourceValue = parseValue(name, config, value, entry->flags);
+ }
+
+ if (!resourceValue) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "failed to parse value for resource " << name
+ << " (" << resId << ") with configuration '"
+ << config << "'");
+ return false;
+ }
+
+ if (!mTable->addResourceAllowMangled(name, config, {}, std::move(resourceValue),
+ mContext->getDiagnostics())) {
+ return false;
+ }
+
+ if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
+ Symbol symbol;
+ symbol.state = SymbolState::kPublic;
+ symbol.source = mSource.withLine(0);
+ if (!mTable->setSymbolStateAllowMangled(name, resId, symbol,
+ mContext->getDiagnostics())) {
+ return false;
+ }
+ }
+
+ // Add this resource name->id mapping to the index so
+ // that we can resolve all ID references to name references.
+ auto cacheIter = mIdIndex.find(resId);
+ if (cacheIter == mIdIndex.end()) {
+ mIdIndex.insert({ resId, name });
+ }
+ }
+ return true;
+}
+
+std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Res_value* value,
+ uint16_t flags) {
+ if (name.type == ResourceType::kId) {
+ return util::make_unique<Id>();
+ }
+
+ const uint32_t data = util::deviceToHost32(value->data);
+
+ if (value->dataType == Res_value::TYPE_STRING) {
+ StringPiece16 str = util::getString(mValuePool, data);
+
+ const ResStringPool_span* spans = mValuePool.styleAt(data);
+
+ // Check if the string has a valid style associated with it.
+ if (spans != nullptr && spans->name.index != ResStringPool_span::END) {
+ StyleString styleStr = { str.toString() };
+ while (spans->name.index != ResStringPool_span::END) {
+ styleStr.spans.push_back(Span{
+ util::getString(mValuePool, spans->name.index).toString(),
+ spans->firstChar,
+ spans->lastChar
+ });
+ spans++;
+ }
+ return util::make_unique<StyledString>(mTable->stringPool.makeRef(
+ styleStr, StringPool::Context{1, config}));
+ } else {
+ if (name.type != ResourceType::kString &&
+ util::stringStartsWith<char16_t>(str, u"res/")) {
+ // This must be a FileReference.
+ return util::make_unique<FileReference>(mTable->stringPool.makeRef(
+ str, StringPool::Context{ 0, config }));
+ }
+
+ // There are no styles associated with this string, so treat it as
+ // a simple string.
+ return util::make_unique<String>(mTable->stringPool.makeRef(
+ str, StringPool::Context{1, config}));
+ }
+ }
+
+ if (value->dataType == Res_value::TYPE_REFERENCE ||
+ value->dataType == Res_value::TYPE_ATTRIBUTE) {
+ const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
+ Reference::Type::kResource : Reference::Type::kAttribute;
+
+ if (data == 0) {
+ // A reference of 0, must be the magic @null reference.
+ Res_value nullType = {};
+ nullType.dataType = Res_value::TYPE_REFERENCE;
+ return util::make_unique<BinaryPrimitive>(nullType);
+ }
+
+ // This is a normal reference.
+ return util::make_unique<Reference>(data, type);
+ }
+
+ // Treat this as a raw binary primitive.
+ return util::make_unique<BinaryPrimitive>(*value);
+}
+
+std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ switch (name.type) {
+ case ResourceType::kStyle:
+ return parseStyle(name, config, map);
+ case ResourceType::kAttrPrivate:
+ // fallthrough
+ case ResourceType::kAttr:
+ return parseAttr(name, config, map);
+ case ResourceType::kArray:
+ return parseArray(name, config, map);
+ case ResourceType::kPlurals:
+ return parsePlural(name, config, map);
+ default:
+ assert(false && "unknown map type");
+ break;
+ }
+ return {};
+}
+
+std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ if (util::deviceToHost32(map->parent.ident) != 0) {
+ // The parent is a regular reference to a resource.
+ style->parent = Reference(util::deviceToHost32(map->parent.ident));
+ }
+
+ for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ continue;
+ }
+
+ Style::Entry styleEntry;
+ styleEntry.key = Reference(util::deviceToHost32(mapEntry.name.ident));
+ styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
+ if (!styleEntry.value) {
+ return {};
+ }
+ style->entries.push_back(std::move(styleEntry));
+ }
+ return style;
+}
+
+std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ const bool isWeak = (util::deviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
+
+ // First we must discover what type of attribute this is. Find the type mask.
+ auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
+ return util::deviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE;
+ });
+
+ if (typeMaskIter != end(map)) {
+ attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
+ }
+
+ for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ResTable_map::ATTR_MIN:
+ attr->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case ResTable_map::ATTR_MAX:
+ attr->maxInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ }
+ continue;
+ }
+
+ if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
+ Attribute::Symbol symbol;
+ symbol.value = util::deviceToHost32(mapEntry.value.data);
+ symbol.symbol = Reference(util::deviceToHost32(mapEntry.name.ident));
+ attr->symbols.push_back(std::move(symbol));
+ }
+ }
+
+ // TODO(adamlesinski): Find i80n, attributes.
+ return attr;
+}
+
+std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+ for (const ResTable_map& mapEntry : map) {
+ array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
+ }
+ return array;
+}
+
+std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ for (const ResTable_map& mapEntry : map) {
+ std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
+ if (!item) {
+ return {};
+ }
+
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ResTable_map::ATTR_ZERO:
+ plural->values[Plural::Zero] = std::move(item);
+ break;
+ case ResTable_map::ATTR_ONE:
+ plural->values[Plural::One] = std::move(item);
+ break;
+ case ResTable_map::ATTR_TWO:
+ plural->values[Plural::Two] = std::move(item);
+ break;
+ case ResTable_map::ATTR_FEW:
+ plural->values[Plural::Few] = std::move(item);
+ break;
+ case ResTable_map::ATTR_MANY:
+ plural->values[Plural::Many] = std::move(item);
+ break;
+ case ResTable_map::ATTR_OTHER:
+ plural->values[Plural::Other] = std::move(item);
+ break;
+ }
+ }
+ return plural;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 3aab301ec199..12bc13db38f2 100644
--- a/tools/aapt2/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -17,11 +17,13 @@
#ifndef AAPT_BINARY_RESOURCE_PARSER_H
#define AAPT_BINARY_RESOURCE_PARSER_H
-#include "Resolver.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "Source.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Util.h"
+
#include <androidfw/ResourceTypes.h>
#include <string>
@@ -42,11 +44,8 @@ public:
* Creates a parser, which will read `len` bytes from `data`, and
* add any resources parsed to `table`. `source` is for logging purposes.
*/
- BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
- const std::shared_ptr<IResolver>& resolver,
- const Source& source,
- const std::u16string& defaultPackage,
- const void* data, size_t len);
+ BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
+ const void* data, size_t dataLen);
BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
@@ -56,71 +55,47 @@ public:
bool parse();
private:
- // Helper method to retrieve the symbol name for a given table offset specified
- // as a pointer.
- bool getSymbol(const void* data, ResourceNameRef* outSymbol);
-
bool parseTable(const android::ResChunk_header* chunk);
- bool parseSymbolTable(const android::ResChunk_header* chunk);
-
- // Looks up the resource ID in the reference and converts it to a name if available.
- bool idToName(Reference* reference);
-
bool parsePackage(const android::ResChunk_header* chunk);
- bool parsePublic(const android::ResChunk_header* chunk);
bool parseTypeSpec(const android::ResChunk_header* chunk);
- bool parseType(const android::ResChunk_header* chunk);
+ bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
- std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
- const ConfigDescription& config, const android::Res_value* value, uint16_t flags);
+ std::unique_ptr<Item> parseValue(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::Res_value* value, uint16_t flags);
std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
- std::unique_ptr<Style> parseStyle(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
- std::unique_ptr<Array> parseArray(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ std::unique_ptr<Array> parseArray(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
-
- std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
- std::shared_ptr<ResourceTable> mTable;
+ /**
+ * If the mapEntry is a special type that denotes meta data (source, comment), then it is
+ * read and added to the Value.
+ * Returns true if the mapEntry was meta data.
+ */
+ bool collectMetaData(const android::ResTable_map& mapEntry, Value* value);
- std::shared_ptr<IResolver> mResolver;
+ IAaptContext* mContext;
+ ResourceTable* mTable;
const Source mSource;
- // The package name of the resource table.
- std::u16string mDefaultPackage;
-
const void* mData;
const size_t mDataLen;
- // The array of symbol entries. Each element points to an offset
- // in the table and an index into the symbol table string pool.
- const SymbolTable_entry* mSymbolEntries = nullptr;
-
- // Number of symbol entries.
- size_t mSymbolEntryCount = 0;
-
- // The symbol table string pool. Holds the names of symbols
- // referenced in this table but not defined nor resolved to an
- // ID.
- android::ResStringPool mSymbolPool;
-
- // The source string pool. Resource entries may have an extra
- // field that points into this string pool, which denotes where
- // the resource was parsed from originally.
- android::ResStringPool mSourcePool;
-
// The standard value string pool for resource values.
android::ResStringPool mValuePool;
@@ -146,13 +121,11 @@ namespace android {
*/
inline const ResTable_map* begin(const ResTable_map_entry* map) {
- return reinterpret_cast<const ResTable_map*>(
- reinterpret_cast<const uint8_t*>(map) + map->size);
+ return (const ResTable_map*)((const uint8_t*) map + aapt::util::deviceToHost32(map->size));
}
inline const ResTable_map* end(const ResTable_map_entry* map) {
- return reinterpret_cast<const ResTable_map*>(
- reinterpret_cast<const uint8_t*>(map) + map->size) + map->count;
+ return begin(map) + aapt::util::deviceToHost32(map->count);
}
} // namespace android
diff --git a/tools/aapt2/ResChunkPullParser.cpp b/tools/aapt2/unflatten/ResChunkPullParser.cpp
index 78ea60e795fc..6f8bb1b29b62 100644
--- a/tools/aapt2/ResChunkPullParser.cpp
+++ b/tools/aapt2/unflatten/ResChunkPullParser.cpp
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-#include "ResChunkPullParser.h"
+#include "unflatten/ResChunkPullParser.h"
+#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
#include <cstddef>
@@ -31,12 +32,11 @@ ResChunkPullParser::Event ResChunkPullParser::next() {
if (mEvent == Event::StartDocument) {
mCurrentChunk = mData;
} else {
- mCurrentChunk = reinterpret_cast<const ResChunk_header*>(
- reinterpret_cast<const char*>(mCurrentChunk) + mCurrentChunk->size);
+ mCurrentChunk = (const ResChunk_header*)
+ (((const char*) mCurrentChunk) + util::deviceToHost32(mCurrentChunk->size));
}
- const std::ptrdiff_t diff = reinterpret_cast<const char*>(mCurrentChunk)
- - reinterpret_cast<const char*>(mData);
+ const std::ptrdiff_t diff = (const char*) mCurrentChunk - (const char*) mData;
assert(diff >= 0 && "diff is negative");
const size_t offset = static_cast<const size_t>(diff);
@@ -49,15 +49,16 @@ ResChunkPullParser::Event ResChunkPullParser::next() {
return (mEvent = Event::BadDocument);
}
- if (mCurrentChunk->headerSize < sizeof(ResChunk_header)) {
+ if (util::deviceToHost16(mCurrentChunk->headerSize) < sizeof(ResChunk_header)) {
mLastError = "chunk has too small header";
mCurrentChunk = nullptr;
return (mEvent = Event::BadDocument);
- } else if (mCurrentChunk->size < mCurrentChunk->headerSize) {
+ } else if (util::deviceToHost32(mCurrentChunk->size) <
+ util::deviceToHost16(mCurrentChunk->headerSize)) {
mLastError = "chunk's total size is smaller than header";
mCurrentChunk = nullptr;
return (mEvent = Event::BadDocument);
- } else if (offset + mCurrentChunk->size > mLen) {
+ } else if (offset + util::deviceToHost32(mCurrentChunk->size) > mLen) {
mLastError = "chunk's data extends past the end of the document";
mCurrentChunk = nullptr;
return (mEvent = Event::BadDocument);
diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h
index 1426ed23a5c7..a51d5bfdc9b3 100644
--- a/tools/aapt2/ResChunkPullParser.h
+++ b/tools/aapt2/unflatten/ResChunkPullParser.h
@@ -17,6 +17,8 @@
#ifndef AAPT_RES_CHUNK_PULL_PARSER_H
#define AAPT_RES_CHUNK_PULL_PARSER_H
+#include "util/Util.h"
+
#include <androidfw/ResourceTypes.h>
#include <string>
@@ -76,18 +78,18 @@ private:
template <typename T>
inline static const T* convertTo(const android::ResChunk_header* chunk) {
- if (chunk->headerSize < sizeof(T)) {
+ if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) {
return nullptr;
}
return reinterpret_cast<const T*>(chunk);
}
-inline static const uint8_t* getChunkData(const android::ResChunk_header& chunk) {
- return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
+inline static const uint8_t* getChunkData(const android::ResChunk_header* chunk) {
+ return reinterpret_cast<const uint8_t*>(chunk) + util::deviceToHost16(chunk->headerSize);
}
-inline static size_t getChunkDataLen(const android::ResChunk_header& chunk) {
- return chunk.size - chunk.headerSize;
+inline static uint32_t getChunkDataLen(const android::ResChunk_header* chunk) {
+ return util::deviceToHost32(chunk->size) - util::deviceToHost16(chunk->headerSize);
}
//
diff --git a/tools/aapt2/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp
index 8f571728d729..c88e3c102415 100644
--- a/tools/aapt2/BigBuffer.cpp
+++ b/tools/aapt2/util/BigBuffer.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
#include <algorithm>
#include <memory>
diff --git a/tools/aapt2/BigBuffer.h b/tools/aapt2/util/BigBuffer.h
index 8b6569c6a8d6..cad2a2e63be1 100644
--- a/tools/aapt2/BigBuffer.h
+++ b/tools/aapt2/util/BigBuffer.h
@@ -20,6 +20,7 @@
#include <cassert>
#include <cstring>
#include <memory>
+#include <type_traits>
#include <vector>
namespace aapt {
@@ -124,6 +125,7 @@ inline size_t BigBuffer::size() const {
template <typename T>
inline T* BigBuffer::nextBlock(size_t count) {
+ static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type");
assert(count != 0);
return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count));
}
diff --git a/tools/aapt2/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp
index 01ee8d7e9ad5..2a24f123e18e 100644
--- a/tools/aapt2/BigBuffer_test.cpp
+++ b/tools/aapt2/util/BigBuffer_test.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
#include <gtest/gtest.h>
diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/util/Files.cpp
index 8484148f29d3..f5e49f11c082 100644
--- a/tools/aapt2/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -14,20 +14,24 @@
* limitations under the License.
*/
-#include "Files.h"
-#include "Util.h"
+#include "util/Files.h"
+#include "util/Util.h"
+#include <algorithm>
+#include <android-base/file.h>
#include <cerrno>
+#include <cstdio>
#include <dirent.h>
#include <string>
#include <sys/stat.h>
-#ifdef HAVE_MS_C_RUNTIME
+#ifdef _WIN32
// Windows includes.
#include <direct.h>
#endif
namespace aapt {
+namespace file {
FileType getFileType(const StringPiece& path) {
struct stat sb;
@@ -61,15 +65,15 @@ FileType getFileType(const StringPiece& path) {
}
}
-std::vector<std::string> listFiles(const StringPiece& root) {
+std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) {
DIR* dir = opendir(root.data());
if (dir == nullptr) {
- Logger::error(Source{ root.toString() })
- << "unable to open file: "
- << strerror(errno)
- << "."
- << std::endl;
- return {};
+ if (outError) {
+ std::stringstream errorStr;
+ errorStr << "unable to open file: " << strerror(errno);
+ *outError = errorStr.str();
+ return {};
+ }
}
std::vector<std::string> files;
@@ -83,7 +87,7 @@ std::vector<std::string> listFiles(const StringPiece& root) {
}
inline static int mkdirImpl(const StringPiece& path) {
-#ifdef HAVE_MS_C_RUNTIME
+#ifdef _WIN32
return _mkdir(path.toString().c_str());
#else
return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
@@ -94,7 +98,7 @@ bool mkdirs(const StringPiece& path) {
const char* start = path.begin();
const char* end = path.end();
for (const char* current = start; current != end; ++current) {
- if (*current == sDirSep) {
+ if (*current == sDirSep && current != start) {
StringPiece parentPath(start, current - start);
int result = mkdirImpl(parentPath);
if (result < 0 && errno != EEXIST) {
@@ -105,17 +109,105 @@ bool mkdirs(const StringPiece& path) {
return mkdirImpl(path) == 0 || errno == EEXIST;
}
-std::string getStem(const StringPiece& path) {
+StringPiece getStem(const StringPiece& path) {
const char* start = path.begin();
const char* end = path.end();
for (const char* current = end - 1; current != start - 1; --current) {
if (*current == sDirSep) {
- return std::string(start, current - start);
+ return StringPiece(start, current - start);
+ }
+ }
+ return {};
+}
+
+StringPiece getFilename(const StringPiece& path) {
+ const char* end = path.end();
+ const char* lastDirSep = path.begin();
+ for (const char* c = path.begin(); c != end; ++c) {
+ if (*c == sDirSep) {
+ lastDirSep = c + 1;
}
}
+ return StringPiece(lastDirSep, end - lastDirSep);
+}
+
+StringPiece getExtension(const StringPiece& path) {
+ StringPiece filename = getFilename(path);
+ const char* const end = filename.end();
+ const char* c = std::find(filename.begin(), end, '.');
+ if (c != end) {
+ return StringPiece(c, end - c);
+ }
return {};
}
+void appendPath(std::string* base, StringPiece part) {
+ assert(base);
+ const bool baseHasTrailingSep = (!base->empty() && *(base->end() - 1) == sDirSep);
+ const bool partHasLeadingSep = (!part.empty() && *(part.begin()) == sDirSep);
+ if (baseHasTrailingSep && partHasLeadingSep) {
+ // Remove the part's leading sep
+ part = part.substr(1, part.size() - 1);
+ } else if (!baseHasTrailingSep && !partHasLeadingSep) {
+ // None of the pieces has a separator.
+ *base += sDirSep;
+ }
+ base->append(part.data(), part.size());
+}
+
+std::string packageToPath(const StringPiece& package) {
+ std::string outPath;
+ for (StringPiece part : util::tokenize<char>(package, '.')) {
+ appendPath(&outPath, part);
+ }
+ return outPath;
+}
+
+Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) {
+ std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose };
+ if (!f) {
+ if (outError) *outError = strerror(errno);
+ return {};
+ }
+
+ int fd = fileno(f.get());
+
+ struct stat fileStats = {};
+ if (fstat(fd, &fileStats) != 0) {
+ if (outError) *outError = strerror(errno);
+ return {};
+ }
+
+ android::FileMap fileMap;
+ if (fileStats.st_size == 0) {
+ // mmap doesn't like a length of 0. Instead we return an empty FileMap.
+ return std::move(fileMap);
+ }
+
+ if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) {
+ if (outError) *outError = strerror(errno);
+ return {};
+ }
+ return std::move(fileMap);
+}
+
+bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outArgList,
+ std::string* outError) {
+ std::string contents;
+ if (!android::base::ReadFileToString(path.toString(), &contents)) {
+ if (outError) *outError = "failed to read argument-list file";
+ return false;
+ }
+
+ for (StringPiece line : util::tokenize<char>(contents, ' ')) {
+ line = util::trimWhitespace(line);
+ if (!line.empty()) {
+ outArgList->push_back(line.toString());
+ }
+ }
+ return true;
+}
+
bool FileFilter::setPattern(const StringPiece& pattern) {
mPatternTokens = util::splitAndLowercase(pattern, ':');
return true;
@@ -169,14 +261,10 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const {
if (ignore) {
if (chatty) {
- Logger::warn()
- << "skipping " <<
- (type == FileType::kDirectory ? "dir '" : "file '")
- << filename
- << "' due to ignore pattern '"
- << token
- << "'."
- << std::endl;
+ mDiag->warn(DiagMessage() << "skipping "
+ << (type == FileType::kDirectory ? "dir '" : "file '")
+ << filename << "' due to ignore pattern '"
+ << token << "'");
}
return false;
}
@@ -184,5 +272,5 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const {
return true;
}
-
+} // namespace file
} // namespace aapt
diff --git a/tools/aapt2/Files.h b/tools/aapt2/util/Files.h
index 844fd2b07189..4d8a1feb63b1 100644
--- a/tools/aapt2/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -17,15 +17,20 @@
#ifndef AAPT_FILES_H
#define AAPT_FILES_H
-#include "Logger.h"
+#include "Diagnostics.h"
+#include "Maybe.h"
#include "Source.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
+
+#include <utils/FileMap.h>
#include <cassert>
+#include <memory>
#include <string>
#include <vector>
namespace aapt {
+namespace file {
#ifdef _WIN32
constexpr const char sDirSep = '\\';
@@ -56,14 +61,7 @@ std::vector<std::string> listFiles(const StringPiece& root);
/*
* Appends a path to `base`, separated by the directory separator.
*/
-void appendPath(std::string* base, const StringPiece& part);
-
-/*
- * Appends a series of paths to `base`, separated by the
- * system directory separator.
- */
-template <typename... Ts >
-void appendPath(std::string* base, const StringPiece& part, const Ts&... parts);
+void appendPath(std::string* base, StringPiece part);
/*
* Makes all the directories in `path`. The last element in the path
@@ -74,7 +72,34 @@ bool mkdirs(const StringPiece& path);
/**
* Returns all but the last part of the path.
*/
-std::string getStem(const StringPiece& path);
+StringPiece getStem(const StringPiece& path);
+
+/**
+ * Returns the last part of the path with extension.
+ */
+StringPiece getFilename(const StringPiece& path);
+
+/**
+ * Returns the extension of the path. This is the entire string after
+ * the first '.' of the last part of the path.
+ */
+StringPiece getExtension(const StringPiece& path);
+
+/**
+ * Converts a package name (com.android.app) to a path: com/android/app
+ */
+std::string packageToPath(const StringPiece& package);
+
+/**
+ * Creates a FileMap for the file at path.
+ */
+Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError);
+
+/**
+ * Reads the file at path and appends each line to the outArgList vector.
+ */
+bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outArgList,
+ std::string* outError);
/*
* Filter that determines which resource files/directories are
@@ -84,6 +109,9 @@ std::string getStem(const StringPiece& path);
*/
class FileFilter {
public:
+ FileFilter(IDiagnostics* diag) : mDiag(diag) {
+ }
+
/*
* Patterns syntax:
* - Delimiter is :
@@ -106,23 +134,11 @@ public:
bool operator()(const std::string& filename, FileType type) const;
private:
+ IDiagnostics* mDiag;
std::vector<std::string> mPatternTokens;
};
-inline void appendPath(std::string* base, const StringPiece& part) {
- assert(base);
- *base += sDirSep;
- base->append(part.data(), part.size());
-}
-
-template <typename... Ts >
-void appendPath(std::string* base, const StringPiece& part, const Ts&... parts) {
- assert(base);
- *base += sDirSep;
- base->append(part.data(), part.size());
- appendPath(base, parts...);
-}
-
+} // namespace file
} // namespace aapt
#endif // AAPT_FILES_H
diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp
new file mode 100644
index 000000000000..efb04593ff82
--- /dev/null
+++ b/tools/aapt2/util/Files_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "test/Test.h"
+#include "util/Files.h"
+
+#include <sstream>
+
+namespace aapt {
+namespace file {
+
+class FilesTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ std::stringstream builder;
+ builder << "hello" << sDirSep << "there";
+ mExpectedPath = builder.str();
+ }
+
+protected:
+ std::string mExpectedPath;
+};
+
+TEST_F(FilesTest, appendPath) {
+ std::string base = "hello";
+ appendPath(&base, "there");
+ EXPECT_EQ(mExpectedPath, base);
+}
+
+TEST_F(FilesTest, appendPathWithLeadingOrTrailingSeparators) {
+ std::string base = "hello/";
+ appendPath(&base, "there");
+ EXPECT_EQ(mExpectedPath, base);
+
+ base = "hello";
+ appendPath(&base, "/there");
+ EXPECT_EQ(mExpectedPath, base);
+
+ base = "hello/";
+ appendPath(&base, "/there");
+ EXPECT_EQ(mExpectedPath, base);
+}
+
+} // namespace files
+} // namespace aapt
diff --git a/tools/aapt2/util/ImmutableMap.h b/tools/aapt2/util/ImmutableMap.h
new file mode 100644
index 000000000000..b1f9e9d2fb57
--- /dev/null
+++ b/tools/aapt2/util/ImmutableMap.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_UTIL_IMMUTABLEMAP_H
+#define AAPT_UTIL_IMMUTABLEMAP_H
+
+#include "util/TypeTraits.h"
+
+#include <utility>
+#include <vector>
+
+namespace aapt {
+
+template <typename TKey, typename TValue>
+class ImmutableMap {
+ static_assert(is_comparable<TKey, TKey>::value, "key is not comparable");
+
+private:
+ std::vector<std::pair<TKey, TValue>> mData;
+
+ explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data) : mData(std::move(data)) {
+ }
+
+public:
+ using const_iterator = typename decltype(mData)::const_iterator;
+
+ ImmutableMap(ImmutableMap&&) = default;
+ ImmutableMap& operator=(ImmutableMap&&) = default;
+
+ ImmutableMap(const ImmutableMap&) = delete;
+ ImmutableMap& operator=(const ImmutableMap&) = delete;
+
+ static ImmutableMap<TKey, TValue> createPreSorted(
+ std::initializer_list<std::pair<TKey, TValue>> list) {
+ return ImmutableMap(std::vector<std::pair<TKey, TValue>>(list.begin(), list.end()));
+ }
+
+ static ImmutableMap<TKey, TValue> createAndSort(
+ std::initializer_list<std::pair<TKey, TValue>> list) {
+ std::vector<std::pair<TKey, TValue>> data(list.begin(), list.end());
+ std::sort(data.begin(), data.end());
+ return ImmutableMap(std::move(data));
+ }
+
+ template <typename TKey2,
+ typename = typename std::enable_if<is_comparable<TKey, TKey2>::value>::type>
+ const_iterator find(const TKey2& key) const {
+ auto cmp = [](const std::pair<TKey, TValue>& candidate, const TKey2& target) -> bool {
+ return candidate.first < target;
+ };
+
+ const_iterator endIter = end();
+ auto iter = std::lower_bound(mData.begin(), endIter, key, cmp);
+ if (iter == endIter || iter->first == key) {
+ return iter;
+ }
+ return endIter;
+ }
+
+ const_iterator begin() const {
+ return mData.begin();
+ }
+
+ const_iterator end() const {
+ return mData.end();
+ }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_UTIL_IMMUTABLEMAP_H */
diff --git a/tools/aapt2/Maybe.h b/tools/aapt2/util/Maybe.h
index ff6625f4bb5e..595db960d5e5 100644
--- a/tools/aapt2/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -17,6 +17,8 @@
#ifndef AAPT_MAYBE_H
#define AAPT_MAYBE_H
+#include "util/TypeTraits.h"
+
#include <cassert>
#include <type_traits>
#include <utility>
@@ -72,7 +74,7 @@ public:
* True if this holds a value, false if
* it holds Nothing.
*/
- operator bool() const;
+ explicit operator bool() const;
/**
* Gets the value if one exists, or else
@@ -275,6 +277,35 @@ inline Maybe<T> make_nothing() {
return Maybe<T>();
}
+/**
+ * Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined.
+ * That way the compiler will show an error at the callsite when comparing two Maybe<> objects
+ * whose inner types can't be compared.
+ */
+template <typename T, typename U>
+typename std::enable_if<
+ has_eq_op<T, U>::value,
+ bool
+>::type operator==(const Maybe<T>& a, const Maybe<U>& b) {
+ if (a && b) {
+ return a.value() == b.value();
+ } else if (!a && !b) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Same as operator== but negated.
+ */
+template <typename T, typename U>
+typename std::enable_if<
+ has_eq_op<T, U>::value,
+ bool
+>::type operator!=(const Maybe<T>& a, const Maybe<U>& b) {
+ return !(a == b);
+}
+
} // namespace aapt
#endif // AAPT_MAYBE_H
diff --git a/tools/aapt2/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
index 71bbb940beda..5d42dc3ac3ab 100644
--- a/tools/aapt2/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -14,11 +14,12 @@
* limitations under the License.
*/
+#include "test/Common.h"
+#include "util/Maybe.h"
+
#include <gtest/gtest.h>
#include <string>
-#include "Maybe.h"
-
namespace aapt {
struct Dummy {
@@ -85,22 +86,22 @@ struct Dummy {
TEST(MaybeTest, MakeNothing) {
Maybe<int> val = make_nothing<int>();
- EXPECT_FALSE(val);
+ AAPT_EXPECT_FALSE(val);
Maybe<std::string> val2 = make_nothing<std::string>();
- EXPECT_FALSE(val2);
+ AAPT_EXPECT_FALSE(val2);
val2 = make_nothing<std::string>();
- EXPECT_FALSE(val2);
+ AAPT_EXPECT_FALSE(val2);
}
TEST(MaybeTest, MakeSomething) {
Maybe<int> val = make_value(23);
- ASSERT_TRUE(val);
+ AAPT_ASSERT_TRUE(val);
EXPECT_EQ(23, val.value());
Maybe<std::string> val2 = make_value(std::string("hey"));
- ASSERT_TRUE(val2);
+ AAPT_ASSERT_TRUE(val2);
EXPECT_EQ(std::string("hey"), val2.value());
}
@@ -118,4 +119,17 @@ TEST(MaybeTest, MoveAssign) {
}
}
+TEST(MaybeTest, Equality) {
+ Maybe<int> a = 1;
+ Maybe<int> b = 1;
+ Maybe<int> c;
+
+ Maybe<int> emptyA, emptyB;
+
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(b, a);
+ EXPECT_NE(a, c);
+ EXPECT_EQ(emptyA, emptyB);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/StringPiece.h b/tools/aapt2/util/StringPiece.h
index e2a1597caeda..f91bccc93019 100644
--- a/tools/aapt2/StringPiece.h
+++ b/tools/aapt2/util/StringPiece.h
@@ -36,6 +36,7 @@ template <typename TChar>
class BasicStringPiece {
public:
using const_iterator = const TChar*;
+ using difference_type = size_t;
BasicStringPiece();
BasicStringPiece(const BasicStringPiece<TChar>& str);
@@ -56,6 +57,7 @@ public:
bool empty() const;
std::basic_string<TChar> toString() const;
+ bool contains(const BasicStringPiece<TChar>& rhs) const;
int compare(const BasicStringPiece<TChar>& rhs) const;
bool operator<(const BasicStringPiece<TChar>& rhs) const;
bool operator>(const BasicStringPiece<TChar>& rhs) const;
@@ -163,6 +165,17 @@ inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const {
}
template <>
+inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const {
+ if (!mData || !rhs.mData) {
+ return false;
+ }
+ if (rhs.mLength > mLength) {
+ return false;
+ }
+ return strstr(mData, rhs.mData) != nullptr;
+}
+
+template <>
inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
const char nullStr = '\0';
const char* b1 = mData != nullptr ? mData : &nullStr;
@@ -184,6 +197,16 @@ inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<ch
return out.write(utf8.string(), utf8.size());
}
+template <>
+inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const {
+ if (!mData || !rhs.mData) {
+ return false;
+ }
+ if (rhs.mLength > mLength) {
+ return false;
+ }
+ return strstr16(mData, rhs.mData) != nullptr;
+}
template <>
inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
@@ -229,4 +252,9 @@ inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<ch
} // namespace aapt
+inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
+ android::String8 utf8(str.data(), str.size());
+ return out.write(utf8.string(), utf8.size());
+}
+
#endif // AAPT_STRING_PIECE_H
diff --git a/tools/aapt2/StringPiece_test.cpp b/tools/aapt2/util/StringPiece_test.cpp
index 43f7a370d23c..853a9a46fde8 100644
--- a/tools/aapt2/StringPiece_test.cpp
+++ b/tools/aapt2/util/StringPiece_test.cpp
@@ -19,7 +19,7 @@
#include <string>
#include <vector>
-#include "StringPiece.h"
+#include "util/StringPiece.h"
namespace aapt {
@@ -59,4 +59,36 @@ TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) {
EXPECT_TRUE(StringPiece(car) > banana);
}
+TEST(StringPieceTest, ContainsOtherStringPiece) {
+ StringPiece text("I am a leaf on the wind.");
+ StringPiece startNeedle("I am");
+ StringPiece endNeedle("wind.");
+ StringPiece middleNeedle("leaf");
+ StringPiece emptyNeedle("");
+ StringPiece missingNeedle("soar");
+ StringPiece longNeedle("This string is longer than the text.");
+
+ EXPECT_TRUE(text.contains(startNeedle));
+ EXPECT_TRUE(text.contains(endNeedle));
+ EXPECT_TRUE(text.contains(middleNeedle));
+ EXPECT_TRUE(text.contains(emptyNeedle));
+ EXPECT_FALSE(text.contains(missingNeedle));
+ EXPECT_FALSE(text.contains(longNeedle));
+
+ StringPiece16 text16(u"I am a leaf on the wind.");
+ StringPiece16 startNeedle16(u"I am");
+ StringPiece16 endNeedle16(u"wind.");
+ StringPiece16 middleNeedle16(u"leaf");
+ StringPiece16 emptyNeedle16(u"");
+ StringPiece16 missingNeedle16(u"soar");
+ StringPiece16 longNeedle16(u"This string is longer than the text.");
+
+ EXPECT_TRUE(text16.contains(startNeedle16));
+ EXPECT_TRUE(text16.contains(endNeedle16));
+ EXPECT_TRUE(text16.contains(middleNeedle16));
+ EXPECT_TRUE(text16.contains(emptyNeedle16));
+ EXPECT_FALSE(text16.contains(missingNeedle16));
+ EXPECT_FALSE(text16.contains(longNeedle16));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/util/TypeTraits.h b/tools/aapt2/util/TypeTraits.h
new file mode 100644
index 000000000000..76c13d615e41
--- /dev/null
+++ b/tools/aapt2/util/TypeTraits.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_UTIL_TYPETRAITS_H
+#define AAPT_UTIL_TYPETRAITS_H
+
+#include <type_traits>
+
+namespace aapt {
+
+#define DEFINE_HAS_BINARY_OP_TRAIT(name, op) \
+ template <typename T, typename U> \
+ struct name { \
+ template <typename V, typename W> \
+ static constexpr decltype(std::declval<V>() op std::declval<W>(), bool()) test(int) { \
+ return true; \
+ } \
+ template <typename V, typename W> \
+ static constexpr bool test(...) { \
+ return false; \
+ } \
+ static constexpr bool value = test<T, U>(int()); \
+}
+
+DEFINE_HAS_BINARY_OP_TRAIT(has_eq_op, ==);
+DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <);
+
+/**
+ * Type trait that checks if two types can be equated (==) and compared (<).
+ */
+template <typename T, typename U>
+struct is_comparable {
+ static constexpr bool value = has_eq_op<T, U>::value && has_lt_op<T, U>::value;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_UTIL_TYPETRAITS_H */
diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/util/Util.cpp
index 03ecd1aca310..5a87c334c59e 100644
--- a/tools/aapt2/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-#include "BigBuffer.h"
-#include "Maybe.h"
-#include "StringPiece.h"
-#include "Util.h"
+#include "util/BigBuffer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
#include <algorithm>
#include <ostream>
@@ -28,9 +28,6 @@
namespace aapt {
namespace util {
-constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
-constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
-
static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
const std::function<char(char)>& f) {
std::vector<std::string> parts;
@@ -76,6 +73,25 @@ StringPiece16 trimWhitespace(const StringPiece16& str) {
return StringPiece16(start, end - start);
}
+StringPiece trimWhitespace(const StringPiece& str) {
+ if (str.size() == 0 || str.data() == nullptr) {
+ return str;
+ }
+
+ const char* start = str.data();
+ const char* end = str.data() + str.length();
+
+ while (start != end && isspace(*start)) {
+ start++;
+ }
+
+ while (end != start && isspace(*(end - 1))) {
+ end--;
+ }
+
+ return StringPiece(start, end - start);
+}
+
StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
const StringPiece16& allowedChars) {
const auto endIter = str.end();
@@ -122,6 +138,29 @@ bool isJavaClassName(const StringPiece16& str) {
return pieces >= 2;
}
+bool isJavaPackageName(const StringPiece16& str) {
+ if (str.empty()) {
+ return false;
+ }
+
+ size_t pieces = 0;
+ for (const StringPiece16& piece : tokenize(str, u'.')) {
+ pieces++;
+ if (piece.empty()) {
+ return false;
+ }
+
+ if (piece.data()[0] == u'_' || piece.data()[piece.size() - 1] == u'_') {
+ return false;
+ }
+
+ if (findNonAlphaNumericAndNotInSet(piece, u"_") != piece.end()) {
+ return false;
+ }
+ }
+ return pieces >= 1;
+}
+
Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
const StringPiece16& className) {
if (className.empty()) {
@@ -136,10 +175,11 @@ Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
return {};
}
- std::u16string result(package.data(), package.size());
if (className.data()[0] != u'.') {
- result += u'.';
+ return {};
}
+
+ std::u16string result(package.data(), package.size());
result.append(className.data(), className.size());
if (!isJavaClassName(result)) {
return {};
@@ -147,6 +187,105 @@ Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
return result;
}
+static size_t consumeDigits(const char16_t* start, const char16_t* end) {
+ const char16_t* c = start;
+ for (; c != end && *c >= u'0' && *c <= u'9'; c++) {}
+ return static_cast<size_t>(c - start);
+}
+
+bool verifyJavaStringFormat(const StringPiece16& str) {
+ const char16_t* c = str.begin();
+ const char16_t* const end = str.end();
+
+ size_t argCount = 0;
+ bool nonpositional = false;
+ while (c != end) {
+ if (*c == u'%' && c + 1 < end) {
+ c++;
+
+ if (*c == u'%') {
+ c++;
+ continue;
+ }
+
+ argCount++;
+
+ size_t numDigits = consumeDigits(c, end);
+ if (numDigits > 0) {
+ c += numDigits;
+ if (c != end && *c != u'$') {
+ // The digits were a size, but not a positional argument.
+ nonpositional = true;
+ }
+ } else if (*c == u'<') {
+ // Reusing last argument, bad idea since positions can be moved around
+ // during translation.
+ nonpositional = true;
+
+ c++;
+
+ // Optionally we can have a $ after
+ if (c != end && *c == u'$') {
+ c++;
+ }
+ } else {
+ nonpositional = true;
+ }
+
+ // Ignore size, width, flags, etc.
+ while (c != end && (*c == u'-' ||
+ *c == u'#' ||
+ *c == u'+' ||
+ *c == u' ' ||
+ *c == u',' ||
+ *c == u'(' ||
+ (*c >= u'0' && *c <= '9'))) {
+ c++;
+ }
+
+ /*
+ * This is a shortcut to detect strings that are going to Time.format()
+ * instead of String.format()
+ *
+ * Comparison of String.format() and Time.format() args:
+ *
+ * String: ABC E GH ST X abcdefgh nost x
+ * Time: DEFGHKMS W Za d hkm s w yz
+ *
+ * Therefore we know it's definitely Time if we have:
+ * DFKMWZkmwyz
+ */
+ if (c != end) {
+ switch (*c) {
+ case 'D':
+ case 'F':
+ case 'K':
+ case 'M':
+ case 'W':
+ case 'Z':
+ case 'k':
+ case 'm':
+ case 'w':
+ case 'y':
+ case 'z':
+ return true;
+ }
+ }
+ }
+
+ if (c != end) {
+ c++;
+ }
+ }
+
+ if (argCount > 1 && nonpositional) {
+ // Multiple arguments were specified, but some or all were non positional. Translated
+ // strings may rearrange the order of the arguments, which will break the string.
+ return false;
+ }
+ return true;
+}
+
static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) {
char16_t code = 0;
for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
@@ -175,7 +314,51 @@ StringBuilder& StringBuilder::append(const StringPiece16& str) {
const char16_t* start = str.begin();
const char16_t* current = start;
while (current != end) {
- if (*current == u'"') {
+ if (mLastCharWasEscape) {
+ switch (*current) {
+ case u't':
+ mStr += u'\t';
+ break;
+ case u'n':
+ mStr += u'\n';
+ break;
+ case u'#':
+ mStr += u'#';
+ break;
+ case u'@':
+ mStr += u'@';
+ break;
+ case u'?':
+ mStr += u'?';
+ break;
+ case u'"':
+ mStr += u'"';
+ break;
+ case u'\'':
+ mStr += u'\'';
+ break;
+ case u'\\':
+ mStr += u'\\';
+ break;
+ case u'u': {
+ current++;
+ Maybe<char16_t> c = parseUnicodeCodepoint(&current, end);
+ if (!c) {
+ mError = "invalid unicode escape sequence";
+ return *this;
+ }
+ mStr += c.value();
+ current -= 1;
+ break;
+ }
+
+ default:
+ // Ignore.
+ break;
+ }
+ mLastCharWasEscape = false;
+ start = current + 1;
+ } else if (*current == u'"') {
if (!mQuote && mTrailingSpace) {
// We found an opening quote, and we have
// trailing space, so we should append that
@@ -208,52 +391,7 @@ StringBuilder& StringBuilder::append(const StringPiece16& str) {
}
mStr.append(start, current - start);
start = current + 1;
-
- current++;
- if (current != end) {
- switch (*current) {
- case u't':
- mStr += u'\t';
- break;
- case u'n':
- mStr += u'\n';
- break;
- case u'#':
- mStr += u'#';
- break;
- case u'@':
- mStr += u'@';
- break;
- case u'?':
- mStr += u'?';
- break;
- case u'"':
- mStr += u'"';
- break;
- case u'\'':
- mStr += u'\'';
- break;
- case u'\\':
- mStr += u'\\';
- break;
- case u'u': {
- current++;
- Maybe<char16_t> c = parseUnicodeCodepoint(&current, end);
- if (!c) {
- mError = "invalid unicode escape sequence";
- return *this;
- }
- mStr += c.value();
- current -= 1;
- break;
- }
-
- default:
- // Ignore.
- break;
- }
- start = current + 1;
- }
+ mLastCharWasEscape = true;
} else if (!mQuote) {
// This is not quoted text, so look for whitespace.
if (isspace16(*current)) {
@@ -303,8 +441,10 @@ std::string utf16ToUtf8(const StringPiece16& utf16) {
}
std::string utf8;
+ // Make room for '\0' explicitly.
+ utf8.resize(utf8Length + 1);
+ utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8Length + 1);
utf8.resize(utf8Length);
- utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin());
return utf8;
}
@@ -327,16 +467,28 @@ std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) {
return data;
}
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) {
- if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) {
- StringPiece16 schemaPrefix = kSchemaPrefix;
- StringPiece16 package = namespaceUri;
- return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size())
- .toString();
- } else if (namespaceUri == kSchemaAuto) {
- return std::u16string();
+bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
+ StringPiece16* outEntry, StringPiece16* outSuffix) {
+ if (!stringStartsWith<char16_t>(path, u"res/")) {
+ return false;
+ }
+
+ StringPiece16::const_iterator lastOccurence = path.end();
+ for (auto iter = path.begin() + StringPiece16(u"res/").size(); iter != path.end(); ++iter) {
+ if (*iter == u'/') {
+ lastOccurence = iter;
+ }
+ }
+
+ if (lastOccurence == path.end()) {
+ return false;
}
- return {};
+
+ auto iter = std::find(lastOccurence, path.end(), u'.');
+ *outSuffix = StringPiece16(iter, path.end() - iter);
+ *outEntry = StringPiece16(lastOccurence + 1, iter - lastOccurence - 1);
+ *outPrefix = StringPiece16(path.begin(), lastOccurence - path.begin() + 1);
+ return true;
}
} // namespace util
diff --git a/tools/aapt2/Util.h b/tools/aapt2/util/Util.h
index 9cdb152bf41f..0dacbd773488 100644
--- a/tools/aapt2/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -17,9 +17,9 @@
#ifndef AAPT_UTIL_H
#define AAPT_UTIL_H
-#include "BigBuffer.h"
-#include "Maybe.h"
-#include "StringPiece.h"
+#include "util/BigBuffer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
#include <androidfw/ResourceTypes.h>
#include <functional>
@@ -62,6 +62,8 @@ bool stringEndsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& s
*/
StringPiece16 trimWhitespace(const StringPiece16& str);
+StringPiece trimWhitespace(const StringPiece& str);
+
/**
* UTF-16 isspace(). It basically checks for lower range characters that are
* whitespace.
@@ -83,6 +85,11 @@ StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16
bool isJavaClassName(const StringPiece16& str);
/**
+ * Tests that the string is a valid Java package name.
+ */
+bool isJavaPackageName(const StringPiece16& str);
+
+/**
* Converts the class name to a fully qualified class name from the given `package`. Ex:
*
* asdf --> package.asdf
@@ -151,6 +158,23 @@ inline StringPiece16 getString(const android::ResStringPool& pool, size_t idx) {
return StringPiece16();
}
+inline StringPiece getString8(const android::ResStringPool& pool, size_t idx) {
+ size_t len;
+ const char* str = pool.string8At(idx, &len);
+ if (str != nullptr) {
+ return StringPiece(str, len);
+ }
+ return StringPiece();
+}
+
+/**
+ * Checks that the Java string format contains no non-positional arguments (arguments without
+ * explicitly specifying an index) when there are more than one argument. This is an error
+ * because translations may rearrange the order of the arguments in the string, which will
+ * break the string interpolation.
+ */
+bool verifyJavaStringFormat(const StringPiece16& str);
+
class StringBuilder {
public:
StringBuilder& append(const StringPiece16& str);
@@ -162,6 +186,7 @@ private:
std::u16string mStr;
bool mQuote = false;
bool mTrailingSpace = false;
+ bool mLastCharWasEscape = false;
std::string mError;
};
@@ -213,11 +238,12 @@ public:
private:
friend class Tokenizer<Char>;
- iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok);
+ iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end);
- BasicStringPiece<Char> str;
- Char separator;
- BasicStringPiece<Char> token;
+ BasicStringPiece<Char> mStr;
+ Char mSeparator;
+ BasicStringPiece<Char> mToken;
+ bool mEnd;
};
Tokenizer(BasicStringPiece<Char> str, Char sep);
@@ -236,36 +262,38 @@ inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) {
template <typename Char>
typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() {
- const Char* start = token.end();
- const Char* end = str.end();
+ const Char* start = mToken.end();
+ const Char* end = mStr.end();
if (start == end) {
- token.assign(token.end(), 0);
+ mEnd = true;
+ mToken.assign(mToken.end(), 0);
return *this;
}
start += 1;
const Char* current = start;
while (current != end) {
- if (*current == separator) {
- token.assign(start, current - start);
+ if (*current == mSeparator) {
+ mToken.assign(start, current - start);
return *this;
}
++current;
}
- token.assign(start, end - start);
+ mToken.assign(start, end - start);
return *this;
}
template <typename Char>
inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() {
- return token;
+ return mToken;
}
template <typename Char>
inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const {
// We check equality here a bit differently.
// We need to know that the addresses are the same.
- return token.begin() == rhs.token.begin() && token.end() == rhs.token.end();
+ return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() &&
+ mEnd == rhs.mEnd;
}
template <typename Char>
@@ -275,8 +303,8 @@ inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const {
template <typename Char>
inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep,
- BasicStringPiece<Char> tok) :
- str(s), separator(sep), token(tok) {
+ BasicStringPiece<Char> tok, bool end) :
+ mStr(s), mSeparator(sep), mToken(tok), mEnd(end) {
}
template <typename Char>
@@ -291,18 +319,37 @@ inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() {
template <typename Char>
inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) :
- mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))),
- mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) {
+ mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)),
+ mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) {
+}
+
+inline uint16_t hostToDevice16(uint16_t value) {
+ return htods(value);
+}
+
+inline uint32_t hostToDevice32(uint32_t value) {
+ return htodl(value);
+}
+
+inline uint16_t deviceToHost16(uint16_t value) {
+ return dtohs(value);
+}
+
+inline uint32_t deviceToHost32(uint32_t value) {
+ return dtohl(value);
}
/**
- * Returns a package name if the namespace URI is of the form:
- * http://schemas.android.com/apk/res/<package>
+ * Given a path like: res/xml-sw600dp/foo.xml
+ *
+ * Extracts "res/xml-sw600dp/" into outPrefix.
+ * Extracts "foo" into outEntry.
+ * Extracts ".xml" into outSuffix.
*
- * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
- * returns an empty package name.
+ * Returns true if successful.
*/
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri);
+bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
+ StringPiece16* outEntry, StringPiece16* outSuffix);
} // namespace util
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
new file mode 100644
index 000000000000..1e0c7fa9152d
--- /dev/null
+++ b/tools/aapt2/util/Util_test.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "test/Common.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+TEST(UtilTest, TrimOnlyWhitespace) {
+ const std::u16string full = u"\n ";
+
+ StringPiece16 trimmed = util::trimWhitespace(full);
+ EXPECT_TRUE(trimmed.empty());
+ EXPECT_EQ(0u, trimmed.size());
+}
+
+TEST(UtilTest, StringEndsWith) {
+ EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml"));
+}
+
+TEST(UtilTest, StringStartsWith) {
+ EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he"));
+}
+
+TEST(UtilTest, StringBuilderSplitEscapeSequence) {
+ EXPECT_EQ(StringPiece16(u"this is a new\nline."),
+ util::StringBuilder().append(u"this is a new\\")
+ .append(u"nline.")
+ .str());
+}
+
+TEST(UtilTest, StringBuilderWhitespaceRemoval) {
+ EXPECT_EQ(StringPiece16(u"hey guys this is so cool"),
+ util::StringBuilder().append(u" hey guys ")
+ .append(u" this is so cool ")
+ .str());
+
+ EXPECT_EQ(StringPiece16(u" wow, so many \t spaces. what?"),
+ util::StringBuilder().append(u" \" wow, so many \t ")
+ .append(u"spaces. \"what? ")
+ .str());
+
+ EXPECT_EQ(StringPiece16(u"where is the pie?"),
+ util::StringBuilder().append(u" where \t ")
+ .append(u" \nis the "" pie?")
+ .str());
+}
+
+TEST(UtilTest, StringBuilderEscaping) {
+ EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"),
+ util::StringBuilder().append(u" hey guys\\n ")
+ .append(u" this \\t is so\\\\ cool ")
+ .str());
+
+ EXPECT_EQ(StringPiece16(u"@?#\\\'"),
+ util::StringBuilder().append(u"\\@\\?\\#\\\\\\'")
+ .str());
+}
+
+TEST(UtilTest, StringBuilderMisplacedQuote) {
+ util::StringBuilder builder{};
+ EXPECT_FALSE(builder.append(u"they're coming!"));
+}
+
+TEST(UtilTest, StringBuilderUnicodeCodes) {
+ EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"),
+ util::StringBuilder().append(u"\\u00AF\\u0AF0 woah")
+ .str());
+
+ EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo"));
+}
+
+TEST(UtilTest, TokenizeInput) {
+ auto tokenizer = util::tokenize(StringPiece16(u"this| is|the|end"), u'|');
+ auto iter = tokenizer.begin();
+ ASSERT_EQ(*iter, StringPiece16(u"this"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece16(u" is"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece16(u"the"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece16(u"end"));
+ ++iter;
+ ASSERT_EQ(tokenizer.end(), iter);
+}
+
+TEST(UtilTest, TokenizeEmptyString) {
+ auto tokenizer = util::tokenize(StringPiece16(u""), u'|');
+ auto iter = tokenizer.begin();
+ ASSERT_NE(tokenizer.end(), iter);
+ ASSERT_EQ(StringPiece16(), *iter);
+ ++iter;
+ ASSERT_EQ(tokenizer.end(), iter);
+}
+
+TEST(UtilTest, TokenizeAtEnd) {
+ auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.');
+ auto iter = tokenizer.begin();
+ ASSERT_EQ(*iter, StringPiece16(u"one"));
+ ++iter;
+ ASSERT_NE(iter, tokenizer.end());
+ ASSERT_EQ(*iter, StringPiece16());
+}
+
+TEST(UtilTest, IsJavaClassName) {
+ EXPECT_TRUE(util::isJavaClassName(u"android.test.Class"));
+ EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner"));
+ EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class"));
+ EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_"));
+ EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner"));
+ EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$"));
+ EXPECT_FALSE(util::isJavaClassName(u".test.Class"));
+ EXPECT_FALSE(util::isJavaClassName(u"android"));
+}
+
+TEST(UtilTest, IsJavaPackageName) {
+ EXPECT_TRUE(util::isJavaPackageName(u"android"));
+ EXPECT_TRUE(util::isJavaPackageName(u"android.test"));
+ EXPECT_TRUE(util::isJavaPackageName(u"android.test_thing"));
+ EXPECT_FALSE(util::isJavaPackageName(u"_android"));
+ EXPECT_FALSE(util::isJavaPackageName(u"android_"));
+ EXPECT_FALSE(util::isJavaPackageName(u"android."));
+ EXPECT_FALSE(util::isJavaPackageName(u".android"));
+ EXPECT_FALSE(util::isJavaPackageName(u"android._test"));
+ EXPECT_FALSE(util::isJavaPackageName(u".."));
+}
+
+TEST(UtilTest, FullyQualifiedClassName) {
+ Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
+ AAPT_ASSERT_FALSE(res);
+
+ res = util::getFullyQualifiedClassName(u"android", u".asdf");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.asdf");
+
+ res = util::getFullyQualifiedClassName(u"android", u".a.b");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.a.b");
+
+ res = util::getFullyQualifiedClassName(u"android", u"a.b");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"a.b");
+
+ res = util::getFullyQualifiedClassName(u"", u"a.b");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"a.b");
+
+ res = util::getFullyQualifiedClassName(u"", u"");
+ AAPT_ASSERT_FALSE(res);
+
+ res = util::getFullyQualifiedClassName(u"android", u"./Apple");
+ AAPT_ASSERT_FALSE(res);
+}
+
+TEST(UtilTest, ExtractResourcePathComponents) {
+ StringPiece16 prefix, entry, suffix;
+ ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.xml", &prefix, &entry,
+ &suffix));
+ EXPECT_EQ(prefix, u"res/xml-sw600dp/");
+ EXPECT_EQ(entry, u"entry");
+ EXPECT_EQ(suffix, u".xml");
+
+ ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.9.png", &prefix, &entry,
+ &suffix));
+
+ EXPECT_EQ(prefix, u"res/xml-sw600dp/");
+ EXPECT_EQ(entry, u"entry");
+ EXPECT_EQ(suffix, u".9.png");
+
+ EXPECT_FALSE(util::extractResFilePathParts(u"AndroidManifest.xml", &prefix, &entry, &suffix));
+ EXPECT_FALSE(util::extractResFilePathParts(u"res/.xml", &prefix, &entry, &suffix));
+
+ ASSERT_TRUE(util::extractResFilePathParts(u"res//.", &prefix, &entry, &suffix));
+ EXPECT_EQ(prefix, u"res//");
+ EXPECT_EQ(entry, u"");
+ EXPECT_EQ(suffix, u".");
+}
+
+TEST(UtilTest, VerifyJavaStringFormat) {
+ ASSERT_TRUE(util::verifyJavaStringFormat(u"%09.34f"));
+ ASSERT_TRUE(util::verifyJavaStringFormat(u"%9$.34f %8$"));
+ ASSERT_TRUE(util::verifyJavaStringFormat(u"%% %%"));
+ ASSERT_FALSE(util::verifyJavaStringFormat(u"%09$f %f"));
+ ASSERT_FALSE(util::verifyJavaStringFormat(u"%09f %08s"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
new file mode 100644
index 000000000000..0ef67eaf3dc5
--- /dev/null
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "xml/XmlActionExecutor.h"
+
+namespace aapt {
+namespace xml {
+
+static bool wrapperOne(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) {
+ return f(el);
+}
+
+static bool wrapperTwo(XmlNodeAction::ActionFuncWithDiag& f, Element* el,
+ SourcePathDiagnostics* diag) {
+ return f(el, diag);
+}
+
+void XmlNodeAction::action(XmlNodeAction::ActionFunc f) {
+ mActions.emplace_back(std::bind(wrapperOne, std::move(f),
+ std::placeholders::_1,
+ std::placeholders::_2));
+}
+
+void XmlNodeAction::action(XmlNodeAction::ActionFuncWithDiag f) {
+ mActions.emplace_back(std::bind(wrapperTwo, std::move(f),
+ std::placeholders::_1,
+ std::placeholders::_2));
+}
+
+static void printElementToDiagMessage(const Element* el, DiagMessage* msg) {
+ *msg << "<";
+ if (!el->namespaceUri.empty()) {
+ *msg << el->namespaceUri << ":";
+ }
+ *msg << el->name << ">";
+}
+
+bool XmlNodeAction::execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag,
+ Element* el) const {
+ bool error = false;
+ for (const ActionFuncWithDiag& action : mActions) {
+ error |= !action(el, diag);
+ }
+
+ for (Element* childEl : el->getChildElements()) {
+ if (childEl->namespaceUri.empty()) {
+ std::map<std::u16string, XmlNodeAction>::const_iterator iter =
+ mMap.find(childEl->name);
+ if (iter != mMap.end()) {
+ error |= !iter->second.execute(policy, diag, childEl);
+ continue;
+ }
+ }
+
+ if (policy == XmlActionExecutorPolicy::Whitelist) {
+ DiagMessage errorMsg(childEl->lineNumber);
+ errorMsg << "unknown element ";
+ printElementToDiagMessage(childEl, &errorMsg);
+ errorMsg << " found";
+ diag->error(errorMsg);
+ error = true;
+ }
+ }
+ return !error;
+}
+
+bool XmlActionExecutor::execute(XmlActionExecutorPolicy policy, IDiagnostics* diag,
+ XmlResource* doc) const {
+ SourcePathDiagnostics sourceDiag(doc->file.source, diag);
+
+ Element* el = findRootElement(doc);
+ if (!el) {
+ if (policy == XmlActionExecutorPolicy::Whitelist) {
+ sourceDiag.error(DiagMessage() << "no root XML tag found");
+ return false;
+ }
+ return true;
+ }
+
+ if (el->namespaceUri.empty()) {
+ std::map<std::u16string, XmlNodeAction>::const_iterator iter = mMap.find(el->name);
+ if (iter != mMap.end()) {
+ return iter->second.execute(policy, &sourceDiag, el);
+ }
+ }
+
+ if (policy == XmlActionExecutorPolicy::Whitelist) {
+ DiagMessage errorMsg(el->lineNumber);
+ errorMsg << "unknown element ";
+ printElementToDiagMessage(el, &errorMsg);
+ errorMsg << " found";
+ sourceDiag.error(errorMsg);
+ return false;
+ }
+ return true;
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
new file mode 100644
index 000000000000..36b94dbfde05
--- /dev/null
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XML_XMLPATTERN_H
+#define AAPT_XML_XMLPATTERN_H
+
+#include "Diagnostics.h"
+#include "xml/XmlDom.h"
+
+#include <android-base/macros.h>
+#include <functional>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace xml {
+
+enum class XmlActionExecutorPolicy {
+ /**
+ * Actions on run if elements are matched, errors occur only when actions return false.
+ */
+ None,
+
+ /**
+ * The actions defined must match and run. If an element is found that does not match
+ * an action, an error occurs.
+ */
+ Whitelist,
+};
+
+/**
+ * Contains the actions to perform at this XML node. This is a recursive data structure that
+ * holds XmlNodeActions for child XML nodes.
+ */
+class XmlNodeAction {
+public:
+ using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>;
+ using ActionFunc = std::function<bool(Element*)>;
+
+ /**
+ * Find or create a child XmlNodeAction that will be performed for the child element
+ * with the name `name`.
+ */
+ XmlNodeAction& operator[](const std::u16string& name) {
+ return mMap[name];
+ }
+
+ /**
+ * Add an action to be performed at this XmlNodeAction.
+ */
+ void action(ActionFunc f);
+ void action(ActionFuncWithDiag);
+
+private:
+ friend class XmlActionExecutor;
+
+ bool execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, Element* el) const;
+
+ std::map<std::u16string, XmlNodeAction> mMap;
+ std::vector<ActionFuncWithDiag> mActions;
+};
+
+/**
+ * Allows the definition of actions to execute at specific XML elements defined by their
+ * hierarchy.
+ */
+class XmlActionExecutor {
+public:
+ XmlActionExecutor() = default;
+
+ /**
+ * Find or create a root XmlNodeAction that will be performed for the root XML element
+ * with the name `name`.
+ */
+ XmlNodeAction& operator[](const std::u16string& name) {
+ return mMap[name];
+ }
+
+ /**
+ * Execute the defined actions for this XmlResource.
+ * Returns true if all actions return true, otherwise returns false.
+ */
+ bool execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, XmlResource* doc) const;
+
+private:
+ std::map<std::u16string, XmlNodeAction> mMap;
+
+ DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor);
+};
+
+} // namespace xml
+} // namespace aapt
+
+#endif /* AAPT_XML_XMLPATTERN_H */
diff --git a/tools/aapt2/xml/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp
new file mode 100644
index 000000000000..ebf287a251f2
--- /dev/null
+++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "test/Test.h"
+#include "xml/XmlActionExecutor.h"
+
+namespace aapt {
+namespace xml {
+
+TEST(XmlActionExecutorTest, BuildsAccessibleNestedPattern) {
+ XmlActionExecutor executor;
+ XmlNodeAction& manifestAction = executor[u"manifest"];
+ XmlNodeAction& applicationAction = manifestAction[u"application"];
+
+ Element* manifestEl = nullptr;
+ manifestAction.action([&](Element* manifest) -> bool {
+ manifestEl = manifest;
+ return true;
+ });
+
+ Element* applicationEl = nullptr;
+ applicationAction.action([&](Element* application) -> bool {
+ applicationEl = application;
+ return true;
+ });
+
+ std::unique_ptr<XmlResource> doc = test::buildXmlDom("<manifest><application /></manifest>");
+
+ StdErrDiagnostics diag;
+ ASSERT_TRUE(executor.execute(XmlActionExecutorPolicy::None, &diag, doc.get()));
+ ASSERT_NE(nullptr, manifestEl);
+ EXPECT_EQ(std::u16string(u"manifest"), manifestEl->name);
+
+ ASSERT_NE(nullptr, applicationEl);
+ EXPECT_EQ(std::u16string(u"application"), applicationEl->name);
+}
+
+TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) {
+ XmlActionExecutor executor;
+ executor[u"manifest"][u"application"];
+
+ std::unique_ptr<XmlResource> doc = test::buildXmlDom(
+ "<manifest><application /><activity /></manifest>");
+ StdErrDiagnostics diag;
+ ASSERT_FALSE(executor.execute(XmlActionExecutorPolicy::Whitelist, &diag, doc.get()));
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 763029fd4157..0ce333af3115 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-#include "Logger.h"
-#include "Util.h"
#include "XmlDom.h"
#include "XmlPullParser.h"
+#include "util/Util.h"
#include <cassert>
+#include <expat.h>
#include <memory>
#include <stack>
#include <string>
@@ -65,7 +65,7 @@ static void addToStack(Stack* stack, XML_Parser parser, std::unique_ptr<Node> no
stack->root = std::move(node);
}
- if (thisNode->type != NodeType::kText) {
+ if (!nodeCast<Text>(thisNode)) {
stack->nodeStack.push(thisNode);
}
}
@@ -126,7 +126,7 @@ static void XMLCALL endElementHandler(void* userData, const char* name) {
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
assert(!stack->nodeStack.empty());
- stack->nodeStack.top()->comment = std::move(stack->pendingComment);
+ //stack->nodeStack.top()->comment = std::move(stack->pendingComment);
stack->nodeStack.pop();
}
@@ -143,8 +143,7 @@ static void XMLCALL characterDataHandler(void* userData, const char* s, int len)
Node* currentParent = stack->nodeStack.top();
if (!currentParent->children.empty()) {
Node* lastChild = currentParent->children.back().get();
- if (lastChild->type == NodeType::kText) {
- Text* text = static_cast<Text*>(lastChild);
+ if (Text* text = nodeCast<Text>(lastChild)) {
text->text += util::utf8ToUtf16(StringPiece(s, len));
return;
}
@@ -166,7 +165,7 @@ static void XMLCALL commentDataHandler(void* userData, const char* comment) {
stack->pendingComment += util::utf8ToUtf16(comment);
}
-std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) {
+std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) {
Stack stack;
XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
@@ -182,20 +181,23 @@ std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) {
in->read(buffer, sizeof(buffer) / sizeof(buffer[0]));
if (in->bad() && !in->eof()) {
stack.root = {};
- logger->error() << strerror(errno) << std::endl;
+ diag->error(DiagMessage(source) << strerror(errno));
break;
}
if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) {
stack.root = {};
- logger->error(XML_GetCurrentLineNumber(parser))
- << XML_ErrorString(XML_GetErrorCode(parser)) << std::endl;
+ diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser)))
+ << XML_ErrorString(XML_GetErrorCode(parser)));
break;
}
}
XML_ParserFree(parser);
- return std::move(stack.root);
+ if (stack.root) {
+ return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root));
+ }
+ return {};
}
static void copyAttributes(Element* el, android::ResXMLParser* parser) {
@@ -224,21 +226,26 @@ static void copyAttributes(Element* el, android::ResXMLParser* parser) {
}
}
-std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger) {
+std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
+ const Source& source) {
+ // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
+ // causes errors when qualifying it with android::
+ using namespace android;
+
std::unique_ptr<Node> root;
std::stack<Node*> nodeStack;
- android::ResXMLTree tree;
- if (tree.setTo(data, dataLen) != android::NO_ERROR) {
+ ResXMLTree tree;
+ if (tree.setTo(data, dataLen) != NO_ERROR) {
return {};
}
- android::ResXMLParser::event_code_t code;
- while ((code = tree.next()) != android::ResXMLParser::BAD_DOCUMENT &&
- code != android::ResXMLParser::END_DOCUMENT) {
+ ResXMLParser::event_code_t code;
+ while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT &&
+ code != ResXMLParser::END_DOCUMENT) {
std::unique_ptr<Node> newNode;
switch (code) {
- case android::ResXMLParser::START_NAMESPACE: {
+ case ResXMLParser::START_NAMESPACE: {
std::unique_ptr<Namespace> node = util::make_unique<Namespace>();
size_t len;
const char16_t* str16 = tree.getNamespacePrefix(&len);
@@ -254,7 +261,7 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo
break;
}
- case android::ResXMLParser::START_TAG: {
+ case ResXMLParser::START_TAG: {
std::unique_ptr<Element> node = util::make_unique<Element>();
size_t len;
const char16_t* str16 = tree.getElementNamespace(&len);
@@ -273,7 +280,7 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo
break;
}
- case android::ResXMLParser::TEXT: {
+ case ResXMLParser::TEXT: {
std::unique_ptr<Text> node = util::make_unique<Text>();
size_t len;
const char16_t* str16 = tree.getText(&len);
@@ -284,8 +291,8 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo
break;
}
- case android::ResXMLParser::END_NAMESPACE:
- case android::ResXMLParser::END_TAG:
+ case ResXMLParser::END_NAMESPACE:
+ case ResXMLParser::END_TAG:
assert(!nodeStack.empty());
nodeStack.pop();
break;
@@ -307,53 +314,37 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo
nodeStack.top()->addChild(std::move(newNode));
}
- if (thisNode->type != NodeType::kText) {
+ if (!nodeCast<Text>(thisNode)) {
nodeStack.push(thisNode);
}
}
}
- return std::move(root);
-}
-
-Node::Node(NodeType type) : type(type), parent(nullptr), lineNumber(0), columnNumber(0) {
-}
-
-void Node::addChild(std::unique_ptr<Node> child) {
- child->parent = this;
- children.push_back(std::move(child));
+ return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
}
-Namespace::Namespace() : BaseNode(NodeType::kNamespace) {
+Element* findRootElement(XmlResource* doc) {
+ return findRootElement(doc->root.get());
}
-std::unique_ptr<Node> Namespace::clone() const {
- Namespace* ns = new Namespace();
- ns->lineNumber = lineNumber;
- ns->columnNumber = columnNumber;
- ns->comment = comment;
- ns->namespacePrefix = namespacePrefix;
- ns->namespaceUri = namespaceUri;
- for (auto& child : children) {
- ns->addChild(child->clone());
+Element* findRootElement(Node* node) {
+ if (!node) {
+ return nullptr;
}
- return std::unique_ptr<Node>(ns);
-}
-Element::Element() : BaseNode(NodeType::kElement) {
+ Element* el = nullptr;
+ while ((el = nodeCast<Element>(node)) == nullptr) {
+ if (node->children.empty()) {
+ return nullptr;
+ }
+ // We are looking for the first element, and namespaces can only have one child.
+ node = node->children.front().get();
+ }
+ return el;
}
-std::unique_ptr<Node> Element::clone() const {
- Element* el = new Element();
- el->lineNumber = lineNumber;
- el->columnNumber = columnNumber;
- el->comment = comment;
- el->namespaceUri = namespaceUri;
- el->name = name;
- el->attributes = attributes;
- for (auto& child : children) {
- el->addChild(child->clone());
- }
- return std::unique_ptr<Node>(el);
+void Node::addChild(std::unique_ptr<Node> child) {
+ child->parent = this;
+ children.push_back(std::move(child));
}
Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) {
@@ -366,29 +357,29 @@ Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16&
}
Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) {
- return findChildWithAttribute(ns, name, nullptr);
+ return findChildWithAttribute(ns, name, {}, {}, {});
}
Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
- const Attribute* reqAttr) {
+ const StringPiece16& attrNs, const StringPiece16& attrName,
+ const StringPiece16& attrValue) {
for (auto& childNode : children) {
Node* child = childNode.get();
- while (child->type == NodeType::kNamespace) {
+ while (nodeCast<Namespace>(child)) {
if (child->children.empty()) {
break;
}
child = child->children[0].get();
}
- if (child->type == NodeType::kElement) {
- Element* el = static_cast<Element*>(child);
+ if (Element* el = nodeCast<Element>(child)) {
if (ns == el->namespaceUri && name == el->name) {
- if (!reqAttr) {
+ if (attrNs.empty() && attrName.empty()) {
return el;
}
- Attribute* attrName = el->findAttribute(reqAttr->namespaceUri, reqAttr->name);
- if (attrName && attrName->value == reqAttr->value) {
+ Attribute* attr = el->findAttribute(attrNs, attrName);
+ if (attr && attrValue == attr->value) {
return el;
}
}
@@ -401,30 +392,52 @@ std::vector<Element*> Element::getChildElements() {
std::vector<Element*> elements;
for (auto& childNode : children) {
Node* child = childNode.get();
- while (child->type == NodeType::kNamespace) {
+ while (nodeCast<Namespace>(child)) {
if (child->children.empty()) {
break;
}
child = child->children[0].get();
}
- if (child->type == NodeType::kElement) {
- elements.push_back(static_cast<Element*>(child));
+ if (Element* el = nodeCast<Element>(child)) {
+ elements.push_back(el);
}
}
return elements;
}
-Text::Text() : BaseNode(NodeType::kText) {
+void PackageAwareVisitor::visit(Namespace* ns) {
+ bool added = false;
+ if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) {
+ ExtractedPackage& package = maybePackage.value();
+ mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, std::move(package) });
+ added = true;
+ }
+
+ Visitor::visit(ns);
+
+ if (added) {
+ mPackageDecls.pop_back();
+ }
}
-std::unique_ptr<Node> Text::clone() const {
- Text* el = new Text();
- el->lineNumber = lineNumber;
- el->columnNumber = columnNumber;
- el->comment = comment;
- el->text = text;
- return std::unique_ptr<Node>(el);
+Maybe<ExtractedPackage> PackageAwareVisitor::transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const {
+ if (alias.empty()) {
+ return ExtractedPackage{ localPackage.toString(), false /* private */ };
+ }
+
+ const auto rend = mPackageDecls.rend();
+ for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{ localPackage.toString(),
+ iter->package.privateNamespace };
+ }
+ return iter->package;
+ }
+ }
+ return {};
}
} // namespace xml
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
new file mode 100644
index 000000000000..b374d20039a5
--- /dev/null
+++ b/tools/aapt2/xml/XmlDom.h
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XML_DOM_H
+#define AAPT_XML_DOM_H
+
+#include "Diagnostics.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
+
+#include <istream>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace xml {
+
+struct RawVisitor;
+
+/**
+ * Base class for all XML nodes.
+ */
+struct Node {
+ Node* parent = nullptr;
+ size_t lineNumber = 0;
+ size_t columnNumber = 0;
+ std::u16string comment;
+ std::vector<std::unique_ptr<Node>> children;
+
+ virtual ~Node() = default;
+
+ void addChild(std::unique_ptr<Node> child);
+ virtual void accept(RawVisitor* visitor) = 0;
+};
+
+/**
+ * Base class that implements the visitor methods for a
+ * subclass of Node.
+ */
+template <typename Derived>
+struct BaseNode : public Node {
+ virtual void accept(RawVisitor* visitor) override;
+};
+
+/**
+ * A Namespace XML node. Can only have one child.
+ */
+struct Namespace : public BaseNode<Namespace> {
+ std::u16string namespacePrefix;
+ std::u16string namespaceUri;
+};
+
+struct AaptAttribute {
+ Maybe<ResourceId> id;
+ aapt::Attribute attribute;
+};
+
+/**
+ * An XML attribute.
+ */
+struct Attribute {
+ std::u16string namespaceUri;
+ std::u16string name;
+ std::u16string value;
+
+ Maybe<AaptAttribute> compiledAttribute;
+ std::unique_ptr<Item> compiledValue;
+};
+
+/**
+ * An Element XML node.
+ */
+struct Element : public BaseNode<Element> {
+ std::u16string namespaceUri;
+ std::u16string name;
+ std::vector<Attribute> attributes;
+
+ Attribute* findAttribute(const StringPiece16& ns, const StringPiece16& name);
+ xml::Element* findChild(const StringPiece16& ns, const StringPiece16& name);
+ xml::Element* findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
+ const StringPiece16& attrNs,
+ const StringPiece16& attrName,
+ const StringPiece16& attrValue);
+ std::vector<xml::Element*> getChildElements();
+};
+
+/**
+ * A Text (CDATA) XML node. Can not have any children.
+ */
+struct Text : public BaseNode<Text> {
+ std::u16string text;
+};
+
+/**
+ * An XML resource with a source, name, and XML tree.
+ */
+struct XmlResource {
+ ResourceFile file;
+ std::unique_ptr<xml::Node> root;
+};
+
+/**
+ * Inflates an XML DOM from a text stream, logging errors to the logger.
+ * Returns the root node on success, or nullptr on failure.
+ */
+std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source);
+
+/**
+ * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
+ * Returns the root node on success, or nullptr on failure.
+ */
+std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
+ const Source& source);
+
+Element* findRootElement(XmlResource* doc);
+Element* findRootElement(Node* node);
+
+/**
+ * A visitor interface for the different XML Node subtypes. This will not traverse into
+ * children. Use Visitor for that.
+ */
+struct RawVisitor {
+ virtual ~RawVisitor() = default;
+
+ virtual void visit(Namespace* node) {}
+ virtual void visit(Element* node) {}
+ virtual void visit(Text* text) {}
+};
+
+/**
+ * Visitor whose default implementation visits the children nodes of any node.
+ */
+struct Visitor : public RawVisitor {
+ using RawVisitor::visit;
+
+ void visit(Namespace* node) override {
+ visitChildren(node);
+ }
+
+ void visit(Element* node) override {
+ visitChildren(node);
+ }
+
+ void visit(Text* text) override {
+ visitChildren(text);
+ }
+
+ void visitChildren(Node* node) {
+ for (auto& child : node->children) {
+ child->accept(this);
+ }
+ }
+};
+
+/**
+ * An XML DOM visitor that will record the package name for a namespace prefix.
+ */
+class PackageAwareVisitor : public Visitor, public IPackageDeclStack {
+private:
+ struct PackageDecl {
+ std::u16string prefix;
+ ExtractedPackage package;
+ };
+
+ std::vector<PackageDecl> mPackageDecls;
+
+public:
+ using Visitor::visit;
+
+ void visit(Namespace* ns) override;
+ Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const override;
+};
+
+// Implementations
+
+template <typename Derived>
+void BaseNode<Derived>::accept(RawVisitor* visitor) {
+ visitor->visit(static_cast<Derived*>(this));
+}
+
+template <typename T>
+struct NodeCastImpl : public RawVisitor {
+ using RawVisitor::visit;
+
+ T* value = nullptr;
+
+ void visit(T* v) override {
+ value = v;
+ }
+};
+
+template <typename T>
+T* nodeCast(Node* node) {
+ NodeCastImpl<T> visitor;
+ node->accept(&visitor);
+ return visitor.value;
+}
+
+} // namespace xml
+} // namespace aapt
+
+#endif // AAPT_XML_DOM_H
diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 021714410e75..431ee2c8fb46 100644
--- a/tools/aapt2/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "XmlDom.h"
+#include "xml/XmlDom.h"
#include <gtest/gtest.h>
#include <sstream>
@@ -36,12 +36,13 @@ TEST(XmlDomTest, Inflate) {
</Layout>
)EOF";
- SourceLogger logger(Source{ "/test/path" });
- std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
- ASSERT_NE(root, nullptr);
+ const Source source = { "test.xml" };
+ StdErrDiagnostics diag;
+ std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source);
+ ASSERT_NE(doc, nullptr);
- EXPECT_EQ(root->type, xml::NodeType::kNamespace);
- xml::Namespace* ns = static_cast<xml::Namespace*>(root.get());
+ xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get());
+ ASSERT_NE(ns, nullptr);
EXPECT_EQ(ns->namespaceUri, u"http://schemas.android.com/apk/res/android");
EXPECT_EQ(ns->namespacePrefix, u"android");
}
diff --git a/tools/aapt2/SourceXmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index 8099044f616d..323ec05b5f2c 100644
--- a/tools/aapt2/SourceXmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -14,18 +14,20 @@
* limitations under the License.
*/
+#include "util/Maybe.h"
+#include "util/Util.h"
+#include "xml/XmlPullParser.h"
+#include "xml/XmlUtil.h"
+
#include <iostream>
#include <string>
-#include "Maybe.h"
-#include "SourceXmlPullParser.h"
-#include "Util.h"
-
namespace aapt {
+namespace xml {
constexpr char kXmlNamespaceSep = 1;
-SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
+XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
XML_SetUserData(mParser, this);
XML_SetElementHandler(mParser, startElementHandler, endElementHandler);
@@ -35,11 +37,11 @@ SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(),
mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ });
}
-SourceXmlPullParser::~SourceXmlPullParser() {
+XmlPullParser::~XmlPullParser() {
XML_ParserFree(mParser);
}
-SourceXmlPullParser::Event SourceXmlPullParser::next() {
+XmlPullParser::Event XmlPullParser::next() {
const Event currentEvent = getEvent();
if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {
return currentEvent;
@@ -72,14 +74,14 @@ SourceXmlPullParser::Event SourceXmlPullParser::next() {
// Record namespace prefixes and package names so that we can do our own
// handling of references that use namespace aliases.
if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
- Maybe<std::u16string> result = util::extractPackageFromNamespace(getNamespaceUri());
+ Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri());
if (event == Event::kStartNamespace) {
if (result) {
- mPackageAliases.emplace_back(getNamespacePrefix(), result.value());
+ mPackageAliases.emplace_back(
+ PackageDecl{ getNamespacePrefix(), std::move(result.value()) });
}
} else {
if (result) {
- assert(mPackageAliases.back().second == result.value());
mPackageAliases.pop_back();
}
}
@@ -88,34 +90,34 @@ SourceXmlPullParser::Event SourceXmlPullParser::next() {
return event;
}
-SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const {
+XmlPullParser::Event XmlPullParser::getEvent() const {
return mEventQueue.front().event;
}
-const std::string& SourceXmlPullParser::getLastError() const {
+const std::string& XmlPullParser::getLastError() const {
return mLastError;
}
-const std::u16string& SourceXmlPullParser::getComment() const {
- return mEventQueue.front().comment;
+const std::u16string& XmlPullParser::getComment() const {
+ return mEventQueue.front().data1;
}
-size_t SourceXmlPullParser::getLineNumber() const {
+size_t XmlPullParser::getLineNumber() const {
return mEventQueue.front().lineNumber;
}
-size_t SourceXmlPullParser::getDepth() const {
+size_t XmlPullParser::getDepth() const {
return mEventQueue.front().depth;
}
-const std::u16string& SourceXmlPullParser::getText() const {
+const std::u16string& XmlPullParser::getText() const {
if (getEvent() != Event::kText) {
return mEmpty;
}
return mEventQueue.front().data1;
}
-const std::u16string& SourceXmlPullParser::getNamespacePrefix() const {
+const std::u16string& XmlPullParser::getNamespacePrefix() const {
const Event currentEvent = getEvent();
if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
return mEmpty;
@@ -123,7 +125,7 @@ const std::u16string& SourceXmlPullParser::getNamespacePrefix() const {
return mEventQueue.front().data1;
}
-const std::u16string& SourceXmlPullParser::getNamespaceUri() const {
+const std::u16string& XmlPullParser::getNamespaceUri() const {
const Event currentEvent = getEvent();
if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
return mEmpty;
@@ -131,23 +133,26 @@ const std::u16string& SourceXmlPullParser::getNamespaceUri() const {
return mEventQueue.front().data2;
}
-bool SourceXmlPullParser::applyPackageAlias(std::u16string* package,
- const std::u16string& defaultPackage) const {
+Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const {
+ if (alias.empty()) {
+ return ExtractedPackage{ localPackage.toString(), false /* private */ };
+ }
+
const auto endIter = mPackageAliases.rend();
for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
- if (iter->first == *package) {
- if (iter->second.empty()) {
- *package = defaultPackage;
- } else {
- *package = iter->second;
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{ localPackage.toString(),
+ iter->package.privateNamespace };
}
- return true;
+ return iter->package;
}
}
- return false;
+ return {};
}
-const std::u16string& SourceXmlPullParser::getElementNamespace() const {
+const std::u16string& XmlPullParser::getElementNamespace() const {
const Event currentEvent = getEvent();
if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
return mEmpty;
@@ -155,7 +160,7 @@ const std::u16string& SourceXmlPullParser::getElementNamespace() const {
return mEventQueue.front().data1;
}
-const std::u16string& SourceXmlPullParser::getElementName() const {
+const std::u16string& XmlPullParser::getElementName() const {
const Event currentEvent = getEvent();
if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
return mEmpty;
@@ -163,15 +168,15 @@ const std::u16string& SourceXmlPullParser::getElementName() const {
return mEventQueue.front().data2;
}
-XmlPullParser::const_iterator SourceXmlPullParser::beginAttributes() const {
+XmlPullParser::const_iterator XmlPullParser::beginAttributes() const {
return mEventQueue.front().attributes.begin();
}
-XmlPullParser::const_iterator SourceXmlPullParser::endAttributes() const {
+XmlPullParser::const_iterator XmlPullParser::endAttributes() const {
return mEventQueue.front().attributes.end();
}
-size_t SourceXmlPullParser::getAttributeCount() const {
+size_t XmlPullParser::getAttributeCount() const {
if (getEvent() != Event::kStartElement) {
return 0;
}
@@ -196,9 +201,9 @@ static void splitName(const char* name, std::u16string& outNs, std::u16string& o
}
}
-void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
+void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
const char* uri) {
- SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string();
parser->mNamespaceUris.push(namespaceUri);
parser->mEventQueue.push(EventData{
@@ -210,9 +215,9 @@ void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const ch
});
}
-void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char* name,
+void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name,
const char** attrs) {
- SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
EventData data = {
Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++
@@ -233,8 +238,8 @@ void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char
parser->mEventQueue.push(std::move(data));
}
-void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
- SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
parser->mEventQueue.push(EventData{
Event::kText,
@@ -244,8 +249,8 @@ void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const cha
});
}
-void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* name) {
- SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
EventData data = {
Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth)
@@ -256,8 +261,8 @@ void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char*
parser->mEventQueue.push(std::move(data));
}
-void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
- SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
parser->mEventQueue.push(EventData{
Event::kEndNamespace,
@@ -269,8 +274,8 @@ void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char
parser->mNamespaceUris.pop();
}
-void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char* comment) {
- SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
parser->mEventQueue.push(EventData{
Event::kComment,
@@ -280,4 +285,24 @@ void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char*
});
}
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name) {
+ auto iter = parser->findAttribute(u"", name);
+ if (iter != parser->endAttributes()) {
+ return StringPiece16(util::trimWhitespace(iter->value));
+ }
+ return {};
+}
+
+Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name) {
+ auto iter = parser->findAttribute(u"", name);
+ if (iter != parser->endAttributes()) {
+ StringPiece16 trimmed = util::trimWhitespace(iter->value);
+ if (!trimmed.empty()) {
+ return trimmed;
+ }
+ }
+ return {};
+}
+
+} // namespace xml
} // namespace aapt
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index accfd30a4775..7e7070e5e5ea 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -17,16 +17,25 @@
#ifndef AAPT_XML_PULL_PARSER_H
#define AAPT_XML_PULL_PARSER_H
+#include "Resource.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "xml/XmlUtil.h"
+
#include <algorithm>
+#include <expat.h>
+#include <istream>
#include <ostream>
+#include <queue>
+#include <stack>
#include <string>
#include <vector>
-#include "StringPiece.h"
-
namespace aapt {
+namespace xml {
-class XmlPullParser {
+class XmlPullParser : public IPackageDeclStack {
public:
enum class Event {
kBadDocument,
@@ -41,43 +50,58 @@ public:
kComment,
};
- static void skipCurrentElement(XmlPullParser* parser);
+ /**
+ * Skips to the next direct descendant node of the given startDepth,
+ * skipping namespace nodes.
+ *
+ * When nextChildNode returns true, you can expect Comments, Text, and StartElement events.
+ */
+ static bool nextChildNode(XmlPullParser* parser, size_t startDepth);
+ static bool skipCurrentElement(XmlPullParser* parser);
static bool isGoodEvent(Event event);
- virtual ~XmlPullParser() {}
+ XmlPullParser(std::istream& in);
+ ~XmlPullParser();
/**
* Returns the current event that is being processed.
*/
- virtual Event getEvent() const = 0;
+ Event getEvent() const;
- virtual const std::string& getLastError() const = 0;
+ const std::string& getLastError() const;
/**
* Note, unlike XmlPullParser, the first call to next() will return
* StartElement of the first element.
*/
- virtual Event next() = 0;
+ Event next();
//
// These are available for all nodes.
//
- virtual const std::u16string& getComment() const = 0;
- virtual size_t getLineNumber() const = 0;
- virtual size_t getDepth() const = 0;
+ const std::u16string& getComment() const;
+ size_t getLineNumber() const;
+ size_t getDepth() const;
/**
* Returns the character data for a Text event.
*/
- virtual const std::u16string& getText() const = 0;
+ const std::u16string& getText() const;
//
// Namespace prefix and URI are available for StartNamespace and EndNamespace.
//
- virtual const std::u16string& getNamespacePrefix() const = 0;
- virtual const std::u16string& getNamespaceUri() const = 0;
+ const std::u16string& getNamespacePrefix() const;
+ const std::u16string& getNamespaceUri() const;
+
+ //
+ // These are available for StartElement and EndElement.
+ //
+
+ const std::u16string& getElementNamespace() const;
+ const std::u16string& getElementName() const;
/*
* Uses the current stack of namespaces to resolve the package. Eg:
@@ -90,15 +114,8 @@ public:
* If xmlns:app="http://schemas.android.com/apk/res-auto", then
* 'package' will be set to 'defaultPackage'.
*/
- virtual bool applyPackageAlias(std::u16string* package,
- const std::u16string& defaultPackage) const = 0;
-
- //
- // These are available for StartElement and EndElement.
- //
-
- virtual const std::u16string& getElementNamespace() const = 0;
- virtual const std::u16string& getElementName() const = 0;
+ Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const override;
//
// Remaining methods are for retrieving information about attributes
@@ -121,12 +138,55 @@ public:
using const_iterator = std::vector<Attribute>::const_iterator;
- virtual const_iterator beginAttributes() const = 0;
- virtual const_iterator endAttributes() const = 0;
- virtual size_t getAttributeCount() const = 0;
+ const_iterator beginAttributes() const;
+ const_iterator endAttributes() const;
+ size_t getAttributeCount() const;
const_iterator findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const;
+
+private:
+ static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
+ static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
+ static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
+ static void XMLCALL endElementHandler(void* userData, const char* name);
+ static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
+ static void XMLCALL commentDataHandler(void* userData, const char* comment);
+
+ struct EventData {
+ Event event;
+ size_t lineNumber;
+ size_t depth;
+ std::u16string data1;
+ std::u16string data2;
+ std::vector<Attribute> attributes;
+ };
+
+ std::istream& mIn;
+ XML_Parser mParser;
+ char mBuffer[16384];
+ std::queue<EventData> mEventQueue;
+ std::string mLastError;
+ const std::u16string mEmpty;
+ size_t mDepth;
+ std::stack<std::u16string> mNamespaceUris;
+
+ struct PackageDecl {
+ std::u16string prefix;
+ ExtractedPackage package;
+ };
+ std::vector<PackageDecl> mPackageAliases;
};
+/**
+ * Finds the attribute in the current element within the global namespace.
+ */
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
+/**
+ * Finds the attribute in the current element within the global namespace. The attribute's value
+ * must not be the empty string.
+ */
+Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
//
// Implementation
//
@@ -146,13 +206,35 @@ inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event even
return out;
}
-inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
+inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) {
+ Event event;
+
+ // First get back to the start depth.
+ while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {}
+
+ // Now look for the first good node.
+ while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) {
+ switch (event) {
+ case Event::kText:
+ case Event::kComment:
+ case Event::kStartElement:
+ return true;
+ default:
+ break;
+ }
+ event = parser->next();
+ }
+ return false;
+}
+
+inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
int depth = 1;
while (depth > 0) {
switch (parser->next()) {
case Event::kEndDocument:
+ return true;
case Event::kBadDocument:
- return;
+ return false;
case Event::kStartElement:
depth++;
break;
@@ -163,6 +245,7 @@ inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
break;
}
}
+ return true;
}
inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) {
@@ -209,6 +292,7 @@ inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece16
return endIter;
}
+} // namespace xml
} // namespace aapt
#endif // AAPT_XML_PULL_PARSER_H
diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
new file mode 100644
index 000000000000..8fa2c6d274c8
--- /dev/null
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "util/StringPiece.h"
+#include "xml/XmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+
+namespace aapt {
+
+TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) {
+ std::stringstream str;
+ str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>";
+ xml::XmlPullParser parser(str);
+
+ const size_t depthOuter = parser.getDepth();
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
+
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
+ EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName()));
+
+ const size_t depthA = parser.getDepth();
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthA));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
+ EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName()));
+
+ const size_t depthB = parser.getDepth();
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
+ EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName()));
+
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
+ EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName()));
+
+ ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
+ EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
new file mode 100644
index 000000000000..ab9f544d67ea
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "util/Maybe.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri) {
+ if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPublicPrefix)) {
+ StringPiece16 schemaPrefix = kSchemaPublicPrefix;
+ StringPiece16 package = namespaceUri;
+ package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+ if (package.empty()) {
+ return {};
+ }
+ return ExtractedPackage{ package.toString(), false /* isPrivate */ };
+
+ } else if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPrivatePrefix)) {
+ StringPiece16 schemaPrefix = kSchemaPrivatePrefix;
+ StringPiece16 package = namespaceUri;
+ package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+ if (package.empty()) {
+ return {};
+ }
+ return ExtractedPackage{ package.toString(), true /* isPrivate */ };
+
+ } else if (namespaceUri == kSchemaAuto) {
+ return ExtractedPackage{ std::u16string(), true /* isPrivate */ };
+ }
+ return {};
+}
+
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+ const StringPiece16& localPackage, Reference* inRef) {
+ if (inRef->name) {
+ if (Maybe<ExtractedPackage> transformedPackage =
+ declStack->transformPackageAlias(inRef->name.value().package, localPackage)) {
+ ExtractedPackage& extractedPackage = transformedPackage.value();
+ inRef->name.value().package = std::move(extractedPackage.package);
+
+ // If the reference was already private (with a * prefix) and the namespace is public,
+ // we keep the reference private.
+ inRef->privateReference |= extractedPackage.privateNamespace;
+ }
+ }
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
new file mode 100644
index 000000000000..98e5520a6ea2
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XML_XMLUTIL_H
+#define AAPT_XML_XMLUTIL_H
+
+#include "ResourceValues.h"
+#include "util/Maybe.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
+constexpr const char16_t* kSchemaPublicPrefix = u"http://schemas.android.com/apk/res/";
+constexpr const char16_t* kSchemaPrivatePrefix = u"http://schemas.android.com/apk/prv/res/";
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+/**
+ * Result of extracting a package name from a namespace URI declaration.
+ */
+struct ExtractedPackage {
+ /**
+ * The name of the package. This can be the empty string, which means that the package
+ * should be assumed to be the package being compiled.
+ */
+ std::u16string package;
+
+ /**
+ * True if the package's private namespace was declared. This means that private resources
+ * are made visible.
+ */
+ bool privateNamespace;
+};
+
+/**
+ * Returns an ExtractedPackage struct if the namespace URI is of the form:
+ * http://schemas.android.com/apk/res/<package> or
+ * http://schemas.android.com/apk/prv/res/<package>
+ *
+ * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
+ * returns an empty package name.
+ */
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri);
+
+/**
+ * Interface representing a stack of XML namespace declarations. When looking up the package
+ * for a namespace prefix, the stack is checked from top to bottom.
+ */
+struct IPackageDeclStack {
+ virtual ~IPackageDeclStack() = default;
+
+ /**
+ * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
+ */
+ virtual Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const = 0;
+};
+
+/**
+ * Helper function for transforming the original Reference inRef to a fully qualified reference
+ * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of
+ * the package declaration was private.
+ */
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+ const StringPiece16& localPackage, Reference* inRef);
+
+} // namespace xml
+} // namespace aapt
+
+#endif /* AAPT_XML_XMLUTIL_H */
diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp
new file mode 100644
index 000000000000..319e7707d874
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil_test.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "test/Common.h"
+#include "xml/XmlUtil.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(XmlUtilTest, ExtractPackageFromNamespace) {
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"com.android"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(
+ u"http://schemas.android.com/apk/prv/res/"));
+
+ Maybe<xml::ExtractedPackage> p =
+ xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/a");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(u"a"), p.value().package);
+ EXPECT_FALSE(p.value().privateNamespace);
+
+ p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/android");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(u"android"), p.value().package);
+ EXPECT_TRUE(p.value().privateNamespace);
+
+ p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/com.test");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(u"com.test"), p.value().package);
+ EXPECT_TRUE(p.value().privateNamespace);
+
+ p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res-auto");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(), p.value().package);
+ EXPECT_TRUE(p.value().privateNamespace);
+}
+
+} // namespace aapt
diff --git a/tools/aidl b/tools/aidl
new file mode 100644
index 000000000000..8a42fa0d0a35
--- /dev/null
+++ b/tools/aidl
@@ -0,0 +1,4 @@
+Where has aidl gone?
+
+ aidl has moved to //system/tools/aidl as part of adding support for
+ generating bindings in C++.
diff --git a/tools/aidl/AST.cpp b/tools/aidl/AST.cpp
deleted file mode 100644
index bfa67656b323..000000000000
--- a/tools/aidl/AST.cpp
+++ /dev/null
@@ -1,912 +0,0 @@
-#include "AST.h"
-#include "Type.h"
-
-void
-WriteModifiers(FILE* to, int mod, int mask)
-{
- int m = mod & mask;
-
- if (m & OVERRIDE) {
- fprintf(to, "@Override ");
- }
-
- if ((m & SCOPE_MASK) == PUBLIC) {
- fprintf(to, "public ");
- }
- else if ((m & SCOPE_MASK) == PRIVATE) {
- fprintf(to, "private ");
- }
- else if ((m & SCOPE_MASK) == PROTECTED) {
- fprintf(to, "protected ");
- }
-
- if (m & STATIC) {
- fprintf(to, "static ");
- }
-
- if (m & FINAL) {
- fprintf(to, "final ");
- }
-
- if (m & ABSTRACT) {
- fprintf(to, "abstract ");
- }
-}
-
-void
-WriteArgumentList(FILE* to, const vector<Expression*>& arguments)
-{
- size_t N = arguments.size();
- for (size_t i=0; i<N; i++) {
- arguments[i]->Write(to);
- if (i != N-1) {
- fprintf(to, ", ");
- }
- }
-}
-
-ClassElement::ClassElement()
-{
-}
-
-ClassElement::~ClassElement()
-{
-}
-
-Field::Field()
- :ClassElement(),
- modifiers(0),
- variable(NULL)
-{
-}
-
-Field::Field(int m, Variable* v)
- :ClassElement(),
- modifiers(m),
- variable(v)
-{
-}
-
-Field::~Field()
-{
-}
-
-void
-Field::GatherTypes(set<Type*>* types) const
-{
- types->insert(this->variable->type);
-}
-
-void
-Field::Write(FILE* to)
-{
- if (this->comment.length() != 0) {
- fprintf(to, "%s\n", this->comment.c_str());
- }
- WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | FINAL | OVERRIDE);
- fprintf(to, "%s %s", this->variable->type->QualifiedName().c_str(),
- this->variable->name.c_str());
- if (this->value.length() != 0) {
- fprintf(to, " = %s", this->value.c_str());
- }
- fprintf(to, ";\n");
-}
-
-Expression::~Expression()
-{
-}
-
-LiteralExpression::LiteralExpression(const string& v)
- :value(v)
-{
-}
-
-LiteralExpression::~LiteralExpression()
-{
-}
-
-void
-LiteralExpression::Write(FILE* to)
-{
- fprintf(to, "%s", this->value.c_str());
-}
-
-StringLiteralExpression::StringLiteralExpression(const string& v)
- :value(v)
-{
-}
-
-StringLiteralExpression::~StringLiteralExpression()
-{
-}
-
-void
-StringLiteralExpression::Write(FILE* to)
-{
- fprintf(to, "\"%s\"", this->value.c_str());
-}
-
-Variable::Variable()
- :type(NULL),
- name(),
- dimension(0)
-{
-}
-
-Variable::Variable(Type* t, const string& n)
- :type(t),
- name(n),
- dimension(0)
-{
-}
-
-Variable::Variable(Type* t, const string& n, int d)
- :type(t),
- name(n),
- dimension(d)
-{
-}
-
-Variable::~Variable()
-{
-}
-
-void
-Variable::GatherTypes(set<Type*>* types) const
-{
- types->insert(this->type);
-}
-
-void
-Variable::WriteDeclaration(FILE* to)
-{
- string dim;
- for (int i=0; i<this->dimension; i++) {
- dim += "[]";
- }
- fprintf(to, "%s%s %s", this->type->QualifiedName().c_str(), dim.c_str(),
- this->name.c_str());
-}
-
-void
-Variable::Write(FILE* to)
-{
- fprintf(to, "%s", name.c_str());
-}
-
-FieldVariable::FieldVariable(Expression* o, const string& n)
- :object(o),
- clazz(NULL),
- name(n)
-{
-}
-
-FieldVariable::FieldVariable(Type* c, const string& n)
- :object(NULL),
- clazz(c),
- name(n)
-{
-}
-
-FieldVariable::~FieldVariable()
-{
-}
-
-void
-FieldVariable::Write(FILE* to)
-{
- if (this->object != NULL) {
- this->object->Write(to);
- }
- else if (this->clazz != NULL) {
- fprintf(to, "%s", this->clazz->QualifiedName().c_str());
- }
- fprintf(to, ".%s", name.c_str());
-}
-
-
-Statement::~Statement()
-{
-}
-
-StatementBlock::StatementBlock()
-{
-}
-
-StatementBlock::~StatementBlock()
-{
-}
-
-void
-StatementBlock::Write(FILE* to)
-{
- fprintf(to, "{\n");
- int N = this->statements.size();
- for (int i=0; i<N; i++) {
- this->statements[i]->Write(to);
- }
- fprintf(to, "}\n");
-}
-
-void
-StatementBlock::Add(Statement* statement)
-{
- this->statements.push_back(statement);
-}
-
-void
-StatementBlock::Add(Expression* expression)
-{
- this->statements.push_back(new ExpressionStatement(expression));
-}
-
-ExpressionStatement::ExpressionStatement(Expression* e)
- :expression(e)
-{
-}
-
-ExpressionStatement::~ExpressionStatement()
-{
-}
-
-void
-ExpressionStatement::Write(FILE* to)
-{
- this->expression->Write(to);
- fprintf(to, ";\n");
-}
-
-Assignment::Assignment(Variable* l, Expression* r)
- :lvalue(l),
- rvalue(r),
- cast(NULL)
-{
-}
-
-Assignment::Assignment(Variable* l, Expression* r, Type* c)
- :lvalue(l),
- rvalue(r),
- cast(c)
-{
-}
-
-Assignment::~Assignment()
-{
-}
-
-void
-Assignment::Write(FILE* to)
-{
- this->lvalue->Write(to);
- fprintf(to, " = ");
- if (this->cast != NULL) {
- fprintf(to, "(%s)", this->cast->QualifiedName().c_str());
- }
- this->rvalue->Write(to);
-}
-
-MethodCall::MethodCall(const string& n)
- :obj(NULL),
- clazz(NULL),
- name(n)
-{
-}
-
-MethodCall::MethodCall(const string& n, int argc = 0, ...)
- :obj(NULL),
- clazz(NULL),
- name(n)
-{
- va_list args;
- va_start(args, argc);
- init(argc, args);
- va_end(args);
-}
-
-MethodCall::MethodCall(Expression* o, const string& n)
- :obj(o),
- clazz(NULL),
- name(n)
-{
-}
-
-MethodCall::MethodCall(Type* t, const string& n)
- :obj(NULL),
- clazz(t),
- name(n)
-{
-}
-
-MethodCall::MethodCall(Expression* o, const string& n, int argc = 0, ...)
- :obj(o),
- clazz(NULL),
- name(n)
-{
- va_list args;
- va_start(args, argc);
- init(argc, args);
- va_end(args);
-}
-
-MethodCall::MethodCall(Type* t, const string& n, int argc = 0, ...)
- :obj(NULL),
- clazz(t),
- name(n)
-{
- va_list args;
- va_start(args, argc);
- init(argc, args);
- va_end(args);
-}
-
-MethodCall::~MethodCall()
-{
-}
-
-void
-MethodCall::init(int n, va_list args)
-{
- for (int i=0; i<n; i++) {
- Expression* expression = (Expression*)va_arg(args, void*);
- this->arguments.push_back(expression);
- }
-}
-
-void
-MethodCall::Write(FILE* to)
-{
- if (this->obj != NULL) {
- this->obj->Write(to);
- fprintf(to, ".");
- }
- else if (this->clazz != NULL) {
- fprintf(to, "%s.", this->clazz->QualifiedName().c_str());
- }
- fprintf(to, "%s(", this->name.c_str());
- WriteArgumentList(to, this->arguments);
- fprintf(to, ")");
-}
-
-Comparison::Comparison(Expression* l, const string& o, Expression* r)
- :lvalue(l),
- op(o),
- rvalue(r)
-{
-}
-
-Comparison::~Comparison()
-{
-}
-
-void
-Comparison::Write(FILE* to)
-{
- fprintf(to, "(");
- this->lvalue->Write(to);
- fprintf(to, "%s", this->op.c_str());
- this->rvalue->Write(to);
- fprintf(to, ")");
-}
-
-NewExpression::NewExpression(Type* t)
- :type(t)
-{
-}
-
-NewExpression::NewExpression(Type* t, int argc = 0, ...)
- :type(t)
-{
- va_list args;
- va_start(args, argc);
- init(argc, args);
- va_end(args);
-}
-
-NewExpression::~NewExpression()
-{
-}
-
-void
-NewExpression::init(int n, va_list args)
-{
- for (int i=0; i<n; i++) {
- Expression* expression = (Expression*)va_arg(args, void*);
- this->arguments.push_back(expression);
- }
-}
-
-void
-NewExpression::Write(FILE* to)
-{
- fprintf(to, "new %s(", this->type->InstantiableName().c_str());
- WriteArgumentList(to, this->arguments);
- fprintf(to, ")");
-}
-
-NewArrayExpression::NewArrayExpression(Type* t, Expression* s)
- :type(t),
- size(s)
-{
-}
-
-NewArrayExpression::~NewArrayExpression()
-{
-}
-
-void
-NewArrayExpression::Write(FILE* to)
-{
- fprintf(to, "new %s[", this->type->QualifiedName().c_str());
- size->Write(to);
- fprintf(to, "]");
-}
-
-Ternary::Ternary()
- :condition(NULL),
- ifpart(NULL),
- elsepart(NULL)
-{
-}
-
-Ternary::Ternary(Expression* a, Expression* b, Expression* c)
- :condition(a),
- ifpart(b),
- elsepart(c)
-{
-}
-
-Ternary::~Ternary()
-{
-}
-
-void
-Ternary::Write(FILE* to)
-{
- fprintf(to, "((");
- this->condition->Write(to);
- fprintf(to, ")?(");
- this->ifpart->Write(to);
- fprintf(to, "):(");
- this->elsepart->Write(to);
- fprintf(to, "))");
-}
-
-Cast::Cast()
- :type(NULL),
- expression(NULL)
-{
-}
-
-Cast::Cast(Type* t, Expression* e)
- :type(t),
- expression(e)
-{
-}
-
-Cast::~Cast()
-{
-}
-
-void
-Cast::Write(FILE* to)
-{
- fprintf(to, "((%s)", this->type->QualifiedName().c_str());
- expression->Write(to);
- fprintf(to, ")");
-}
-
-VariableDeclaration::VariableDeclaration(Variable* l, Expression* r, Type* c)
- :lvalue(l),
- cast(c),
- rvalue(r)
-{
-}
-
-VariableDeclaration::VariableDeclaration(Variable* l)
- :lvalue(l),
- cast(NULL),
- rvalue(NULL)
-{
-}
-
-VariableDeclaration::~VariableDeclaration()
-{
-}
-
-void
-VariableDeclaration::Write(FILE* to)
-{
- this->lvalue->WriteDeclaration(to);
- if (this->rvalue != NULL) {
- fprintf(to, " = ");
- if (this->cast != NULL) {
- fprintf(to, "(%s)", this->cast->QualifiedName().c_str());
- }
- this->rvalue->Write(to);
- }
- fprintf(to, ";\n");
-}
-
-IfStatement::IfStatement()
- :expression(NULL),
- statements(new StatementBlock),
- elseif(NULL)
-{
-}
-
-IfStatement::~IfStatement()
-{
-}
-
-void
-IfStatement::Write(FILE* to)
-{
- if (this->expression != NULL) {
- fprintf(to, "if (");
- this->expression->Write(to);
- fprintf(to, ") ");
- }
- this->statements->Write(to);
- if (this->elseif != NULL) {
- fprintf(to, "else ");
- this->elseif->Write(to);
- }
-}
-
-ReturnStatement::ReturnStatement(Expression* e)
- :expression(e)
-{
-}
-
-ReturnStatement::~ReturnStatement()
-{
-}
-
-void
-ReturnStatement::Write(FILE* to)
-{
- fprintf(to, "return ");
- this->expression->Write(to);
- fprintf(to, ";\n");
-}
-
-TryStatement::TryStatement()
- :statements(new StatementBlock)
-{
-}
-
-TryStatement::~TryStatement()
-{
-}
-
-void
-TryStatement::Write(FILE* to)
-{
- fprintf(to, "try ");
- this->statements->Write(to);
-}
-
-CatchStatement::CatchStatement(Variable* e)
- :statements(new StatementBlock),
- exception(e)
-{
-}
-
-CatchStatement::~CatchStatement()
-{
-}
-
-void
-CatchStatement::Write(FILE* to)
-{
- fprintf(to, "catch ");
- if (this->exception != NULL) {
- fprintf(to, "(");
- this->exception->WriteDeclaration(to);
- fprintf(to, ") ");
- }
- this->statements->Write(to);
-}
-
-FinallyStatement::FinallyStatement()
- :statements(new StatementBlock)
-{
-}
-
-FinallyStatement::~FinallyStatement()
-{
-}
-
-void
-FinallyStatement::Write(FILE* to)
-{
- fprintf(to, "finally ");
- this->statements->Write(to);
-}
-
-Case::Case()
- :statements(new StatementBlock)
-{
-}
-
-Case::Case(const string& c)
- :statements(new StatementBlock)
-{
- cases.push_back(c);
-}
-
-Case::~Case()
-{
-}
-
-void
-Case::Write(FILE* to)
-{
- int N = this->cases.size();
- if (N > 0) {
- for (int i=0; i<N; i++) {
- string s = this->cases[i];
- if (s.length() != 0) {
- fprintf(to, "case %s:\n", s.c_str());
- } else {
- fprintf(to, "default:\n");
- }
- }
- } else {
- fprintf(to, "default:\n");
- }
- statements->Write(to);
-}
-
-SwitchStatement::SwitchStatement(Expression* e)
- :expression(e)
-{
-}
-
-SwitchStatement::~SwitchStatement()
-{
-}
-
-void
-SwitchStatement::Write(FILE* to)
-{
- fprintf(to, "switch (");
- this->expression->Write(to);
- fprintf(to, ")\n{\n");
- int N = this->cases.size();
- for (int i=0; i<N; i++) {
- this->cases[i]->Write(to);
- }
- fprintf(to, "}\n");
-}
-
-Break::Break()
-{
-}
-
-Break::~Break()
-{
-}
-
-void
-Break::Write(FILE* to)
-{
- fprintf(to, "break;\n");
-}
-
-Method::Method()
- :ClassElement(),
- modifiers(0),
- returnType(NULL), // (NULL means constructor)
- returnTypeDimension(0),
- statements(NULL)
-{
-}
-
-Method::~Method()
-{
-}
-
-void
-Method::GatherTypes(set<Type*>* types) const
-{
- size_t N, i;
-
- if (this->returnType) {
- types->insert(this->returnType);
- }
-
- N = this->parameters.size();
- for (i=0; i<N; i++) {
- this->parameters[i]->GatherTypes(types);
- }
-
- N = this->exceptions.size();
- for (i=0; i<N; i++) {
- types->insert(this->exceptions[i]);
- }
-}
-
-void
-Method::Write(FILE* to)
-{
- size_t N, i;
-
- if (this->comment.length() != 0) {
- fprintf(to, "%s\n", this->comment.c_str());
- }
-
- WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | ABSTRACT | FINAL | OVERRIDE);
-
- if (this->returnType != NULL) {
- string dim;
- for (i=0; i<this->returnTypeDimension; i++) {
- dim += "[]";
- }
- fprintf(to, "%s%s ", this->returnType->QualifiedName().c_str(),
- dim.c_str());
- }
-
- fprintf(to, "%s(", this->name.c_str());
-
- N = this->parameters.size();
- for (i=0; i<N; i++) {
- this->parameters[i]->WriteDeclaration(to);
- if (i != N-1) {
- fprintf(to, ", ");
- }
- }
-
- fprintf(to, ")");
-
- N = this->exceptions.size();
- for (i=0; i<N; i++) {
- if (i == 0) {
- fprintf(to, " throws ");
- } else {
- fprintf(to, ", ");
- }
- fprintf(to, "%s", this->exceptions[i]->QualifiedName().c_str());
- }
-
- if (this->statements == NULL) {
- fprintf(to, ";\n");
- } else {
- fprintf(to, "\n");
- this->statements->Write(to);
- }
-}
-
-Class::Class()
- :modifiers(0),
- what(CLASS),
- type(NULL),
- extends(NULL)
-{
-}
-
-Class::~Class()
-{
-}
-
-void
-Class::GatherTypes(set<Type*>* types) const
-{
- int N, i;
-
- types->insert(this->type);
- if (this->extends != NULL) {
- types->insert(this->extends);
- }
-
- N = this->interfaces.size();
- for (i=0; i<N; i++) {
- types->insert(this->interfaces[i]);
- }
-
- N = this->elements.size();
- for (i=0; i<N; i++) {
- this->elements[i]->GatherTypes(types);
- }
-}
-
-void
-Class::Write(FILE* to)
-{
- size_t N, i;
-
- if (this->comment.length() != 0) {
- fprintf(to, "%s\n", this->comment.c_str());
- }
-
- WriteModifiers(to, this->modifiers, ALL_MODIFIERS);
-
- if (this->what == Class::CLASS) {
- fprintf(to, "class ");
- } else {
- fprintf(to, "interface ");
- }
-
- string name = this->type->Name();
- size_t pos = name.rfind('.');
- if (pos != string::npos) {
- name = name.c_str() + pos + 1;
- }
-
- fprintf(to, "%s", name.c_str());
-
- if (this->extends != NULL) {
- fprintf(to, " extends %s", this->extends->QualifiedName().c_str());
- }
-
- N = this->interfaces.size();
- if (N != 0) {
- if (this->what == Class::CLASS) {
- fprintf(to, " implements");
- } else {
- fprintf(to, " extends");
- }
- for (i=0; i<N; i++) {
- fprintf(to, " %s", this->interfaces[i]->QualifiedName().c_str());
- }
- }
-
- fprintf(to, "\n");
- fprintf(to, "{\n");
-
- N = this->elements.size();
- for (i=0; i<N; i++) {
- this->elements[i]->Write(to);
- }
-
- fprintf(to, "}\n");
-
-}
-
-Document::Document()
-{
-}
-
-Document::~Document()
-{
-}
-
-static string
-escape_backslashes(const string& str)
-{
- string result;
- const size_t I=str.length();
- for (size_t i=0; i<I; i++) {
- char c = str[i];
- if (c == '\\') {
- result += "\\\\";
- } else {
- result += c;
- }
- }
- return result;
-}
-
-void
-Document::Write(FILE* to)
-{
- size_t N, i;
-
- if (this->comment.length() != 0) {
- fprintf(to, "%s\n", this->comment.c_str());
- }
- fprintf(to, "/*\n"
- " * This file is auto-generated. DO NOT MODIFY.\n"
- " * Original file: %s\n"
- " */\n", escape_backslashes(this->originalSrc).c_str());
- if (this->package.length() != 0) {
- fprintf(to, "package %s;\n", this->package.c_str());
- }
-
- N = this->classes.size();
- for (i=0; i<N; i++) {
- Class* c = this->classes[i];
- c->Write(to);
- }
-}
-
diff --git a/tools/aidl/AST.h b/tools/aidl/AST.h
deleted file mode 100644
index ead5e7ae3439..000000000000
--- a/tools/aidl/AST.h
+++ /dev/null
@@ -1,371 +0,0 @@
-#ifndef AIDL_AST_H
-#define AIDL_AST_H
-
-#include <string>
-#include <vector>
-#include <set>
-#include <stdarg.h>
-#include <stdio.h>
-
-using namespace std;
-
-class Type;
-
-enum {
- PACKAGE_PRIVATE = 0x00000000,
- PUBLIC = 0x00000001,
- PRIVATE = 0x00000002,
- PROTECTED = 0x00000003,
- SCOPE_MASK = 0x00000003,
-
- STATIC = 0x00000010,
- FINAL = 0x00000020,
- ABSTRACT = 0x00000040,
-
- OVERRIDE = 0x00000100,
-
- ALL_MODIFIERS = 0xffffffff
-};
-
-// Write the modifiers that are set in both mod and mask
-void WriteModifiers(FILE* to, int mod, int mask);
-
-struct ClassElement
-{
- ClassElement();
- virtual ~ClassElement();
-
- virtual void GatherTypes(set<Type*>* types) const = 0;
- virtual void Write(FILE* to) = 0;
-};
-
-struct Expression
-{
- virtual ~Expression();
- virtual void Write(FILE* to) = 0;
-};
-
-struct LiteralExpression : public Expression
-{
- string value;
-
- LiteralExpression(const string& value);
- virtual ~LiteralExpression();
- virtual void Write(FILE* to);
-};
-
-// TODO: also escape the contents. not needed for now
-struct StringLiteralExpression : public Expression
-{
- string value;
-
- StringLiteralExpression(const string& value);
- virtual ~StringLiteralExpression();
- virtual void Write(FILE* to);
-};
-
-struct Variable : public Expression
-{
- Type* type;
- string name;
- int dimension;
-
- Variable();
- Variable(Type* type, const string& name);
- Variable(Type* type, const string& name, int dimension);
- virtual ~Variable();
-
- virtual void GatherTypes(set<Type*>* types) const;
- void WriteDeclaration(FILE* to);
- void Write(FILE* to);
-};
-
-struct FieldVariable : public Expression
-{
- Expression* object;
- Type* clazz;
- string name;
-
- FieldVariable(Expression* object, const string& name);
- FieldVariable(Type* clazz, const string& name);
- virtual ~FieldVariable();
-
- void Write(FILE* to);
-};
-
-struct Field : public ClassElement
-{
- string comment;
- int modifiers;
- Variable *variable;
- string value;
-
- Field();
- Field(int modifiers, Variable* variable);
- virtual ~Field();
-
- virtual void GatherTypes(set<Type*>* types) const;
- virtual void Write(FILE* to);
-};
-
-struct Statement
-{
- virtual ~Statement();
- virtual void Write(FILE* to) = 0;
-};
-
-struct StatementBlock : public Statement
-{
- vector<Statement*> statements;
-
- StatementBlock();
- virtual ~StatementBlock();
- virtual void Write(FILE* to);
-
- void Add(Statement* statement);
- void Add(Expression* expression);
-};
-
-struct ExpressionStatement : public Statement
-{
- Expression* expression;
-
- ExpressionStatement(Expression* expression);
- virtual ~ExpressionStatement();
- virtual void Write(FILE* to);
-};
-
-struct Assignment : public Expression
-{
- Variable* lvalue;
- Expression* rvalue;
- Type* cast;
-
- Assignment(Variable* lvalue, Expression* rvalue);
- Assignment(Variable* lvalue, Expression* rvalue, Type* cast);
- virtual ~Assignment();
- virtual void Write(FILE* to);
-};
-
-struct MethodCall : public Expression
-{
- Expression* obj;
- Type* clazz;
- string name;
- vector<Expression*> arguments;
- vector<string> exceptions;
-
- MethodCall(const string& name);
- MethodCall(const string& name, int argc, ...);
- MethodCall(Expression* obj, const string& name);
- MethodCall(Type* clazz, const string& name);
- MethodCall(Expression* obj, const string& name, int argc, ...);
- MethodCall(Type* clazz, const string& name, int argc, ...);
- virtual ~MethodCall();
- virtual void Write(FILE* to);
-
-private:
- void init(int n, va_list args);
-};
-
-struct Comparison : public Expression
-{
- Expression* lvalue;
- string op;
- Expression* rvalue;
-
- Comparison(Expression* lvalue, const string& op, Expression* rvalue);
- virtual ~Comparison();
- virtual void Write(FILE* to);
-};
-
-struct NewExpression : public Expression
-{
- Type* type;
- vector<Expression*> arguments;
-
- NewExpression(Type* type);
- NewExpression(Type* type, int argc, ...);
- virtual ~NewExpression();
- virtual void Write(FILE* to);
-
-private:
- void init(int n, va_list args);
-};
-
-struct NewArrayExpression : public Expression
-{
- Type* type;
- Expression* size;
-
- NewArrayExpression(Type* type, Expression* size);
- virtual ~NewArrayExpression();
- virtual void Write(FILE* to);
-};
-
-struct Ternary : public Expression
-{
- Expression* condition;
- Expression* ifpart;
- Expression* elsepart;
-
- Ternary();
- Ternary(Expression* condition, Expression* ifpart, Expression* elsepart);
- virtual ~Ternary();
- virtual void Write(FILE* to);
-};
-
-struct Cast : public Expression
-{
- Type* type;
- Expression* expression;
-
- Cast();
- Cast(Type* type, Expression* expression);
- virtual ~Cast();
- virtual void Write(FILE* to);
-};
-
-struct VariableDeclaration : public Statement
-{
- Variable* lvalue;
- Type* cast;
- Expression* rvalue;
-
- VariableDeclaration(Variable* lvalue);
- VariableDeclaration(Variable* lvalue, Expression* rvalue, Type* cast = NULL);
- virtual ~VariableDeclaration();
- virtual void Write(FILE* to);
-};
-
-struct IfStatement : public Statement
-{
- Expression* expression;
- StatementBlock* statements;
- IfStatement* elseif;
-
- IfStatement();
- virtual ~IfStatement();
- virtual void Write(FILE* to);
-};
-
-struct ReturnStatement : public Statement
-{
- Expression* expression;
-
- ReturnStatement(Expression* expression);
- virtual ~ReturnStatement();
- virtual void Write(FILE* to);
-};
-
-struct TryStatement : public Statement
-{
- StatementBlock* statements;
-
- TryStatement();
- virtual ~TryStatement();
- virtual void Write(FILE* to);
-};
-
-struct CatchStatement : public Statement
-{
- StatementBlock* statements;
- Variable* exception;
-
- CatchStatement(Variable* exception);
- virtual ~CatchStatement();
- virtual void Write(FILE* to);
-};
-
-struct FinallyStatement : public Statement
-{
- StatementBlock* statements;
-
- FinallyStatement();
- virtual ~FinallyStatement();
- virtual void Write(FILE* to);
-};
-
-struct Case
-{
- vector<string> cases;
- StatementBlock* statements;
-
- Case();
- Case(const string& c);
- virtual ~Case();
- virtual void Write(FILE* to);
-};
-
-struct SwitchStatement : public Statement
-{
- Expression* expression;
- vector<Case*> cases;
-
- SwitchStatement(Expression* expression);
- virtual ~SwitchStatement();
- virtual void Write(FILE* to);
-};
-
-struct Break : public Statement
-{
- Break();
- virtual ~Break();
- virtual void Write(FILE* to);
-};
-
-struct Method : public ClassElement
-{
- string comment;
- int modifiers;
- Type* returnType;
- size_t returnTypeDimension;
- string name;
- vector<Variable*> parameters;
- vector<Type*> exceptions;
- StatementBlock* statements;
-
- Method();
- virtual ~Method();
-
- virtual void GatherTypes(set<Type*>* types) const;
- virtual void Write(FILE* to);
-};
-
-struct Class : public ClassElement
-{
- enum {
- CLASS,
- INTERFACE
- };
-
- string comment;
- int modifiers;
- int what; // CLASS or INTERFACE
- Type* type;
- Type* extends;
- vector<Type*> interfaces;
- vector<ClassElement*> elements;
-
- Class();
- virtual ~Class();
-
- virtual void GatherTypes(set<Type*>* types) const;
- virtual void Write(FILE* to);
-};
-
-struct Document
-{
- string comment;
- string package;
- string originalSrc;
- set<Type*> imports;
- vector<Class*> classes;
-
- Document();
- virtual ~Document();
-
- virtual void Write(FILE* to);
-};
-
-#endif // AIDL_AST_H
diff --git a/tools/aidl/Android.mk b/tools/aidl/Android.mk
deleted file mode 100644
index efd60a2cda99..000000000000
--- a/tools/aidl/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2007 The Android Open Source Project
-#
-# Copies files into the directory structure described by a manifest
-
-# This tool is prebuilt if we're doing an app-only build.
-ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),)
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- aidl_language_l.l \
- aidl_language_y.y \
- aidl.cpp \
- aidl_language.cpp \
- options.cpp \
- search_path.cpp \
- AST.cpp \
- Type.cpp \
- generate_java.cpp \
- generate_java_binder.cpp \
- generate_java_rpc.cpp
-
-LOCAL_CFLAGS := -g
-LOCAL_MODULE := aidl
-
-include $(BUILD_HOST_EXECUTABLE)
-
-endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK
diff --git a/tools/aidl/NOTICE b/tools/aidl/NOTICE
deleted file mode 100644
index c5b1efa7aac7..000000000000
--- a/tools/aidl/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
- Copyright (c) 2005-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.
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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/tools/aidl/Type.cpp b/tools/aidl/Type.cpp
deleted file mode 100644
index 2267750623ad..000000000000
--- a/tools/aidl/Type.cpp
+++ /dev/null
@@ -1,1442 +0,0 @@
-#include "Type.h"
-
-#include <sys/types.h>
-
-Namespace NAMES;
-
-Type* VOID_TYPE;
-Type* BOOLEAN_TYPE;
-Type* BYTE_TYPE;
-Type* CHAR_TYPE;
-Type* INT_TYPE;
-Type* LONG_TYPE;
-Type* FLOAT_TYPE;
-Type* DOUBLE_TYPE;
-Type* STRING_TYPE;
-Type* OBJECT_TYPE;
-Type* CHAR_SEQUENCE_TYPE;
-Type* TEXT_UTILS_TYPE;
-Type* REMOTE_EXCEPTION_TYPE;
-Type* RUNTIME_EXCEPTION_TYPE;
-Type* IBINDER_TYPE;
-Type* IINTERFACE_TYPE;
-Type* BINDER_NATIVE_TYPE;
-Type* BINDER_PROXY_TYPE;
-Type* PARCEL_TYPE;
-Type* PARCELABLE_INTERFACE_TYPE;
-Type* CONTEXT_TYPE;
-Type* MAP_TYPE;
-Type* LIST_TYPE;
-Type* CLASSLOADER_TYPE;
-Type* RPC_DATA_TYPE;
-Type* RPC_ERROR_TYPE;
-Type* EVENT_FAKE_TYPE;
-
-Expression* NULL_VALUE;
-Expression* THIS_VALUE;
-Expression* SUPER_VALUE;
-Expression* TRUE_VALUE;
-Expression* FALSE_VALUE;
-
-void
-register_base_types()
-{
- VOID_TYPE = new BasicType("void",
- "XXX", "XXX", "XXX", "XXX", "XXX",
- "XXX", "XXX", "XXX", "XXX", "XXX");
- NAMES.Add(VOID_TYPE);
-
- BOOLEAN_TYPE = new BooleanType();
- NAMES.Add(BOOLEAN_TYPE);
-
- BYTE_TYPE = new BasicType("byte",
- "writeByte", "readByte", "writeByteArray", "createByteArray", "readByteArray",
- "putByte", "getByte", "putByteArray", "createByteArray", "getByteArray");
- NAMES.Add(BYTE_TYPE);
-
- CHAR_TYPE = new CharType();
- NAMES.Add(CHAR_TYPE);
-
- INT_TYPE = new BasicType("int",
- "writeInt", "readInt", "writeIntArray", "createIntArray", "readIntArray",
- "putInteger", "getInteger", "putIntegerArray", "createIntegerArray", "getIntegerArray");
- NAMES.Add(INT_TYPE);
-
- LONG_TYPE = new BasicType("long",
- "writeLong", "readLong", "writeLongArray", "createLongArray", "readLongArray",
- "putLong", "getLong", "putLongArray", "createLongArray", "getLongArray");
- NAMES.Add(LONG_TYPE);
-
- FLOAT_TYPE = new BasicType("float",
- "writeFloat", "readFloat", "writeFloatArray", "createFloatArray", "readFloatArray",
- "putFloat", "getFloat", "putFloatArray", "createFloatArray", "getFloatArray");
- NAMES.Add(FLOAT_TYPE);
-
- DOUBLE_TYPE = new BasicType("double",
- "writeDouble", "readDouble", "writeDoubleArray", "createDoubleArray", "readDoubleArray",
- "putDouble", "getDouble", "putDoubleArray", "createDoubleArray", "getDoubleArray");
- NAMES.Add(DOUBLE_TYPE);
-
- STRING_TYPE = new StringType();
- NAMES.Add(STRING_TYPE);
-
- OBJECT_TYPE = new Type("java.lang", "Object", Type::BUILT_IN, false, false, false);
- NAMES.Add(OBJECT_TYPE);
-
- CHAR_SEQUENCE_TYPE = new CharSequenceType();
- NAMES.Add(CHAR_SEQUENCE_TYPE);
-
- MAP_TYPE = new MapType();
- NAMES.Add(MAP_TYPE);
-
- LIST_TYPE = new ListType();
- NAMES.Add(LIST_TYPE);
-
- TEXT_UTILS_TYPE = new Type("android.text", "TextUtils", Type::BUILT_IN, false, false, false);
- NAMES.Add(TEXT_UTILS_TYPE);
-
- REMOTE_EXCEPTION_TYPE = new RemoteExceptionType();
- NAMES.Add(REMOTE_EXCEPTION_TYPE);
-
- RUNTIME_EXCEPTION_TYPE = new RuntimeExceptionType();
- NAMES.Add(RUNTIME_EXCEPTION_TYPE);
-
- IBINDER_TYPE = new IBinderType();
- NAMES.Add(IBINDER_TYPE);
-
- IINTERFACE_TYPE = new IInterfaceType();
- NAMES.Add(IINTERFACE_TYPE);
-
- BINDER_NATIVE_TYPE = new BinderType();
- NAMES.Add(BINDER_NATIVE_TYPE);
-
- BINDER_PROXY_TYPE = new BinderProxyType();
- NAMES.Add(BINDER_PROXY_TYPE);
-
- PARCEL_TYPE = new ParcelType();
- NAMES.Add(PARCEL_TYPE);
-
- PARCELABLE_INTERFACE_TYPE = new ParcelableInterfaceType();
- NAMES.Add(PARCELABLE_INTERFACE_TYPE);
-
- CONTEXT_TYPE = new Type("android.content", "Context", Type::BUILT_IN, false, false, false);
- NAMES.Add(CONTEXT_TYPE);
-
- RPC_DATA_TYPE = new RpcDataType();
- NAMES.Add(RPC_DATA_TYPE);
-
- RPC_ERROR_TYPE = new UserDataType("android.support.place.rpc", "RpcError",
- true, __FILE__, __LINE__);
- NAMES.Add(RPC_ERROR_TYPE);
-
- EVENT_FAKE_TYPE = new Type("event", Type::BUILT_IN, false, false, false);
- NAMES.Add(EVENT_FAKE_TYPE);
-
- CLASSLOADER_TYPE = new ClassLoaderType();
- NAMES.Add(CLASSLOADER_TYPE);
-
- NULL_VALUE = new LiteralExpression("null");
- THIS_VALUE = new LiteralExpression("this");
- SUPER_VALUE = new LiteralExpression("super");
- TRUE_VALUE = new LiteralExpression("true");
- FALSE_VALUE = new LiteralExpression("false");
-
- NAMES.AddGenericType("java.util", "List", 1);
- NAMES.AddGenericType("java.util", "Map", 2);
-}
-
-static Type*
-make_generic_type(const string& package, const string& name,
- const vector<Type*>& args)
-{
- if (package == "java.util" && name == "List") {
- return new GenericListType("java.util", "List", args);
- }
- return NULL;
- //return new GenericType(package, name, args);
-}
-
-// ================================================================
-
-Type::Type(const string& name, int kind, bool canWriteToParcel, bool canWriteToRpcData,
- bool canBeOut)
- :m_package(),
- m_name(name),
- m_declFile(""),
- m_declLine(-1),
- m_kind(kind),
- m_canWriteToParcel(canWriteToParcel),
- m_canWriteToRpcData(canWriteToRpcData),
- m_canBeOut(canBeOut)
-{
- m_qualifiedName = name;
-}
-
-Type::Type(const string& package, const string& name,
- int kind, bool canWriteToParcel, bool canWriteToRpcData,
- bool canBeOut, const string& declFile, int declLine)
- :m_package(package),
- m_name(name),
- m_declFile(declFile),
- m_declLine(declLine),
- m_kind(kind),
- m_canWriteToParcel(canWriteToParcel),
- m_canWriteToRpcData(canWriteToRpcData),
- m_canBeOut(canBeOut)
-{
- if (package.length() > 0) {
- m_qualifiedName = package;
- m_qualifiedName += '.';
- }
- m_qualifiedName += name;
-}
-
-Type::~Type()
-{
-}
-
-bool
-Type::CanBeArray() const
-{
- return false;
-}
-
-string
-Type::ImportType() const
-{
- return m_qualifiedName;
-}
-
-string
-Type::CreatorName() const
-{
- return "";
-}
-
-string
-Type::RpcCreatorName() const
-{
- return "";
-}
-
-string
-Type::InstantiableName() const
-{
- return QualifiedName();
-}
-
-
-void
-Type::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%sn",
- __FILE__, __LINE__, m_qualifiedName.c_str());
- addTo->Add(new LiteralExpression("/* WriteToParcel error "
- + m_qualifiedName + " */"));
-}
-
-void
-Type::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
- __FILE__, __LINE__, m_qualifiedName.c_str());
- addTo->Add(new LiteralExpression("/* CreateFromParcel error "
- + m_qualifiedName + " */"));
-}
-
-void
-Type::ReadFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
- __FILE__, __LINE__, m_qualifiedName.c_str());
- addTo->Add(new LiteralExpression("/* ReadFromParcel error "
- + m_qualifiedName + " */"));
-}
-
-void
-Type::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
- __FILE__, __LINE__, m_qualifiedName.c_str());
- addTo->Add(new LiteralExpression("/* WriteArrayToParcel error "
- + m_qualifiedName + " */"));
-}
-
-void
-Type::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
- __FILE__, __LINE__, m_qualifiedName.c_str());
- addTo->Add(new LiteralExpression("/* CreateArrayFromParcel error "
- + m_qualifiedName + " */"));
-}
-
-void
-Type::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
- __FILE__, __LINE__, m_qualifiedName.c_str());
- addTo->Add(new LiteralExpression("/* ReadArrayFromParcel error "
- + m_qualifiedName + " */"));
-}
-
-void
-Type::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
- __FILE__, __LINE__, m_qualifiedName.c_str());
- addTo->Add(new LiteralExpression("/* WriteToRpcData error "
- + m_qualifiedName + " */"));
-}
-
-void
-Type::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
- Variable** cl)
-{
- fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
- __FILE__, __LINE__, m_qualifiedName.c_str());
- addTo->Add(new LiteralExpression("/* ReadFromRpcData error "
- + m_qualifiedName + " */"));
-}
-
-void
-Type::SetQualifiedName(const string& qualified)
-{
- m_qualifiedName = qualified;
-}
-
-Expression*
-Type::BuildWriteToParcelFlags(int flags)
-{
- if (flags == 0) {
- return new LiteralExpression("0");
- }
- if ((flags&PARCELABLE_WRITE_RETURN_VALUE) != 0) {
- return new FieldVariable(PARCELABLE_INTERFACE_TYPE,
- "PARCELABLE_WRITE_RETURN_VALUE");
- }
- return new LiteralExpression("0");
-}
-
-// ================================================================
-
-BasicType::BasicType(const string& name, const string& marshallParcel,
- const string& unmarshallParcel, const string& writeArrayParcel,
- const string& createArrayParcel, const string& readArrayParcel,
- const string& marshallRpc, const string& unmarshallRpc,
- const string& writeArrayRpc, const string& createArrayRpc, const string& readArrayRpc)
- :Type(name, BUILT_IN, true, true, false),
- m_marshallParcel(marshallParcel),
- m_unmarshallParcel(unmarshallParcel),
- m_writeArrayParcel(writeArrayParcel),
- m_createArrayParcel(createArrayParcel),
- m_readArrayParcel(readArrayParcel),
- m_marshallRpc(marshallRpc),
- m_unmarshallRpc(unmarshallRpc),
- m_writeArrayRpc(writeArrayRpc),
- m_createArrayRpc(createArrayRpc),
- m_readArrayRpc(readArrayRpc)
-{
-}
-
-void
-BasicType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, m_marshallParcel, 1, v));
-}
-
-void
-BasicType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(parcel, m_unmarshallParcel)));
-}
-
-bool
-BasicType::CanBeArray() const
-{
- return true;
-}
-
-void
-BasicType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, m_writeArrayParcel, 1, v));
-}
-
-void
-BasicType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(parcel, m_createArrayParcel)));
-}
-
-void
-BasicType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new MethodCall(parcel, m_readArrayParcel, 1, v));
-}
-
-void
-BasicType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags)
-{
- addTo->Add(new MethodCall(data, m_marshallRpc, 2, k, v));
-}
-
-void
-BasicType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
- Variable** cl)
-{
- addTo->Add(new Assignment(v, new MethodCall(data, m_unmarshallRpc, 1, k)));
-}
-
-// ================================================================
-
-BooleanType::BooleanType()
- :Type("boolean", BUILT_IN, true, true, false)
-{
-}
-
-void
-BooleanType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeInt", 1,
- new Ternary(v, new LiteralExpression("1"),
- new LiteralExpression("0"))));
-}
-
-void
-BooleanType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new Comparison(new LiteralExpression("0"),
- "!=", new MethodCall(parcel, "readInt"))));
-}
-
-bool
-BooleanType::CanBeArray() const
-{
- return true;
-}
-
-void
-BooleanType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeBooleanArray", 1, v));
-}
-
-void
-BooleanType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(parcel, "createBooleanArray")));
-}
-
-void
-BooleanType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new MethodCall(parcel, "readBooleanArray", 1, v));
-}
-
-void
-BooleanType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags)
-{
- addTo->Add(new MethodCall(data, "putBoolean", 2, k, v));
-}
-
-void
-BooleanType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
- Variable** cl)
-{
- addTo->Add(new Assignment(v, new MethodCall(data, "getBoolean", 1, k)));
-}
-
-// ================================================================
-
-CharType::CharType()
- :Type("char", BUILT_IN, true, true, false)
-{
-}
-
-void
-CharType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeInt", 1,
- new Cast(INT_TYPE, v)));
-}
-
-void
-CharType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(parcel, "readInt"), this));
-}
-
-bool
-CharType::CanBeArray() const
-{
- return true;
-}
-
-void
-CharType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeCharArray", 1, v));
-}
-
-void
-CharType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(parcel, "createCharArray")));
-}
-
-void
-CharType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new MethodCall(parcel, "readCharArray", 1, v));
-}
-
-void
-CharType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags)
-{
- addTo->Add(new MethodCall(data, "putChar", 2, k, v));
-}
-
-void
-CharType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
- Variable** cl)
-{
- addTo->Add(new Assignment(v, new MethodCall(data, "getChar", 1, k)));
-}
-
-// ================================================================
-
-StringType::StringType()
- :Type("java.lang", "String", BUILT_IN, true, true, false)
-{
-}
-
-string
-StringType::CreatorName() const
-{
- return "android.os.Parcel.STRING_CREATOR";
-}
-
-void
-StringType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeString", 1, v));
-}
-
-void
-StringType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(parcel, "readString")));
-}
-
-bool
-StringType::CanBeArray() const
-{
- return true;
-}
-
-void
-StringType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeStringArray", 1, v));
-}
-
-void
-StringType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(parcel, "createStringArray")));
-}
-
-void
-StringType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new MethodCall(parcel, "readStringArray", 1, v));
-}
-
-void
-StringType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags)
-{
- addTo->Add(new MethodCall(data, "putString", 2, k, v));
-}
-
-void
-StringType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(data, "getString", 1, k)));
-}
-
-// ================================================================
-
-CharSequenceType::CharSequenceType()
- :Type("java.lang", "CharSequence", BUILT_IN, true, true, false)
-{
-}
-
-string
-CharSequenceType::CreatorName() const
-{
- return "android.os.Parcel.STRING_CREATOR";
-}
-
-void
-CharSequenceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- // if (v != null) {
- // parcel.writeInt(1);
- // v.writeToParcel(parcel);
- // } else {
- // parcel.writeInt(0);
- // }
- IfStatement* elsepart = new IfStatement();
- elsepart->statements->Add(new MethodCall(parcel, "writeInt", 1,
- new LiteralExpression("0")));
- IfStatement* ifpart = new IfStatement;
- ifpart->expression = new Comparison(v, "!=", NULL_VALUE);
- ifpart->elseif = elsepart;
- ifpart->statements->Add(new MethodCall(parcel, "writeInt", 1,
- new LiteralExpression("1")));
- ifpart->statements->Add(new MethodCall(TEXT_UTILS_TYPE, "writeToParcel",
- 3, v, parcel, BuildWriteToParcelFlags(flags)));
-
- addTo->Add(ifpart);
-}
-
-void
-CharSequenceType::CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- // if (0 != parcel.readInt()) {
- // v = TextUtils.createFromParcel(parcel)
- // } else {
- // v = null;
- // }
- IfStatement* elsepart = new IfStatement();
- elsepart->statements->Add(new Assignment(v, NULL_VALUE));
-
- IfStatement* ifpart = new IfStatement();
- ifpart->expression = new Comparison(new LiteralExpression("0"), "!=",
- new MethodCall(parcel, "readInt"));
- ifpart->elseif = elsepart;
- ifpart->statements->Add(new Assignment(v,
- new MethodCall(TEXT_UTILS_TYPE,
- "CHAR_SEQUENCE_CREATOR.createFromParcel", 1, parcel)));
-
- addTo->Add(ifpart);
-}
-
-
-// ================================================================
-
-RemoteExceptionType::RemoteExceptionType()
- :Type("android.os", "RemoteException", BUILT_IN, false, false, false)
-{
-}
-
-void
-RemoteExceptionType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-void
-RemoteExceptionType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-// ================================================================
-
-RuntimeExceptionType::RuntimeExceptionType()
- :Type("java.lang", "RuntimeException", BUILT_IN, false, false, false)
-{
-}
-
-void
-RuntimeExceptionType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-void
-RuntimeExceptionType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-
-// ================================================================
-
-IBinderType::IBinderType()
- :Type("android.os", "IBinder", BUILT_IN, true, false, false)
-{
-}
-
-void
-IBinderType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeStrongBinder", 1, v));
-}
-
-void
-IBinderType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(parcel, "readStrongBinder")));
-}
-
-void
-IBinderType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeBinderArray", 1, v));
-}
-
-void
-IBinderType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- addTo->Add(new Assignment(v, new MethodCall(parcel, "createBinderArray")));
-}
-
-void
-IBinderType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- addTo->Add(new MethodCall(parcel, "readBinderArray", 1, v));
-}
-
-
-// ================================================================
-
-IInterfaceType::IInterfaceType()
- :Type("android.os", "IInterface", BUILT_IN, false, false, false)
-{
-}
-
-void
-IInterfaceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-void
-IInterfaceType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-
-// ================================================================
-
-BinderType::BinderType()
- :Type("android.os", "Binder", BUILT_IN, false, false, false)
-{
-}
-
-void
-BinderType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-void
-BinderType::CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-
-// ================================================================
-
-BinderProxyType::BinderProxyType()
- :Type("android.os", "BinderProxy", BUILT_IN, false, false, false)
-{
-}
-
-void
-BinderProxyType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-void
-BinderProxyType::CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-
-// ================================================================
-
-ParcelType::ParcelType()
- :Type("android.os", "Parcel", BUILT_IN, false, false, false)
-{
-}
-
-void
-ParcelType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-void
-ParcelType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-// ================================================================
-
-ParcelableInterfaceType::ParcelableInterfaceType()
- :Type("android.os", "Parcelable", BUILT_IN, false, false, false)
-{
-}
-
-void
-ParcelableInterfaceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-void
-ParcelableInterfaceType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
-}
-
-// ================================================================
-
-MapType::MapType()
- :Type("java.util", "Map", BUILT_IN, true, false, true)
-{
-}
-
-void
-MapType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeMap", 1, v));
-}
-
-static void EnsureClassLoader(StatementBlock* addTo, Variable** cl)
-{
- // We don't want to look up the class loader once for every
- // collection argument, so ensure we do it at most once per method.
- if (*cl == NULL) {
- *cl = new Variable(CLASSLOADER_TYPE, "cl");
- addTo->Add(new VariableDeclaration(*cl,
- new LiteralExpression("this.getClass().getClassLoader()"),
- CLASSLOADER_TYPE));
- }
-}
-
-void
-MapType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl)
-{
- EnsureClassLoader(addTo, cl);
- addTo->Add(new Assignment(v, new MethodCall(parcel, "readHashMap", 1, *cl)));
-}
-
-void
-MapType::ReadFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl)
-{
- EnsureClassLoader(addTo, cl);
- addTo->Add(new MethodCall(parcel, "readMap", 2, v, *cl));
-}
-
-
-// ================================================================
-
-ListType::ListType()
- :Type("java.util", "List", BUILT_IN, true, true, true)
-{
-}
-
-string
-ListType::InstantiableName() const
-{
- return "java.util.ArrayList";
-}
-
-void
-ListType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeList", 1, v));
-}
-
-void
-ListType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl)
-{
- EnsureClassLoader(addTo, cl);
- addTo->Add(new Assignment(v, new MethodCall(parcel, "readArrayList", 1, *cl)));
-}
-
-void
-ListType::ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl)
-{
- EnsureClassLoader(addTo, cl);
- addTo->Add(new MethodCall(parcel, "readList", 2, v, *cl));
-}
-
-void
-ListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags)
-{
- addTo->Add(new MethodCall(data, "putList", 2, k, v));
-}
-
-void
-ListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
- Variable** cl)
-{
- addTo->Add(new Assignment(v, new MethodCall(data, "getList", 1, k)));
-}
-
-// ================================================================
-
-UserDataType::UserDataType(const string& package, const string& name,
- bool builtIn, bool canWriteToParcel, bool canWriteToRpcData,
- const string& declFile, int declLine)
- :Type(package, name, builtIn ? BUILT_IN : USERDATA, canWriteToParcel, canWriteToRpcData,
- true, declFile, declLine)
-{
-}
-
-string
-UserDataType::CreatorName() const
-{
- return QualifiedName() + ".CREATOR";
-}
-
-string
-UserDataType::RpcCreatorName() const
-{
- return QualifiedName() + ".RPC_CREATOR";
-}
-
-void
-UserDataType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- // if (v != null) {
- // parcel.writeInt(1);
- // v.writeToParcel(parcel);
- // } else {
- // parcel.writeInt(0);
- // }
- IfStatement* elsepart = new IfStatement();
- elsepart->statements->Add(new MethodCall(parcel, "writeInt", 1,
- new LiteralExpression("0")));
- IfStatement* ifpart = new IfStatement;
- ifpart->expression = new Comparison(v, "!=", NULL_VALUE);
- ifpart->elseif = elsepart;
- ifpart->statements->Add(new MethodCall(parcel, "writeInt", 1,
- new LiteralExpression("1")));
- ifpart->statements->Add(new MethodCall(v, "writeToParcel", 2,
- parcel, BuildWriteToParcelFlags(flags)));
-
- addTo->Add(ifpart);
-}
-
-void
-UserDataType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- // if (0 != parcel.readInt()) {
- // v = CLASS.CREATOR.createFromParcel(parcel)
- // } else {
- // v = null;
- // }
- IfStatement* elsepart = new IfStatement();
- elsepart->statements->Add(new Assignment(v, NULL_VALUE));
-
- IfStatement* ifpart = new IfStatement();
- ifpart->expression = new Comparison(new LiteralExpression("0"), "!=",
- new MethodCall(parcel, "readInt"));
- ifpart->elseif = elsepart;
- ifpart->statements->Add(new Assignment(v,
- new MethodCall(v->type, "CREATOR.createFromParcel", 1, parcel)));
-
- addTo->Add(ifpart);
-}
-
-void
-UserDataType::ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- // TODO: really, we don't need to have this extra check, but we
- // don't have two separate marshalling code paths
- // if (0 != parcel.readInt()) {
- // v.readFromParcel(parcel)
- // }
- IfStatement* ifpart = new IfStatement();
- ifpart->expression = new Comparison(new LiteralExpression("0"), "!=",
- new MethodCall(parcel, "readInt"));
- ifpart->statements->Add(new MethodCall(v, "readFromParcel", 1, parcel));
- addTo->Add(ifpart);
-}
-
-bool
-UserDataType::CanBeArray() const
-{
- return true;
-}
-
-void
-UserDataType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- addTo->Add(new MethodCall(parcel, "writeTypedArray", 2, v,
- BuildWriteToParcelFlags(flags)));
-}
-
-void
-UserDataType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- string creator = v->type->QualifiedName() + ".CREATOR";
- addTo->Add(new Assignment(v, new MethodCall(parcel,
- "createTypedArray", 1, new LiteralExpression(creator))));
-}
-
-void
-UserDataType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- string creator = v->type->QualifiedName() + ".CREATOR";
- addTo->Add(new MethodCall(parcel, "readTypedArray", 2,
- v, new LiteralExpression(creator)));
-}
-
-void
-UserDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags)
-{
- // data.putFlattenable(k, v);
- addTo->Add(new MethodCall(data, "putFlattenable", 2, k, v));
-}
-
-void
-UserDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl)
-{
- // data.getFlattenable(k, CLASS.RPC_CREATOR);
- addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenable", 2, k,
- new FieldVariable(v->type, "RPC_CREATOR"))));
-}
-
-// ================================================================
-
-InterfaceType::InterfaceType(const string& package, const string& name,
- bool builtIn, bool oneway,
- const string& declFile, int declLine)
- :Type(package, name, builtIn ? BUILT_IN : INTERFACE, true, false, false,
- declFile, declLine)
- ,m_oneway(oneway)
-{
-}
-
-bool
-InterfaceType::OneWay() const
-{
- return m_oneway;
-}
-
-void
-InterfaceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- // parcel.writeStrongBinder(v != null ? v.asBinder() : null);
- addTo->Add(new MethodCall(parcel, "writeStrongBinder", 1,
- new Ternary(
- new Comparison(v, "!=", NULL_VALUE),
- new MethodCall(v, "asBinder"),
- NULL_VALUE)));
-}
-
-void
-InterfaceType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- // v = Interface.asInterface(parcel.readStrongBinder());
- string type = v->type->QualifiedName();
- type += ".Stub";
- addTo->Add(new Assignment(v,
- new MethodCall( NAMES.Find(type), "asInterface", 1,
- new MethodCall(parcel, "readStrongBinder"))));
-}
-
-
-// ================================================================
-
-GenericType::GenericType(const string& package, const string& name,
- const vector<Type*>& args)
- :Type(package, name, BUILT_IN, true, true, true)
-{
- m_args = args;
-
- m_importName = package + '.' + name;
-
- string gen = "<";
- int N = args.size();
- for (int i=0; i<N; i++) {
- Type* t = args[i];
- gen += t->QualifiedName();
- if (i != N-1) {
- gen += ',';
- }
- }
- gen += '>';
- m_genericArguments = gen;
- SetQualifiedName(m_importName + gen);
-}
-
-const vector<Type*>&
-GenericType::GenericArgumentTypes() const
-{
- return m_args;
-}
-
-string
-GenericType::GenericArguments() const
-{
- return m_genericArguments;
-}
-
-string
-GenericType::ImportType() const
-{
- return m_importName;
-}
-
-void
-GenericType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- fprintf(stderr, "implement GenericType::WriteToParcel\n");
-}
-
-void
-GenericType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- fprintf(stderr, "implement GenericType::CreateFromParcel\n");
-}
-
-void
-GenericType::ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- fprintf(stderr, "implement GenericType::ReadFromParcel\n");
-}
-
-
-// ================================================================
-
-GenericListType::GenericListType(const string& package, const string& name,
- const vector<Type*>& args)
- :GenericType(package, name, args),
- m_creator(args[0]->CreatorName())
-{
-}
-
-string
-GenericListType::CreatorName() const
-{
- return "android.os.Parcel.arrayListCreator";
-}
-
-string
-GenericListType::InstantiableName() const
-{
- return "java.util.ArrayList" + GenericArguments();
-}
-
-void
-GenericListType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
-{
- if (m_creator == STRING_TYPE->CreatorName()) {
- addTo->Add(new MethodCall(parcel, "writeStringList", 1, v));
- } else if (m_creator == IBINDER_TYPE->CreatorName()) {
- addTo->Add(new MethodCall(parcel, "writeBinderList", 1, v));
- } else {
- // parcel.writeTypedListXX(arg);
- addTo->Add(new MethodCall(parcel, "writeTypedList", 1, v));
- }
-}
-
-void
-GenericListType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
-{
- if (m_creator == STRING_TYPE->CreatorName()) {
- addTo->Add(new Assignment(v,
- new MethodCall(parcel, "createStringArrayList", 0)));
- } else if (m_creator == IBINDER_TYPE->CreatorName()) {
- addTo->Add(new Assignment(v,
- new MethodCall(parcel, "createBinderArrayList", 0)));
- } else {
- // v = _data.readTypedArrayList(XXX.creator);
- addTo->Add(new Assignment(v,
- new MethodCall(parcel, "createTypedArrayList", 1,
- new LiteralExpression(m_creator))));
- }
-}
-
-void
-GenericListType::ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable**)
-{
- if (m_creator == STRING_TYPE->CreatorName()) {
- addTo->Add(new MethodCall(parcel, "readStringList", 1, v));
- } else if (m_creator == IBINDER_TYPE->CreatorName()) {
- addTo->Add(new MethodCall(parcel, "readBinderList", 1, v));
- } else {
- // v = _data.readTypedList(v, XXX.creator);
- addTo->Add(new MethodCall(parcel, "readTypedList", 2,
- v,
- new LiteralExpression(m_creator)));
- }
-}
-
-void
-GenericListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags)
-{
- Type* generic = GenericArgumentTypes()[0];
- if (generic == RPC_DATA_TYPE) {
- addTo->Add(new MethodCall(data, "putRpcDataList", 2, k, v));
- } else if (generic->RpcCreatorName() != "") {
- addTo->Add(new MethodCall(data, "putFlattenableList", 2, k, v));
- } else {
- addTo->Add(new MethodCall(data, "putList", 2, k, v));
- }
-}
-
-void
-GenericListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl)
-{
- Type* generic = GenericArgumentTypes()[0];
- if (generic == RPC_DATA_TYPE) {
- addTo->Add(new Assignment(v, new MethodCall(data, "getRpcDataList", 2, k)));
- } else if (generic->RpcCreatorName() != "") {
- addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenableList", 2, k,
- new LiteralExpression(generic->RpcCreatorName()))));
- } else {
- string classArg = GenericArgumentTypes()[0]->QualifiedName();
- classArg += ".class";
- addTo->Add(new Assignment(v, new MethodCall(data, "getList", 2, k,
- new LiteralExpression(classArg))));
- }
-}
-
-
-// ================================================================
-
-RpcDataType::RpcDataType()
- :UserDataType("android.support.place.rpc", "RpcData", true, true, true)
-{
-}
-
-void
-RpcDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags)
-{
- addTo->Add(new MethodCall(data, "putRpcData", 2, k, v));
-}
-
-void
-RpcDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
- Variable** cl)
-{
- addTo->Add(new Assignment(v, new MethodCall(data, "getRpcData", 1, k)));
-}
-
-
-// ================================================================
-
-ClassLoaderType::ClassLoaderType()
- :Type("java.lang", "ClassLoader", BUILT_IN, false, false, false)
-{
-}
-
-
-// ================================================================
-
-Namespace::Namespace()
-{
-}
-
-Namespace::~Namespace()
-{
- int N = m_types.size();
- for (int i=0; i<N; i++) {
- delete m_types[i];
- }
-}
-
-void
-Namespace::Add(Type* type)
-{
- Type* t = Find(type->QualifiedName());
- if (t == NULL) {
- m_types.push_back(type);
- }
-}
-
-void
-Namespace::AddGenericType(const string& package, const string& name, int args)
-{
- Generic g;
- g.package = package;
- g.name = name;
- g.qualified = package + '.' + name;
- g.args = args;
- m_generics.push_back(g);
-}
-
-Type*
-Namespace::Find(const string& name) const
-{
- int N = m_types.size();
- for (int i=0; i<N; i++) {
- if (m_types[i]->QualifiedName() == name) {
- return m_types[i];
- }
- }
- return NULL;
-}
-
-Type*
-Namespace::Find(const char* package, const char* name) const
-{
- string s;
- if (package != NULL) {
- s += package;
- s += '.';
- }
- s += name;
- return Find(s);
-}
-
-static string
-normalize_generic(const string& s)
-{
- string r;
- int N = s.size();
- for (int i=0; i<N; i++) {
- char c = s[i];
- if (!isspace(c)) {
- r += c;
- }
- }
- return r;
-}
-
-Type*
-Namespace::Search(const string& name)
-{
- // an exact match wins
- Type* result = Find(name);
- if (result != NULL) {
- return result;
- }
-
- // try the class names
- // our language doesn't allow you to not specify outer classes
- // when referencing an inner class. that could be changed, and this
- // would be the place to do it, but I don't think the complexity in
- // scoping rules is worth it.
- int N = m_types.size();
- for (int i=0; i<N; i++) {
- if (m_types[i]->Name() == name) {
- return m_types[i];
- }
- }
-
- // we got to here and it's not a generic, give up
- if (name.find('<') == name.npos) {
- return NULL;
- }
-
- // remove any whitespace
- string normalized = normalize_generic(name);
-
- // find the part before the '<', find a generic for it
- ssize_t baseIndex = normalized.find('<');
- string base(normalized.c_str(), baseIndex);
- const Generic* g = search_generic(base);
- if (g == NULL) {
- return NULL;
- }
-
- // For each of the args, do a recursive search on it. We don't allow
- // generics within generics like Java does, because we're really limiting
- // them to just built-in container classes, at least for now. Our syntax
- // ensures this right now as well.
- vector<Type*> args;
- size_t start = baseIndex + 1;
- size_t end = start;
- while (normalized[start] != '\0') {
- end = normalized.find(',', start);
- if (end == normalized.npos) {
- end = normalized.find('>', start);
- }
- string s(normalized.c_str()+start, end-start);
- Type* t = this->Search(s);
- if (t == NULL) {
- // maybe we should print a warning here?
- return NULL;
- }
- args.push_back(t);
- start = end+1;
- }
-
- // construct a GenericType, add it to our name set so they always get
- // the same object, and return it.
- result = make_generic_type(g->package, g->name, args);
- if (result == NULL) {
- return NULL;
- }
-
- this->Add(result);
- return this->Find(result->QualifiedName());
-}
-
-const Namespace::Generic*
-Namespace::search_generic(const string& name) const
-{
- int N = m_generics.size();
-
- // first exact match
- for (int i=0; i<N; i++) {
- const Generic& g = m_generics[i];
- if (g.qualified == name) {
- return &g;
- }
- }
-
- // then name match
- for (int i=0; i<N; i++) {
- const Generic& g = m_generics[i];
- if (g.name == name) {
- return &g;
- }
- }
-
- return NULL;
-}
-
-void
-Namespace::Dump() const
-{
- int n = m_types.size();
- for (int i=0; i<n; i++) {
- Type* t = m_types[i];
- printf("type: package=%s name=%s qualifiedName=%s\n",
- t->Package().c_str(), t->Name().c_str(),
- t->QualifiedName().c_str());
- }
-}
diff --git a/tools/aidl/Type.h b/tools/aidl/Type.h
deleted file mode 100644
index ae12720142e8..000000000000
--- a/tools/aidl/Type.h
+++ /dev/null
@@ -1,542 +0,0 @@
-#ifndef AIDL_TYPE_H
-#define AIDL_TYPE_H
-
-#include "AST.h"
-#include <string>
-#include <vector>
-
-using namespace std;
-
-class Type
-{
-public:
- // kinds
- enum {
- BUILT_IN,
- USERDATA,
- INTERFACE,
- GENERATED
- };
-
- // WriteToParcel flags
- enum {
- PARCELABLE_WRITE_RETURN_VALUE = 0x0001
- };
-
- Type(const string& name, int kind, bool canWriteToParcel,
- bool canWriteToRpcData, bool canBeOut);
- Type(const string& package, const string& name,
- int kind, bool canWriteToParcel, bool canWriteToRpcData, bool canBeOut,
- const string& declFile = "", int declLine = -1);
- virtual ~Type();
-
- inline string Package() const { return m_package; }
- inline string Name() const { return m_name; }
- inline string QualifiedName() const { return m_qualifiedName; }
- inline int Kind() const { return m_kind; }
- inline string DeclFile() const { return m_declFile; }
- inline int DeclLine() const { return m_declLine; }
- inline bool CanWriteToParcel() const { return m_canWriteToParcel; }
- inline bool CanWriteToRpcData() const { return m_canWriteToRpcData; }
- inline bool CanBeOutParameter() const { return m_canBeOut; }
-
- virtual string ImportType() const;
- virtual string CreatorName() const;
- virtual string RpcCreatorName() const;
- virtual string InstantiableName() const;
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual bool CanBeArray() const;
-
- virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags);
- virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl);
-
-protected:
- void SetQualifiedName(const string& qualified);
- Expression* BuildWriteToParcelFlags(int flags);
-
-private:
- Type();
- Type(const Type&);
-
- string m_package;
- string m_name;
- string m_qualifiedName;
- string m_declFile;
- int m_declLine;
- int m_kind;
- bool m_canWriteToParcel;
- bool m_canWriteToRpcData;
- bool m_canBeOut;
-};
-
-class BasicType : public Type
-{
-public:
- BasicType(const string& name,
- const string& marshallParcel,
- const string& unmarshallParcel,
- const string& writeArrayParcel,
- const string& createArrayParcel,
- const string& readArrayParcel,
- const string& marshallRpc,
- const string& unmarshallRpc,
- const string& writeArrayRpc,
- const string& createArrayRpc,
- const string& readArrayRpc);
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual bool CanBeArray() const;
-
- virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags);
- virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl);
-
-private:
- string m_marshallParcel;
- string m_unmarshallParcel;
- string m_writeArrayParcel;
- string m_createArrayParcel;
- string m_readArrayParcel;
- string m_marshallRpc;
- string m_unmarshallRpc;
- string m_writeArrayRpc;
- string m_createArrayRpc;
- string m_readArrayRpc;
-};
-
-class BooleanType : public Type
-{
-public:
- BooleanType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual bool CanBeArray() const;
-
- virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags);
- virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl);
-};
-
-class CharType : public Type
-{
-public:
- CharType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual bool CanBeArray() const;
-
- virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags);
- virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl);
-};
-
-
-class StringType : public Type
-{
-public:
- StringType();
-
- virtual string CreatorName() const;
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual bool CanBeArray() const;
-
- virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags);
- virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl);
-};
-
-class CharSequenceType : public Type
-{
-public:
- CharSequenceType();
-
- virtual string CreatorName() const;
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class RemoteExceptionType : public Type
-{
-public:
- RemoteExceptionType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class RuntimeExceptionType : public Type
-{
-public:
- RuntimeExceptionType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class IBinderType : public Type
-{
-public:
- IBinderType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class IInterfaceType : public Type
-{
-public:
- IInterfaceType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class BinderType : public Type
-{
-public:
- BinderType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class BinderProxyType : public Type
-{
-public:
- BinderProxyType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class ParcelType : public Type
-{
-public:
- ParcelType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class ParcelableInterfaceType : public Type
-{
-public:
- ParcelableInterfaceType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class MapType : public Type
-{
-public:
- MapType();
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-};
-
-class ListType : public Type
-{
-public:
- ListType();
-
- virtual string InstantiableName() const;
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags);
- virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl);
-};
-
-class UserDataType : public Type
-{
-public:
- UserDataType(const string& package, const string& name,
- bool builtIn, bool canWriteToParcel, bool canWriteToRpcData,
- const string& declFile = "", int declLine = -1);
-
- virtual string CreatorName() const;
- virtual string RpcCreatorName() const;
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual bool CanBeArray() const;
-
- virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags);
- virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl);
-};
-
-class InterfaceType : public Type
-{
-public:
- InterfaceType(const string& package, const string& name,
- bool builtIn, bool oneway,
- const string& declFile, int declLine);
-
- bool OneWay() const;
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
-private:
- bool m_oneway;
-};
-
-
-class GenericType : public Type
-{
-public:
- GenericType(const string& package, const string& name,
- const vector<Type*>& args);
-
- const vector<Type*>& GenericArgumentTypes() const;
- string GenericArguments() const;
-
- virtual string ImportType() const;
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
-private:
- string m_genericArguments;
- string m_importName;
- vector<Type*> m_args;
-};
-
-class RpcDataType : public UserDataType
-{
-public:
- RpcDataType();
-
- virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags);
- virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl);
-};
-
-class ClassLoaderType : public Type
-{
-public:
- ClassLoaderType();
-};
-
-class GenericListType : public GenericType
-{
-public:
- GenericListType(const string& package, const string& name,
- const vector<Type*>& args);
-
- virtual string CreatorName() const;
- virtual string InstantiableName() const;
-
- virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags);
- virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
- virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl);
-
- virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, int flags);
- virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data, Variable** cl);
-
-private:
- string m_creator;
-};
-
-class Namespace
-{
-public:
- Namespace();
- ~Namespace();
- void Add(Type* type);
-
- // args is the number of template types (what is this called?)
- void AddGenericType(const string& package, const string& name, int args);
-
- // lookup a specific class name
- Type* Find(const string& name) const;
- Type* Find(const char* package, const char* name) const;
-
- // try to search by either a full name or a partial name
- Type* Search(const string& name);
-
- void Dump() const;
-
-private:
- struct Generic {
- string package;
- string name;
- string qualified;
- int args;
- };
-
- const Generic* search_generic(const string& name) const;
-
- vector<Type*> m_types;
- vector<Generic> m_generics;
-};
-
-extern Namespace NAMES;
-
-extern Type* VOID_TYPE;
-extern Type* BOOLEAN_TYPE;
-extern Type* BYTE_TYPE;
-extern Type* CHAR_TYPE;
-extern Type* INT_TYPE;
-extern Type* LONG_TYPE;
-extern Type* FLOAT_TYPE;
-extern Type* DOUBLE_TYPE;
-extern Type* OBJECT_TYPE;
-extern Type* STRING_TYPE;
-extern Type* CHAR_SEQUENCE_TYPE;
-extern Type* TEXT_UTILS_TYPE;
-extern Type* REMOTE_EXCEPTION_TYPE;
-extern Type* RUNTIME_EXCEPTION_TYPE;
-extern Type* IBINDER_TYPE;
-extern Type* IINTERFACE_TYPE;
-extern Type* BINDER_NATIVE_TYPE;
-extern Type* BINDER_PROXY_TYPE;
-extern Type* PARCEL_TYPE;
-extern Type* PARCELABLE_INTERFACE_TYPE;
-
-extern Type* CONTEXT_TYPE;
-
-extern Type* RPC_DATA_TYPE;
-extern Type* RPC_ERROR_TYPE;
-extern Type* RPC_CONTEXT_TYPE;
-extern Type* EVENT_FAKE_TYPE;
-
-extern Expression* NULL_VALUE;
-extern Expression* THIS_VALUE;
-extern Expression* SUPER_VALUE;
-extern Expression* TRUE_VALUE;
-extern Expression* FALSE_VALUE;
-
-void register_base_types();
-
-#endif // AIDL_TYPE_H
diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp
deleted file mode 100644
index 14c9f95a247b..000000000000
--- a/tools/aidl/aidl.cpp
+++ /dev/null
@@ -1,1158 +0,0 @@
-
-#include "aidl_language.h"
-#include "options.h"
-#include "search_path.h"
-#include "Type.h"
-#include "generate_java.h"
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/param.h>
-#include <sys/stat.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <map>
-
-#ifdef HAVE_MS_C_RUNTIME
-#include <io.h>
-#include <direct.h>
-#include <sys/stat.h>
-#endif
-
-#ifndef O_BINARY
-# define O_BINARY 0
-#endif
-
-// The following are gotten as the offset from the allowable id's between
-// android.os.IBinder.FIRST_CALL_TRANSACTION=1 and
-// android.os.IBinder.LAST_CALL_TRANSACTION=16777215
-#define MIN_USER_SET_METHOD_ID 0
-#define MAX_USER_SET_METHOD_ID 16777214
-
-using namespace std;
-
-static void
-test_document(document_item_type* d)
-{
- while (d) {
- if (d->item_type == INTERFACE_TYPE_BINDER) {
- interface_type* c = (interface_type*)d;
- printf("interface %s %s {\n", c->package, c->name.data);
- interface_item_type *q = (interface_item_type*)c->interface_items;
- while (q) {
- if (q->item_type == METHOD_TYPE) {
- method_type *m = (method_type*)q;
- printf(" %s %s(", m->type.type.data, m->name.data);
- arg_type *p = m->args;
- while (p) {
- printf("%s %s",p->type.type.data,p->name.data);
- if (p->next) printf(", ");
- p=p->next;
- }
- printf(")");
- printf(";\n");
- }
- q=q->next;
- }
- printf("}\n");
- }
- else if (d->item_type == USER_DATA_TYPE) {
- user_data_type* b = (user_data_type*)d;
- if ((b->flattening_methods & PARCELABLE_DATA) != 0) {
- printf("parcelable %s %s;\n", b->package, b->name.data);
- }
- if ((b->flattening_methods & RPC_DATA) != 0) {
- printf("flattenable %s %s;\n", b->package, b->name.data);
- }
- }
- else {
- printf("UNKNOWN d=0x%08lx d->item_type=%d\n", (long)d, d->item_type);
- }
- d = d->next;
- }
-}
-
-// ==========================================================
-int
-convert_direction(const char* direction)
-{
- if (direction == NULL) {
- return IN_PARAMETER;
- }
- if (0 == strcmp(direction, "in")) {
- return IN_PARAMETER;
- }
- if (0 == strcmp(direction, "out")) {
- return OUT_PARAMETER;
- }
- return INOUT_PARAMETER;
-}
-
-// ==========================================================
-struct import_info {
- const char* from;
- const char* filename;
- buffer_type statement;
- const char* neededClass;
- document_item_type* doc;
- struct import_info* next;
-};
-
-document_item_type* g_document = NULL;
-import_info* g_imports = NULL;
-
-static void
-main_document_parsed(document_item_type* d)
-{
- g_document = d;
-}
-
-static void
-main_import_parsed(buffer_type* statement)
-{
- import_info* import = (import_info*)malloc(sizeof(import_info));
- memset(import, 0, sizeof(import_info));
- import->from = strdup(g_currentFilename);
- import->statement.lineno = statement->lineno;
- import->statement.data = strdup(statement->data);
- import->statement.extra = NULL;
- import->next = g_imports;
- import->neededClass = parse_import_statement(statement->data);
- g_imports = import;
-}
-
-static ParserCallbacks g_mainCallbacks = {
- &main_document_parsed,
- &main_import_parsed
-};
-
-char*
-parse_import_statement(const char* text)
-{
- const char* end;
- int len;
-
- while (isspace(*text)) {
- text++;
- }
- while (!isspace(*text)) {
- text++;
- }
- while (isspace(*text)) {
- text++;
- }
- end = text;
- while (!isspace(*end) && *end != ';') {
- end++;
- }
- len = end-text;
-
- char* rv = (char*)malloc(len+1);
- memcpy(rv, text, len);
- rv[len] = '\0';
-
- return rv;
-}
-
-// ==========================================================
-static void
-import_import_parsed(buffer_type* statement)
-{
-}
-
-static ParserCallbacks g_importCallbacks = {
- &main_document_parsed,
- &import_import_parsed
-};
-
-// ==========================================================
-static int
-check_filename(const char* filename, const char* package, buffer_type* name)
-{
- const char* p;
- string expected;
- string fn;
- size_t len;
- char cwd[MAXPATHLEN];
- bool valid = false;
-
-#ifdef HAVE_WINDOWS_PATHS
- if (isalpha(filename[0]) && filename[1] == ':'
- && filename[2] == OS_PATH_SEPARATOR) {
-#else
- if (filename[0] == OS_PATH_SEPARATOR) {
-#endif
- fn = filename;
- } else {
- fn = getcwd(cwd, sizeof(cwd));
- len = fn.length();
- if (fn[len-1] != OS_PATH_SEPARATOR) {
- fn += OS_PATH_SEPARATOR;
- }
- fn += filename;
- }
-
- if (package) {
- expected = package;
- expected += '.';
- }
-
- len = expected.length();
- for (size_t i=0; i<len; i++) {
- if (expected[i] == '.') {
- expected[i] = OS_PATH_SEPARATOR;
- }
- }
-
- p = strchr(name->data, '.');
- len = p ? p-name->data : strlen(name->data);
- expected.append(name->data, len);
-
- expected += ".aidl";
-
- len = fn.length();
- valid = (len >= expected.length());
-
- if (valid) {
- p = fn.c_str() + (len - expected.length());
-
-#ifdef HAVE_WINDOWS_PATHS
- if (OS_PATH_SEPARATOR != '/') {
- // Input filename under cygwin most likely has / separators
- // whereas the expected string uses \\ separators. Adjust
- // them accordingly.
- for (char *c = const_cast<char *>(p); *c; ++c) {
- if (*c == '/') *c = OS_PATH_SEPARATOR;
- }
- }
-#endif
-
- // aidl assumes case-insensitivity on Mac Os and Windows.
-#if defined(__linux__)
- valid = (expected == p);
-#else
- valid = !strcasecmp(expected.c_str(), p);
-#endif
- }
-
- if (!valid) {
- fprintf(stderr, "%s:%d interface %s should be declared in a file"
- " called %s.\n",
- filename, name->lineno, name->data, expected.c_str());
- return 1;
- }
-
- return 0;
-}
-
-static int
-check_filenames(const char* filename, document_item_type* items)
-{
- int err = 0;
- while (items) {
- if (items->item_type == USER_DATA_TYPE) {
- user_data_type* p = (user_data_type*)items;
- err |= check_filename(filename, p->package, &p->name);
- }
- else if (items->item_type == INTERFACE_TYPE_BINDER
- || items->item_type == INTERFACE_TYPE_RPC) {
- interface_type* c = (interface_type*)items;
- err |= check_filename(filename, c->package, &c->name);
- }
- else {
- fprintf(stderr, "aidl: internal error unkown document type %d.\n",
- items->item_type);
- return 1;
- }
- items = items->next;
- }
- return err;
-}
-
-// ==========================================================
-static const char*
-kind_to_string(int kind)
-{
- switch (kind)
- {
- case Type::INTERFACE:
- return "an interface";
- case Type::USERDATA:
- return "a user data";
- default:
- return "ERROR";
- }
-}
-
-static char*
-rfind(char* str, char c)
-{
- char* p = str + strlen(str) - 1;
- while (p >= str) {
- if (*p == c) {
- return p;
- }
- p--;
- }
- return NULL;
-}
-
-static int
-gather_types(const char* filename, document_item_type* items)
-{
- int err = 0;
- while (items) {
- Type* type;
- if (items->item_type == USER_DATA_TYPE) {
- user_data_type* p = (user_data_type*)items;
- type = new UserDataType(p->package ? p->package : "", p->name.data,
- false, ((p->flattening_methods & PARCELABLE_DATA) != 0),
- ((p->flattening_methods & RPC_DATA) != 0), filename, p->name.lineno);
- }
- else if (items->item_type == INTERFACE_TYPE_BINDER
- || items->item_type == INTERFACE_TYPE_RPC) {
- interface_type* c = (interface_type*)items;
- type = new InterfaceType(c->package ? c->package : "",
- c->name.data, false, c->oneway,
- filename, c->name.lineno);
- }
- else {
- fprintf(stderr, "aidl: internal error %s:%d\n", __FILE__, __LINE__);
- return 1;
- }
-
- Type* old = NAMES.Find(type->QualifiedName());
- if (old == NULL) {
- NAMES.Add(type);
-
- if (items->item_type == INTERFACE_TYPE_BINDER) {
- // for interfaces, also add the stub and proxy types, we don't
- // bother checking these for duplicates, because the parser
- // won't let us do it.
- interface_type* c = (interface_type*)items;
-
- string name = c->name.data;
- name += ".Stub";
- Type* stub = new Type(c->package ? c->package : "",
- name, Type::GENERATED, false, false, false,
- filename, c->name.lineno);
- NAMES.Add(stub);
-
- name = c->name.data;
- name += ".Stub.Proxy";
- Type* proxy = new Type(c->package ? c->package : "",
- name, Type::GENERATED, false, false, false,
- filename, c->name.lineno);
- NAMES.Add(proxy);
- }
- else if (items->item_type == INTERFACE_TYPE_RPC) {
- // for interfaces, also add the service base type, we don't
- // bother checking these for duplicates, because the parser
- // won't let us do it.
- interface_type* c = (interface_type*)items;
-
- string name = c->name.data;
- name += ".ServiceBase";
- Type* base = new Type(c->package ? c->package : "",
- name, Type::GENERATED, false, false, false,
- filename, c->name.lineno);
- NAMES.Add(base);
- }
- } else {
- if (old->Kind() == Type::BUILT_IN) {
- fprintf(stderr, "%s:%d attempt to redefine built in class %s\n",
- filename, type->DeclLine(),
- type->QualifiedName().c_str());
- err = 1;
- }
- else if (type->Kind() != old->Kind()) {
- const char* oldKind = kind_to_string(old->Kind());
- const char* newKind = kind_to_string(type->Kind());
-
- fprintf(stderr, "%s:%d attempt to redefine %s as %s,\n",
- filename, type->DeclLine(),
- type->QualifiedName().c_str(), newKind);
- fprintf(stderr, "%s:%d previously defined here as %s.\n",
- old->DeclFile().c_str(), old->DeclLine(), oldKind);
- err = 1;
- }
- }
-
- items = items->next;
- }
- return err;
-}
-
-// ==========================================================
-static bool
-matches_keyword(const char* str)
-{
- static const char* KEYWORDS[] = { "abstract", "assert", "boolean", "break",
- "byte", "case", "catch", "char", "class", "const", "continue",
- "default", "do", "double", "else", "enum", "extends", "final",
- "finally", "float", "for", "goto", "if", "implements", "import",
- "instanceof", "int", "interface", "long", "native", "new", "package",
- "private", "protected", "public", "return", "short", "static",
- "strictfp", "super", "switch", "synchronized", "this", "throw",
- "throws", "transient", "try", "void", "volatile", "while",
- "true", "false", "null",
- NULL
- };
- const char** k = KEYWORDS;
- while (*k) {
- if (0 == strcmp(str, *k)) {
- return true;
- }
- k++;
- }
- return false;
-}
-
-static int
-check_method(const char* filename, int kind, method_type* m)
-{
- int err = 0;
-
- // return type
- Type* returnType = NAMES.Search(m->type.type.data);
- if (returnType == NULL) {
- fprintf(stderr, "%s:%d unknown return type %s\n", filename,
- m->type.type.lineno, m->type.type.data);
- err = 1;
- return err;
- }
-
- if (returnType == EVENT_FAKE_TYPE) {
- if (kind != INTERFACE_TYPE_RPC) {
- fprintf(stderr, "%s:%d event methods only supported for rpc interfaces\n",
- filename, m->type.type.lineno);
- err = 1;
- }
- } else {
- if (!(kind == INTERFACE_TYPE_BINDER ? returnType->CanWriteToParcel()
- : returnType->CanWriteToRpcData())) {
- fprintf(stderr, "%s:%d return type %s can't be marshalled.\n", filename,
- m->type.type.lineno, m->type.type.data);
- err = 1;
- }
- }
-
- if (m->type.dimension > 0 && !returnType->CanBeArray()) {
- fprintf(stderr, "%s:%d return type %s%s can't be an array.\n", filename,
- m->type.array_token.lineno, m->type.type.data,
- m->type.array_token.data);
- err = 1;
- }
-
- if (m->type.dimension > 1) {
- fprintf(stderr, "%s:%d return type %s%s only one"
- " dimensional arrays are supported\n", filename,
- m->type.array_token.lineno, m->type.type.data,
- m->type.array_token.data);
- err = 1;
- }
-
- int index = 1;
-
- arg_type* arg = m->args;
- while (arg) {
- Type* t = NAMES.Search(arg->type.type.data);
-
- // check the arg type
- if (t == NULL) {
- fprintf(stderr, "%s:%d parameter %s (%d) unknown type %s\n",
- filename, m->type.type.lineno, arg->name.data, index,
- arg->type.type.data);
- err = 1;
- goto next;
- }
-
- if (t == EVENT_FAKE_TYPE) {
- fprintf(stderr, "%s:%d parameter %s (%d) event can not be used as a parameter %s\n",
- filename, m->type.type.lineno, arg->name.data, index,
- arg->type.type.data);
- err = 1;
- goto next;
- }
-
- if (!(kind == INTERFACE_TYPE_BINDER ? t->CanWriteToParcel() : t->CanWriteToRpcData())) {
- fprintf(stderr, "%s:%d parameter %d: '%s %s' can't be marshalled.\n",
- filename, m->type.type.lineno, index,
- arg->type.type.data, arg->name.data);
- err = 1;
- }
-
- if (returnType == EVENT_FAKE_TYPE
- && convert_direction(arg->direction.data) != IN_PARAMETER) {
- fprintf(stderr, "%s:%d parameter %d: '%s %s' All paremeters on events must be 'in'.\n",
- filename, m->type.type.lineno, index,
- arg->type.type.data, arg->name.data);
- err = 1;
- goto next;
- }
-
- if (arg->direction.data == NULL
- && (arg->type.dimension != 0 || t->CanBeOutParameter())) {
- fprintf(stderr, "%s:%d parameter %d: '%s %s' can be an out"
- " parameter, so you must declare it as in,"
- " out or inout.\n",
- filename, m->type.type.lineno, index,
- arg->type.type.data, arg->name.data);
- err = 1;
- }
-
- if (convert_direction(arg->direction.data) != IN_PARAMETER
- && !t->CanBeOutParameter()
- && arg->type.dimension == 0) {
- fprintf(stderr, "%s:%d parameter %d: '%s %s %s' can only be an in"
- " parameter.\n",
- filename, m->type.type.lineno, index,
- arg->direction.data, arg->type.type.data,
- arg->name.data);
- err = 1;
- }
-
- if (arg->type.dimension > 0 && !t->CanBeArray()) {
- fprintf(stderr, "%s:%d parameter %d: '%s %s%s %s' can't be an"
- " array.\n", filename,
- m->type.array_token.lineno, index, arg->direction.data,
- arg->type.type.data, arg->type.array_token.data,
- arg->name.data);
- err = 1;
- }
-
- if (arg->type.dimension > 1) {
- fprintf(stderr, "%s:%d parameter %d: '%s %s%s %s' only one"
- " dimensional arrays are supported\n", filename,
- m->type.array_token.lineno, index, arg->direction.data,
- arg->type.type.data, arg->type.array_token.data,
- arg->name.data);
- err = 1;
- }
-
- // check that the name doesn't match a keyword
- if (matches_keyword(arg->name.data)) {
- fprintf(stderr, "%s:%d parameter %d %s is named the same as a"
- " Java or aidl keyword\n",
- filename, m->name.lineno, index, arg->name.data);
- err = 1;
- }
-
-next:
- index++;
- arg = arg->next;
- }
-
- return err;
-}
-
-static int
-check_types(const char* filename, document_item_type* items)
-{
- int err = 0;
- while (items) {
- // (nothing to check for USER_DATA_TYPE)
- if (items->item_type == INTERFACE_TYPE_BINDER
- || items->item_type == INTERFACE_TYPE_RPC) {
- map<string,method_type*> methodNames;
- interface_type* c = (interface_type*)items;
-
- interface_item_type* member = c->interface_items;
- while (member) {
- if (member->item_type == METHOD_TYPE) {
- method_type* m = (method_type*)member;
-
- err |= check_method(filename, items->item_type, m);
-
- // prevent duplicate methods
- if (methodNames.find(m->name.data) == methodNames.end()) {
- methodNames[m->name.data] = m;
- } else {
- fprintf(stderr,"%s:%d attempt to redefine method %s,\n",
- filename, m->name.lineno, m->name.data);
- method_type* old = methodNames[m->name.data];
- fprintf(stderr, "%s:%d previously defined here.\n",
- filename, old->name.lineno);
- err = 1;
- }
- }
- member = member->next;
- }
- }
-
- items = items->next;
- }
- return err;
-}
-
-// ==========================================================
-static int
-exactly_one_interface(const char* filename, const document_item_type* items, const Options& options,
- bool* onlyParcelable)
-{
- if (items == NULL) {
- fprintf(stderr, "%s: file does not contain any interfaces\n",
- filename);
- return 1;
- }
-
- const document_item_type* next = items->next;
- // Allow parcelables to skip the "one-only" rule.
- if (items->next != NULL && next->item_type != USER_DATA_TYPE) {
- int lineno = -1;
- if (next->item_type == INTERFACE_TYPE_BINDER) {
- lineno = ((interface_type*)next)->interface_token.lineno;
- }
- else if (next->item_type == INTERFACE_TYPE_RPC) {
- lineno = ((interface_type*)next)->interface_token.lineno;
- }
- fprintf(stderr, "%s:%d aidl can only handle one interface per file\n",
- filename, lineno);
- return 1;
- }
-
- if (items->item_type == USER_DATA_TYPE) {
- *onlyParcelable = true;
- if (options.failOnParcelable) {
- fprintf(stderr, "%s:%d aidl can only generate code for interfaces, not"
- " parcelables or flattenables,\n", filename,
- ((user_data_type*)items)->keyword_token.lineno);
- fprintf(stderr, "%s:%d .aidl files that only declare parcelables or flattenables"
- "may not go in the Makefile.\n", filename,
- ((user_data_type*)items)->keyword_token.lineno);
- return 1;
- }
- } else {
- *onlyParcelable = false;
- }
-
- return 0;
-}
-
-// ==========================================================
-void
-generate_dep_file(const Options& options, const document_item_type* items)
-{
- /* we open the file in binary mode to ensure that the same output is
- * generated on all platforms !!
- */
- FILE* to = NULL;
- if (options.autoDepFile) {
- string fileName = options.outputFileName + ".d";
- to = fopen(fileName.c_str(), "wb");
- } else {
- to = fopen(options.depFileName.c_str(), "wb");
- }
-
- if (to == NULL) {
- return;
- }
-
- const char* slash = "\\";
- import_info* import = g_imports;
- if (import == NULL) {
- slash = "";
- }
-
- if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
- fprintf(to, "%s: \\\n", options.outputFileName.c_str());
- } else {
- // parcelable: there's no output file.
- fprintf(to, " : \\\n");
- }
- fprintf(to, " %s %s\n", options.inputFileName.c_str(), slash);
-
- while (import) {
- if (import->next == NULL) {
- slash = "";
- }
- if (import->filename) {
- fprintf(to, " %s %s\n", import->filename, slash);
- }
- import = import->next;
- }
-
- fprintf(to, "\n");
-
- // Output "<imported_file>: " so make won't fail if the imported file has
- // been deleted, moved or renamed in incremental build.
- import = g_imports;
- while (import) {
- if (import->filename) {
- fprintf(to, "%s :\n", import->filename);
- }
- import = import->next;
- }
-
- fclose(to);
-}
-
-// ==========================================================
-static string
-generate_outputFileName2(const Options& options, const buffer_type& name, const char* package)
-{
- string result;
-
- // create the path to the destination folder based on the
- // interface package name
- result = options.outputBaseFolder;
- result += OS_PATH_SEPARATOR;
-
- string packageStr = package;
- size_t len = packageStr.length();
- for (size_t i=0; i<len; i++) {
- if (packageStr[i] == '.') {
- packageStr[i] = OS_PATH_SEPARATOR;
- }
- }
-
- result += packageStr;
-
- // add the filename by replacing the .aidl extension to .java
- const char* p = strchr(name.data, '.');
- len = p ? p-name.data : strlen(name.data);
-
- result += OS_PATH_SEPARATOR;
- result.append(name.data, len);
- result += ".java";
-
- return result;
-}
-
-// ==========================================================
-static string
-generate_outputFileName(const Options& options, const document_item_type* items)
-{
- // items has already been checked to have only one interface.
- if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
- interface_type* type = (interface_type*)items;
-
- return generate_outputFileName2(options, type->name, type->package);
- } else if (items->item_type == USER_DATA_TYPE) {
- user_data_type* type = (user_data_type*)items;
- return generate_outputFileName2(options, type->name, type->package);
- }
-
- // I don't think we can come here, but safer than returning NULL.
- string result;
- return result;
-}
-
-
-
-// ==========================================================
-static void
-check_outputFilePath(const string& path) {
- size_t len = path.length();
- for (size_t i=0; i<len ; i++) {
- if (path[i] == OS_PATH_SEPARATOR) {
- string p = path.substr(0, i);
- if (access(path.data(), F_OK) != 0) {
-#ifdef HAVE_MS_C_RUNTIME
- _mkdir(p.data());
-#else
- mkdir(p.data(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
-#endif
- }
- }
- }
-}
-
-
-// ==========================================================
-static int
-parse_preprocessed_file(const string& filename)
-{
- int err;
-
- FILE* f = fopen(filename.c_str(), "rb");
- if (f == NULL) {
- fprintf(stderr, "aidl: can't open preprocessed file: %s\n",
- filename.c_str());
- return 1;
- }
-
- int lineno = 1;
- char line[1024];
- char type[1024];
- char fullname[1024];
- while (fgets(line, sizeof(line), f)) {
- // skip comments and empty lines
- if (!line[0] || strncmp(line, "//", 2) == 0) {
- continue;
- }
-
- sscanf(line, "%s %[^; \r\n\t];", type, fullname);
-
- char* packagename;
- char* classname = rfind(fullname, '.');
- if (classname != NULL) {
- *classname = '\0';
- classname++;
- packagename = fullname;
- } else {
- classname = fullname;
- packagename = NULL;
- }
-
- //printf("%s:%d:...%s...%s...%s...\n", filename.c_str(), lineno,
- // type, packagename, classname);
- document_item_type* doc;
-
- if (0 == strcmp("parcelable", type)) {
- user_data_type* parcl = (user_data_type*)malloc(
- sizeof(user_data_type));
- memset(parcl, 0, sizeof(user_data_type));
- parcl->document_item.item_type = USER_DATA_TYPE;
- parcl->keyword_token.lineno = lineno;
- parcl->keyword_token.data = strdup(type);
- parcl->package = packagename ? strdup(packagename) : NULL;
- parcl->name.lineno = lineno;
- parcl->name.data = strdup(classname);
- parcl->semicolon_token.lineno = lineno;
- parcl->semicolon_token.data = strdup(";");
- parcl->flattening_methods = PARCELABLE_DATA;
- doc = (document_item_type*)parcl;
- }
- else if (0 == strcmp("flattenable", type)) {
- user_data_type* parcl = (user_data_type*)malloc(
- sizeof(user_data_type));
- memset(parcl, 0, sizeof(user_data_type));
- parcl->document_item.item_type = USER_DATA_TYPE;
- parcl->keyword_token.lineno = lineno;
- parcl->keyword_token.data = strdup(type);
- parcl->package = packagename ? strdup(packagename) : NULL;
- parcl->name.lineno = lineno;
- parcl->name.data = strdup(classname);
- parcl->semicolon_token.lineno = lineno;
- parcl->semicolon_token.data = strdup(";");
- parcl->flattening_methods = RPC_DATA;
- doc = (document_item_type*)parcl;
- }
- else if (0 == strcmp("interface", type)) {
- interface_type* iface = (interface_type*)malloc(
- sizeof(interface_type));
- memset(iface, 0, sizeof(interface_type));
- iface->document_item.item_type = INTERFACE_TYPE_BINDER;
- iface->interface_token.lineno = lineno;
- iface->interface_token.data = strdup(type);
- iface->package = packagename ? strdup(packagename) : NULL;
- iface->name.lineno = lineno;
- iface->name.data = strdup(classname);
- iface->open_brace_token.lineno = lineno;
- iface->open_brace_token.data = strdup("{");
- iface->close_brace_token.lineno = lineno;
- iface->close_brace_token.data = strdup("}");
- doc = (document_item_type*)iface;
- }
- else {
- fprintf(stderr, "%s:%d: bad type in line: %s\n",
- filename.c_str(), lineno, line);
- fclose(f);
- return 1;
- }
- err = gather_types(filename.c_str(), doc);
- lineno++;
- }
-
- if (!feof(f)) {
- fprintf(stderr, "%s:%d: error reading file, line to long.\n",
- filename.c_str(), lineno);
- return 1;
- }
-
- fclose(f);
- return 0;
-}
-
-static int
-check_and_assign_method_ids(const char * filename, interface_item_type* first_item)
-{
- // Check whether there are any methods with manually assigned id's and any that are not.
- // Either all method id's must be manually assigned or all of them must not.
- // Also, check for duplicates of user set id's and that the id's are within the proper bounds.
- set<int> usedIds;
- interface_item_type* item = first_item;
- bool hasUnassignedIds = false;
- bool hasAssignedIds = false;
- while (item != NULL) {
- if (item->item_type == METHOD_TYPE) {
- method_type* method_item = (method_type*)item;
- if (method_item->hasId) {
- hasAssignedIds = true;
- method_item->assigned_id = atoi(method_item->id.data);
- // Ensure that the user set id is not duplicated.
- if (usedIds.find(method_item->assigned_id) != usedIds.end()) {
- // We found a duplicate id, so throw an error.
- fprintf(stderr,
- "%s:%d Found duplicate method id (%d) for method: %s\n",
- filename, method_item->id.lineno,
- method_item->assigned_id, method_item->name.data);
- return 1;
- }
- // Ensure that the user set id is within the appropriate limits
- if (method_item->assigned_id < MIN_USER_SET_METHOD_ID ||
- method_item->assigned_id > MAX_USER_SET_METHOD_ID) {
- fprintf(stderr, "%s:%d Found out of bounds id (%d) for method: %s\n",
- filename, method_item->id.lineno,
- method_item->assigned_id, method_item->name.data);
- fprintf(stderr, " Value for id must be between %d and %d inclusive.\n",
- MIN_USER_SET_METHOD_ID, MAX_USER_SET_METHOD_ID);
- return 1;
- }
- usedIds.insert(method_item->assigned_id);
- } else {
- hasUnassignedIds = true;
- }
- if (hasAssignedIds && hasUnassignedIds) {
- fprintf(stderr,
- "%s: You must either assign id's to all methods or to none of them.\n",
- filename);
- return 1;
- }
- }
- item = item->next;
- }
-
- // In the case that all methods have unassigned id's, set a unique id for them.
- if (hasUnassignedIds) {
- int newId = 0;
- item = first_item;
- while (item != NULL) {
- if (item->item_type == METHOD_TYPE) {
- method_type* method_item = (method_type*)item;
- method_item->assigned_id = newId++;
- }
- item = item->next;
- }
- }
-
- // success
- return 0;
-}
-
-// ==========================================================
-static int
-compile_aidl(Options& options)
-{
- int err = 0, N;
-
- set_import_paths(options.importPaths);
-
- register_base_types();
-
- // import the preprocessed file
- N = options.preprocessedFiles.size();
- for (int i=0; i<N; i++) {
- const string& s = options.preprocessedFiles[i];
- err |= parse_preprocessed_file(s);
- }
- if (err != 0) {
- return err;
- }
-
- // parse the main file
- g_callbacks = &g_mainCallbacks;
- err = parse_aidl(options.inputFileName.c_str());
- document_item_type* mainDoc = g_document;
- g_document = NULL;
-
- // parse the imports
- g_callbacks = &g_mainCallbacks;
- import_info* import = g_imports;
- while (import) {
- if (NAMES.Find(import->neededClass) == NULL) {
- import->filename = find_import_file(import->neededClass);
- if (!import->filename) {
- fprintf(stderr, "%s:%d: couldn't find import for class %s\n",
- import->from, import->statement.lineno,
- import->neededClass);
- err |= 1;
- } else {
- err |= parse_aidl(import->filename);
- import->doc = g_document;
- if (import->doc == NULL) {
- err |= 1;
- }
- }
- }
- import = import->next;
- }
- // bail out now if parsing wasn't successful
- if (err != 0 || mainDoc == NULL) {
- //fprintf(stderr, "aidl: parsing failed, stopping.\n");
- return 1;
- }
-
- // complain about ones that aren't in the right files
- err |= check_filenames(options.inputFileName.c_str(), mainDoc);
- import = g_imports;
- while (import) {
- err |= check_filenames(import->filename, import->doc);
- import = import->next;
- }
-
- // gather the types that have been declared
- err |= gather_types(options.inputFileName.c_str(), mainDoc);
- import = g_imports;
- while (import) {
- err |= gather_types(import->filename, import->doc);
- import = import->next;
- }
-
-#if 0
- printf("---- main doc ----\n");
- test_document(mainDoc);
-
- import = g_imports;
- while (import) {
- printf("---- import doc ----\n");
- test_document(import->doc);
- import = import->next;
- }
- NAMES.Dump();
-#endif
-
- // check the referenced types in mainDoc to make sure we've imported them
- err |= check_types(options.inputFileName.c_str(), mainDoc);
-
- // finally, there really only needs to be one thing in mainDoc, and it
- // needs to be an interface.
- bool onlyParcelable = false;
- err |= exactly_one_interface(options.inputFileName.c_str(), mainDoc, options, &onlyParcelable);
-
- // If this includes an interface definition, then assign method ids and validate.
- if (!onlyParcelable) {
- err |= check_and_assign_method_ids(options.inputFileName.c_str(),
- ((interface_type*)mainDoc)->interface_items);
- }
-
- // after this, there shouldn't be any more errors because of the
- // input.
- if (err != 0 || mainDoc == NULL) {
- return 1;
- }
-
- // if needed, generate the outputFileName from the outputBaseFolder
- if (options.outputFileName.length() == 0 &&
- options.outputBaseFolder.length() > 0) {
- options.outputFileName = generate_outputFileName(options, mainDoc);
- }
-
- // if we were asked to, generate a make dependency file
- // unless it's a parcelable *and* it's supposed to fail on parcelable
- if ((options.autoDepFile || options.depFileName != "") &&
- !(onlyParcelable && options.failOnParcelable)) {
- // make sure the folders of the output file all exists
- check_outputFilePath(options.outputFileName);
- generate_dep_file(options, mainDoc);
- }
-
- // they didn't ask to fail on parcelables, so just exit quietly.
- if (onlyParcelable && !options.failOnParcelable) {
- return 0;
- }
-
- // make sure the folders of the output file all exists
- check_outputFilePath(options.outputFileName);
-
- err = generate_java(options.outputFileName, options.inputFileName.c_str(),
- (interface_type*)mainDoc);
-
- return err;
-}
-
-static int
-preprocess_aidl(const Options& options)
-{
- vector<string> lines;
- int err;
-
- // read files
- int N = options.filesToPreprocess.size();
- for (int i=0; i<N; i++) {
- g_callbacks = &g_mainCallbacks;
- err = parse_aidl(options.filesToPreprocess[i].c_str());
- if (err != 0) {
- return err;
- }
- document_item_type* doc = g_document;
- string line;
- if (doc->item_type == USER_DATA_TYPE) {
- user_data_type* parcelable = (user_data_type*)doc;
- if ((parcelable->flattening_methods & PARCELABLE_DATA) != 0) {
- line = "parcelable ";
- }
- if ((parcelable->flattening_methods & RPC_DATA) != 0) {
- line = "flattenable ";
- }
- if (parcelable->package) {
- line += parcelable->package;
- line += '.';
- }
- line += parcelable->name.data;
- } else {
- line = "interface ";
- interface_type* iface = (interface_type*)doc;
- if (iface->package) {
- line += iface->package;
- line += '.';
- }
- line += iface->name.data;
- }
- line += ";\n";
- lines.push_back(line);
- }
-
- // write preprocessed file
- int fd = open( options.outputFileName.c_str(),
- O_RDWR|O_CREAT|O_TRUNC|O_BINARY,
-#ifdef HAVE_MS_C_RUNTIME
- _S_IREAD|_S_IWRITE);
-#else
- S_IRUSR|S_IWUSR|S_IRGRP);
-#endif
- if (fd == -1) {
- fprintf(stderr, "aidl: could not open file for write: %s\n",
- options.outputFileName.c_str());
- return 1;
- }
-
- N = lines.size();
- for (int i=0; i<N; i++) {
- const string& s = lines[i];
- int len = s.length();
- if (len != write(fd, s.c_str(), len)) {
- fprintf(stderr, "aidl: error writing to file %s\n",
- options.outputFileName.c_str());
- close(fd);
- unlink(options.outputFileName.c_str());
- return 1;
- }
- }
-
- close(fd);
- return 0;
-}
-
-// ==========================================================
-int
-main(int argc, const char **argv)
-{
- Options options;
- int result = parse_options(argc, argv, &options);
- if (result) {
- return result;
- }
-
- switch (options.task)
- {
- case COMPILE_AIDL:
- return compile_aidl(options);
- case PREPROCESS_AIDL:
- return preprocess_aidl(options);
- }
- fprintf(stderr, "aidl: internal error\n");
- return 1;
-}
diff --git a/tools/aidl/aidl_language.cpp b/tools/aidl/aidl_language.cpp
deleted file mode 100644
index cd6a3bd5dfc1..000000000000
--- a/tools/aidl/aidl_language.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-#include "aidl_language.h"
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-
-#ifdef HAVE_MS_C_RUNTIME
-int isatty(int fd)
-{
- return (fd == 0);
-}
-#endif
-
-#if 0
-ParserCallbacks k_parserCallbacks = {
- NULL
-};
-#endif
-
-ParserCallbacks* g_callbacks = NULL; // &k_parserCallbacks;
-
diff --git a/tools/aidl/aidl_language.h b/tools/aidl/aidl_language.h
deleted file mode 100644
index de1370c086f5..000000000000
--- a/tools/aidl/aidl_language.h
+++ /dev/null
@@ -1,172 +0,0 @@
-#ifndef DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H
-#define DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H
-
-
-typedef enum {
- NO_EXTRA_TEXT = 0,
- SHORT_COMMENT,
- LONG_COMMENT,
- COPY_TEXT,
- WHITESPACE
-} which_extra_text;
-
-typedef struct extra_text_type {
- unsigned lineno;
- which_extra_text which;
- char* data;
- unsigned len;
- struct extra_text_type* next;
-} extra_text_type;
-
-typedef struct buffer_type {
- unsigned lineno;
- unsigned token;
- char *data;
- extra_text_type* extra;
-} buffer_type;
-
-typedef struct type_type {
- buffer_type type;
- buffer_type array_token;
- int dimension;
-} type_type;
-
-typedef struct arg_type {
- buffer_type comma_token; // empty in the first one in the list
- buffer_type direction;
- type_type type;
- buffer_type name;
- struct arg_type *next;
-} arg_type;
-
-enum {
- METHOD_TYPE
-};
-
-typedef struct interface_item_type {
- unsigned item_type;
- struct interface_item_type* next;
-} interface_item_type;
-
-typedef struct method_type {
- interface_item_type interface_item;
- type_type type;
- bool oneway;
- buffer_type oneway_token;
- buffer_type name;
- buffer_type open_paren_token;
- arg_type* args;
- buffer_type close_paren_token;
- bool hasId;
- buffer_type equals_token;
- buffer_type id;
- // XXX missing comments/copy text here
- buffer_type semicolon_token;
- buffer_type* comments_token; // points into this structure, DO NOT DELETE
- int assigned_id;
-} method_type;
-
-enum {
- USER_DATA_TYPE = 12,
- INTERFACE_TYPE_BINDER,
- INTERFACE_TYPE_RPC
-};
-
-typedef struct document_item_type {
- unsigned item_type;
- struct document_item_type* next;
-} document_item_type;
-
-
-// for user_data_type.flattening_methods
-enum {
- PARCELABLE_DATA = 0x1,
- RPC_DATA = 0x2
-};
-
-typedef struct user_data_type {
- document_item_type document_item;
- buffer_type keyword_token; // only the first one
- char* package;
- buffer_type name;
- buffer_type semicolon_token;
- int flattening_methods;
-} user_data_type;
-
-typedef struct interface_type {
- document_item_type document_item;
- buffer_type interface_token;
- bool oneway;
- buffer_type oneway_token;
- char* package;
- buffer_type name;
- buffer_type open_brace_token;
- interface_item_type* interface_items;
- buffer_type close_brace_token;
- buffer_type* comments_token; // points into this structure, DO NOT DELETE
-} interface_type;
-
-typedef union lexer_type {
- buffer_type buffer;
- type_type type;
- arg_type *arg;
- method_type* method;
- interface_item_type* interface_item;
- interface_type* interface_obj;
- user_data_type* user_data;
- document_item_type* document_item;
-} lexer_type;
-
-
-#define YYSTYPE lexer_type
-
-#if __cplusplus
-extern "C" {
-#endif
-
-int parse_aidl(char const *);
-
-// strips off the leading whitespace, the "import" text
-// also returns whether it's a local or system import
-// we rely on the input matching the import regex from below
-char* parse_import_statement(const char* text);
-
-// in, out or inout
-enum {
- IN_PARAMETER = 1,
- OUT_PARAMETER = 2,
- INOUT_PARAMETER = 3
-};
-int convert_direction(const char* direction);
-
-// callbacks from within the parser
-// these functions all take ownership of the strings
-typedef struct ParserCallbacks {
- void (*document)(document_item_type* items);
- void (*import)(buffer_type* statement);
-} ParserCallbacks;
-
-extern ParserCallbacks* g_callbacks;
-
-// true if there was an error parsing, false otherwise
-extern int g_error;
-
-// the name of the file we're currently parsing
-extern char const* g_currentFilename;
-
-// the package name for our current file
-extern char const* g_currentPackage;
-
-typedef enum {
- STATEMENT_INSIDE_INTERFACE
-} error_type;
-
-void init_buffer_type(buffer_type* buf, int lineno);
-
-
-#if __cplusplus
-}
-#endif
-
-
-#endif // DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H
diff --git a/tools/aidl/aidl_language_l.l b/tools/aidl/aidl_language_l.l
deleted file mode 100644
index 3d33e7a14913..000000000000
--- a/tools/aidl/aidl_language_l.l
+++ /dev/null
@@ -1,214 +0,0 @@
-%{
-#include "aidl_language.h"
-#include "aidl_language_y.h"
-#include "search_path.h"
-#include <string.h>
-#include <stdlib.h>
-
-extern YYSTYPE yylval;
-
-// comment and whitespace handling
-// these functions save a copy of the buffer
-static void begin_extra_text(unsigned lineno, which_extra_text which);
-static void append_extra_text(char* text);
-static extra_text_type* get_extra_text(void); // you now own the object
- // this returns
-static void drop_extra_text(void);
-
-// package handling
-static void do_package_statement(const char* importText);
-
-#define SET_BUFFER(t) \
- do { \
- yylval.buffer.lineno = yylineno; \
- yylval.buffer.token = (t); \
- yylval.buffer.data = strdup(yytext); \
- yylval.buffer.extra = get_extra_text(); \
- } while(0)
-
-%}
-
-%option yylineno
-%option noyywrap
-
-%x COPYING LONG_COMMENT
-
-identifier [_a-zA-Z][_a-zA-Z0-9\.]*
-whitespace ([ \t\n\r]+)
-brackets \[{whitespace}?\]
-idvalue (0|[1-9][0-9]*)
-
-%%
-
-
-\%\%\{ { begin_extra_text(yylineno, COPY_TEXT); BEGIN(COPYING); }
-<COPYING>\}\%\% { BEGIN(INITIAL); }
-<COPYING>.*\n { append_extra_text(yytext); }
-<COPYING>.* { append_extra_text(yytext); }
-<COPYING>\n+ { append_extra_text(yytext); }
-
-
-\/\* { begin_extra_text(yylineno, (which_extra_text)LONG_COMMENT);
- BEGIN(LONG_COMMENT); }
-<LONG_COMMENT>[^*]* { append_extra_text(yytext); }
-<LONG_COMMENT>\*+[^/] { append_extra_text(yytext); }
-<LONG_COMMENT>\n { append_extra_text(yytext); }
-<LONG_COMMENT>\**\/ { BEGIN(INITIAL); }
-
-^{whitespace}?import{whitespace}[^ \t\r\n]+{whitespace}?; {
- SET_BUFFER(IMPORT);
- return IMPORT;
- }
-^{whitespace}?package{whitespace}[^ \t\r\n]+{whitespace}?; {
- do_package_statement(yytext);
- SET_BUFFER(PACKAGE);
- return PACKAGE;
- }
-<<EOF>> { yyterminate(); }
-
-\/\/.*\n { begin_extra_text(yylineno, SHORT_COMMENT);
- append_extra_text(yytext); }
-
-{whitespace} { /* begin_extra_text(yylineno, WHITESPACE);
- append_extra_text(yytext); */ }
-
-; { SET_BUFFER(';'); return ';'; }
-\{ { SET_BUFFER('{'); return '{'; }
-\} { SET_BUFFER('}'); return '}'; }
-\( { SET_BUFFER('('); return '('; }
-\) { SET_BUFFER(')'); return ')'; }
-, { SET_BUFFER(','); return ','; }
-= { SET_BUFFER('='); return '='; }
-
- /* keywords */
-parcelable { SET_BUFFER(PARCELABLE); return PARCELABLE; }
-interface { SET_BUFFER(INTERFACE); return INTERFACE; }
-flattenable { SET_BUFFER(FLATTENABLE); return FLATTENABLE; }
-rpc { SET_BUFFER(INTERFACE); return RPC; }
-in { SET_BUFFER(IN); return IN; }
-out { SET_BUFFER(OUT); return OUT; }
-inout { SET_BUFFER(INOUT); return INOUT; }
-oneway { SET_BUFFER(ONEWAY); return ONEWAY; }
-
-{brackets}+ { SET_BUFFER(ARRAY); return ARRAY; }
-{idvalue} { SET_BUFFER(IDVALUE); return IDVALUE; }
-{identifier} { SET_BUFFER(IDENTIFIER); return IDENTIFIER; }
-{identifier}\<{whitespace}*{identifier}({whitespace}*,{whitespace}*{identifier})*{whitespace}*\> {
- SET_BUFFER(GENERIC); return GENERIC; }
-
- /* syntax error! */
-. { printf("UNKNOWN(%s)", yytext);
- yylval.buffer.lineno = yylineno;
- yylval.buffer.token = IDENTIFIER;
- yylval.buffer.data = strdup(yytext);
- return IDENTIFIER;
- }
-
-%%
-
-// comment and whitespace handling
-// ================================================
-extra_text_type* g_extraText = NULL;
-extra_text_type* g_nextExtraText = NULL;
-
-void begin_extra_text(unsigned lineno, which_extra_text which)
-{
- extra_text_type* text = (extra_text_type*)malloc(sizeof(extra_text_type));
- text->lineno = lineno;
- text->which = which;
- text->data = NULL;
- text->len = 0;
- text->next = NULL;
- if (g_nextExtraText == NULL) {
- g_extraText = text;
- } else {
- g_nextExtraText->next = text;
- }
- g_nextExtraText = text;
-}
-
-void append_extra_text(char* text)
-{
- if (g_nextExtraText->data == NULL) {
- g_nextExtraText->data = strdup(text);
- g_nextExtraText->len = strlen(text);
- } else {
- char* orig = g_nextExtraText->data;
- unsigned oldLen = g_nextExtraText->len;
- unsigned len = strlen(text);
- g_nextExtraText->len += len;
- g_nextExtraText->data = (char*)malloc(g_nextExtraText->len+1);
- memcpy(g_nextExtraText->data, orig, oldLen);
- memcpy(g_nextExtraText->data+oldLen, text, len);
- g_nextExtraText->data[g_nextExtraText->len] = '\0';
- free(orig);
- }
-}
-
-extra_text_type*
-get_extra_text(void)
-{
- extra_text_type* result = g_extraText;
- g_extraText = NULL;
- g_nextExtraText = NULL;
- return result;
-}
-
-void drop_extra_text(void)
-{
- extra_text_type* p = g_extraText;
- while (p) {
- extra_text_type* next = p->next;
- free(p->data);
- free(p);
- free(next);
- }
- g_extraText = NULL;
- g_nextExtraText = NULL;
-}
-
-
-// package handling
-// ================================================
-void do_package_statement(const char* importText)
-{
- if (g_currentPackage) free((void*)g_currentPackage);
- g_currentPackage = parse_import_statement(importText);
-}
-
-
-// main parse function
-// ================================================
-char const* g_currentFilename = NULL;
-char const* g_currentPackage = NULL;
-
-int yyparse(void);
-
-int parse_aidl(char const *filename)
-{
- yyin = fopen(filename, "r");
- if (yyin) {
- char const* oldFilename = g_currentFilename;
- char const* oldPackage = g_currentPackage;
- g_currentFilename = strdup(filename);
-
- g_error = 0;
- yylineno = 1;
- int rv = yyparse();
- if (g_error != 0) {
- rv = g_error;
- }
-
- free((void*)g_currentFilename);
- g_currentFilename = oldFilename;
-
- if (g_currentPackage) free((void*)g_currentPackage);
- g_currentPackage = oldPackage;
-
- return rv;
- } else {
- fprintf(stderr, "aidl: unable to open file for read: %s\n", filename);
- return 1;
- }
-}
-
diff --git a/tools/aidl/aidl_language_y.y b/tools/aidl/aidl_language_y.y
deleted file mode 100644
index 9b40d28ad598..000000000000
--- a/tools/aidl/aidl_language_y.y
+++ /dev/null
@@ -1,373 +0,0 @@
-%{
-#include "aidl_language.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-int yyerror(char* errstr);
-int yylex(void);
-extern int yylineno;
-
-static int count_brackets(const char*);
-
-%}
-
-%token IMPORT
-%token PACKAGE
-%token IDENTIFIER
-%token IDVALUE
-%token GENERIC
-%token ARRAY
-%token PARCELABLE
-%token INTERFACE
-%token FLATTENABLE
-%token RPC
-%token IN
-%token OUT
-%token INOUT
-%token ONEWAY
-
-%%
-document:
- document_items { g_callbacks->document($1.document_item); }
- | headers document_items { g_callbacks->document($2.document_item); }
- ;
-
-headers:
- package { }
- | imports { }
- | package imports { }
- ;
-
-package:
- PACKAGE { }
- ;
-
-imports:
- IMPORT { g_callbacks->import(&($1.buffer)); }
- | IMPORT imports { g_callbacks->import(&($1.buffer)); }
- ;
-
-document_items:
- { $$.document_item = NULL; }
- | document_items declaration {
- if ($2.document_item == NULL) {
- // error cases only
- $$ = $1;
- } else {
- document_item_type* p = $1.document_item;
- while (p && p->next) {
- p=p->next;
- }
- if (p) {
- p->next = (document_item_type*)$2.document_item;
- $$ = $1;
- } else {
- $$.document_item = (document_item_type*)$2.document_item;
- }
- }
- }
- | document_items error {
- fprintf(stderr, "%s:%d: syntax error don't know what to do with \"%s\"\n", g_currentFilename,
- $2.buffer.lineno, $2.buffer.data);
- $$ = $1;
- }
- ;
-
-declaration:
- parcelable_decl { $$.document_item = (document_item_type*)$1.user_data; }
- | interface_decl { $$.document_item = (document_item_type*)$1.interface_item; }
- ;
-
-parcelable_decl:
- PARCELABLE IDENTIFIER ';' {
- user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type));
- b->document_item.item_type = USER_DATA_TYPE;
- b->document_item.next = NULL;
- b->keyword_token = $1.buffer;
- b->name = $2.buffer;
- b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
- b->semicolon_token = $3.buffer;
- b->flattening_methods = PARCELABLE_DATA;
- $$.user_data = b;
- }
- | PARCELABLE ';' {
- fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name.\n",
- g_currentFilename, $1.buffer.lineno);
- $$.user_data = NULL;
- }
- | PARCELABLE error ';' {
- fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name, saw \"%s\".\n",
- g_currentFilename, $2.buffer.lineno, $2.buffer.data);
- $$.user_data = NULL;
- }
- | FLATTENABLE IDENTIFIER ';' {
- user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type));
- b->document_item.item_type = USER_DATA_TYPE;
- b->document_item.next = NULL;
- b->keyword_token = $1.buffer;
- b->name = $2.buffer;
- b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
- b->semicolon_token = $3.buffer;
- b->flattening_methods = PARCELABLE_DATA | RPC_DATA;
- $$.user_data = b;
- }
- | FLATTENABLE ';' {
- fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name.\n",
- g_currentFilename, $1.buffer.lineno);
- $$.user_data = NULL;
- }
- | FLATTENABLE error ';' {
- fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name, saw \"%s\".\n",
- g_currentFilename, $2.buffer.lineno, $2.buffer.data);
- $$.user_data = NULL;
- }
-
- ;
-
-interface_header:
- INTERFACE {
- interface_type* c = (interface_type*)malloc(sizeof(interface_type));
- c->document_item.item_type = INTERFACE_TYPE_BINDER;
- c->document_item.next = NULL;
- c->interface_token = $1.buffer;
- c->oneway = false;
- memset(&c->oneway_token, 0, sizeof(buffer_type));
- c->comments_token = &c->interface_token;
- $$.interface_obj = c;
- }
- | ONEWAY INTERFACE {
- interface_type* c = (interface_type*)malloc(sizeof(interface_type));
- c->document_item.item_type = INTERFACE_TYPE_BINDER;
- c->document_item.next = NULL;
- c->interface_token = $2.buffer;
- c->oneway = true;
- c->oneway_token = $1.buffer;
- c->comments_token = &c->oneway_token;
- $$.interface_obj = c;
- }
- | RPC {
- interface_type* c = (interface_type*)malloc(sizeof(interface_type));
- c->document_item.item_type = INTERFACE_TYPE_RPC;
- c->document_item.next = NULL;
- c->interface_token = $1.buffer;
- c->oneway = false;
- memset(&c->oneway_token, 0, sizeof(buffer_type));
- c->comments_token = &c->interface_token;
- $$.interface_obj = c;
- }
- ;
-
-interface_keywords:
- INTERFACE
- | RPC
- ;
-
-interface_decl:
- interface_header IDENTIFIER '{' interface_items '}' {
- interface_type* c = $1.interface_obj;
- c->name = $2.buffer;
- c->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
- c->open_brace_token = $3.buffer;
- c->interface_items = $4.interface_item;
- c->close_brace_token = $5.buffer;
- $$.interface_obj = c;
- }
- | interface_keywords error '{' interface_items '}' {
- fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n",
- g_currentFilename, $2.buffer.lineno, $2.buffer.data);
- $$.document_item = NULL;
- }
- | interface_keywords error '}' {
- fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n",
- g_currentFilename, $2.buffer.lineno, $2.buffer.data);
- $$.document_item = NULL;
- }
-
- ;
-
-interface_items:
- { $$.interface_item = NULL; }
- | interface_items method_decl {
- interface_item_type* p=$1.interface_item;
- while (p && p->next) {
- p=p->next;
- }
- if (p) {
- p->next = (interface_item_type*)$2.method;
- $$ = $1;
- } else {
- $$.interface_item = (interface_item_type*)$2.method;
- }
- }
- | interface_items error ';' {
- fprintf(stderr, "%s:%d: syntax error before ';' (expected method declaration)\n",
- g_currentFilename, $3.buffer.lineno);
- $$ = $1;
- }
- ;
-
-method_decl:
- type IDENTIFIER '(' arg_list ')' ';' {
- method_type *method = (method_type*)malloc(sizeof(method_type));
- method->interface_item.item_type = METHOD_TYPE;
- method->interface_item.next = NULL;
- method->oneway = false;
- method->type = $1.type;
- memset(&method->oneway_token, 0, sizeof(buffer_type));
- method->name = $2.buffer;
- method->open_paren_token = $3.buffer;
- method->args = $4.arg;
- method->close_paren_token = $5.buffer;
- method->hasId = false;
- memset(&method->equals_token, 0, sizeof(buffer_type));
- memset(&method->id, 0, sizeof(buffer_type));
- method->semicolon_token = $6.buffer;
- method->comments_token = &method->type.type;
- $$.method = method;
- }
- | ONEWAY type IDENTIFIER '(' arg_list ')' ';' {
- method_type *method = (method_type*)malloc(sizeof(method_type));
- method->interface_item.item_type = METHOD_TYPE;
- method->interface_item.next = NULL;
- method->oneway = true;
- method->oneway_token = $1.buffer;
- method->type = $2.type;
- method->name = $3.buffer;
- method->open_paren_token = $4.buffer;
- method->args = $5.arg;
- method->close_paren_token = $6.buffer;
- method->hasId = false;
- memset(&method->equals_token, 0, sizeof(buffer_type));
- memset(&method->id, 0, sizeof(buffer_type));
- method->semicolon_token = $7.buffer;
- method->comments_token = &method->oneway_token;
- $$.method = method;
- }
- | type IDENTIFIER '(' arg_list ')' '=' IDVALUE ';' {
- method_type *method = (method_type*)malloc(sizeof(method_type));
- method->interface_item.item_type = METHOD_TYPE;
- method->interface_item.next = NULL;
- method->oneway = false;
- memset(&method->oneway_token, 0, sizeof(buffer_type));
- method->type = $1.type;
- method->name = $2.buffer;
- method->open_paren_token = $3.buffer;
- method->args = $4.arg;
- method->close_paren_token = $5.buffer;
- method->hasId = true;
- method->equals_token = $6.buffer;
- method->id = $7.buffer;
- method->semicolon_token = $8.buffer;
- method->comments_token = &method->type.type;
- $$.method = method;
- }
- | ONEWAY type IDENTIFIER '(' arg_list ')' '=' IDVALUE ';' {
- method_type *method = (method_type*)malloc(sizeof(method_type));
- method->interface_item.item_type = METHOD_TYPE;
- method->interface_item.next = NULL;
- method->oneway = true;
- method->oneway_token = $1.buffer;
- method->type = $2.type;
- method->name = $3.buffer;
- method->open_paren_token = $4.buffer;
- method->args = $5.arg;
- method->close_paren_token = $6.buffer;
- method->hasId = true;
- method->equals_token = $7.buffer;
- method->id = $8.buffer;
- method->semicolon_token = $9.buffer;
- method->comments_token = &method->oneway_token;
- $$.method = method;
- }
- ;
-
-arg_list:
- { $$.arg = NULL; }
- | arg { $$ = $1; }
- | arg_list ',' arg {
- if ($$.arg != NULL) {
- // only NULL on error
- $$ = $1;
- arg_type *p = $1.arg;
- while (p && p->next) {
- p=p->next;
- }
- $3.arg->comma_token = $2.buffer;
- p->next = $3.arg;
- }
- }
- | error {
- fprintf(stderr, "%s:%d: syntax error in parameter list\n", g_currentFilename, $1.buffer.lineno);
- $$.arg = NULL;
- }
- ;
-
-arg:
- direction type IDENTIFIER {
- arg_type* arg = (arg_type*)malloc(sizeof(arg_type));
- memset(&arg->comma_token, 0, sizeof(buffer_type));
- arg->direction = $1.buffer;
- arg->type = $2.type;
- arg->name = $3.buffer;
- arg->next = NULL;
- $$.arg = arg;
- }
- ;
-
-type:
- IDENTIFIER {
- $$.type.type = $1.buffer;
- init_buffer_type(&$$.type.array_token, yylineno);
- $$.type.dimension = 0;
- }
- | IDENTIFIER ARRAY {
- $$.type.type = $1.buffer;
- $$.type.array_token = $2.buffer;
- $$.type.dimension = count_brackets($2.buffer.data);
- }
- | GENERIC {
- $$.type.type = $1.buffer;
- init_buffer_type(&$$.type.array_token, yylineno);
- $$.type.dimension = 0;
- }
- ;
-
-direction:
- { init_buffer_type(&$$.buffer, yylineno); }
- | IN { $$.buffer = $1.buffer; }
- | OUT { $$.buffer = $1.buffer; }
- | INOUT { $$.buffer = $1.buffer; }
- ;
-
-%%
-
-#include <ctype.h>
-#include <stdio.h>
-
-int g_error = 0;
-
-int yyerror(char* errstr)
-{
- fprintf(stderr, "%s:%d: %s\n", g_currentFilename, yylineno, errstr);
- g_error = 1;
- return 1;
-}
-
-void init_buffer_type(buffer_type* buf, int lineno)
-{
- buf->lineno = lineno;
- buf->token = 0;
- buf->data = NULL;
- buf->extra = NULL;
-}
-
-static int count_brackets(const char* s)
-{
- int n=0;
- while (*s) {
- if (*s == '[') n++;
- s++;
- }
- return n;
-}
diff --git a/tools/aidl/generate_java.cpp b/tools/aidl/generate_java.cpp
deleted file mode 100644
index 9e57407e772f..000000000000
--- a/tools/aidl/generate_java.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-#include "generate_java.h"
-#include "Type.h"
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-// =================================================
-VariableFactory::VariableFactory(const string& base)
- :m_base(base),
- m_index(0)
-{
-}
-
-Variable*
-VariableFactory::Get(Type* type)
-{
- char name[100];
- sprintf(name, "%s%d", m_base.c_str(), m_index);
- m_index++;
- Variable* v = new Variable(type, name);
- m_vars.push_back(v);
- return v;
-}
-
-Variable*
-VariableFactory::Get(int index)
-{
- return m_vars[index];
-}
-
-// =================================================
-string
-gather_comments(extra_text_type* extra)
-{
- string s;
- while (extra) {
- if (extra->which == SHORT_COMMENT) {
- s += extra->data;
- }
- else if (extra->which == LONG_COMMENT) {
- s += "/*";
- s += extra->data;
- s += "*/";
- }
- extra = extra->next;
- }
- return s;
-}
-
-string
-append(const char* a, const char* b)
-{
- string s = a;
- s += b;
- return s;
-}
-
-// =================================================
-int
-generate_java(const string& filename, const string& originalSrc,
- interface_type* iface)
-{
- Class* cl;
-
- if (iface->document_item.item_type == INTERFACE_TYPE_BINDER) {
- cl = generate_binder_interface_class(iface);
- }
- else if (iface->document_item.item_type == INTERFACE_TYPE_RPC) {
- cl = generate_rpc_interface_class(iface);
- }
-
- Document* document = new Document;
- document->comment = "";
- if (iface->package) document->package = iface->package;
- document->originalSrc = originalSrc;
- document->classes.push_back(cl);
-
-// printf("outputting... filename=%s\n", filename.c_str());
- FILE* to;
- if (filename == "-") {
- to = stdout;
- } else {
- /* open file in binary mode to ensure that the tool produces the
- * same output on all platforms !!
- */
- to = fopen(filename.c_str(), "wb");
- if (to == NULL) {
- fprintf(stderr, "unable to open %s for write\n", filename.c_str());
- return 1;
- }
- }
-
- document->Write(to);
-
- fclose(to);
- return 0;
-}
-
diff --git a/tools/aidl/generate_java.h b/tools/aidl/generate_java.h
deleted file mode 100644
index 4bfcfeba07c8..000000000000
--- a/tools/aidl/generate_java.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef GENERATE_JAVA_H
-#define GENERATE_JAVA_H
-
-#include "aidl_language.h"
-#include "AST.h"
-
-#include <string>
-
-using namespace std;
-
-int generate_java(const string& filename, const string& originalSrc,
- interface_type* iface);
-
-Class* generate_binder_interface_class(const interface_type* iface);
-Class* generate_rpc_interface_class(const interface_type* iface);
-
-string gather_comments(extra_text_type* extra);
-string append(const char* a, const char* b);
-
-class VariableFactory
-{
-public:
- VariableFactory(const string& base); // base must be short
- Variable* Get(Type* type);
- Variable* Get(int index);
-private:
- vector<Variable*> m_vars;
- string m_base;
- int m_index;
-};
-
-#endif // GENERATE_JAVA_H
-
diff --git a/tools/aidl/generate_java_binder.cpp b/tools/aidl/generate_java_binder.cpp
deleted file mode 100644
index f291ceb2b09f..000000000000
--- a/tools/aidl/generate_java_binder.cpp
+++ /dev/null
@@ -1,560 +0,0 @@
-#include "generate_java.h"
-#include "Type.h"
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-// =================================================
-class StubClass : public Class
-{
-public:
- StubClass(Type* type, Type* interfaceType);
- virtual ~StubClass();
-
- Variable* transact_code;
- Variable* transact_data;
- Variable* transact_reply;
- Variable* transact_flags;
- SwitchStatement* transact_switch;
-private:
- void make_as_interface(Type* interfaceType);
-};
-
-StubClass::StubClass(Type* type, Type* interfaceType)
- :Class()
-{
- this->comment = "/** Local-side IPC implementation stub class. */";
- this->modifiers = PUBLIC | ABSTRACT | STATIC;
- this->what = Class::CLASS;
- this->type = type;
- this->extends = BINDER_NATIVE_TYPE;
- this->interfaces.push_back(interfaceType);
-
- // descriptor
- Field* descriptor = new Field(STATIC | FINAL | PRIVATE,
- new Variable(STRING_TYPE, "DESCRIPTOR"));
- descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
- this->elements.push_back(descriptor);
-
- // ctor
- Method* ctor = new Method;
- ctor->modifiers = PUBLIC;
- ctor->comment = "/** Construct the stub at attach it to the "
- "interface. */";
- ctor->name = "Stub";
- ctor->statements = new StatementBlock;
- MethodCall* attach = new MethodCall(THIS_VALUE, "attachInterface",
- 2, THIS_VALUE, new LiteralExpression("DESCRIPTOR"));
- ctor->statements->Add(attach);
- this->elements.push_back(ctor);
-
- // asInterface
- make_as_interface(interfaceType);
-
- // asBinder
- Method* asBinder = new Method;
- asBinder->modifiers = PUBLIC | OVERRIDE;
- asBinder->returnType = IBINDER_TYPE;
- asBinder->name = "asBinder";
- asBinder->statements = new StatementBlock;
- asBinder->statements->Add(new ReturnStatement(THIS_VALUE));
- this->elements.push_back(asBinder);
-
- // onTransact
- this->transact_code = new Variable(INT_TYPE, "code");
- this->transact_data = new Variable(PARCEL_TYPE, "data");
- this->transact_reply = new Variable(PARCEL_TYPE, "reply");
- this->transact_flags = new Variable(INT_TYPE, "flags");
- Method* onTransact = new Method;
- onTransact->modifiers = PUBLIC | OVERRIDE;
- onTransact->returnType = BOOLEAN_TYPE;
- onTransact->name = "onTransact";
- onTransact->parameters.push_back(this->transact_code);
- onTransact->parameters.push_back(this->transact_data);
- onTransact->parameters.push_back(this->transact_reply);
- onTransact->parameters.push_back(this->transact_flags);
- onTransact->statements = new StatementBlock;
- onTransact->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
- this->elements.push_back(onTransact);
- this->transact_switch = new SwitchStatement(this->transact_code);
-
- onTransact->statements->Add(this->transact_switch);
- MethodCall* superCall = new MethodCall(SUPER_VALUE, "onTransact", 4,
- this->transact_code, this->transact_data,
- this->transact_reply, this->transact_flags);
- onTransact->statements->Add(new ReturnStatement(superCall));
-}
-
-StubClass::~StubClass()
-{
-}
-
-void
-StubClass::make_as_interface(Type *interfaceType)
-{
- Variable* obj = new Variable(IBINDER_TYPE, "obj");
-
- Method* m = new Method;
- m->comment = "/**\n * Cast an IBinder object into an ";
- m->comment += interfaceType->QualifiedName();
- m->comment += " interface,\n";
- m->comment += " * generating a proxy if needed.\n */";
- m->modifiers = PUBLIC | STATIC;
- m->returnType = interfaceType;
- m->name = "asInterface";
- m->parameters.push_back(obj);
- m->statements = new StatementBlock;
-
- IfStatement* ifstatement = new IfStatement();
- ifstatement->expression = new Comparison(obj, "==", NULL_VALUE);
- ifstatement->statements = new StatementBlock;
- ifstatement->statements->Add(new ReturnStatement(NULL_VALUE));
- m->statements->Add(ifstatement);
-
- // IInterface iin = obj.queryLocalInterface(DESCRIPTOR)
- MethodCall* queryLocalInterface = new MethodCall(obj, "queryLocalInterface");
- queryLocalInterface->arguments.push_back(new LiteralExpression("DESCRIPTOR"));
- IInterfaceType* iinType = new IInterfaceType();
- Variable *iin = new Variable(iinType, "iin");
- VariableDeclaration* iinVd = new VariableDeclaration(iin, queryLocalInterface, NULL);
- m->statements->Add(iinVd);
-
- // Ensure the instance type of the local object is as expected.
- // One scenario where this is needed is if another package (with a
- // different class loader) runs in the same process as the service.
-
- // if (iin != null && iin instanceof <interfaceType>) return (<interfaceType>) iin;
- Comparison* iinNotNull = new Comparison(iin, "!=", NULL_VALUE);
- Comparison* instOfCheck = new Comparison(iin, " instanceof ",
- new LiteralExpression(interfaceType->QualifiedName()));
- IfStatement* instOfStatement = new IfStatement();
- instOfStatement->expression = new Comparison(iinNotNull, "&&", instOfCheck);
- instOfStatement->statements = new StatementBlock;
- instOfStatement->statements->Add(new ReturnStatement(new Cast(interfaceType, iin)));
- m->statements->Add(instOfStatement);
-
- string proxyType = interfaceType->QualifiedName();
- proxyType += ".Stub.Proxy";
- NewExpression* ne = new NewExpression(NAMES.Find(proxyType));
- ne->arguments.push_back(obj);
- m->statements->Add(new ReturnStatement(ne));
-
- this->elements.push_back(m);
-}
-
-
-
-// =================================================
-class ProxyClass : public Class
-{
-public:
- ProxyClass(Type* type, InterfaceType* interfaceType);
- virtual ~ProxyClass();
-
- Variable* mRemote;
- bool mOneWay;
-};
-
-ProxyClass::ProxyClass(Type* type, InterfaceType* interfaceType)
- :Class()
-{
- this->modifiers = PRIVATE | STATIC;
- this->what = Class::CLASS;
- this->type = type;
- this->interfaces.push_back(interfaceType);
-
- mOneWay = interfaceType->OneWay();
-
- // IBinder mRemote
- mRemote = new Variable(IBINDER_TYPE, "mRemote");
- this->elements.push_back(new Field(PRIVATE, mRemote));
-
- // Proxy()
- Variable* remote = new Variable(IBINDER_TYPE, "remote");
- Method* ctor = new Method;
- ctor->name = "Proxy";
- ctor->statements = new StatementBlock;
- ctor->parameters.push_back(remote);
- ctor->statements->Add(new Assignment(mRemote, remote));
- this->elements.push_back(ctor);
-
- // IBinder asBinder()
- Method* asBinder = new Method;
- asBinder->modifiers = PUBLIC | OVERRIDE;
- asBinder->returnType = IBINDER_TYPE;
- asBinder->name = "asBinder";
- asBinder->statements = new StatementBlock;
- asBinder->statements->Add(new ReturnStatement(mRemote));
- this->elements.push_back(asBinder);
-}
-
-ProxyClass::~ProxyClass()
-{
-}
-
-// =================================================
-static void
-generate_new_array(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel)
-{
- Variable* len = new Variable(INT_TYPE, v->name + "_length");
- addTo->Add(new VariableDeclaration(len, new MethodCall(parcel, "readInt")));
- IfStatement* lencheck = new IfStatement();
- lencheck->expression = new Comparison(len, "<", new LiteralExpression("0"));
- lencheck->statements->Add(new Assignment(v, NULL_VALUE));
- lencheck->elseif = new IfStatement();
- lencheck->elseif->statements->Add(new Assignment(v,
- new NewArrayExpression(t, len)));
- addTo->Add(lencheck);
-}
-
-static void
-generate_write_to_parcel(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags)
-{
- if (v->dimension == 0) {
- t->WriteToParcel(addTo, v, parcel, flags);
- }
- if (v->dimension == 1) {
- t->WriteArrayToParcel(addTo, v, parcel, flags);
- }
-}
-
-static void
-generate_create_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl)
-{
- if (v->dimension == 0) {
- t->CreateFromParcel(addTo, v, parcel, cl);
- }
- if (v->dimension == 1) {
- t->CreateArrayFromParcel(addTo, v, parcel, cl);
- }
-}
-
-static void
-generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl)
-{
- if (v->dimension == 0) {
- t->ReadFromParcel(addTo, v, parcel, cl);
- }
- if (v->dimension == 1) {
- t->ReadArrayFromParcel(addTo, v, parcel, cl);
- }
-}
-
-
-static void
-generate_method(const method_type* method, Class* interface,
- StubClass* stubClass, ProxyClass* proxyClass, int index)
-{
- arg_type* arg;
- int i;
- bool hasOutParams = false;
-
- const bool oneway = proxyClass->mOneWay || method->oneway;
-
- // == the TRANSACT_ constant =============================================
- string transactCodeName = "TRANSACTION_";
- transactCodeName += method->name.data;
-
- char transactCodeValue[60];
- sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index);
-
- Field* transactCode = new Field(STATIC | FINAL,
- new Variable(INT_TYPE, transactCodeName));
- transactCode->value = transactCodeValue;
- stubClass->elements.push_back(transactCode);
-
- // == the declaration in the interface ===================================
- Method* decl = new Method;
- decl->comment = gather_comments(method->comments_token->extra);
- decl->modifiers = PUBLIC;
- decl->returnType = NAMES.Search(method->type.type.data);
- decl->returnTypeDimension = method->type.dimension;
- decl->name = method->name.data;
-
- arg = method->args;
- while (arg != NULL) {
- decl->parameters.push_back(new Variable(
- NAMES.Search(arg->type.type.data), arg->name.data,
- arg->type.dimension));
- arg = arg->next;
- }
-
- decl->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
-
- interface->elements.push_back(decl);
-
- // == the stub method ====================================================
-
- Case* c = new Case(transactCodeName);
-
- MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data);
-
- // interface token validation is the very first thing we do
- c->statements->Add(new MethodCall(stubClass->transact_data,
- "enforceInterface", 1, new LiteralExpression("DESCRIPTOR")));
-
- // args
- Variable* cl = NULL;
- VariableFactory stubArgs("_arg");
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = stubArgs.Get(t);
- v->dimension = arg->type.dimension;
-
- c->statements->Add(new VariableDeclaration(v));
-
- if (convert_direction(arg->direction.data) & IN_PARAMETER) {
- generate_create_from_parcel(t, c->statements, v,
- stubClass->transact_data, &cl);
- } else {
- if (arg->type.dimension == 0) {
- c->statements->Add(new Assignment(v, new NewExpression(v->type)));
- }
- else if (arg->type.dimension == 1) {
- generate_new_array(v->type, c->statements, v,
- stubClass->transact_data);
- }
- else {
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
- __LINE__);
- }
- }
-
- realCall->arguments.push_back(v);
-
- arg = arg->next;
- }
-
- // the real call
- Variable* _result = NULL;
- if (0 == strcmp(method->type.type.data, "void")) {
- c->statements->Add(realCall);
-
- if (!oneway) {
- // report that there were no exceptions
- MethodCall* ex = new MethodCall(stubClass->transact_reply,
- "writeNoException", 0);
- c->statements->Add(ex);
- }
- } else {
- _result = new Variable(decl->returnType, "_result",
- decl->returnTypeDimension);
- c->statements->Add(new VariableDeclaration(_result, realCall));
-
- if (!oneway) {
- // report that there were no exceptions
- MethodCall* ex = new MethodCall(stubClass->transact_reply,
- "writeNoException", 0);
- c->statements->Add(ex);
- }
-
- // marshall the return value
- generate_write_to_parcel(decl->returnType, c->statements, _result,
- stubClass->transact_reply,
- Type::PARCELABLE_WRITE_RETURN_VALUE);
- }
-
- // out parameters
- i = 0;
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = stubArgs.Get(i++);
-
- if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
- generate_write_to_parcel(t, c->statements, v,
- stubClass->transact_reply,
- Type::PARCELABLE_WRITE_RETURN_VALUE);
- hasOutParams = true;
- }
-
- arg = arg->next;
- }
-
- // return true
- c->statements->Add(new ReturnStatement(TRUE_VALUE));
- stubClass->transact_switch->cases.push_back(c);
-
- // == the proxy method ===================================================
- Method* proxy = new Method;
- proxy->comment = gather_comments(method->comments_token->extra);
- proxy->modifiers = PUBLIC | OVERRIDE;
- proxy->returnType = NAMES.Search(method->type.type.data);
- proxy->returnTypeDimension = method->type.dimension;
- proxy->name = method->name.data;
- proxy->statements = new StatementBlock;
- arg = method->args;
- while (arg != NULL) {
- proxy->parameters.push_back(new Variable(
- NAMES.Search(arg->type.type.data), arg->name.data,
- arg->type.dimension));
- arg = arg->next;
- }
- proxy->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
- proxyClass->elements.push_back(proxy);
-
- // the parcels
- Variable* _data = new Variable(PARCEL_TYPE, "_data");
- proxy->statements->Add(new VariableDeclaration(_data,
- new MethodCall(PARCEL_TYPE, "obtain")));
- Variable* _reply = NULL;
- if (!oneway) {
- _reply = new Variable(PARCEL_TYPE, "_reply");
- proxy->statements->Add(new VariableDeclaration(_reply,
- new MethodCall(PARCEL_TYPE, "obtain")));
- }
-
- // the return value
- _result = NULL;
- if (0 != strcmp(method->type.type.data, "void")) {
- _result = new Variable(proxy->returnType, "_result",
- method->type.dimension);
- proxy->statements->Add(new VariableDeclaration(_result));
- }
-
- // try and finally
- TryStatement* tryStatement = new TryStatement();
- proxy->statements->Add(tryStatement);
- FinallyStatement* finallyStatement = new FinallyStatement();
- proxy->statements->Add(finallyStatement);
-
- // the interface identifier token: the DESCRIPTOR constant, marshalled as a string
- tryStatement->statements->Add(new MethodCall(_data, "writeInterfaceToken",
- 1, new LiteralExpression("DESCRIPTOR")));
-
- // the parameters
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
- int dir = convert_direction(arg->direction.data);
- if (dir == OUT_PARAMETER && arg->type.dimension != 0) {
- IfStatement* checklen = new IfStatement();
- checklen->expression = new Comparison(v, "==", NULL_VALUE);
- checklen->statements->Add(new MethodCall(_data, "writeInt", 1,
- new LiteralExpression("-1")));
- checklen->elseif = new IfStatement();
- checklen->elseif->statements->Add(new MethodCall(_data, "writeInt",
- 1, new FieldVariable(v, "length")));
- tryStatement->statements->Add(checklen);
- }
- else if (dir & IN_PARAMETER) {
- generate_write_to_parcel(t, tryStatement->statements, v, _data, 0);
- }
- arg = arg->next;
- }
-
- // the transact call
- MethodCall* call = new MethodCall(proxyClass->mRemote, "transact", 4,
- new LiteralExpression("Stub." + transactCodeName),
- _data, _reply ? _reply : NULL_VALUE,
- new LiteralExpression(
- oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0"));
- tryStatement->statements->Add(call);
-
- // throw back exceptions.
- if (_reply) {
- MethodCall* ex = new MethodCall(_reply, "readException", 0);
- tryStatement->statements->Add(ex);
- }
-
- // returning and cleanup
- if (_reply != NULL) {
- if (_result != NULL) {
- generate_create_from_parcel(proxy->returnType,
- tryStatement->statements, _result, _reply, &cl);
- }
-
- // the out/inout parameters
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
- if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
- generate_read_from_parcel(t, tryStatement->statements,
- v, _reply, &cl);
- }
- arg = arg->next;
- }
-
- finallyStatement->statements->Add(new MethodCall(_reply, "recycle"));
- }
- finallyStatement->statements->Add(new MethodCall(_data, "recycle"));
-
- if (_result != NULL) {
- proxy->statements->Add(new ReturnStatement(_result));
- }
-}
-
-static void
-generate_interface_descriptors(StubClass* stub, ProxyClass* proxy)
-{
- // the interface descriptor transaction handler
- Case* c = new Case("INTERFACE_TRANSACTION");
- c->statements->Add(new MethodCall(stub->transact_reply, "writeString",
- 1, new LiteralExpression("DESCRIPTOR")));
- c->statements->Add(new ReturnStatement(TRUE_VALUE));
- stub->transact_switch->cases.push_back(c);
-
- // and the proxy-side method returning the descriptor directly
- Method* getDesc = new Method;
- getDesc->modifiers = PUBLIC;
- getDesc->returnType = STRING_TYPE;
- getDesc->returnTypeDimension = 0;
- getDesc->name = "getInterfaceDescriptor";
- getDesc->statements = new StatementBlock;
- getDesc->statements->Add(new ReturnStatement(new LiteralExpression("DESCRIPTOR")));
- proxy->elements.push_back(getDesc);
-}
-
-Class*
-generate_binder_interface_class(const interface_type* iface)
-{
- InterfaceType* interfaceType = static_cast<InterfaceType*>(
- NAMES.Find(iface->package, iface->name.data));
-
- // the interface class
- Class* interface = new Class;
- interface->comment = gather_comments(iface->comments_token->extra);
- interface->modifiers = PUBLIC;
- interface->what = Class::INTERFACE;
- interface->type = interfaceType;
- interface->interfaces.push_back(IINTERFACE_TYPE);
-
- // the stub inner class
- StubClass* stub = new StubClass(
- NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()),
- interfaceType);
- interface->elements.push_back(stub);
-
- // the proxy inner class
- ProxyClass* proxy = new ProxyClass(
- NAMES.Find(iface->package,
- append(iface->name.data, ".Stub.Proxy").c_str()),
- interfaceType);
- stub->elements.push_back(proxy);
-
- // stub and proxy support for getInterfaceDescriptor()
- generate_interface_descriptors(stub, proxy);
-
- // all the declared methods of the interface
- int index = 0;
- interface_item_type* item = iface->interface_items;
- while (item != NULL) {
- if (item->item_type == METHOD_TYPE) {
- method_type * method_item = (method_type*) item;
- generate_method(method_item, interface, stub, proxy, method_item->assigned_id);
- }
- item = item->next;
- index++;
- }
-
- return interface;
-}
-
diff --git a/tools/aidl/generate_java_rpc.cpp b/tools/aidl/generate_java_rpc.cpp
deleted file mode 100644
index 5e4daccf6334..000000000000
--- a/tools/aidl/generate_java_rpc.cpp
+++ /dev/null
@@ -1,1001 +0,0 @@
-#include "generate_java.h"
-#include "Type.h"
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-Type* SERVICE_CONTEXT_TYPE = new Type("android.content",
- "Context", Type::BUILT_IN, false, false, false);
-Type* PRESENTER_BASE_TYPE = new Type("android.support.place.connector",
- "EventListener", Type::BUILT_IN, false, false, false);
-Type* PRESENTER_LISTENER_BASE_TYPE = new Type("android.support.place.connector",
- "EventListener.Listener", Type::BUILT_IN, false, false, false);
-Type* RPC_BROKER_TYPE = new Type("android.support.place.connector", "Broker",
- Type::BUILT_IN, false, false, false);
-Type* RPC_CONTAINER_TYPE = new Type("com.android.athome.connector", "ConnectorContainer",
- Type::BUILT_IN, false, false, false);
-Type* PLACE_INFO_TYPE = new Type("android.support.place.connector", "PlaceInfo",
- Type::BUILT_IN, false, false, false);
-// TODO: Just use Endpoint, so this works for all endpoints.
-Type* RPC_CONNECTOR_TYPE = new Type("android.support.place.connector", "Connector",
- Type::BUILT_IN, false, false, false);
-Type* RPC_ENDPOINT_INFO_TYPE = new UserDataType("android.support.place.rpc",
- "EndpointInfo", true, __FILE__, __LINE__);
-Type* RPC_RESULT_HANDLER_TYPE = new UserDataType("android.support.place.rpc", "RpcResultHandler",
- true, __FILE__, __LINE__);
-Type* RPC_ERROR_LISTENER_TYPE = new Type("android.support.place.rpc", "RpcErrorHandler",
- Type::BUILT_IN, false, false, false);
-Type* RPC_CONTEXT_TYPE = new UserDataType("android.support.place.rpc", "RpcContext", true,
- __FILE__, __LINE__);
-
-static void generate_create_from_data(Type* t, StatementBlock* addTo, const string& key,
- Variable* v, Variable* data, Variable** cl);
-static void generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from);
-static void generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v,
- Variable* data);
-
-static string
-format_int(int n)
-{
- char str[20];
- sprintf(str, "%d", n);
- return string(str);
-}
-
-static string
-class_name_leaf(const string& str)
-{
- string::size_type pos = str.rfind('.');
- if (pos == string::npos) {
- return str;
- } else {
- return string(str, pos+1);
- }
-}
-
-static string
-results_class_name(const string& n)
-{
- string str = n;
- str[0] = toupper(str[0]);
- str.insert(0, "On");
- return str;
-}
-
-static string
-results_method_name(const string& n)
-{
- string str = n;
- str[0] = toupper(str[0]);
- str.insert(0, "on");
- return str;
-}
-
-static string
-push_method_name(const string& n)
-{
- string str = n;
- str[0] = toupper(str[0]);
- str.insert(0, "push");
- return str;
-}
-
-// =================================================
-class DispatcherClass : public Class
-{
-public:
- DispatcherClass(const interface_type* iface, Expression* target);
- virtual ~DispatcherClass();
-
- void AddMethod(const method_type* method);
- void DoneWithMethods();
-
- Method* processMethod;
- Variable* actionParam;
- Variable* requestParam;
- Variable* rpcContextParam;
- Variable* errorParam;
- Variable* requestData;
- Variable* resultData;
- IfStatement* dispatchIfStatement;
- Expression* targetExpression;
-
-private:
- void generate_process();
-};
-
-DispatcherClass::DispatcherClass(const interface_type* iface, Expression* target)
- :Class(),
- dispatchIfStatement(NULL),
- targetExpression(target)
-{
- generate_process();
-}
-
-DispatcherClass::~DispatcherClass()
-{
-}
-
-void
-DispatcherClass::generate_process()
-{
- // byte[] process(String action, byte[] params, RpcContext context, RpcError status)
- this->processMethod = new Method;
- this->processMethod->modifiers = PUBLIC;
- this->processMethod->returnType = BYTE_TYPE;
- this->processMethod->returnTypeDimension = 1;
- this->processMethod->name = "process";
- this->processMethod->statements = new StatementBlock;
-
- this->actionParam = new Variable(STRING_TYPE, "action");
- this->processMethod->parameters.push_back(this->actionParam);
-
- this->requestParam = new Variable(BYTE_TYPE, "requestParam", 1);
- this->processMethod->parameters.push_back(this->requestParam);
-
- this->rpcContextParam = new Variable(RPC_CONTEXT_TYPE, "context", 0);
- this->processMethod->parameters.push_back(this->rpcContextParam);
-
- this->errorParam = new Variable(RPC_ERROR_TYPE, "errorParam", 0);
- this->processMethod->parameters.push_back(this->errorParam);
-
- this->requestData = new Variable(RPC_DATA_TYPE, "request");
- this->processMethod->statements->Add(new VariableDeclaration(requestData,
- new NewExpression(RPC_DATA_TYPE, 1, this->requestParam)));
-
- this->resultData = new Variable(RPC_DATA_TYPE, "resultData");
- this->processMethod->statements->Add(new VariableDeclaration(this->resultData,
- NULL_VALUE));
-}
-
-void
-DispatcherClass::AddMethod(const method_type* method)
-{
- arg_type* arg;
-
- // The if/switch statement
- IfStatement* ifs = new IfStatement();
- ifs->expression = new MethodCall(new StringLiteralExpression(method->name.data), "equals",
- 1, this->actionParam);
- StatementBlock* block = ifs->statements = new StatementBlock;
- if (this->dispatchIfStatement == NULL) {
- this->dispatchIfStatement = ifs;
- this->processMethod->statements->Add(dispatchIfStatement);
- } else {
- this->dispatchIfStatement->elseif = ifs;
- this->dispatchIfStatement = ifs;
- }
-
- // The call to decl (from above)
- MethodCall* realCall = new MethodCall(this->targetExpression, method->name.data);
-
- // args
- Variable* classLoader = NULL;
- VariableFactory stubArgs("_arg");
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = stubArgs.Get(t);
- v->dimension = arg->type.dimension;
-
- // Unmarshall the parameter
- block->Add(new VariableDeclaration(v));
- if (convert_direction(arg->direction.data) & IN_PARAMETER) {
- generate_create_from_data(t, block, arg->name.data, v,
- this->requestData, &classLoader);
- } else {
- if (arg->type.dimension == 0) {
- block->Add(new Assignment(v, new NewExpression(v->type)));
- }
- else if (arg->type.dimension == 1) {
- generate_new_array(v->type, block, v, this->requestData);
- }
- else {
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
- __LINE__);
- }
- }
-
- // Add that parameter to the method call
- realCall->arguments.push_back(v);
-
- arg = arg->next;
- }
-
- // Add a final parameter: RpcContext. Contains data about
- // incoming request (e.g., certificate)
- realCall->arguments.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
-
- Type* returnType = NAMES.Search(method->type.type.data);
- if (returnType == EVENT_FAKE_TYPE) {
- returnType = VOID_TYPE;
- }
-
- // the real call
- bool first = true;
- Variable* _result = NULL;
- if (returnType == VOID_TYPE) {
- block->Add(realCall);
- } else {
- _result = new Variable(returnType, "_result",
- method->type.dimension);
- block->Add(new VariableDeclaration(_result, realCall));
-
- // need the result RpcData
- if (first) {
- block->Add(new Assignment(this->resultData,
- new NewExpression(RPC_DATA_TYPE)));
- first = false;
- }
-
- // marshall the return value
- generate_write_to_data(returnType, block,
- new StringLiteralExpression("_result"), _result, this->resultData);
- }
-
- // out parameters
- int i = 0;
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = stubArgs.Get(i++);
-
- if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
- // need the result RpcData
- if (first) {
- block->Add(new Assignment(this->resultData, new NewExpression(RPC_DATA_TYPE)));
- first = false;
- }
-
- generate_write_to_data(t, block, new StringLiteralExpression(arg->name.data),
- v, this->resultData);
- }
-
- arg = arg->next;
- }
-}
-
-void
-DispatcherClass::DoneWithMethods()
-{
- if (this->dispatchIfStatement == NULL) {
- return;
- }
-
- this->elements.push_back(this->processMethod);
-
- IfStatement* fallthrough = new IfStatement();
- fallthrough->statements = new StatementBlock;
- fallthrough->statements->Add(new ReturnStatement(
- new MethodCall(SUPER_VALUE, "process", 4,
- this->actionParam, this->requestParam,
- this->rpcContextParam,
- this->errorParam)));
- this->dispatchIfStatement->elseif = fallthrough;
- IfStatement* s = new IfStatement;
- s->statements = new StatementBlock;
- this->processMethod->statements->Add(s);
- s->expression = new Comparison(this->resultData, "!=", NULL_VALUE);
- s->statements->Add(new ReturnStatement(new MethodCall(this->resultData, "serialize")));
- s->elseif = new IfStatement;
- s = s->elseif;
- s->statements->Add(new ReturnStatement(NULL_VALUE));
-}
-
-// =================================================
-class RpcProxyClass : public Class
-{
-public:
- RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType);
- virtual ~RpcProxyClass();
-
- Variable* endpoint;
- Variable* broker;
-
-private:
- void generate_ctor();
- void generate_get_endpoint_info();
-};
-
-RpcProxyClass::RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType)
- :Class()
-{
- this->comment = gather_comments(iface->comments_token->extra);
- this->modifiers = PUBLIC;
- this->what = Class::CLASS;
- this->type = interfaceType;
-
- // broker
- this->broker = new Variable(RPC_BROKER_TYPE, "_broker");
- this->elements.push_back(new Field(PRIVATE, this->broker));
- // endpoint
- this->endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "_endpoint");
- this->elements.push_back(new Field(PRIVATE, this->endpoint));
-
- // methods
- generate_ctor();
- generate_get_endpoint_info();
-}
-
-RpcProxyClass::~RpcProxyClass()
-{
-}
-
-void
-RpcProxyClass::generate_ctor()
-{
- Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
- Variable* endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "endpoint");
- Method* ctor = new Method;
- ctor->modifiers = PUBLIC;
- ctor->name = class_name_leaf(this->type->Name());
- ctor->statements = new StatementBlock;
- ctor->parameters.push_back(broker);
- ctor->parameters.push_back(endpoint);
- this->elements.push_back(ctor);
-
- ctor->statements->Add(new Assignment(this->broker, broker));
- ctor->statements->Add(new Assignment(this->endpoint, endpoint));
-}
-
-void
-RpcProxyClass::generate_get_endpoint_info()
-{
- Method* get = new Method;
- get->modifiers = PUBLIC;
- get->returnType = RPC_ENDPOINT_INFO_TYPE;
- get->name = "getEndpointInfo";
- get->statements = new StatementBlock;
- this->elements.push_back(get);
-
- get->statements->Add(new ReturnStatement(this->endpoint));
-}
-
-// =================================================
-class EventListenerClass : public DispatcherClass
-{
-public:
- EventListenerClass(const interface_type* iface, Type* listenerType);
- virtual ~EventListenerClass();
-
- Variable* _listener;
-
-private:
- void generate_ctor();
-};
-
-Expression*
-generate_get_listener_expression(Type* cast)
-{
- return new Cast(cast, new MethodCall(THIS_VALUE, "getView"));
-}
-
-EventListenerClass::EventListenerClass(const interface_type* iface, Type* listenerType)
- :DispatcherClass(iface, new FieldVariable(THIS_VALUE, "_listener"))
-{
- this->modifiers = PRIVATE;
- this->what = Class::CLASS;
- this->type = new Type(iface->package ? iface->package : "",
- append(iface->name.data, ".Presenter"),
- Type::GENERATED, false, false, false);
- this->extends = PRESENTER_BASE_TYPE;
-
- this->_listener = new Variable(listenerType, "_listener");
- this->elements.push_back(new Field(PRIVATE, this->_listener));
-
- // methods
- generate_ctor();
-}
-
-EventListenerClass::~EventListenerClass()
-{
-}
-
-void
-EventListenerClass::generate_ctor()
-{
- Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
- Variable* listener = new Variable(this->_listener->type, "listener");
- Method* ctor = new Method;
- ctor->modifiers = PUBLIC;
- ctor->name = class_name_leaf(this->type->Name());
- ctor->statements = new StatementBlock;
- ctor->parameters.push_back(broker);
- ctor->parameters.push_back(listener);
- this->elements.push_back(ctor);
-
- ctor->statements->Add(new MethodCall("super", 2, broker, listener));
- ctor->statements->Add(new Assignment(this->_listener, listener));
-}
-
-// =================================================
-class ListenerClass : public Class
-{
-public:
- ListenerClass(const interface_type* iface);
- virtual ~ListenerClass();
-
- bool needed;
-
-private:
- void generate_ctor();
-};
-
-ListenerClass::ListenerClass(const interface_type* iface)
- :Class(),
- needed(false)
-{
- this->comment = "/** Extend this to listen to the events from this class. */";
- this->modifiers = STATIC | PUBLIC ;
- this->what = Class::CLASS;
- this->type = new Type(iface->package ? iface->package : "",
- append(iface->name.data, ".Listener"),
- Type::GENERATED, false, false, false);
- this->extends = PRESENTER_LISTENER_BASE_TYPE;
-}
-
-ListenerClass::~ListenerClass()
-{
-}
-
-// =================================================
-class EndpointBaseClass : public DispatcherClass
-{
-public:
- EndpointBaseClass(const interface_type* iface);
- virtual ~EndpointBaseClass();
-
- bool needed;
-
-private:
- void generate_ctor();
-};
-
-EndpointBaseClass::EndpointBaseClass(const interface_type* iface)
- :DispatcherClass(iface, THIS_VALUE),
- needed(false)
-{
- this->comment = "/** Extend this to implement a link service. */";
- this->modifiers = STATIC | PUBLIC | ABSTRACT;
- this->what = Class::CLASS;
- this->type = new Type(iface->package ? iface->package : "",
- append(iface->name.data, ".EndpointBase"),
- Type::GENERATED, false, false, false);
- this->extends = RPC_CONNECTOR_TYPE;
-
- // methods
- generate_ctor();
-}
-
-EndpointBaseClass::~EndpointBaseClass()
-{
-}
-
-void
-EndpointBaseClass::generate_ctor()
-{
- Variable* container = new Variable(RPC_CONTAINER_TYPE, "container");
- Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
- Variable* place = new Variable(PLACE_INFO_TYPE, "placeInfo");
- Method* ctor = new Method;
- ctor->modifiers = PUBLIC;
- ctor->name = class_name_leaf(this->type->Name());
- ctor->statements = new StatementBlock;
- ctor->parameters.push_back(container);
- ctor->parameters.push_back(broker);
- ctor->parameters.push_back(place);
- this->elements.push_back(ctor);
-
- ctor->statements->Add(new MethodCall("super", 3, container, broker, place));
-}
-
-// =================================================
-class ResultDispatcherClass : public Class
-{
-public:
- ResultDispatcherClass();
- virtual ~ResultDispatcherClass();
-
- void AddMethod(int index, const string& name, Method** method, Variable** param);
-
- bool needed;
- Variable* methodId;
- Variable* callback;
- Method* onResultMethod;
- Variable* resultParam;
- SwitchStatement* methodSwitch;
-
-private:
- void generate_ctor();
- void generate_onResult();
-};
-
-ResultDispatcherClass::ResultDispatcherClass()
- :Class(),
- needed(false)
-{
- this->modifiers = PRIVATE | FINAL;
- this->what = Class::CLASS;
- this->type = new Type("_ResultDispatcher", Type::GENERATED, false, false, false);
- this->interfaces.push_back(RPC_RESULT_HANDLER_TYPE);
-
- // methodId
- this->methodId = new Variable(INT_TYPE, "methodId");
- this->elements.push_back(new Field(PRIVATE, this->methodId));
- this->callback = new Variable(OBJECT_TYPE, "callback");
- this->elements.push_back(new Field(PRIVATE, this->callback));
-
- // methods
- generate_ctor();
- generate_onResult();
-}
-
-ResultDispatcherClass::~ResultDispatcherClass()
-{
-}
-
-void
-ResultDispatcherClass::generate_ctor()
-{
- Variable* methodIdParam = new Variable(INT_TYPE, "methId");
- Variable* callbackParam = new Variable(OBJECT_TYPE, "cbObj");
- Method* ctor = new Method;
- ctor->modifiers = PUBLIC;
- ctor->name = class_name_leaf(this->type->Name());
- ctor->statements = new StatementBlock;
- ctor->parameters.push_back(methodIdParam);
- ctor->parameters.push_back(callbackParam);
- this->elements.push_back(ctor);
-
- ctor->statements->Add(new Assignment(this->methodId, methodIdParam));
- ctor->statements->Add(new Assignment(this->callback, callbackParam));
-}
-
-void
-ResultDispatcherClass::generate_onResult()
-{
- this->onResultMethod = new Method;
- this->onResultMethod->modifiers = PUBLIC;
- this->onResultMethod->returnType = VOID_TYPE;
- this->onResultMethod->returnTypeDimension = 0;
- this->onResultMethod->name = "onResult";
- this->onResultMethod->statements = new StatementBlock;
- this->elements.push_back(this->onResultMethod);
-
- this->resultParam = new Variable(BYTE_TYPE, "result", 1);
- this->onResultMethod->parameters.push_back(this->resultParam);
-
- this->methodSwitch = new SwitchStatement(this->methodId);
- this->onResultMethod->statements->Add(this->methodSwitch);
-}
-
-void
-ResultDispatcherClass::AddMethod(int index, const string& name, Method** method, Variable** param)
-{
- Method* m = new Method;
- m->modifiers = PUBLIC;
- m->returnType = VOID_TYPE;
- m->returnTypeDimension = 0;
- m->name = name;
- m->statements = new StatementBlock;
- *param = new Variable(BYTE_TYPE, "result", 1);
- m->parameters.push_back(*param);
- this->elements.push_back(m);
- *method = m;
-
- Case* c = new Case(format_int(index));
- c->statements->Add(new MethodCall(new LiteralExpression("this"), name, 1, this->resultParam));
- c->statements->Add(new Break());
-
- this->methodSwitch->cases.push_back(c);
-}
-
-// =================================================
-static void
-generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from)
-{
- fprintf(stderr, "aidl: implement generate_new_array %s:%d\n", __FILE__, __LINE__);
- exit(1);
-}
-
-static void
-generate_create_from_data(Type* t, StatementBlock* addTo, const string& key, Variable* v,
- Variable* data, Variable** cl)
-{
- Expression* k = new StringLiteralExpression(key);
- if (v->dimension == 0) {
- t->CreateFromRpcData(addTo, k, v, data, cl);
- }
- if (v->dimension == 1) {
- //t->ReadArrayFromRpcData(addTo, v, data, cl);
- fprintf(stderr, "aidl: implement generate_create_from_data for arrays%s:%d\n",
- __FILE__, __LINE__);
- }
-}
-
-static void
-generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v, Variable* data)
-{
- if (v->dimension == 0) {
- t->WriteToRpcData(addTo, k, v, data, 0);
- }
- if (v->dimension == 1) {
- //t->WriteArrayToParcel(addTo, v, data);
- fprintf(stderr, "aidl: implement generate_write_to_data for arrays%s:%d\n",
- __FILE__, __LINE__);
- }
-}
-
-// =================================================
-static Type*
-generate_results_method(const method_type* method, RpcProxyClass* proxyClass)
-{
- arg_type* arg;
-
- string resultsMethodName = results_method_name(method->name.data);
- Type* resultsInterfaceType = new Type(results_class_name(method->name.data),
- Type::GENERATED, false, false, false);
-
- if (!method->oneway) {
- Class* resultsClass = new Class;
- resultsClass->modifiers = STATIC | PUBLIC;
- resultsClass->what = Class::INTERFACE;
- resultsClass->type = resultsInterfaceType;
-
- Method* resultMethod = new Method;
- resultMethod->comment = gather_comments(method->comments_token->extra);
- resultMethod->modifiers = PUBLIC;
- resultMethod->returnType = VOID_TYPE;
- resultMethod->returnTypeDimension = 0;
- resultMethod->name = resultsMethodName;
- if (0 != strcmp("void", method->type.type.data)) {
- resultMethod->parameters.push_back(new Variable(NAMES.Search(method->type.type.data),
- "_result", method->type.dimension));
- }
- arg = method->args;
- while (arg != NULL) {
- if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
- resultMethod->parameters.push_back(new Variable(
- NAMES.Search(arg->type.type.data), arg->name.data,
- arg->type.dimension));
- }
- arg = arg->next;
- }
- resultsClass->elements.push_back(resultMethod);
-
- if (resultMethod->parameters.size() > 0) {
- proxyClass->elements.push_back(resultsClass);
- return resultsInterfaceType;
- }
- }
- //delete resultsInterfaceType;
- return NULL;
-}
-
-static void
-generate_proxy_method(const method_type* method, RpcProxyClass* proxyClass,
- ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
-{
- arg_type* arg;
- Method* proxyMethod = new Method;
- proxyMethod->comment = gather_comments(method->comments_token->extra);
- proxyMethod->modifiers = PUBLIC;
- proxyMethod->returnType = VOID_TYPE;
- proxyMethod->returnTypeDimension = 0;
- proxyMethod->name = method->name.data;
- proxyMethod->statements = new StatementBlock;
- proxyClass->elements.push_back(proxyMethod);
-
- // The local variables
- Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
- proxyMethod->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));
-
- // Add the arguments
- arg = method->args;
- while (arg != NULL) {
- if (convert_direction(arg->direction.data) & IN_PARAMETER) {
- // Function signature
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
- proxyMethod->parameters.push_back(v);
-
- // Input parameter marshalling
- generate_write_to_data(t, proxyMethod->statements,
- new StringLiteralExpression(arg->name.data), v, _data);
- }
- arg = arg->next;
- }
-
- // If there is a results interface for this class
- Expression* resultParameter;
- if (resultsInterfaceType != NULL) {
- // Result interface parameter
- Variable* resultListener = new Variable(resultsInterfaceType, "_result");
- proxyMethod->parameters.push_back(resultListener);
-
- // Add the results dispatcher callback
- resultsDispatcherClass->needed = true;
- resultParameter = new NewExpression(resultsDispatcherClass->type, 2,
- new LiteralExpression(format_int(index)), resultListener);
- } else {
- resultParameter = NULL_VALUE;
- }
-
- // All proxy methods take an error parameter
- Variable* errorListener = new Variable(RPC_ERROR_LISTENER_TYPE, "_errors");
- proxyMethod->parameters.push_back(errorListener);
-
- // Call the broker
- proxyMethod->statements->Add(new MethodCall(new FieldVariable(THIS_VALUE, "_broker"),
- "sendRpc", 5,
- proxyClass->endpoint,
- new StringLiteralExpression(method->name.data),
- new MethodCall(_data, "serialize"),
- resultParameter,
- errorListener));
-}
-
-static void
-generate_result_dispatcher_method(const method_type* method,
- ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
-{
- arg_type* arg;
- Method* dispatchMethod;
- Variable* dispatchParam;
- resultsDispatcherClass->AddMethod(index, method->name.data, &dispatchMethod, &dispatchParam);
-
- Variable* classLoader = NULL;
- Variable* resultData = new Variable(RPC_DATA_TYPE, "resultData");
- dispatchMethod->statements->Add(new VariableDeclaration(resultData,
- new NewExpression(RPC_DATA_TYPE, 1, dispatchParam)));
-
- // The callback method itself
- MethodCall* realCall = new MethodCall(
- new Cast(resultsInterfaceType, new FieldVariable(THIS_VALUE, "callback")),
- results_method_name(method->name.data));
-
- // The return value
- {
- Type* t = NAMES.Search(method->type.type.data);
- if (t != VOID_TYPE) {
- Variable* rv = new Variable(t, "rv");
- dispatchMethod->statements->Add(new VariableDeclaration(rv));
- generate_create_from_data(t, dispatchMethod->statements, "_result", rv,
- resultData, &classLoader);
- realCall->arguments.push_back(rv);
- }
- }
-
- VariableFactory stubArgs("arg");
- arg = method->args;
- while (arg != NULL) {
- if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
- // Unmarshall the results
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = stubArgs.Get(t);
- dispatchMethod->statements->Add(new VariableDeclaration(v));
-
- generate_create_from_data(t, dispatchMethod->statements, arg->name.data, v,
- resultData, &classLoader);
-
- // Add the argument to the callback
- realCall->arguments.push_back(v);
- }
- arg = arg->next;
- }
-
- // Call the callback method
- IfStatement* ifst = new IfStatement;
- ifst->expression = new Comparison(new FieldVariable(THIS_VALUE, "callback"), "!=", NULL_VALUE);
- dispatchMethod->statements->Add(ifst);
- ifst->statements->Add(realCall);
-}
-
-static void
-generate_regular_method(const method_type* method, RpcProxyClass* proxyClass,
- EndpointBaseClass* serviceBaseClass, ResultDispatcherClass* resultsDispatcherClass,
- int index)
-{
- arg_type* arg;
-
- // == the callback interface for results ================================
- // the service base class
- Type* resultsInterfaceType = generate_results_method(method, proxyClass);
-
- // == the method in the proxy class =====================================
- generate_proxy_method(method, proxyClass, resultsDispatcherClass, resultsInterfaceType, index);
-
- // == the method in the result dispatcher class =========================
- if (resultsInterfaceType != NULL) {
- generate_result_dispatcher_method(method, resultsDispatcherClass, resultsInterfaceType,
- index);
- }
-
- // == The abstract method that the service developers implement ==========
- Method* decl = new Method;
- decl->comment = gather_comments(method->comments_token->extra);
- decl->modifiers = PUBLIC | ABSTRACT;
- decl->returnType = NAMES.Search(method->type.type.data);
- decl->returnTypeDimension = method->type.dimension;
- decl->name = method->name.data;
- arg = method->args;
- while (arg != NULL) {
- decl->parameters.push_back(new Variable(
- NAMES.Search(arg->type.type.data), arg->name.data,
- arg->type.dimension));
- arg = arg->next;
- }
-
- // Add the default RpcContext param to all methods
- decl->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
-
- serviceBaseClass->elements.push_back(decl);
-
-
- // == the dispatch method in the service base class ======================
- serviceBaseClass->AddMethod(method);
-}
-
-static void
-generate_event_method(const method_type* method, RpcProxyClass* proxyClass,
- EndpointBaseClass* serviceBaseClass, ListenerClass* listenerClass,
- EventListenerClass* presenterClass, int index)
-{
- arg_type* arg;
- listenerClass->needed = true;
-
- // == the push method in the service base class =========================
- Method* push = new Method;
- push->modifiers = PUBLIC;
- push->name = push_method_name(method->name.data);
- push->statements = new StatementBlock;
- push->returnType = VOID_TYPE;
- serviceBaseClass->elements.push_back(push);
-
- // The local variables
- Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
- push->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));
-
- // Add the arguments
- arg = method->args;
- while (arg != NULL) {
- // Function signature
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
- push->parameters.push_back(v);
-
- // Input parameter marshalling
- generate_write_to_data(t, push->statements,
- new StringLiteralExpression(arg->name.data), v, _data);
-
- arg = arg->next;
- }
-
- // Send the notifications
- push->statements->Add(new MethodCall("pushEvent", 2,
- new StringLiteralExpression(method->name.data),
- new MethodCall(_data, "serialize")));
-
- // == the event callback dispatcher method ====================================
- presenterClass->AddMethod(method);
-
- // == the event method in the listener base class =====================
- Method* event = new Method;
- event->modifiers = PUBLIC;
- event->name = method->name.data;
- event->statements = new StatementBlock;
- event->returnType = VOID_TYPE;
- listenerClass->elements.push_back(event);
- arg = method->args;
- while (arg != NULL) {
- event->parameters.push_back(new Variable(
- NAMES.Search(arg->type.type.data), arg->name.data,
- arg->type.dimension));
- arg = arg->next;
- }
-
- // Add a final parameter: RpcContext. Contains data about
- // incoming request (e.g., certificate)
- event->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
-}
-
-static void
-generate_listener_methods(RpcProxyClass* proxyClass, Type* presenterType, Type* listenerType)
-{
- // AndroidAtHomePresenter _presenter;
- // void startListening(Listener listener) {
- // stopListening();
- // _presenter = new Presenter(_broker, listener);
- // _presenter.startListening(_endpoint);
- // }
- // void stopListening() {
- // if (_presenter != null) {
- // _presenter.stopListening();
- // }
- // }
-
- Variable* _presenter = new Variable(presenterType, "_presenter");
- proxyClass->elements.push_back(new Field(PRIVATE, _presenter));
-
- Variable* listener = new Variable(listenerType, "listener");
-
- Method* startListeningMethod = new Method;
- startListeningMethod->modifiers = PUBLIC;
- startListeningMethod->returnType = VOID_TYPE;
- startListeningMethod->name = "startListening";
- startListeningMethod->statements = new StatementBlock;
- startListeningMethod->parameters.push_back(listener);
- proxyClass->elements.push_back(startListeningMethod);
-
- startListeningMethod->statements->Add(new MethodCall(THIS_VALUE, "stopListening"));
- startListeningMethod->statements->Add(new Assignment(_presenter,
- new NewExpression(presenterType, 2, proxyClass->broker, listener)));
- startListeningMethod->statements->Add(new MethodCall(_presenter,
- "startListening", 1, proxyClass->endpoint));
-
- Method* stopListeningMethod = new Method;
- stopListeningMethod->modifiers = PUBLIC;
- stopListeningMethod->returnType = VOID_TYPE;
- stopListeningMethod->name = "stopListening";
- stopListeningMethod->statements = new StatementBlock;
- proxyClass->elements.push_back(stopListeningMethod);
-
- IfStatement* ifst = new IfStatement;
- ifst->expression = new Comparison(_presenter, "!=", NULL_VALUE);
- stopListeningMethod->statements->Add(ifst);
-
- ifst->statements->Add(new MethodCall(_presenter, "stopListening"));
- ifst->statements->Add(new Assignment(_presenter, NULL_VALUE));
-}
-
-Class*
-generate_rpc_interface_class(const interface_type* iface)
-{
- // the proxy class
- InterfaceType* interfaceType = static_cast<InterfaceType*>(
- NAMES.Find(iface->package, iface->name.data));
- RpcProxyClass* proxy = new RpcProxyClass(iface, interfaceType);
-
- // the listener class
- ListenerClass* listener = new ListenerClass(iface);
-
- // the presenter class
- EventListenerClass* presenter = new EventListenerClass(iface, listener->type);
-
- // the service base class
- EndpointBaseClass* base = new EndpointBaseClass(iface);
- proxy->elements.push_back(base);
-
- // the result dispatcher
- ResultDispatcherClass* results = new ResultDispatcherClass();
-
- // all the declared methods of the proxy
- int index = 0;
- interface_item_type* item = iface->interface_items;
- while (item != NULL) {
- if (item->item_type == METHOD_TYPE) {
- if (NAMES.Search(((method_type*)item)->type.type.data) == EVENT_FAKE_TYPE) {
- generate_event_method((method_type*)item, proxy, base, listener, presenter, index);
- } else {
- generate_regular_method((method_type*)item, proxy, base, results, index);
- }
- }
- item = item->next;
- index++;
- }
- presenter->DoneWithMethods();
- base->DoneWithMethods();
-
- // only add this if there are methods with results / out parameters
- if (results->needed) {
- proxy->elements.push_back(results);
- }
- if (listener->needed) {
- proxy->elements.push_back(listener);
- proxy->elements.push_back(presenter);
- generate_listener_methods(proxy, presenter->type, listener->type);
- }
-
- return proxy;
-}
diff --git a/tools/aidl/options.cpp b/tools/aidl/options.cpp
deleted file mode 100644
index 7b2daebec09e..000000000000
--- a/tools/aidl/options.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-
-#include "options.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-static int
-usage()
-{
- fprintf(stderr,
- "usage: aidl OPTIONS INPUT [OUTPUT]\n"
- " aidl --preprocess OUTPUT INPUT...\n"
- "\n"
- "OPTIONS:\n"
- " -I<DIR> search path for import statements.\n"
- " -d<FILE> generate dependency file.\n"
- " -a generate dependency file next to the output file with the name based on the input file.\n"
- " -p<FILE> file created by --preprocess to import.\n"
- " -o<FOLDER> base output folder for generated files.\n"
- " -b fail when trying to compile a parcelable.\n"
- "\n"
- "INPUT:\n"
- " An aidl interface file.\n"
- "\n"
- "OUTPUT:\n"
- " The generated interface files.\n"
- " If omitted and the -o option is not used, the input filename is used, with the .aidl extension changed to a .java extension.\n"
- " If the -o option is used, the generated files will be placed in the base output folder, under their package folder\n"
- );
- return 1;
-}
-
-int
-parse_options(int argc, const char* const* argv, Options *options)
-{
- int i = 1;
-
- if (argc >= 2 && 0 == strcmp(argv[1], "--preprocess")) {
- if (argc < 4) {
- return usage();
- }
- options->outputFileName = argv[2];
- for (int i=3; i<argc; i++) {
- options->filesToPreprocess.push_back(argv[i]);
- }
- options->task = PREPROCESS_AIDL;
- return 0;
- }
-
- options->task = COMPILE_AIDL;
- options->failOnParcelable = false;
- options->autoDepFile = false;
-
- // OPTIONS
- while (i < argc) {
- const char* s = argv[i];
- int len = strlen(s);
- if (s[0] == '-') {
- if (len > 1) {
- // -I<system-import-path>
- if (s[1] == 'I') {
- if (len > 2) {
- options->importPaths.push_back(s+2);
- } else {
- fprintf(stderr, "-I option (%d) requires a path.\n", i);
- return usage();
- }
- }
- else if (s[1] == 'd') {
- if (len > 2) {
- options->depFileName = s+2;
- } else {
- fprintf(stderr, "-d option (%d) requires a file.\n", i);
- return usage();
- }
- }
- else if (s[1] == 'a') {
- options->autoDepFile = true;
- }
- else if (s[1] == 'p') {
- if (len > 2) {
- options->preprocessedFiles.push_back(s+2);
- } else {
- fprintf(stderr, "-p option (%d) requires a file.\n", i);
- return usage();
- }
- }
- else if (s[1] == 'o') {
- if (len > 2) {
- options->outputBaseFolder = s+2;
- } else {
- fprintf(stderr, "-o option (%d) requires a path.\n", i);
- return usage();
- }
- }
- else if (len == 2 && s[1] == 'b') {
- options->failOnParcelable = true;
- }
- else {
- // s[1] is not known
- fprintf(stderr, "unknown option (%d): %s\n", i, s);
- return usage();
- }
- } else {
- // len <= 1
- fprintf(stderr, "unknown option (%d): %s\n", i, s);
- return usage();
- }
- } else {
- // s[0] != '-'
- break;
- }
- i++;
- }
-
- // INPUT
- if (i < argc) {
- options->inputFileName = argv[i];
- i++;
- } else {
- fprintf(stderr, "INPUT required\n");
- return usage();
- }
-
- // OUTPUT
- if (i < argc) {
- options->outputFileName = argv[i];
- i++;
- } else if (options->outputBaseFolder.length() == 0) {
- // copy input into output and change the extension from .aidl to .java
- options->outputFileName = options->inputFileName;
- string::size_type pos = options->outputFileName.size()-5;
- if (options->outputFileName.compare(pos, 5, ".aidl") == 0) { // 5 = strlen(".aidl")
- options->outputFileName.replace(pos, 5, ".java"); // 5 = strlen(".aidl")
- } else {
- fprintf(stderr, "INPUT is not an .aidl file.\n");
- return usage();
- }
- }
-
- // anything remaining?
- if (i != argc) {
- fprintf(stderr, "unknown option%s:", (i==argc-1?(const char*)"":(const char*)"s"));
- for (; i<argc-1; i++) {
- fprintf(stderr, " %s", argv[i]);
- }
- fprintf(stderr, "\n");
- return usage();
- }
-
- return 0;
-}
-
diff --git a/tools/aidl/options.h b/tools/aidl/options.h
deleted file mode 100644
index 387e37d08732..000000000000
--- a/tools/aidl/options.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#ifndef DEVICE_TOOLS_AIDL_H
-#define DEVICE_TOOLS_AIDL_H
-
-#include <string.h>
-#include <string>
-#include <vector>
-
-using namespace std;
-
-enum {
- COMPILE_AIDL,
- PREPROCESS_AIDL
-};
-
-// This struct is the parsed version of the command line options
-struct Options
-{
- int task;
- bool failOnParcelable;
- vector<string> importPaths;
- vector<string> preprocessedFiles;
- string inputFileName;
- string outputFileName;
- string outputBaseFolder;
- string depFileName;
- bool autoDepFile;
-
- vector<string> filesToPreprocess;
-};
-
-// takes the inputs from the command line and fills in the Options struct
-// Returns 0 on success, and nonzero on failure.
-// It also prints the usage statement on failure.
-int parse_options(int argc, const char* const* argv, Options *options);
-
-#endif // DEVICE_TOOLS_AIDL_H
diff --git a/tools/aidl/options_test.cpp b/tools/aidl/options_test.cpp
deleted file mode 100644
index bd106ce54f2d..000000000000
--- a/tools/aidl/options_test.cpp
+++ /dev/null
@@ -1,291 +0,0 @@
-#include <iostream>
-#include "options.h"
-
-const bool VERBOSE = false;
-
-using namespace std;
-
-struct Answer {
- const char* argv[8];
- int result;
- const char* systemSearchPath[8];
- const char* localSearchPath[8];
- const char* inputFileName;
- language_t nativeLanguage;
- const char* outputH;
- const char* outputCPP;
- const char* outputJava;
-};
-
-bool
-match_arrays(const char* const*expected, const vector<string> &got)
-{
- int count = 0;
- while (expected[count] != NULL) {
- count++;
- }
- if (got.size() != count) {
- return false;
- }
- for (int i=0; i<count; i++) {
- if (got[i] != expected[i]) {
- return false;
- }
- }
- return true;
-}
-
-void
-print_array(const char* prefix, const char* const*expected)
-{
- while (*expected) {
- cout << prefix << *expected << endl;
- expected++;
- }
-}
-
-void
-print_array(const char* prefix, const vector<string> &got)
-{
- size_t count = got.size();
- for (size_t i=0; i<count; i++) {
- cout << prefix << got[i] << endl;
- }
-}
-
-static int
-test(const Answer& answer)
-{
- int argc = 0;
- while (answer.argv[argc]) {
- argc++;
- }
-
- int err = 0;
-
- Options options;
- int result = parse_options(argc, answer.argv, &options);
-
- // result
- if (((bool)result) != ((bool)answer.result)) {
- cout << "mismatch: result: got " << result << " expected " <<
- answer.result << endl;
- err = 1;
- }
-
- if (result != 0) {
- // if it failed, everything is invalid
- return err;
- }
-
- // systemSearchPath
- if (!match_arrays(answer.systemSearchPath, options.systemSearchPath)) {
- cout << "mismatch: systemSearchPath: got" << endl;
- print_array(" ", options.systemSearchPath);
- cout << " expected" << endl;
- print_array(" ", answer.systemSearchPath);
- err = 1;
- }
-
- // localSearchPath
- if (!match_arrays(answer.localSearchPath, options.localSearchPath)) {
- cout << "mismatch: localSearchPath: got" << endl;
- print_array(" ", options.localSearchPath);
- cout << " expected" << endl;
- print_array(" ", answer.localSearchPath);
- err = 1;
- }
-
- // inputFileName
- if (answer.inputFileName != options.inputFileName) {
- cout << "mismatch: inputFileName: got " << options.inputFileName
- << " expected " << answer.inputFileName << endl;
- err = 1;
- }
-
- // nativeLanguage
- if (answer.nativeLanguage != options.nativeLanguage) {
- cout << "mismatch: nativeLanguage: got " << options.nativeLanguage
- << " expected " << answer.nativeLanguage << endl;
- err = 1;
- }
-
- // outputH
- if (answer.outputH != options.outputH) {
- cout << "mismatch: outputH: got " << options.outputH
- << " expected " << answer.outputH << endl;
- err = 1;
- }
-
- // outputCPP
- if (answer.outputCPP != options.outputCPP) {
- cout << "mismatch: outputCPP: got " << options.outputCPP
- << " expected " << answer.outputCPP << endl;
- err = 1;
- }
-
- // outputJava
- if (answer.outputJava != options.outputJava) {
- cout << "mismatch: outputJava: got " << options.outputJava
- << " expected " << answer.outputJava << endl;
- err = 1;
- }
-
- return err;
-}
-
-const Answer g_tests[] = {
-
- {
- /* argv */ { "test", "-i/moof", "-I/blah", "-Ibleh", "-imoo", "inputFileName.aidl_cpp", NULL, NULL },
- /* result */ 0,
- /* systemSearchPath */ { "/blah", "bleh", NULL, NULL, NULL, NULL, NULL, NULL },
- /* localSearchPath */ { "/moof", "moo", NULL, NULL, NULL, NULL, NULL, NULL },
- /* inputFileName */ "inputFileName.aidl_cpp",
- /* nativeLanguage */ CPP,
- /* outputH */ "",
- /* outputCPP */ "",
- /* outputJava */ ""
- },
-
- {
- /* argv */ { "test", "inputFileName.aidl_cpp", "-oh", "outputH", NULL, NULL, NULL, NULL },
- /* result */ 0,
- /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* inputFileName */ "inputFileName.aidl_cpp",
- /* nativeLanguage */ CPP,
- /* outputH */ "outputH",
- /* outputCPP */ "",
- /* outputJava */ ""
- },
-
- {
- /* argv */ { "test", "inputFileName.aidl_cpp", "-ocpp", "outputCPP", NULL, NULL, NULL, NULL },
- /* result */ 0,
- /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* inputFileName */ "inputFileName.aidl_cpp",
- /* nativeLanguage */ CPP,
- /* outputH */ "",
- /* outputCPP */ "outputCPP",
- /* outputJava */ ""
- },
-
- {
- /* argv */ { "test", "inputFileName.aidl_cpp", "-ojava", "outputJava", NULL, NULL, NULL, NULL },
- /* result */ 0,
- /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* inputFileName */ "inputFileName.aidl_cpp",
- /* nativeLanguage */ CPP,
- /* outputH */ "",
- /* outputCPP */ "",
- /* outputJava */ "outputJava"
- },
-
- {
- /* argv */ { "test", "inputFileName.aidl_cpp", "-oh", "outputH", "-ocpp", "outputCPP", "-ojava", "outputJava" },
- /* result */ 0,
- /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* inputFileName */ "inputFileName.aidl_cpp",
- /* nativeLanguage */ CPP,
- /* outputH */ "outputH",
- /* outputCPP */ "outputCPP",
- /* outputJava */ "outputJava"
- },
-
- {
- /* argv */ { "test", "inputFileName.aidl_cpp", "-oh", "outputH", "-oh", "outputH1", NULL, NULL },
- /* result */ 1,
- /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* inputFileName */ "",
- /* nativeLanguage */ CPP,
- /* outputH */ "",
- /* outputCPP */ "",
- /* outputJava */ ""
- },
-
- {
- /* argv */ { "test", "inputFileName.aidl_cpp", "-ocpp", "outputCPP", "-ocpp", "outputCPP1", NULL, NULL },
- /* result */ 1,
- /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* inputFileName */ "",
- /* nativeLanguage */ CPP,
- /* outputH */ "",
- /* outputCPP */ "",
- /* outputJava */ ""
- },
-
- {
- /* argv */ { "test", "inputFileName.aidl_cpp", "-ojava", "outputJava", "-ojava", "outputJava1", NULL, NULL },
- /* result */ 1,
- /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
- /* inputFileName */ "",
- /* nativeLanguage */ CPP,
- /* outputH */ "",
- /* outputCPP */ "",
- /* outputJava */ ""
- },
-
-};
-
-int
-main(int argc, const char** argv)
-{
- const int count = sizeof(g_tests)/sizeof(g_tests[0]);
- int matches[count];
-
- int result = 0;
- for (int i=0; i<count; i++) {
- if (VERBOSE) {
- cout << endl;
- cout << "---------------------------------------------" << endl;
- const char* const* p = g_tests[i].argv;
- while (*p) {
- cout << " " << *p;
- p++;
- }
- cout << endl;
- cout << "---------------------------------------------" << endl;
- }
- matches[i] = test(g_tests[i]);
- if (VERBOSE) {
- if (0 == matches[i]) {
- cout << "passed" << endl;
- } else {
- cout << "failed" << endl;
- }
- result |= matches[i];
- }
- }
-
- cout << endl;
- cout << "=============================================" << endl;
- cout << "options_test summary" << endl;
- cout << "=============================================" << endl;
-
- if (!result) {
- cout << "passed" << endl;
- } else {
- cout << "failed the following tests:" << endl;
- for (int i=0; i<count; i++) {
- if (matches[i]) {
- cout << " ";
- const char* const* p = g_tests[i].argv;
- while (*p) {
- cout << " " << *p;
- p++;
- }
- cout << endl;
- }
- }
- }
-
- return result;
-}
-
diff --git a/tools/aidl/search_path.cpp b/tools/aidl/search_path.cpp
deleted file mode 100644
index ffb6cb2932e4..000000000000
--- a/tools/aidl/search_path.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#include <unistd.h>
-#include "search_path.h"
-#include "options.h"
-#include <string.h>
-
-#ifdef HAVE_MS_C_RUNTIME
-#include <io.h>
-#endif
-
-static vector<string> g_importPaths;
-
-void
-set_import_paths(const vector<string>& importPaths)
-{
- g_importPaths = importPaths;
-}
-
-char*
-find_import_file(const char* given)
-{
- string expected = given;
-
- int N = expected.length();
- for (int i=0; i<N; i++) {
- char c = expected[i];
- if (c == '.') {
- expected[i] = OS_PATH_SEPARATOR;
- }
- }
- expected += ".aidl";
-
- vector<string>& paths = g_importPaths;
- for (vector<string>::iterator it=paths.begin(); it!=paths.end(); it++) {
- string f = *it;
- if (f.size() == 0) {
- f = ".";
- f += OS_PATH_SEPARATOR;
- }
- else if (f[f.size()-1] != OS_PATH_SEPARATOR) {
- f += OS_PATH_SEPARATOR;
- }
- f.append(expected);
-
-#ifdef HAVE_MS_C_RUNTIME
- /* check that the file exists and is not write-only */
- if (0 == _access(f.c_str(), 0) && /* mode 0=exist */
- 0 == _access(f.c_str(), 4) ) { /* mode 4=readable */
-#else
- if (0 == access(f.c_str(), R_OK)) {
-#endif
- return strdup(f.c_str());
- }
- }
-
- return NULL;
-}
-
diff --git a/tools/aidl/search_path.h b/tools/aidl/search_path.h
deleted file mode 100644
index 2bf94b12bbf1..000000000000
--- a/tools/aidl/search_path.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef DEVICE_TOOLS_AIDL_SEARCH_PATH_H
-#define DEVICE_TOOLS_AIDL_SEARCH_PATH_H
-
-#include <stdio.h>
-
-#if __cplusplus
-#include <vector>
-#include <string>
-using namespace std;
-extern "C" {
-#endif
-
-// returns a FILE* and the char* for the file that it found
-// given is the class name we're looking for
-char* find_import_file(const char* given);
-
-#if __cplusplus
-}; // extern "C"
-void set_import_paths(const vector<string>& importPaths);
-#endif
-
-#endif // DEVICE_TOOLS_AIDL_SEARCH_PATH_H
-
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index df76bc9ba4cc..ca2d2e75ee89 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -264,6 +264,9 @@ def verify_constants(clazz):
if "static" in f.split and "final" in f.split:
if re.match("[A-Z0-9_]+", f.name) is None:
error(clazz, f, "C2", "Constant field names must be FOO_NAME")
+ elif f.typ != "java.lang.String":
+ if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
+ warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
def verify_enums(clazz):
@@ -417,6 +420,9 @@ def verify_parcelable(clazz):
if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
+ if " final class " not in clazz.raw:
+ error(clazz, None, "FW8", "Parcelable classes must be final")
+
def verify_protected(clazz):
"""Verify that no protected methods or fields are allowed."""
@@ -730,6 +736,13 @@ def verify_exception(clazz):
if "throws java.lang.Exception" in m.raw or "throws java.lang.Throwable" in m.raw or "throws java.lang.Error" in m.raw:
error(clazz, m, "S1", "Methods must not throw generic exceptions")
+ if "throws android.os.RemoteException" in m.raw:
+ if clazz.name == "android.content.ContentProviderClient": continue
+ if clazz.name == "android.os.Binder": continue
+ if clazz.name == "android.os.IBinder": continue
+
+ error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
+
def verify_google(clazz):
"""Verifies that APIs never reference Google."""
@@ -946,6 +959,37 @@ def verify_resource_names(clazz):
error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
+def verify_files(clazz):
+ """Verifies that methods accepting File also accept streams."""
+
+ has_file = set()
+ has_stream = set()
+
+ test = []
+ test.extend(clazz.ctors)
+ test.extend(clazz.methods)
+
+ for m in test:
+ if "java.io.File" in m.args:
+ has_file.add(m)
+ if "java.io.FileDescriptor" in m.args or "android.os.ParcelFileDescriptor" in m.args or "java.io.InputStream" in m.args or "java.io.OutputStream" in m.args:
+ has_stream.add(m.name)
+
+ for m in has_file:
+ if m.name not in has_stream:
+ warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
+
+
+def verify_manager_list(clazz):
+ """Verifies that managers return List<? extends Parcelable> instead of arrays."""
+
+ if not clazz.name.endswith("Manager"): return
+
+ for m in clazz.methods:
+ if m.typ.startswith("android.") and m.typ.endswith("[]"):
+ warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
+
+
def examine_clazz(clazz):
"""Find all style issues in the given class."""
if clazz.pkg.name.startswith("java"): return
@@ -954,6 +998,7 @@ def examine_clazz(clazz):
if clazz.pkg.name.startswith("org.xml"): return
if clazz.pkg.name.startswith("org.json"): return
if clazz.pkg.name.startswith("org.w3c"): return
+ if clazz.pkg.name.startswith("android.icu."): return
verify_constants(clazz)
verify_enums(clazz)
@@ -989,6 +1034,8 @@ def examine_clazz(clazz):
verify_context_first(clazz)
verify_listener_last(clazz)
verify_resource_names(clazz)
+ verify_files(clazz)
+ verify_manager_list(clazz)
def examine_stream(stream):
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py
new file mode 100755
index 000000000000..ea36e2cb0d6a
--- /dev/null
+++ b/tools/fonts/fontchain_lint.py
@@ -0,0 +1,559 @@
+#!/usr/bin/env python
+
+import collections
+import copy
+import glob
+import itertools
+from os import path
+import sys
+from xml.etree import ElementTree
+
+from fontTools import ttLib
+
+EMOJI_VS = 0xFE0F
+
+LANG_TO_SCRIPT = {
+ 'as': 'Beng',
+ 'bn': 'Beng',
+ 'cy': 'Latn',
+ 'da': 'Latn',
+ 'de': 'Latn',
+ 'en': 'Latn',
+ 'es': 'Latn',
+ 'et': 'Latn',
+ 'eu': 'Latn',
+ 'fr': 'Latn',
+ 'ga': 'Latn',
+ 'gu': 'Gujr',
+ 'hi': 'Deva',
+ 'hr': 'Latn',
+ 'hu': 'Latn',
+ 'hy': 'Armn',
+ 'ja': 'Jpan',
+ 'kn': 'Knda',
+ 'ko': 'Kore',
+ 'ml': 'Mlym',
+ 'mn': 'Cyrl',
+ 'mr': 'Deva',
+ 'nb': 'Latn',
+ 'nn': 'Latn',
+ 'or': 'Orya',
+ 'pa': 'Guru',
+ 'pt': 'Latn',
+ 'sl': 'Latn',
+ 'ta': 'Taml',
+ 'te': 'Telu',
+ 'tk': 'Latn',
+}
+
+def lang_to_script(lang_code):
+ lang = lang_code.lower()
+ while lang not in LANG_TO_SCRIPT:
+ hyphen_idx = lang.rfind('-')
+ assert hyphen_idx != -1, (
+ 'We do not know what script the "%s" language is written in.'
+ % lang_code)
+ assumed_script = lang[hyphen_idx+1:]
+ if len(assumed_script) == 4 and assumed_script.isalpha():
+ # This is actually the script
+ return assumed_script.title()
+ lang = lang[:hyphen_idx]
+ return LANG_TO_SCRIPT[lang]
+
+
+def printable(inp):
+ if type(inp) is set: # set of character sequences
+ return '{' + ', '.join([printable(seq) for seq in inp]) + '}'
+ if type(inp) is tuple: # character sequence
+ return '<' + (', '.join([printable(ch) for ch in inp])) + '>'
+ else: # single character
+ return 'U+%04X' % inp
+
+
+def open_font(font):
+ font_file, index = font
+ font_path = path.join(_fonts_dir, font_file)
+ if index is not None:
+ return ttLib.TTFont(font_path, fontNumber=index)
+ else:
+ return ttLib.TTFont(font_path)
+
+
+def get_best_cmap(font):
+ ttfont = open_font(font)
+ all_unicode_cmap = None
+ bmp_cmap = None
+ for cmap in ttfont['cmap'].tables:
+ specifier = (cmap.format, cmap.platformID, cmap.platEncID)
+ if specifier == (4, 3, 1):
+ assert bmp_cmap is None, 'More than one BMP cmap in %s' % (font, )
+ bmp_cmap = cmap
+ elif specifier == (12, 3, 10):
+ assert all_unicode_cmap is None, (
+ 'More than one UCS-4 cmap in %s' % (font, ))
+ all_unicode_cmap = cmap
+
+ return all_unicode_cmap.cmap if all_unicode_cmap else bmp_cmap.cmap
+
+
+def get_variation_sequences_cmap(font):
+ ttfont = open_font(font)
+ vs_cmap = None
+ for cmap in ttfont['cmap'].tables:
+ specifier = (cmap.format, cmap.platformID, cmap.platEncID)
+ if specifier == (14, 0, 5):
+ assert vs_cmap is None, 'More than one VS cmap in %s' % (font, )
+ vs_cmap = cmap
+ return vs_cmap
+
+
+def get_emoji_map(font):
+ # Add normal characters
+ emoji_map = copy.copy(get_best_cmap(font))
+ reverse_cmap = {glyph: code for code, glyph in emoji_map.items()}
+
+ # Add variation sequences
+ vs_dict = get_variation_sequences_cmap(font).uvsDict
+ for vs in vs_dict:
+ for base, glyph in vs_dict[vs]:
+ if glyph is None:
+ emoji_map[(base, vs)] = emoji_map[base]
+ else:
+ emoji_map[(base, vs)] = glyph
+
+ # Add GSUB rules
+ ttfont = open_font(font)
+ for lookup in ttfont['GSUB'].table.LookupList.Lookup:
+ assert lookup.LookupType == 4, 'We only understand type 4 lookups'
+ for subtable in lookup.SubTable:
+ ligatures = subtable.ligatures
+ for first_glyph in ligatures:
+ for ligature in ligatures[first_glyph]:
+ sequence = [first_glyph] + ligature.Component
+ sequence = [reverse_cmap[glyph] for glyph in sequence]
+ sequence = tuple(sequence)
+ # Make sure no starting subsequence of 'sequence' has been
+ # seen before.
+ for sub_len in range(2, len(sequence)+1):
+ subsequence = sequence[:sub_len]
+ assert subsequence not in emoji_map
+ emoji_map[sequence] = ligature.LigGlyph
+
+ return emoji_map
+
+
+def assert_font_supports_any_of_chars(font, chars):
+ best_cmap = get_best_cmap(font)
+ for char in chars:
+ if char in best_cmap:
+ return
+ sys.exit('None of characters in %s were found in %s' % (chars, font))
+
+
+def assert_font_supports_all_of_chars(font, chars):
+ best_cmap = get_best_cmap(font)
+ for char in chars:
+ assert char in best_cmap, (
+ 'U+%04X was not found in %s' % (char, font))
+
+
+def assert_font_supports_none_of_chars(font, chars):
+ best_cmap = get_best_cmap(font)
+ for char in chars:
+ assert char not in best_cmap, (
+ 'U+%04X was found in %s' % (char, font))
+
+
+def assert_font_supports_all_sequences(font, sequences):
+ vs_dict = get_variation_sequences_cmap(font).uvsDict
+ for base, vs in sorted(sequences):
+ assert vs in vs_dict and (base, None) in vs_dict[vs], (
+ '<U+%04X, U+%04X> was not found in %s' % (base, vs, font))
+
+
+def check_hyphens(hyphens_dir):
+ # Find all the scripts that need automatic hyphenation
+ scripts = set()
+ for hyb_file in glob.iglob(path.join(hyphens_dir, '*.hyb')):
+ hyb_file = path.basename(hyb_file)
+ assert hyb_file.startswith('hyph-'), (
+ 'Unknown hyphenation file %s' % hyb_file)
+ lang_code = hyb_file[hyb_file.index('-')+1:hyb_file.index('.')]
+ scripts.add(lang_to_script(lang_code))
+
+ HYPHENS = {0x002D, 0x2010}
+ for script in scripts:
+ fonts = _script_to_font_map[script]
+ assert fonts, 'No fonts found for the "%s" script' % script
+ for font in fonts:
+ assert_font_supports_any_of_chars(font, HYPHENS)
+
+
+class FontRecord(object):
+ def __init__(self, name, scripts, variant, weight, style, font):
+ self.name = name
+ self.scripts = scripts
+ self.variant = variant
+ self.weight = weight
+ self.style = style
+ self.font = font
+
+
+def parse_fonts_xml(fonts_xml_path):
+ global _script_to_font_map, _fallback_chain
+ _script_to_font_map = collections.defaultdict(set)
+ _fallback_chain = []
+ tree = ElementTree.parse(fonts_xml_path)
+ for family in tree.findall('family'):
+ name = family.get('name')
+ variant = family.get('variant')
+ langs = family.get('lang')
+ if name:
+ assert variant is None, (
+ 'No variant expected for LGC font %s.' % name)
+ assert langs is None, (
+ 'No language expected for LGC fonts %s.' % name)
+ else:
+ assert variant in {None, 'elegant', 'compact'}, (
+ 'Unexpected value for variant: %s' % variant)
+
+ if langs:
+ langs = langs.split()
+ scripts = {lang_to_script(lang) for lang in langs}
+ else:
+ scripts = set()
+
+ for child in family:
+ assert child.tag == 'font', (
+ 'Unknown tag <%s>' % child.tag)
+ font_file = child.text
+ weight = int(child.get('weight'))
+ assert weight % 100 == 0, (
+ 'Font weight "%d" is not a multiple of 100.' % weight)
+
+ style = child.get('style')
+ assert style in {'normal', 'italic'}, (
+ 'Unknown style "%s"' % style)
+
+ index = child.get('index')
+ if index:
+ index = int(index)
+
+ _fallback_chain.append(FontRecord(
+ name,
+ frozenset(scripts),
+ variant,
+ weight,
+ style,
+ (font_file, index)))
+
+ if name: # non-empty names are used for default LGC fonts
+ map_scripts = {'Latn', 'Grek', 'Cyrl'}
+ else:
+ map_scripts = scripts
+ for script in map_scripts:
+ _script_to_font_map[script].add((font_file, index))
+
+
+def check_emoji_coverage(all_emoji, equivalent_emoji):
+ emoji_fonts = [
+ record.font for record in _fallback_chain
+ if 'Zsye' in record.scripts]
+ assert len(emoji_fonts) == 1, 'There are %d emoji fonts.' % len(emoji_fonts)
+ emoji_font = emoji_fonts[0]
+ coverage = get_emoji_map(emoji_font)
+
+ for sequence in all_emoji:
+ assert sequence in coverage, (
+ '%s is not supported in the emoji font.' % printable(sequence))
+
+ for sequence in coverage:
+ if sequence in {0x0000, 0x000D, 0x0020}:
+ # The font needs to support a few extra characters, which is OK
+ continue
+ assert sequence in all_emoji, (
+ 'Emoji font should not support %s.' % printable(sequence))
+
+ for first, second in sorted(equivalent_emoji.items()):
+ assert coverage[first] == coverage[second], (
+ '%s and %s should map to the same glyph.' % (
+ printable(first),
+ printable(second)))
+
+ for glyph in set(coverage.values()):
+ maps_to_glyph = [seq for seq in coverage if coverage[seq] == glyph]
+ if len(maps_to_glyph) > 1:
+ # There are more than one sequences mapping to the same glyph. We
+ # need to make sure they were expected to be equivalent.
+ equivalent_seqs = set()
+ for seq in maps_to_glyph:
+ equivalent_seq = seq
+ while equivalent_seq in equivalent_emoji:
+ equivalent_seq = equivalent_emoji[equivalent_seq]
+ equivalent_seqs.add(equivalent_seq)
+ assert len(equivalent_seqs) == 1, (
+ 'The sequences %s should not result in the same glyph %s' % (
+ printable(equivalent_seqs),
+ glyph))
+
+
+def check_emoji_defaults(default_emoji):
+ missing_text_chars = _emoji_properties['Emoji'] - default_emoji
+ emoji_font_seen = False
+ for record in _fallback_chain:
+ if 'Zsye' in record.scripts:
+ emoji_font_seen = True
+ # No need to check the emoji font
+ continue
+ # For later fonts, we only check them if they have a script
+ # defined, since the defined script may get them to a higher
+ # score even if they appear after the emoji font.
+ if emoji_font_seen and not record.scripts:
+ continue
+
+ # Check default emoji-style characters
+ assert_font_supports_none_of_chars(record.font, sorted(default_emoji))
+
+ # Mark default text-style characters appearing in fonts above the emoji
+ # font as seen
+ if not emoji_font_seen:
+ missing_text_chars -= set(get_best_cmap(record.font))
+
+ # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and
+ # webdings yet.
+ missing_text_chars -= _chars_by_age['7.0']
+ # TODO: Remove these after b/26113320 is fixed
+ missing_text_chars -= {
+ 0x263A, # WHITE SMILING FACE
+ 0x270C, # VICTORY HAND
+ 0x2744, # SNOWFLAKE
+ 0x2764, # HEAVY BLACK HEART
+ }
+ assert missing_text_chars == set(), (
+ 'Text style version of some emoji characters are missing: ' + repr(missing_text_chars))
+
+
+# Setting reverse to true returns a dictionary that maps the values to sets of
+# characters, useful for some binary properties. Otherwise, we get a
+# dictionary that maps characters to the property values, assuming there's only
+# one property in the file.
+def parse_unicode_datafile(file_path, reverse=False):
+ if reverse:
+ output_dict = collections.defaultdict(set)
+ else:
+ output_dict = {}
+ with open(file_path) as datafile:
+ for line in datafile:
+ if '#' in line:
+ line = line[:line.index('#')]
+ line = line.strip()
+ if not line:
+ continue
+
+ chars, prop = line.split(';')
+ chars = chars.strip()
+ prop = prop.strip()
+
+ if ' ' in chars: # character sequence
+ sequence = [int(ch, 16) for ch in chars.split(' ')]
+ additions = [tuple(sequence)]
+ elif '..' in chars: # character range
+ char_start, char_end = chars.split('..')
+ char_start = int(char_start, 16)
+ char_end = int(char_end, 16)
+ additions = xrange(char_start, char_end+1)
+ else: # singe character
+ additions = [int(chars, 16)]
+ if reverse:
+ output_dict[prop].update(additions)
+ else:
+ for addition in additions:
+ assert addition not in output_dict
+ output_dict[addition] = prop
+ return output_dict
+
+
+def parse_standardized_variants(file_path):
+ emoji_set = set()
+ text_set = set()
+ with open(file_path) as datafile:
+ for line in datafile:
+ if '#' in line:
+ line = line[:line.index('#')]
+ line = line.strip()
+ if not line:
+ continue
+ sequence, description, _ = line.split(';')
+ sequence = sequence.strip().split(' ')
+ base = int(sequence[0], 16)
+ vs = int(sequence[1], 16)
+ description = description.strip()
+ if description == 'text style':
+ text_set.add((base, vs))
+ elif description == 'emoji style':
+ emoji_set.add((base, vs))
+ return text_set, emoji_set
+
+
+def parse_ucd(ucd_path):
+ global _emoji_properties, _chars_by_age
+ global _text_variation_sequences, _emoji_variation_sequences
+ global _emoji_sequences, _emoji_zwj_sequences
+ _emoji_properties = parse_unicode_datafile(
+ path.join(ucd_path, 'emoji-data.txt'), reverse=True)
+ _chars_by_age = parse_unicode_datafile(
+ path.join(ucd_path, 'DerivedAge.txt'), reverse=True)
+ sequences = parse_standardized_variants(
+ path.join(ucd_path, 'StandardizedVariants.txt'))
+ _text_variation_sequences, _emoji_variation_sequences = sequences
+ _emoji_sequences = parse_unicode_datafile(
+ path.join(ucd_path, 'emoji-sequences.txt'))
+ _emoji_zwj_sequences = parse_unicode_datafile(
+ path.join(ucd_path, 'emoji-zwj-sequences.txt'))
+
+ # filter modern pentathlon, as it seems likely to be removed from final spec
+ # also filter rifle
+ def is_excluded(n):
+ return n in [0x1f93b, 0x1f946]
+
+ def contains_excluded(t):
+ if type(t) == int:
+ return is_excluded(t)
+ return any(is_excluded(cp) for cp in t)
+
+ # filter modern pentathlon, as it seems likely to be removed from final spec
+ _emoji_properties['Emoji'] = set(
+ t for t in _emoji_properties['Emoji'] if not contains_excluded(t))
+ _emoji_sequences = dict(
+ (t, v) for (t, v) in _emoji_sequences.items() if not contains_excluded(t))
+
+def flag_sequence(territory_code):
+ return tuple(0x1F1E6 + ord(ch) - ord('A') for ch in territory_code)
+
+
+UNSUPPORTED_FLAGS = frozenset({
+ flag_sequence('BL'), flag_sequence('BQ'), flag_sequence('DG'),
+ flag_sequence('EA'), flag_sequence('EH'), flag_sequence('FK'),
+ flag_sequence('GF'), flag_sequence('GP'), flag_sequence('GS'),
+ flag_sequence('MF'), flag_sequence('MQ'), flag_sequence('NC'),
+ flag_sequence('PM'), flag_sequence('RE'), flag_sequence('TF'),
+ flag_sequence('WF'), flag_sequence('XK'), flag_sequence('YT'),
+})
+
+EQUIVALENT_FLAGS = {
+ flag_sequence('BV'): flag_sequence('NO'),
+ flag_sequence('CP'): flag_sequence('FR'),
+ flag_sequence('HM'): flag_sequence('AU'),
+ flag_sequence('SJ'): flag_sequence('NO'),
+ flag_sequence('UM'): flag_sequence('US'),
+}
+
+COMBINING_KEYCAP = 0x20E3
+
+LEGACY_ANDROID_EMOJI = {
+ 0xFE4E5: flag_sequence('JP'),
+ 0xFE4E6: flag_sequence('US'),
+ 0xFE4E7: flag_sequence('FR'),
+ 0xFE4E8: flag_sequence('DE'),
+ 0xFE4E9: flag_sequence('IT'),
+ 0xFE4EA: flag_sequence('GB'),
+ 0xFE4EB: flag_sequence('ES'),
+ 0xFE4EC: flag_sequence('RU'),
+ 0xFE4ED: flag_sequence('CN'),
+ 0xFE4EE: flag_sequence('KR'),
+ 0xFE82C: (ord('#'), COMBINING_KEYCAP),
+ 0xFE82E: (ord('1'), COMBINING_KEYCAP),
+ 0xFE82F: (ord('2'), COMBINING_KEYCAP),
+ 0xFE830: (ord('3'), COMBINING_KEYCAP),
+ 0xFE831: (ord('4'), COMBINING_KEYCAP),
+ 0xFE832: (ord('5'), COMBINING_KEYCAP),
+ 0xFE833: (ord('6'), COMBINING_KEYCAP),
+ 0xFE834: (ord('7'), COMBINING_KEYCAP),
+ 0xFE835: (ord('8'), COMBINING_KEYCAP),
+ 0xFE836: (ord('9'), COMBINING_KEYCAP),
+ 0xFE837: (ord('0'), COMBINING_KEYCAP),
+}
+
+ZWJ_IDENTICALS = {
+ # KISS
+ (0x1F469, 0x200D, 0x2764, 0x200D, 0x1F48B, 0x200D, 0x1F468): 0x1F48F,
+ # COUPLE WITH HEART
+ (0x1F469, 0x200D, 0x2764, 0x200D, 0x1F468): 0x1F491,
+ # FAMILY
+ (0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F466): 0x1F46A,
+}
+
+def compute_expected_emoji():
+ equivalent_emoji = {}
+ sequence_pieces = set()
+ all_sequences = set()
+ all_sequences.update(_emoji_variation_sequences)
+
+ for sequence in _emoji_sequences.keys():
+ sequence = tuple(ch for ch in sequence if ch != EMOJI_VS)
+ all_sequences.add(sequence)
+ sequence_pieces.update(sequence)
+
+ for sequence in _emoji_zwj_sequences.keys():
+ sequence = tuple(ch for ch in sequence if ch != EMOJI_VS)
+ all_sequences.add(sequence)
+ sequence_pieces.update(sequence)
+ # Add reverse of all emoji ZWJ sequences, which are added to the fonts
+ # as a workaround to get the sequences work in RTL text.
+ reversed_seq = tuple(reversed(sequence))
+ all_sequences.add(reversed_seq)
+ equivalent_emoji[reversed_seq] = sequence
+
+ # Add all two-letter flag sequences, as even the unsupported ones should
+ # resolve to a flag tofu.
+ all_letters = [chr(code) for code in range(ord('A'), ord('Z')+1)]
+ all_two_letter_codes = itertools.product(all_letters, repeat=2)
+ all_flags = {flag_sequence(code) for code in all_two_letter_codes}
+ all_sequences.update(all_flags)
+ tofu_flags = UNSUPPORTED_FLAGS | (all_flags - set(_emoji_sequences.keys()))
+
+ all_emoji = (
+ _emoji_properties['Emoji'] |
+ all_sequences |
+ sequence_pieces |
+ set(LEGACY_ANDROID_EMOJI.keys()))
+ default_emoji = (
+ _emoji_properties['Emoji_Presentation'] |
+ all_sequences |
+ set(LEGACY_ANDROID_EMOJI.keys()))
+
+ first_tofu_flag = sorted(tofu_flags)[0]
+ for flag in tofu_flags:
+ if flag != first_tofu_flag:
+ equivalent_emoji[flag] = first_tofu_flag
+ equivalent_emoji.update(EQUIVALENT_FLAGS)
+ equivalent_emoji.update(LEGACY_ANDROID_EMOJI)
+ equivalent_emoji.update(ZWJ_IDENTICALS)
+ for seq in _emoji_variation_sequences:
+ equivalent_emoji[seq] = seq[0]
+
+ return all_emoji, default_emoji, equivalent_emoji
+
+
+def main():
+ target_out = sys.argv[1]
+ global _fonts_dir
+ _fonts_dir = path.join(target_out, 'fonts')
+
+ fonts_xml_path = path.join(target_out, 'etc', 'fonts.xml')
+ parse_fonts_xml(fonts_xml_path)
+
+ hyphens_dir = path.join(target_out, 'usr', 'hyphen-data')
+ check_hyphens(hyphens_dir)
+
+ check_emoji = sys.argv[2]
+ if check_emoji == 'true':
+ ucd_path = sys.argv[3]
+ parse_ucd(ucd_path)
+ all_emoji, default_emoji, equivalent_emoji = compute_expected_emoji()
+ check_emoji_coverage(all_emoji, equivalent_emoji)
+ check_emoji_defaults(default_emoji)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/layoutlib/.idea/codeStyleSettings.xml b/tools/layoutlib/.idea/codeStyleSettings.xml
index 89f7b341fb8b..ac90d1e540be 100644
--- a/tools/layoutlib/.idea/codeStyleSettings.xml
+++ b/tools/layoutlib/.idea/codeStyleSettings.xml
@@ -40,6 +40,7 @@
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="JAVA">
+ <option name="KEEP_LINE_BREAKS" value="false" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
@@ -55,6 +56,7 @@
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
+ <option name="WRAP_LONG_LINES" value="true" />
<arrangement>
<groups>
<group>
diff --git a/tools/layoutlib/.idea/compiler.xml b/tools/layoutlib/.idea/compiler.xml
index 5aaaf18f9d94..35961a2896ac 100644
--- a/tools/layoutlib/.idea/compiler.xml
+++ b/tools/layoutlib/.idea/compiler.xml
@@ -21,7 +21,5 @@
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
- <bytecodeTargetLevel target="1.6" />
</component>
-</project>
-
+</project> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
index 5bb3e3e47922..3681f2aaf3f1 100644
--- a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
+++ b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="UTF-8"?>
<component name="InspectionProjectProfileManager">
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
@@ -8,6 +7,15 @@
<option name="CHECK_TRY_CATCH_SECTION" value="true" />
<option name="CHECK_METHOD_BODY" value="true" />
</inspection_tool>
+ <inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
+ <option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
+ <option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
+ </inspection_tool>
<inspection_tool class="ToArrayCallWithZeroLengthArrayArgument" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="false" />
+ <option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="false" />
+ <option name="SUGGEST_PRIVATE_FOR_INNERS" value="true" />
+ </inspection_tool>
</profile>
</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/junit.xml b/tools/layoutlib/.idea/libraries/junit.xml
new file mode 100644
index 000000000000..c889f5ff6c97
--- /dev/null
+++ b/tools/layoutlib/.idea/libraries/junit.xml
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+ <library name="junit">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/junit_intermediates/javalib.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="file://$PROJECT_DIR$/../../../../external/junit/src" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/misc.xml b/tools/layoutlib/.idea/misc.xml
index b474bdc00013..44b47f2c91d4 100644
--- a/tools/layoutlib/.idea/misc.xml
+++ b/tools/layoutlib/.idea/misc.xml
@@ -37,7 +37,7 @@
</value>
</option>
</component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml b/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
index 4f0eb8dc23a4..b402849f22a3 100644
--- a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
+++ b/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
@@ -26,4 +26,4 @@
<ConfigurationWrapper RunnerId="Run" />
<method />
</configuration>
-</component> \ No newline at end of file
+</component>
diff --git a/tools/layoutlib/.idea/runConfigurations/Create.xml b/tools/layoutlib/.idea/runConfigurations/Create.xml
index 58f057ac7289..536a23fdfeba 100644
--- a/tools/layoutlib/.idea/runConfigurations/Create.xml
+++ b/tools/layoutlib/.idea/runConfigurations/Create.xml
@@ -2,8 +2,8 @@
<configuration default="false" name="Create" type="Application" factoryName="Application" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="com.android.tools.layoutlib.create.Main" />
- <option name="VM_PARAMETERS" value="" />
- <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/icu4j-icudata-jarjar_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/icu4j-icutzdata-jarjar_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar" />
+ <option name="VM_PARAMETERS" value="-ea" />
+ <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icudata-host-jarjar_intermediates/classes-jarjar.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icutzdata-host-jarjar_intermediates/classes-jarjar.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/../../../../" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" value="" />
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 61ddb04bc08c..f87f6c53c8dc 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -16,8 +16,6 @@
LOCAL_PATH := $(my-dir)
include $(CLEAR_VARS)
-LOCAL_JAVACFLAGS := -source 6 -target 6
-
#
# Define rules to build temp_layoutlib.jar, which contains a subset of
# the classes in framework.jar. The layoutlib_create tool is used to
@@ -30,6 +28,9 @@ LOCAL_JAVACFLAGS := -source 6 -target 6
built_framework_dep := $(call java-lib-deps,framework)
built_framework_classes := $(call java-lib-files,framework)
+built_oj_dep := $(call java-lib-deps,core-oj)
+built_oj_classes := $(call java-lib-files,core-oj)
+
built_core_dep := $(call java-lib-deps,core-libart)
built_core_classes := $(call java-lib-files,core-libart)
@@ -37,10 +38,8 @@ built_ext_dep := $(call java-lib-deps,ext)
built_ext_classes := $(call java-lib-files,ext)
built_ext_data := $(call intermediates-dir-for, \
JAVA_LIBRARIES,ext,,COMMON)/javalib.jar
-built_icudata_dep := $(call java-lib-deps,icu4j-icudata-jarjar)
-built_icudata_data := $(call java-lib-files,icu4j-icudata-jarjar)
-built_icutzdata_dep := $(call java-lib-deps,icu4j-icutzdata-jarjar)
-built_icutzdata_data := $(call java-lib-files,icu4j-icutzdata-jarjar)
+built_icudata_dep := $(call java-lib-deps,icu4j-icudata-host-jarjar,HOST)
+built_icutzdata_dep := $(call java-lib-deps,icu4j-icutzdata-host-jarjar,HOST)
built_layoutlib_create_jar := $(call intermediates-dir-for, \
JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar
@@ -56,7 +55,8 @@ LOCAL_BUILT_MODULE_STEM := javalib.jar
include $(BUILD_SYSTEM)/base_rules.mk
#######################################
-$(LOCAL_BUILT_MODULE): $(built_core_dep) \
+$(LOCAL_BUILT_MODULE): $(built_oj_dep) \
+ $(built_core_dep) \
$(built_framework_dep) \
$(built_ext_dep) \
$(built_ext_data) \
@@ -69,11 +69,12 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \
$(hide) ls -l $(built_framework_classes)
$(hide) java -ea -jar $(built_layoutlib_create_jar) \
$@ \
+ $(built_oj_classes) \
$(built_core_classes) \
$(built_framework_classes) \
$(built_ext_classes) \
- $(built_icudata_data) \
- $(built_icutzdata_data) \
+ $(built_icudata_dep) \
+ $(built_icutzdata_dep) \
$(built_ext_data)
$(hide) ls -l $(built_framework_classes)
diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk
index 0dbdd5627e63..3dd8002bcff5 100644
--- a/tools/layoutlib/bridge/Android.mk
+++ b/tools/layoutlib/bridge/Android.mk
@@ -18,8 +18,6 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_JAVA_RESOURCE_DIRS := resources
-LOCAL_JAVACFLAGS := -source 6 -target 6
-
LOCAL_JAVA_LIBRARIES := \
layoutlib_api-prebuilt \
diff --git a/tools/layoutlib/bridge/bridge.iml b/tools/layoutlib/bridge/bridge.iml
index ccc10b325b77..57d08cb22c7e 100644
--- a/tools/layoutlib/bridge/bridge.iml
+++ b/tools/layoutlib/bridge/bridge.iml
@@ -84,6 +84,6 @@
</SOURCES>
</library>
</orderEntry>
- <orderEntry type="library" scope="TEST" name="JUnit4" level="application" />
+ <orderEntry type="library" scope="TEST" name="junit" level="project" />
</component>
</module> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index ef681d2c9d26..d0e431acadff 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -25,15 +25,9 @@ import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.internal.util.XmlUtils;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.Nullable;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
@@ -43,7 +37,6 @@ import android.util.TypedValue;
import android.view.LayoutInflater_Delegate;
import android.view.ViewGroup.LayoutParams;
-import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
@@ -306,76 +299,22 @@ public final class BridgeTypedArray extends TypedArray {
return defValue;
}
- /**
- * Retrieve the ColorStateList for the attribute at <var>index</var>.
- * The value may be either a single solid color or a reference to
- * a color or complex {@link android.content.res.ColorStateList} description.
- *
- * @param index Index of attribute to retrieve.
- *
- * @return ColorStateList for the attribute, or null if not defined.
- */
@Override
public ColorStateList getColorStateList(int index) {
if (!hasValue(index)) {
return null;
}
- ResourceValue resValue = mResourceData[index];
- String value = resValue.getValue();
-
- if (value == null) {
- return null;
- }
-
-
- try {
- XmlPullParser parser = null;
- Boolean psiParserSupport = mContext.getLayoutlibCallback().getFlag(
- RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
- if (psiParserSupport != null && psiParserSupport) {
- // Get the state list file content from callback to parse PSI file
- parser = mContext.getLayoutlibCallback().getXmlFileParser(value);
- }
- if (parser == null) {
- // If used with a version of Android Studio that does not implement getXmlFileParser
- // fall back to reading the file from disk
- File f = new File(value);
- if (f.isFile()) {
- parser = ParserFactory.create(f);
- }
- }
- if (parser != null) {
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- parser, mContext, resValue.isFramework());
- try {
- return ColorStateList.createFromXml(mContext.getResources(), blockParser,
- mContext.getTheme());
- } finally {
- blockParser.ensurePopped();
- }
- }
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value, e, null);
- return null;
- } catch (Exception e) {
- // this is an error and not warning since the file existence is checked before
- // attempting to parse it.
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed to parse file " + value, e, null);
+ return ResourceHelper.getColorStateList(mResourceData[index], mContext);
+ }
+ @Override
+ public ComplexColor getComplexColor(int index) {
+ if (!hasValue(index)) {
return null;
}
- try {
- int color = ResourceHelper.getColor(value);
- return ColorStateList.valueOf(color);
- } catch (NumberFormatException e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null);
- }
-
- return null;
+ return ResourceHelper.getComplexColor(mResourceData[index], mContext);
}
/**
diff --git a/tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java b/tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java
new file mode 100644
index 000000000000..09c0260196cf
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources.Theme;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+
+/**
+ * Class that provides access to the {@link GradientColor#createFromXmlInner(Resources,
+ * XmlPullParser, AttributeSet, Theme)} and {@link ColorStateList#createFromXmlInner(Resources,
+ * XmlPullParser, AttributeSet, Theme)} methods
+ */
+public class ComplexColor_Accessor {
+ public static GradientColor createGradientColorFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws IOException, XmlPullParserException {
+ return GradientColor.createFromXmlInner(r, parser, attrs, theme);
+ }
+
+ public static ColorStateList createColorStateListFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws IOException, XmlPullParserException {
+ return ColorStateList.createFromXmlInner(r, parser, attrs, theme);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index 6e8e42ff1073..ea320c701c24 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.ArrayResourceValue;
import com.android.ide.common.rendering.api.DensityBasedResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
@@ -84,7 +85,7 @@ public class Resources_Delegate {
return new BridgeTypedArray(resources, resources.mContext, numEntries, platformFile);
}
- private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
+ private static Pair<ResourceType, String> getResourceInfo(Resources resources, int id,
boolean[] platformResFlag_out) {
// first get the String related to this id in the framework
Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
@@ -97,11 +98,7 @@ public class Resources_Delegate {
if (resourceInfo != null) {
platformResFlag_out[0] = true;
- String attributeName = resourceInfo.getSecond();
-
- return Pair.of(attributeName,
- resources.mContext.getRenderResources().getFrameworkResource(
- resourceInfo.getFirst(), attributeName));
+ return resourceInfo;
}
// didn't find a match in the framework? look in the project.
@@ -110,13 +107,24 @@ public class Resources_Delegate {
if (resourceInfo != null) {
platformResFlag_out[0] = false;
- String attributeName = resourceInfo.getSecond();
-
- return Pair.of(attributeName,
- resources.mContext.getRenderResources().getProjectResource(
- resourceInfo.getFirst(), attributeName));
+ return resourceInfo;
}
}
+ return null;
+ }
+
+ private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
+ boolean[] platformResFlag_out) {
+ Pair<ResourceType, String> resourceInfo =
+ getResourceInfo(resources, id, platformResFlag_out);
+
+ if (resourceInfo != null) {
+ String attributeName = resourceInfo.getSecond();
+ RenderResources renderResources = resources.mContext.getRenderResources();
+ return Pair.of(attributeName, platformResFlag_out[0] ?
+ renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) :
+ renderResources.getProjectResource(resourceInfo.getFirst(), attributeName));
+ }
return null;
}
@@ -609,7 +617,6 @@ public class Resources_Delegate {
if (value != null) {
ResourceValue resValue = value.getSecond();
- assert resValue != null;
if (resValue != null) {
String v = resValue.getValue();
if (v != null) {
@@ -627,17 +634,57 @@ public class Resources_Delegate {
@LayoutlibDelegate
static String getResourceEntryName(Resources resources, int resid) throws NotFoundException {
- throw new UnsupportedOperationException();
+ Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
+ if (resourceInfo != null) {
+ return resourceInfo.getSecond();
+ }
+ throwException(resid, null);
+ return null;
+
}
@LayoutlibDelegate
static String getResourceName(Resources resources, int resid) throws NotFoundException {
- throw new UnsupportedOperationException();
+ boolean[] platformOut = new boolean[1];
+ Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
+ String packageName;
+ if (resourceInfo != null) {
+ if (platformOut[0]) {
+ packageName = SdkConstants.ANDROID_NS_NAME;
+ } else {
+ packageName = resources.mContext.getPackageName();
+ packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName;
+ }
+ return packageName + ':' + resourceInfo.getFirst().getName() + '/' +
+ resourceInfo.getSecond();
+ }
+ throwException(resid, null);
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getResourcePackageName(Resources resources, int resid) throws NotFoundException {
+ boolean[] platformOut = new boolean[1];
+ Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
+ if (resourceInfo != null) {
+ if (platformOut[0]) {
+ return SdkConstants.ANDROID_NS_NAME;
+ }
+ String packageName = resources.mContext.getPackageName();
+ return packageName == null ? SdkConstants.APP_PREFIX : packageName;
+ }
+ throwException(resid, null);
+ return null;
}
@LayoutlibDelegate
static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
- throw new UnsupportedOperationException();
+ Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
+ if (resourceInfo != null) {
+ return resourceInfo.getFirst().getName();
+ }
+ throwException(resid, null);
+ return null;
}
@LayoutlibDelegate
@@ -850,22 +897,17 @@ public class Resources_Delegate {
* @throws NotFoundException
*/
private static void throwException(Resources resources, int id) throws NotFoundException {
- // first get the String related to this id in the framework
- Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
-
- // if the name is unknown in the framework, get it from the custom view loader.
- if (resourceInfo == null && resources.mLayoutlibCallback != null) {
- resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id);
- }
+ throwException(id, getResourceInfo(resources, id, new boolean[1]));
+ }
+ private static void throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo) {
String message;
if (resourceInfo != null) {
message = String.format(
"Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
resourceInfo.getFirst(), id, resourceInfo.getSecond());
} else {
- message = String.format(
- "Could not resolve resource value: 0x%1$X.", id);
+ message = String.format("Could not resolve resource value: 0x%1$X.", id);
}
throw new NotFoundException(message);
diff --git a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java
deleted file mode 100644
index 34ae825baac8..000000000000
--- a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Composite;
-
-/**
- * Delegate implementing the native methods of android.graphics.AvoidXfermode
- *
- * Through the layoutlib_create tool, the original native methods of AvoidXfermode have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original AvoidXfermode class.
- *
- * Because this extends {@link Xfermode_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
- * {@link Xfermode_Delegate}.
- *
- */
-public class AvoidXfermode_Delegate extends Xfermode_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public Composite getComposite(int alpha) {
- // FIXME
- return null;
- }
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Avoid Xfermodes are not supported in Layout Preview mode.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(int opColor, int tolerance, int nativeMode) {
- AvoidXfermode_Delegate newDelegate = new AvoidXfermode_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index 07376828a1fe..f1da3a266448 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -17,9 +17,14 @@
package android.graphics;
import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.resources.Density;
+import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.Nullable;
@@ -38,6 +43,7 @@ import java.util.EnumSet;
import java.util.Set;
import javax.imageio.ImageIO;
+import libcore.util.NativeAllocationRegistry_Delegate;
/**
* Delegate implementing the native methods of android.graphics.Bitmap
@@ -61,13 +67,14 @@ public final class Bitmap_Delegate {
// ---- delegate manager ----
private static final DelegateManager<Bitmap_Delegate> sManager =
- new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class);
+ new DelegateManager<>(Bitmap_Delegate.class);
+ private static long sFinalizer = -1;
// ---- delegate helper data ----
// ---- delegate data ----
private final Config mConfig;
- private BufferedImage mImage;
+ private final BufferedImage mImage;
private boolean mHasAlpha = true;
private boolean mHasMipMap = false; // TODO: check the default.
private boolean mIsPremultiplied = true;
@@ -114,10 +121,25 @@ public final class Bitmap_Delegate {
* @see Bitmap#isMutable()
* @see Bitmap#getDensity()
*/
- public static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
+ private static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
Density density) throws IOException {
// create a delegate with the content of the file.
- Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
+ BufferedImage image = ImageIO.read(input);
+ if (image == null && input.exists()) {
+ // There was a problem decoding the image, or the decoder isn't registered. Webp maybe.
+ // Replace with a broken image icon.
+ BridgeContext currentContext = RenderAction.getCurrentContext();
+ if (currentContext != null) {
+ RenderResources resources = currentContext.getRenderResources();
+ ResourceValue broken = resources.getFrameworkResource(ResourceType.DRAWABLE,
+ "ic_menu_report_image");
+ File brokenFile = new File(broken.getValue());
+ if (brokenFile.exists()) {
+ image = ImageIO.read(brokenFile);
+ }
+ }
+ }
+ Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
return createBitmap(delegate, createFlags, density.getDpiValue());
}
@@ -281,13 +303,25 @@ public final class Bitmap_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeDestructor(long nativeBitmap) {
- sManager.removeJavaReferenceFor(nativeBitmap);
+ /*package*/ static Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) {
+ // Unused method; no implementation provided.
+ assert false;
+ return null;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nativeGetNativeFinalizer() {
+ synchronized (Bitmap_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+ }
+ return sFinalizer;
+ }
}
@LayoutlibDelegate
/*package*/ static boolean nativeRecycle(long nativeBitmap) {
- sManager.removeJavaReferenceFor(nativeBitmap);
+ // In our case reycle() is a no-op. We will let the finalizer to dispose the bitmap.
return true;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index ba0d399ce52f..fa880f0710c4 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.layoutlib.bridge.impl.PorterDuffUtility;
+import com.android.ninepatch.NinePatchChunk;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.Nullable;
@@ -38,6 +39,8 @@ import java.awt.geom.Arc2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
/**
* Delegate implementing the native methods of android.graphics.Canvas
@@ -57,6 +60,7 @@ public final class Canvas_Delegate {
// ---- delegate manager ----
private static final DelegateManager<Canvas_Delegate> sManager =
new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class);
+ private static long sFinalizer = -1;
// ---- delegate helper data ----
@@ -138,7 +142,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setBitmap(long canvas, Bitmap bitmap) {
+ public static void native_setBitmap(long canvas, Bitmap bitmap) {
Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
if (canvasDelegate == null || bitmapDelegate==null) {
@@ -149,7 +153,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_isOpaque(long nativeCanvas) {
+ public static boolean native_isOpaque(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -160,7 +164,10 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_getWidth(long nativeCanvas) {
+ public static void native_setHighContrastText(long nativeCanvas, boolean highContrastText){}
+
+ @LayoutlibDelegate
+ public static int native_getWidth(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -171,7 +178,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_getHeight(long nativeCanvas) {
+ public static int native_getHeight(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -182,7 +189,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_save(long nativeCanvas, int saveFlags) {
+ public static int native_save(long nativeCanvas, int saveFlags) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -193,7 +200,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_saveLayer(long nativeCanvas, float l,
+ public static int native_saveLayer(long nativeCanvas, float l,
float t, float r, float b,
long paint, int layerFlags) {
// get the delegate from the native int.
@@ -212,7 +219,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_saveLayerAlpha(long nativeCanvas, float l,
+ public static int native_saveLayerAlpha(long nativeCanvas, float l,
float t, float r, float b,
int alpha, int layerFlags) {
// get the delegate from the native int.
@@ -225,7 +232,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_restore(long nativeCanvas, boolean throwOnUnderflow) {
+ public static void native_restore(long nativeCanvas, boolean throwOnUnderflow) {
// FIXME: implement throwOnUnderflow.
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
@@ -237,7 +244,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_restoreToCount(long nativeCanvas, int saveCount,
+ public static void native_restoreToCount(long nativeCanvas, int saveCount,
boolean throwOnUnderflow) {
// FIXME: implement throwOnUnderflow.
// get the delegate from the native int.
@@ -250,7 +257,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_getSaveCount(long nativeCanvas) {
+ public static int native_getSaveCount(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -261,7 +268,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_translate(long nativeCanvas, float dx, float dy) {
+ public static void native_translate(long nativeCanvas, float dx, float dy) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -272,7 +279,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_scale(long nativeCanvas, float sx, float sy) {
+ public static void native_scale(long nativeCanvas, float sx, float sy) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -283,7 +290,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_rotate(long nativeCanvas, float degrees) {
+ public static void native_rotate(long nativeCanvas, float degrees) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -294,7 +301,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_skew(long nativeCanvas, float kx, float ky) {
+ public static void native_skew(long nativeCanvas, float kx, float ky) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -318,7 +325,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_concat(long nCanvas, long nMatrix) {
+ public static void native_concat(long nCanvas, long nMatrix) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
@@ -346,7 +353,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setMatrix(long nCanvas, long nMatrix) {
+ public static void native_setMatrix(long nCanvas, long nMatrix) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
@@ -376,7 +383,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_clipRect(long nCanvas,
+ public static boolean native_clipRect(long nCanvas,
float left, float top,
float right, float bottom,
int regionOp) {
@@ -390,7 +397,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_clipPath(long nativeCanvas,
+ public static boolean native_clipPath(long nativeCanvas,
long nativePath,
int regionOp) {
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
@@ -407,7 +414,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_clipRegion(long nativeCanvas,
+ public static boolean native_clipRegion(long nativeCanvas,
long nativeRegion,
int regionOp) {
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
@@ -424,7 +431,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeSetDrawFilter(long nativeCanvas, long nativeFilter) {
+ public static void nativeSetDrawFilter(long nativeCanvas, long nativeFilter) {
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
@@ -439,7 +446,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_getClipBounds(long nativeCanvas,
+ public static boolean native_getClipBounds(long nativeCanvas,
Rect bounds) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
@@ -460,7 +467,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_getCTM(long canvas, long matrix) {
+ public static void native_getCTM(long canvas, long matrix) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
if (canvasDelegate == null) {
@@ -477,13 +484,13 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_quickReject(long nativeCanvas, long path) {
+ public static boolean native_quickReject(long nativeCanvas, long path) {
// FIXME properly implement quickReject
return false;
}
@LayoutlibDelegate
- /*package*/ static boolean native_quickReject(long nativeCanvas,
+ public static boolean native_quickReject(long nativeCanvas,
float left, float top,
float right, float bottom) {
// FIXME properly implement quickReject
@@ -491,7 +498,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawColor(long nativeCanvas, final int color, final int mode) {
+ public static void native_drawColor(long nativeCanvas, final int color, final int mode) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -522,14 +529,14 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawPaint(long nativeCanvas, long paint) {
+ public static void native_drawPaint(long nativeCanvas, long paint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawPaint is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
- /*package*/ static void native_drawPoint(long nativeCanvas, float x, float y,
+ public static void native_drawPoint(long nativeCanvas, float x, float y,
long nativePaint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -537,7 +544,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawPoints(long nativeCanvas, float[] pts, int offset, int count,
+ public static void native_drawPoints(long nativeCanvas, float[] pts, int offset, int count,
long nativePaint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -545,7 +552,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawLine(long nativeCanvas,
+ public static void native_drawLine(long nativeCanvas,
final float startX, final float startY, final float stopX, final float stopY,
long paint) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
@@ -558,7 +565,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawLines(long nativeCanvas,
+ public static void native_drawLines(long nativeCanvas,
final float[] pts, final int offset, final int count,
long nativePaint) {
draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
@@ -574,7 +581,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawRect(long nativeCanvas,
+ public static void native_drawRect(long nativeCanvas,
final float left, final float top, final float right, final float bottom, long paint) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
@@ -600,7 +607,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawOval(long nativeCanvas, final float left,
+ public static void native_drawOval(long nativeCanvas, final float left,
final float top, final float right, final float bottom, long paint) {
if (right > left && bottom > top) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
@@ -627,7 +634,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawCircle(long nativeCanvas,
+ public static void native_drawCircle(long nativeCanvas,
float cx, float cy, float radius, long paint) {
native_drawOval(nativeCanvas,
cx - radius, cy - radius, cx + radius, cy + radius,
@@ -635,7 +642,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawArc(long nativeCanvas,
+ public static void native_drawArc(long nativeCanvas,
final float left, final float top, final float right, final float bottom,
final float startAngle, final float sweep,
final boolean useCenter, long paint) {
@@ -667,7 +674,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawRoundRect(long nativeCanvas,
+ public static void native_drawRoundRect(long nativeCanvas,
final float left, final float top, final float right, final float bottom,
final float rx, final float ry, long paint) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
@@ -697,7 +704,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawPath(long nativeCanvas, long path, long paint) {
+ public static void native_drawPath(long nativeCanvas, long path, long paint) {
final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
if (pathDelegate == null) {
return;
@@ -749,7 +756,62 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
+ public static void native_drawRegion(long nativeCanvas, long nativeRegion,
+ long nativePaint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Some canvas paths may not be drawn", null, null);
+ }
+
+ @LayoutlibDelegate
+ public static void native_drawNinePatch(Canvas thisCanvas, long nativeCanvas,
+ long nativeBitmap, long ninePatch, final float dstLeft, final float dstTop,
+ final float dstRight, final float dstBottom, long nativePaintOrZero,
+ final int screenDensity, final int bitmapDensity) {
+
+ // get the delegate from the native int.
+ final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
+ if (bitmapDelegate == null) {
+ return;
+ }
+
+ byte[] c = NinePatch_Delegate.getChunk(ninePatch);
+ if (c == null) {
+ // not a 9-patch?
+ BufferedImage image = bitmapDelegate.getImage();
+ drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
+ image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
+ (int) dstBottom);
+ return;
+ }
+
+ final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
+ assert chunkObject != null;
+ if (chunkObject == null) {
+ return;
+ }
+
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ // this one can be null
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
+
+ canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paint) {
+ chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
+ (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
+ bitmapDensity);
+ }
+ }, paintDelegate, true, false);
+
+ }
+
+ @LayoutlibDelegate
+ public static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
float left, float top,
long nativePaintOrZero,
int canvasDensity,
@@ -771,7 +833,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
+ public static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
float srcLeft, float srcTop, float srcRight, float srcBottom,
float dstLeft, float dstTop, float dstRight, float dstBottom,
long nativePaintOrZero, int screenDensity, int bitmapDensity) {
@@ -787,7 +849,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawBitmap(long nativeCanvas, int[] colors,
+ public static void native_drawBitmap(long nativeCanvas, int[] colors,
int offset, int stride, final float x,
final float y, int width, int height,
boolean hasAlpha,
@@ -812,7 +874,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
+ public static void nativeDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
long nMatrix, long nPaint) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
@@ -853,7 +915,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap,
+ public static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap,
int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
int colorOffset, long nPaint) {
// FIXME
@@ -862,7 +924,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeDrawVertices(long nCanvas, int mode, int n,
+ public static void nativeDrawVertices(long nCanvas, int mode, int n,
float[] verts, int vertOffset,
float[] texs, int texOffset,
int[] colors, int colorOffset,
@@ -874,14 +936,14 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawText(long nativeCanvas, char[] text, int index, int count,
+ public static void native_drawText(long nativeCanvas, char[] text, int index, int count,
float startX, float startY, int flags, long paint, long typeface) {
drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0,
paint, typeface);
}
@LayoutlibDelegate
- /*package*/ static void native_drawText(long nativeCanvas, String text,
+ public static void native_drawText(long nativeCanvas, String text,
int start, int end, float x, float y, final int flags, long paint,
long typeface) {
int count = end - start;
@@ -892,7 +954,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawTextRun(long nativeCanvas, String text,
+ public static void native_drawTextRun(long nativeCanvas, String text,
int start, int end, int contextStart, int contextEnd,
float x, float y, boolean isRtl, long paint, long typeface) {
int count = end - start;
@@ -903,14 +965,14 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawTextRun(long nativeCanvas, char[] text,
+ public static void native_drawTextRun(long nativeCanvas, char[] text,
int start, int count, int contextStart, int contextCount,
float x, float y, boolean isRtl, long paint, long typeface) {
drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface);
}
@LayoutlibDelegate
- /*package*/ static void native_drawTextOnPath(long nativeCanvas,
+ public static void native_drawTextOnPath(long nativeCanvas,
char[] text, int index,
int count, long path,
float hOffset,
@@ -922,7 +984,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawTextOnPath(long nativeCanvas,
+ public static void native_drawTextOnPath(long nativeCanvas,
String text, long path,
float hOffset,
float vOffset,
@@ -934,20 +996,21 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void finalizer(long nativeCanvas) {
- // get the delegate from the native int so that it can be disposed.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
+ /*package*/ static long getNativeFinalizer() {
+ synchronized (Canvas_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
+ Canvas_Delegate delegate = sManager.getDelegate(nativePtr);
+ if (delegate != null) {
+ delegate.dispose();
+ }
+ sManager.removeJavaReferenceFor(nativePtr);
+ });
+ }
}
-
- canvasDelegate.dispose();
-
- // remove it from the manager.
- sManager.removeJavaReferenceFor(nativeCanvas);
+ return sFinalizer;
}
-
// ---- Private delegate/helper methods ----
/**
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index c7b24bcb352d..50efc7f7db86 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -33,13 +33,13 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
@@ -73,7 +73,7 @@ public class FontFamily_Delegate {
private static final Map<String, FontInfo> sCache =
new LinkedHashMap<String, FontInfo>(CACHE_SIZE) {
@Override
- protected boolean removeEldestEntry(Entry<String, FontInfo> eldest) {
+ protected boolean removeEldestEntry(Map.Entry<String, FontInfo> eldest) {
return size() > CACHE_SIZE;
}
@@ -213,7 +213,7 @@ public class FontFamily_Delegate {
return mValid;
}
- /*package*/ static Font loadFont(String path) {
+ private static Font loadFont(String path) {
if (path.startsWith(SYSTEM_FONTS) ) {
String relativePath = path.substring(SYSTEM_FONTS.length());
File f = new File(sFontLocation, relativePath);
@@ -245,6 +245,13 @@ public class FontFamily_Delegate {
return sFontLocation;
}
+ // ---- delegate methods ----
+ @LayoutlibDelegate
+ /*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex) {
+ final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mNativePtr);
+ return delegate != null && delegate.addFont(path, ttcIndex);
+ }
+
// ---- native methods ----
@LayoutlibDelegate
@@ -270,35 +277,25 @@ public class FontFamily_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFont(long nativeFamily, final String path) {
- final FontFamily_Delegate delegate = getDelegate(nativeFamily);
- if (delegate != null) {
- if (sFontLocation == null) {
- delegate.mPostInitRunnables.add(new Runnable() {
- @Override
- public void run() {
- delegate.addFont(path);
- }
- });
- return true;
- }
- return delegate.addFont(path);
- }
+ /*package*/ static boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex) {
+ assert false : "The only client of this method has been overriden.";
return false;
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, final String path,
- final int weight, final boolean isItalic) {
+ /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
+ int ttcIndex, List<FontListParser.Axis> listOfAxis,
+ int weight, boolean isItalic) {
+ assert false : "The only client of this method has been overriden.";
+ return false;
+ }
+
+ static boolean addFont(long nativeFamily, final String path, final int weight,
+ final boolean isItalic) {
final FontFamily_Delegate delegate = getDelegate(nativeFamily);
if (delegate != null) {
if (sFontLocation == null) {
- delegate.mPostInitRunnables.add(new Runnable() {
- @Override
- public void run() {
- delegate.addFont(path, weight, isItalic);
- }
- });
+ delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic));
return true;
}
return delegate.addFont(path, weight, isItalic);
@@ -309,6 +306,9 @@ public class FontFamily_Delegate {
@LayoutlibDelegate
/*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily);
+ if (ffd == null) {
+ return false;
+ }
ffd.mValid = true;
if (mgr == null) {
return false;
@@ -389,6 +389,15 @@ public class FontFamily_Delegate {
mPostInitRunnables = null;
}
+ private boolean addFont(final String path, int ttcIndex) {
+ // FIXME: support ttc fonts. Hack JRE??
+ if (sFontLocation == null) {
+ mPostInitRunnables.add(() -> addFont(path));
+ return true;
+ }
+ return addFont(path);
+ }
+
private boolean addFont(@NonNull String path) {
return addFont(path, DEFAULT_FONT_WEIGHT, path.endsWith(FONT_SUFFIX_ITALIC));
}
@@ -452,6 +461,7 @@ public class FontFamily_Delegate {
private FontInfo deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
int desiredWeight = outFont.mWeight;
int srcWeight = srcFont.mWeight;
+ assert srcFont.mFont != null;
Font derivedFont = srcFont.mFont;
// Embolden the font if required.
if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
index e8d34d0562aa..1f0eb3bab55b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -169,77 +169,18 @@ public final class NinePatch_Delegate {
sManager.removeJavaReferenceFor(chunk);
}
- @LayoutlibDelegate
- /*package*/ static void nativeDraw(long canvas_instance, RectF loc, Bitmap bitmap_instance,
- long chunk, long paint_instance_or_null, int destDensity, int srcDensity) {
- draw(canvas_instance,
- (int) loc.left, (int) loc.top, (int) loc.right, (int) loc.bottom,
- bitmap_instance, chunk, paint_instance_or_null,
- destDensity, srcDensity);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeDraw(long canvas_instance, Rect loc, Bitmap bitmap_instance,
- long chunk, long paint_instance_or_null, int destDensity, int srcDensity) {
- draw(canvas_instance,
- loc.left, loc.top, loc.right, loc.bottom,
- bitmap_instance, chunk, paint_instance_or_null,
- destDensity, srcDensity);
- }
@LayoutlibDelegate
/*package*/ static long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location) {
return 0;
}
- // ---- Private Helper methods ----
-
- private static void draw(long canvas_instance,
- final int left, final int top, final int right, final int bottom,
- Bitmap bitmap_instance, long chunk, long paint_instance_or_null,
- final int destDensity, final int srcDensity) {
- // get the delegate from the native int.
- final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
- if (bitmap_delegate == null) {
- return;
- }
-
- byte[] c = null;
- NinePatch_Delegate delegate = sManager.getDelegate(chunk);
+ static byte[] getChunk(long nativeNinePatch) {
+ NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch);
if (delegate != null) {
- c = delegate.chunk;
- }
- if (c == null) {
- // not a 9-patch?
- BufferedImage image = bitmap_delegate.getImage();
- Canvas_Delegate.native_drawBitmap(null, canvas_instance, bitmap_instance,
- 0f, 0f, (float)image.getWidth(), (float)image.getHeight(),
- (float)left, (float)top, (float)right, (float)bottom,
- paint_instance_or_null, destDensity, srcDensity);
- return;
+ return delegate.chunk;
}
+ return null;
+ }
- final NinePatchChunk chunkObject = getChunk(c);
- assert chunkObject != null;
- if (chunkObject == null) {
- return;
- }
-
- Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance);
- if (canvas_delegate == null) {
- return;
- }
-
- // this one can be null
- Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);
-
- canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paint) {
- chunkObject.draw(bitmap_delegate.getImage(), graphics,
- left, top, right - left, bottom - top, destDensity, srcDensity);
- }
- }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/);
-
- }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index dbd45c4f68be..33296e1abdc9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -39,6 +39,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
/**
* Delegate implementing the native methods of android.graphics.Paint
*
@@ -65,6 +67,7 @@ public class Paint_Delegate {
// ---- delegate manager ----
private static final DelegateManager<Paint_Delegate> sManager =
new DelegateManager<Paint_Delegate>(Paint_Delegate.class);
+ private static long sFinalizer = -1;
// ---- delegate helper data ----
@@ -220,6 +223,14 @@ public class Paint_Delegate {
return mColorFilter;
}
+ public void setColorFilter(long colorFilterPtr) {
+ mColorFilter = ColorFilter_Delegate.getDelegate(colorFilterPtr);
+ }
+
+ public void setShader(long shaderPtr) {
+ mShader = Shader_Delegate.getDelegate(shaderPtr);
+ }
+
/**
* Returns the {@link Shader} delegate or null if none have been set
*
@@ -250,9 +261,9 @@ public class Paint_Delegate {
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static int getFlags(Paint thisPaint) {
+ /*package*/ static int nGetFlags(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -263,9 +274,9 @@ public class Paint_Delegate {
@LayoutlibDelegate
- /*package*/ static void setFlags(Paint thisPaint, int flags) {
+ /*package*/ static void nSetFlags(Paint thisPaint, long nativePaint, int flags) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -274,14 +285,14 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setFilterBitmap(Paint thisPaint, boolean filter) {
- setFlag(thisPaint, Paint.FILTER_BITMAP_FLAG, filter);
+ /*package*/ static void nSetFilterBitmap(Paint thisPaint, long nativePaint, boolean filter) {
+ setFlag(nativePaint, Paint.FILTER_BITMAP_FLAG, filter);
}
@LayoutlibDelegate
- /*package*/ static int getHinting(Paint thisPaint) {
+ /*package*/ static int nGetHinting(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return Paint.HINTING_ON;
}
@@ -290,9 +301,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setHinting(Paint thisPaint, int mode) {
+ /*package*/ static void nSetHinting(Paint thisPaint, long nativePaint, int mode) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -301,44 +312,48 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setAntiAlias(Paint thisPaint, boolean aa) {
- setFlag(thisPaint, Paint.ANTI_ALIAS_FLAG, aa);
+ /*package*/ static void nSetAntiAlias(Paint thisPaint, long nativePaint, boolean aa) {
+ setFlag(nativePaint, Paint.ANTI_ALIAS_FLAG, aa);
}
@LayoutlibDelegate
- /*package*/ static void setSubpixelText(Paint thisPaint, boolean subpixelText) {
- setFlag(thisPaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
+ /*package*/ static void nSetSubpixelText(Paint thisPaint, long nativePaint,
+ boolean subpixelText) {
+ setFlag(nativePaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
}
@LayoutlibDelegate
- /*package*/ static void setUnderlineText(Paint thisPaint, boolean underlineText) {
- setFlag(thisPaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
+ /*package*/ static void nSetUnderlineText(Paint thisPaint, long nativePaint,
+ boolean underlineText) {
+ setFlag(nativePaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
}
@LayoutlibDelegate
- /*package*/ static void setStrikeThruText(Paint thisPaint, boolean strikeThruText) {
- setFlag(thisPaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
+ /*package*/ static void nSetStrikeThruText(Paint thisPaint, long nativePaint,
+ boolean strikeThruText) {
+ setFlag(nativePaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
}
@LayoutlibDelegate
- /*package*/ static void setFakeBoldText(Paint thisPaint, boolean fakeBoldText) {
- setFlag(thisPaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
+ /*package*/ static void nSetFakeBoldText(Paint thisPaint, long nativePaint,
+ boolean fakeBoldText) {
+ setFlag(nativePaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
}
@LayoutlibDelegate
- /*package*/ static void setDither(Paint thisPaint, boolean dither) {
- setFlag(thisPaint, Paint.DITHER_FLAG, dither);
+ /*package*/ static void nSetDither(Paint thisPaint, long nativePaint, boolean dither) {
+ setFlag(nativePaint, Paint.DITHER_FLAG, dither);
}
@LayoutlibDelegate
- /*package*/ static void setLinearText(Paint thisPaint, boolean linearText) {
- setFlag(thisPaint, Paint.LINEAR_TEXT_FLAG, linearText);
+ /*package*/ static void nSetLinearText(Paint thisPaint, long nativePaint, boolean linearText) {
+ setFlag(nativePaint, Paint.LINEAR_TEXT_FLAG, linearText);
}
@LayoutlibDelegate
- /*package*/ static int getColor(Paint thisPaint) {
+ /*package*/ static int nGetColor(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -347,9 +362,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setColor(Paint thisPaint, int color) {
+ /*package*/ static void nSetColor(Paint thisPaint, long nativePaint, int color) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -358,9 +373,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int getAlpha(Paint thisPaint) {
+ /*package*/ static int nGetAlpha(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -369,9 +384,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setAlpha(Paint thisPaint, int a) {
+ /*package*/ static void nSetAlpha(Paint thisPaint, long nativePaint, int a) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -380,9 +395,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float getStrokeWidth(Paint thisPaint) {
+ /*package*/ static float nGetStrokeWidth(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -391,9 +406,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setStrokeWidth(Paint thisPaint, float width) {
+ /*package*/ static void nSetStrokeWidth(Paint thisPaint, long nativePaint, float width) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -402,9 +417,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float getStrokeMiter(Paint thisPaint) {
+ /*package*/ static float nGetStrokeMiter(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -413,9 +428,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) {
+ /*package*/ static void nSetStrokeMiter(Paint thisPaint, long nativePaint, float miter) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -424,7 +439,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setShadowLayer(long paint, float radius, float dx, float dy,
+ /*package*/ static void nSetShadowLayer(long paint, float radius, float dx, float dy,
int color) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -432,7 +447,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_hasShadowLayer(long paint) {
+ /*package*/ static boolean nHasShadowLayer(long paint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Paint.hasShadowLayer is not supported.", null, null /*data*/);
@@ -440,16 +455,17 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean isElegantTextHeight(Paint thisPaint) {
+ /*package*/ static boolean nIsElegantTextHeight(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT;
}
@LayoutlibDelegate
- /*package*/ static void setElegantTextHeight(Paint thisPaint, boolean elegant) {
+ /*package*/ static void nSetElegantTextHeight(Paint thisPaint, long nativePaint,
+ boolean elegant) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -458,9 +474,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float getTextSize(Paint thisPaint) {
+ /*package*/ static float nGetTextSize(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -469,9 +485,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setTextSize(Paint thisPaint, float textSize) {
+ /*package*/ static void nSetTextSize(Paint thisPaint, long nativePaint, float textSize) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -483,9 +499,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float getTextScaleX(Paint thisPaint) {
+ /*package*/ static float nGetTextScaleX(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -494,9 +510,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) {
+ /*package*/ static void nSetTextScaleX(Paint thisPaint, long nativePaint, float scaleX) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -508,9 +524,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float getTextSkewX(Paint thisPaint) {
+ /*package*/ static float nGetTextSkewX(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -519,9 +535,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) {
+ /*package*/ static void nSetTextSkewX(Paint thisPaint, long nativePaint, float skewX) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -533,9 +549,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float ascent(Paint thisPaint) {
+ /*package*/ static float nAscent(Paint thisPaint, long nativePaint, long nativeTypeface) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -550,9 +566,9 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float descent(Paint thisPaint) {
+ /*package*/ static float nDescent(Paint thisPaint, long nativePaint, long nativeTypeface) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -567,9 +583,10 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) {
+ /*package*/ static float nGetFontMetrics(Paint thisPaint, long nativePaint, long nativeTypeface,
+ FontMetrics metrics) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -578,9 +595,10 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) {
+ /*package*/ static int nGetFontMetricsInt(Paint thisPaint, long nativePaint,
+ long nativeTypeface, FontMetricsInt fmi) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -603,31 +621,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index,
- int count, int bidiFlags) {
- // get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
- if (delegate == null) {
- return 0;
- }
-
- RectF bounds = delegate.measureText(text, index, count, null, 0, bidiFlags);
- return bounds.right - bounds.left;
- }
-
- @LayoutlibDelegate
- /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end,
- int bidiFlags) {
- return native_measureText(thisPaint, text.toCharArray(), start, end - start, bidiFlags);
- }
-
- @LayoutlibDelegate
- /*package*/ static float native_measureText(Paint thisPaint, String text, int bidiFlags) {
- return native_measureText(thisPaint, text.toCharArray(), 0, text.length(), bidiFlags);
- }
-
- @LayoutlibDelegate
- /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, char[] text,
+ /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, char[] text,
int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) {
// get the delegate
@@ -669,21 +663,21 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, String text,
+ /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, String text,
boolean measureForwards,
float maxWidth, int bidiFlags, float[] measuredWidth) {
- return native_breakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(),
+ return nBreakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(),
maxWidth, bidiFlags, measuredWidth);
}
@LayoutlibDelegate
- /*package*/ static long native_init() {
+ /*package*/ static long nInit() {
Paint_Delegate newDelegate = new Paint_Delegate();
return sManager.addNewDelegate(newDelegate);
}
@LayoutlibDelegate
- /*package*/ static long native_initWithPaint(long paint) {
+ /*package*/ static long nInitWithPaint(long paint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(paint);
if (delegate == null) {
@@ -695,7 +689,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_reset(long native_object) {
+ /*package*/ static void nReset(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -706,7 +700,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_set(long native_dst, long native_src) {
+ /*package*/ static void nSet(long native_dst, long native_src) {
// get the delegate from the native int.
Paint_Delegate delegate_dst = sManager.getDelegate(native_dst);
if (delegate_dst == null) {
@@ -723,7 +717,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_getStyle(long native_object) {
+ /*package*/ static int nGetStyle(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -734,7 +728,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setStyle(long native_object, int style) {
+ /*package*/ static void nSetStyle(long native_object, int style) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -745,7 +739,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_getStrokeCap(long native_object) {
+ /*package*/ static int nGetStrokeCap(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -756,7 +750,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setStrokeCap(long native_object, int cap) {
+ /*package*/ static void nSetStrokeCap(long native_object, int cap) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -767,7 +761,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_getStrokeJoin(long native_object) {
+ /*package*/ static int nGetStrokeJoin(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -778,7 +772,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setStrokeJoin(long native_object, int join) {
+ /*package*/ static void nSetStrokeJoin(long native_object, int join) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -789,7 +783,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_getFillPath(long native_object, long src, long dst) {
+ /*package*/ static boolean nGetFillPath(long native_object, long src, long dst) {
Paint_Delegate paint = sManager.getDelegate(native_object);
if (paint == null) {
return false;
@@ -815,7 +809,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long native_setShader(long native_object, long shader) {
+ /*package*/ static long nSetShader(long native_object, long shader) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -828,7 +822,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long native_setColorFilter(long native_object, long filter) {
+ /*package*/ static long nSetColorFilter(long native_object, long filter) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -847,7 +841,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long native_setXfermode(long native_object, long xfermode) {
+ /*package*/ static long nSetXfermode(long native_object, long xfermode) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -860,7 +854,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long native_setPathEffect(long native_object, long effect) {
+ /*package*/ static long nSetPathEffect(long native_object, long effect) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -873,7 +867,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long native_setMaskFilter(long native_object, long maskfilter) {
+ /*package*/ static long nSetMaskFilter(long native_object, long maskfilter) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -892,7 +886,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long native_setTypeface(long native_object, long typeface) {
+ /*package*/ static long nSetTypeface(long native_object, long typeface) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -909,7 +903,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long native_setRasterizer(long native_object, long rasterizer) {
+ /*package*/ static long nSetRasterizer(long native_object, long rasterizer) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -928,7 +922,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_getTextAlign(long native_object) {
+ /*package*/ static int nGetTextAlign(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -939,7 +933,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setTextAlign(long native_object, int align) {
+ /*package*/ static void nSetTextAlign(long native_object, int align) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -950,58 +944,27 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setTextLocale(long native_object, String locale) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return;
- }
-
- delegate.setTextLocale(locale);
- }
-
- @LayoutlibDelegate
- /*package*/ static int native_getTextWidths(long native_object, long native_typeface,
- char[] text, int index, int count, int bidiFlags, float[] widths) {
-
- if (widths != null) {
- for (int i = 0; i< count; i++) {
- widths[i]=0;
- }
- }
+ /*package*/ static int nSetTextLocales(long native_object, String locale) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
return 0;
}
- // native_typeface is passed here since Framework's old implementation did not have the
- // typeface object associated with the Paint. Since, we follow the new framework way,
- // we store the typeface with the paint and use it directly.
- assert (native_typeface == delegate.mNativeTypeface);
-
- RectF bounds = delegate.measureText(text, index, count, widths, 0, bidiFlags);
- return ((int) (bounds.right - bounds.left));
- }
-
- @LayoutlibDelegate
- /*package*/ static int native_getTextWidths(long native_object, long native_typeface,
- String text, int start, int end, int bidiFlags, float[] widths) {
- return native_getTextWidths(native_object, native_typeface, text.toCharArray(), start,
- end - start, bidiFlags, widths);
+ delegate.setTextLocale(locale);
+ return 0;
}
@LayoutlibDelegate
- /* package */static int native_getTextGlyphs(long native_object, String text, int start,
- int end, int contextStart, int contextEnd, int flags, char[] glyphs) {
+ /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
+ int mMinikinLangListId) {
// FIXME
- return 0;
}
@LayoutlibDelegate
- /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface,
+ /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
char[] text, int index, int count, int contextIndex, int contextCount,
- boolean isRtl, float[] advances, int advancesIndex) {
+ int bidiFlags, float[] advances, int advancesIndex) {
if (advances != null)
for (int i = advancesIndex; i< advancesIndex+count; i++)
@@ -1017,25 +980,25 @@ public class Paint_Delegate {
// we store the typeface with the paint and use it directly.
assert (native_typeface == delegate.mNativeTypeface);
- RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, isRtl);
+ RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, bidiFlags);
return bounds.right - bounds.left;
}
@LayoutlibDelegate
- /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface,
+ /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
String text, int start, int end, int contextStart, int contextEnd,
- boolean isRtl, float[] advances, int advancesIndex) {
+ int bidiFlags, float[] advances, int advancesIndex) {
// FIXME: support contextStart and contextEnd
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
- return native_getTextRunAdvances(native_object, native_typeface, buffer, 0, count,
- contextStart, contextEnd - contextStart, isRtl, advances, advancesIndex);
+ return nGetTextAdvances(native_object, native_typeface, buffer, 0, count,
+ contextStart, contextEnd - contextStart, bidiFlags, advances, advancesIndex);
}
@LayoutlibDelegate
- /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, char[] text,
+ /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, char[] text,
int contextStart, int contextLength, int flags, int offset, int cursorOpt) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1044,7 +1007,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, String text,
+ /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, String text,
int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1053,7 +1016,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_getTextPath(long native_object, long native_typeface,
+ /*package*/ static void nGetTextPath(long native_object, long native_typeface,
int bidiFlags, char[] text, int index, int count, float x, float y, long path) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1061,7 +1024,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_getTextPath(long native_object, long native_typeface,
+ /*package*/ static void nGetTextPath(long native_object, long native_typeface,
int bidiFlags, String text, int start, int end, float x, float y, long path) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1069,14 +1032,14 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeGetStringBounds(long nativePaint, long native_typeface,
+ /*package*/ static void nGetStringBounds(long nativePaint, long native_typeface,
String text, int start, int end, int bidiFlags, Rect bounds) {
- nativeGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start,
+ nGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start,
end - start, bidiFlags, bounds);
}
@LayoutlibDelegate
- /*package*/ static void nativeGetCharArrayBounds(long nativePaint, long native_typeface,
+ /*package*/ static void nGetCharArrayBounds(long nativePaint, long native_typeface,
char[] text, int index, int count, int bidiFlags, Rect bounds) {
// get the delegate from the native int.
@@ -1092,12 +1055,18 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void finalizer(long nativePaint) {
- sManager.removeJavaReferenceFor(nativePaint);
+ /*package*/ static long nGetNativeFinalizer() {
+ synchronized (Paint_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
}
@LayoutlibDelegate
- /*package*/ static float native_getLetterSpacing(long nativePaint) {
+ /*package*/ static float nGetLetterSpacing(long nativePaint) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
@@ -1106,7 +1075,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setLetterSpacing(long nativePaint, float letterSpacing) {
+ /*package*/ static void nSetLetterSpacing(long nativePaint, float letterSpacing) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
"Paint.setLetterSpacing() not supported.", null, null);
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -1117,13 +1086,13 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setFontFeatureSettings(long nativePaint, String settings) {
+ /*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
"Paint.setFontFeatureSettings() not supported.", null, null);
}
@LayoutlibDelegate
- /*package*/ static int native_getHyphenEdit(long nativePaint) {
+ /*package*/ static int nGetHyphenEdit(long nativePaint) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
@@ -1132,7 +1101,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setHyphenEdit(long nativePaint, int hyphen) {
+ /*package*/ static void nSetHyphenEdit(long nativePaint, int hyphen) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
@@ -1141,7 +1110,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_hasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
+ /*package*/ static boolean nHasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
String string) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -1169,13 +1138,14 @@ public class Paint_Delegate {
@LayoutlibDelegate
- /*package*/ static float native_getRunAdvance(long nativePaint, long nativeTypeface,
+ /*package*/ static float nGetRunAdvance(long nativePaint, long nativeTypeface,
@NonNull char[] text, int start, int end, int contextStart, int contextEnd,
boolean isRtl, int offset) {
int count = end - start;
float[] advances = new float[count];
- native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count,
- contextStart, contextEnd - contextStart, isRtl, advances, 0);
+ int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+ nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
+ contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
int startOffset = offset - start; // offset from start.
float sum = 0;
for (int i = 0; i < startOffset; i++) {
@@ -1185,13 +1155,14 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int native_getOffsetForAdvance(long nativePaint, long nativeTypeface,
+ /*package*/ static int nGetOffsetForAdvance(long nativePaint, long nativeTypeface,
char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
float advance) {
int count = end - start;
float[] advances = new float[count];
- native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count,
- contextStart, contextEnd - contextStart, isRtl, advances, 0);
+ int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+ nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
+ contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
float sum = 0;
int i;
for (i = 0; i < count && sum < advance; i++) {
@@ -1359,9 +1330,9 @@ public class Paint_Delegate {
mLocale = new Locale(locale);
}
- private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) {
+ private static void setFlag(long nativePaint, int flagMask, boolean flagValue) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index a10ac00fc356..265ebd1755e3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -75,7 +75,7 @@ public final class Path_Delegate {
return sManager.getDelegate(nPath);
}
- public Shape getJavaShape() {
+ public Path2D getJavaShape() {
return mPath;
}
@@ -167,13 +167,13 @@ public final class Path_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setFillType(long nPath, int ft) {
+ public static void native_setFillType(long nPath, int ft) {
Path_Delegate pathDelegate = sManager.getDelegate(nPath);
if (pathDelegate == null) {
return;
}
- pathDelegate.mFillType = Path.sFillTypeArray[ft];
+ pathDelegate.setFillType(Path.sFillTypeArray[ft]);
}
@LayoutlibDelegate
@@ -423,21 +423,13 @@ public final class Path_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_offset(long nPath, float dx, float dy, long dst_path) {
+ /*package*/ static void native_offset(long nPath, float dx, float dy) {
Path_Delegate pathDelegate = sManager.getDelegate(nPath);
if (pathDelegate == null) {
return;
}
- // could be null if the int is 0;
- Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
-
- pathDelegate.offset(dx, dy, dstDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void native_offset(long nPath, float dx, float dy) {
- native_offset(nPath, dx, dy, 0);
+ pathDelegate.offset(dx, dy);
}
@LayoutlibDelegate
@@ -572,7 +564,7 @@ public final class Path_Delegate {
return null;
}
- private static void addPath(long destPath, long srcPath, AffineTransform transform) {
+ public static void addPath(long destPath, long srcPath, AffineTransform transform) {
Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
if (destPathDelegate == null) {
return;
@@ -630,7 +622,7 @@ public final class Path_Delegate {
* Fills the given {@link RectF} with the path bounds.
* @param bounds the RectF to be filled.
*/
- private void fillBounds(RectF bounds) {
+ public void fillBounds(RectF bounds) {
Rectangle2D rect = mPath.getBounds2D();
bounds.left = (float)rect.getMinX();
bounds.right = (float)rect.getMaxX();
@@ -644,7 +636,7 @@ public final class Path_Delegate {
* @param x The x-coordinate of the start of a new contour
* @param y The y-coordinate of the start of a new contour
*/
- private void moveTo(float x, float y) {
+ public void moveTo(float x, float y) {
mPath.moveTo(mLastX = x, mLastY = y);
}
@@ -658,7 +650,7 @@ public final class Path_Delegate {
* @param dy The amount to add to the y-coordinate of the end of the
* previous contour, to specify the start of a new contour
*/
- private void rMoveTo(float dx, float dy) {
+ public void rMoveTo(float dx, float dy) {
dx += mLastX;
dy += mLastY;
mPath.moveTo(mLastX = dx, mLastY = dy);
@@ -672,7 +664,7 @@ public final class Path_Delegate {
* @param x The x-coordinate of the end of a line
* @param y The y-coordinate of the end of a line
*/
- private void lineTo(float x, float y) {
+ public void lineTo(float x, float y) {
if (!hasPoints()) {
mPath.moveTo(mLastX = 0, mLastY = 0);
}
@@ -689,7 +681,7 @@ public final class Path_Delegate {
* @param dy The amount to add to the y-coordinate of the previous point on
* this contour, to specify a line
*/
- private void rLineTo(float dx, float dy) {
+ public void rLineTo(float dx, float dy) {
if (!hasPoints()) {
mPath.moveTo(mLastX = 0, mLastY = 0);
}
@@ -714,7 +706,7 @@ public final class Path_Delegate {
* @param x2 The x-coordinate of the end point on a quadratic curve
* @param y2 The y-coordinate of the end point on a quadratic curve
*/
- private void quadTo(float x1, float y1, float x2, float y2) {
+ public void quadTo(float x1, float y1, float x2, float y2) {
mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
}
@@ -732,7 +724,7 @@ public final class Path_Delegate {
* @param dy2 The amount to add to the y-coordinate of the last point on
* this contour, for the end point of a quadratic curve
*/
- private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
+ public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
if (!hasPoints()) {
mPath.moveTo(mLastX = 0, mLastY = 0);
}
@@ -755,7 +747,7 @@ public final class Path_Delegate {
* @param x3 The x-coordinate of the end point on a cubic curve
* @param y3 The y-coordinate of the end point on a cubic curve
*/
- private void cubicTo(float x1, float y1, float x2, float y2,
+ public void cubicTo(float x1, float y1, float x2, float y2,
float x3, float y3) {
if (!hasPoints()) {
mPath.moveTo(0, 0);
@@ -768,7 +760,7 @@ public final class Path_Delegate {
* current point on this contour. If there is no previous point, then a
* moveTo(0,0) is inserted automatically.
*/
- private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
+ public void rCubicTo(float dx1, float dy1, float dx2, float dy2,
float dx3, float dy3) {
if (!hasPoints()) {
mPath.moveTo(mLastX = 0, mLastY = 0);
@@ -798,7 +790,7 @@ public final class Path_Delegate {
* mod 360.
* @param forceMoveTo If true, always begin a new contour with the arc
*/
- private void arcTo(float left, float top, float right, float bottom, float startAngle,
+ public void arcTo(float left, float top, float right, float bottom, float startAngle,
float sweepAngle,
boolean forceMoveTo) {
Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle,
@@ -812,7 +804,7 @@ public final class Path_Delegate {
* Close the current contour. If the current point is not equal to the
* first point of the contour, a line segment is automatically added.
*/
- private void close() {
+ public void close() {
mPath.closePath();
}
@@ -831,7 +823,7 @@ public final class Path_Delegate {
* @param bottom The bottom of a rectangle to add to the path
* @param dir The direction to wind the rectangle's contour
*/
- private void addRect(float left, float top, float right, float bottom,
+ public void addRect(float left, float top, float right, float bottom,
int dir) {
moveTo(left, top);
@@ -860,21 +852,14 @@ public final class Path_Delegate {
*
* @param dx The amount in the X direction to offset the entire path
* @param dy The amount in the Y direction to offset the entire path
- * @param dst The translated path is written here. If this is null, then
- * the original path is modified.
*/
- public void offset(float dx, float dy, Path_Delegate dst) {
+ public void offset(float dx, float dy) {
GeneralPath newPath = new GeneralPath();
PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
newPath.append(iterator, false /*connect*/);
-
- if (dst != null) {
- dst.mPath = newPath;
- } else {
- mPath = newPath;
- }
+ mPath = newPath;
}
/**
diff --git a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java
deleted file mode 100644
index f27144f45448..000000000000
--- a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Composite;
-
-/**
- * Delegate implementing the native methods of android.graphics.PixelXorXfermode
- *
- * Through the layoutlib_create tool, the original native methods of PixelXorXfermode have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original PixelXorXfermode class.
- *
- * Because this extends {@link Xfermode_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
- * {@link Xfermode_Delegate}.
- *
- * @see Xfermode_Delegate
- */
-public class PixelXorXfermode_Delegate extends Xfermode_Delegate {
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public Composite getComposite(int alpha) {
- // FIXME
- return null;
- }
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Pixel XOR Xfermodes are not supported in Layout Preview mode.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(int opColor) {
- PixelXorXfermode_Delegate newDelegate = new PixelXorXfermode_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 85e65e690dc7..5cd34f6e000f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -24,8 +24,10 @@ import android.graphics.FontFamily_Delegate.FontVariant;
import java.awt.Font;
import java.io.File;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import static android.graphics.FontFamily_Delegate.getFontLocation;
@@ -205,6 +207,17 @@ public final class Typeface_Delegate {
return new File(getFontLocation());
}
+ @LayoutlibDelegate
+ /*package*/ static FontFamily makeFamilyFromParsed(FontListParser.Family family,
+ Map<String, ByteBuffer> bufferForPath) {
+ FontFamily fontFamily = new FontFamily(family.lang, family.variant);
+ for (FontListParser.Font font : family.fonts) {
+ FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.fontName, font.weight,
+ font.isItalic);
+ }
+ return fontFamily;
+ }
+
// ---- Private delegate/helper methods ----
private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
new file mode 100644
index 000000000000..200fe3b1d192
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License") {}
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper_Delegate;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
+import android.graphics.drawable.VectorDrawable_Delegate.VFullPath_Delegate;
+import android.graphics.drawable.VectorDrawable_Delegate.VGroup_Delegate;
+import android.graphics.drawable.VectorDrawable_Delegate.VNativeObject;
+import android.graphics.drawable.VectorDrawable_Delegate.VPathRenderer_Delegate;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link
+ * AnimatedVectorDrawable}
+ * <p>
+ * Through the layoutlib_create tool, the original methods of AnimatedVectorDrawable have been
+ * replaced by calls to methods of the same name in this delegate class.
+ */
+@SuppressWarnings("unused")
+public class AnimatedVectorDrawable_Delegate {
+ private static DelegateManager<AnimatorSetHolder> sAnimatorSets = new
+ DelegateManager<>(AnimatorSetHolder.class);
+ private static DelegateManager<PropertySetter> sHolders = new
+ DelegateManager<>(PropertySetter.class);
+
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreateAnimatorSet() {
+ return sAnimatorSets.addNewDelegate(new AnimatorSetHolder());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nAddAnimator(long setPtr, long propertyValuesHolder,
+ long nativeInterpolator, long startDelay, long duration, int repeatCount) {
+ PropertySetter holder = sHolders.getDelegate(propertyValuesHolder);
+ if (holder == null || holder.getValues() == null) {
+ return;
+ }
+
+ ObjectAnimator animator = new ObjectAnimator();
+ animator.setValues(holder.getValues());
+ animator.setInterpolator(
+ NativeInterpolatorFactoryHelper_Delegate.getDelegate(nativeInterpolator));
+ animator.setStartDelay(startDelay);
+ animator.setDuration(duration);
+ animator.setRepeatCount(repeatCount);
+ animator.setTarget(holder);
+ animator.setPropertyName(holder.getValues().getPropertyName());
+
+ AnimatorSetHolder set = sAnimatorSets.getDelegate(setPtr);
+ assert set != null;
+ set.addAnimator(animator);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue) {
+ VGroup_Delegate group = VNativeObject.getDelegate(nativePtr);
+ Consumer<Float> setter = group.getPropertySetter(propertyId);
+
+ return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue,
+ endValue));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
+ long endValuePtr) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "AnimatedVectorDrawable path " +
+ "animations are not supported.", null, null);
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
+ int startValue, int endValue) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr);
+ Consumer<Integer> setter = path.getIntPropertySetter(propertyId);
+
+ return sHolders.addNewDelegate(IntPropertySetter.of(setter, startValue,
+ endValue));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr);
+ Consumer<Float> setter = path.getFloatPropertySetter(propertyId);
+
+ return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue,
+ endValue));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
+ float endValue) {
+ VPathRenderer_Delegate renderer = VNativeObject.getDelegate(nativePtr);
+
+ return sHolders.addNewDelegate(FloatPropertySetter.of(renderer::setRootAlpha,
+ startValue,
+ endValue));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetPropertyHolderData(long nativePtr, float[] data, int length) {
+ PropertySetter setter = sHolders.getDelegate(nativePtr);
+ assert setter != null;
+
+ setter.setValues(data);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
+ AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+ assert animatorSet != null;
+
+ animatorSet.start();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
+ AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+ assert animatorSet != null;
+
+ animatorSet.reverse();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nEnd(long animatorSetPtr) {
+ AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+ assert animatorSet != null;
+
+ animatorSet.end();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nReset(long animatorSetPtr) {
+ AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+ assert animatorSet != null;
+
+ animatorSet.end();
+ animatorSet.start();
+ }
+
+ private static class AnimatorSetHolder {
+ private ArrayList<Animator> mAnimators = new ArrayList<>();
+ private AnimatorSet mAnimatorSet = null;
+
+ private void addAnimator(@NonNull Animator animator) {
+ mAnimators.add(animator);
+ }
+
+ private void ensureAnimatorSet() {
+ if (mAnimatorSet == null) {
+ mAnimatorSet = new AnimatorSet();
+ mAnimatorSet.playTogether(mAnimators);
+ }
+ }
+
+ private void start() {
+ ensureAnimatorSet();
+
+ mAnimatorSet.start();
+ }
+
+ private void end() {
+ mAnimatorSet.end();
+ }
+
+ private void reset() {
+ end();
+ start();
+ }
+
+ private void reverse() {
+ mAnimatorSet.reverse();
+ }
+ }
+
+ /**
+ * Class that allows setting a value and holds the range of values for the given property.
+ *
+ * @param <T> the type of the property
+ */
+ private static class PropertySetter<T> {
+ final Consumer<T> mValueSetter;
+ private PropertyValuesHolder mValues;
+
+ private PropertySetter(@NonNull Consumer<T> valueSetter) {
+ mValueSetter = valueSetter;
+ }
+
+ /**
+ * Method to set an {@link Integer} value for this property. The default implementation of
+ * this method doesn't do anything. This method is accessed via reflection by the
+ * PropertyValuesHolder.
+ */
+ public void setIntValue(Integer value) {
+ }
+
+ /**
+ * Method to set an {@link Integer} value for this property. The default implementation of
+ * this method doesn't do anything. This method is accessed via reflection by the
+ * PropertyValuesHolder.
+ */
+ public void setFloatValue(Float value) {
+ }
+
+ void setValues(float... values) {
+ mValues = PropertyValuesHolder.ofFloat("floatValue", values);
+ }
+
+ @Nullable
+ PropertyValuesHolder getValues() {
+ return mValues;
+ }
+
+ void setValues(int... values) {
+ mValues = PropertyValuesHolder.ofInt("intValue", values);
+ }
+ }
+
+ private static class IntPropertySetter extends PropertySetter<Integer> {
+ private IntPropertySetter(Consumer<Integer> valueSetter) {
+ super(valueSetter);
+ }
+
+ private static PropertySetter of(Consumer<Integer> valueSetter, int... values) {
+ PropertySetter setter = new IntPropertySetter(valueSetter);
+ setter.setValues(values);
+
+ return setter;
+ }
+
+ public void setIntValue(Integer value) {
+ mValueSetter.accept(value);
+ }
+ }
+
+ private static class FloatPropertySetter extends PropertySetter<Float> {
+ private FloatPropertySetter(Consumer<Float> valueSetter) {
+ super(valueSetter);
+ }
+
+ private static PropertySetter of(Consumer<Float> valueSetter, float... values) {
+ PropertySetter setter = new FloatPropertySetter(valueSetter);
+ setter.setValues(values);
+
+ return setter;
+ }
+
+ public void setFloatValue(Float value) {
+ mValueSetter.accept(value);
+ }
+
+ }
+}
diff --git a/tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
index f29c5c05fcac..3d78931a152c 100644
--- a/tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,19 +14,15 @@
* limitations under the License.
*/
-package libcore.util;
+package android.graphics.drawable;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import java.util.GregorianCalendar;
-
-/**
- * Delegate used to provide alternate implementation of select methods in {@link ZoneInfo.WallTime}
- */
-public class ZoneInfo_WallTime_Delegate {
+import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
+public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate {
@LayoutlibDelegate
- static GregorianCalendar createGregorianCalendar() {
- return new GregorianCalendar();
+ /*package*/ static boolean useLastSeenTarget(VectorDrawableAnimatorRT thisDrawableAnimator) {
+ return true;
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
new file mode 100644
index 000000000000..49f8691986be
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -0,0 +1,1213 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas_Delegate;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Cap;
+import android.graphics.Paint.Join;
+import android.graphics.Paint_Delegate;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.Path_Delegate;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.PathParser_Delegate;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+import static android.graphics.Canvas.CLIP_SAVE_FLAG;
+import static android.graphics.Canvas.MATRIX_SAVE_FLAG;
+import static android.graphics.Paint.Cap.BUTT;
+import static android.graphics.Paint.Cap.ROUND;
+import static android.graphics.Paint.Cap.SQUARE;
+import static android.graphics.Paint.Join.BEVEL;
+import static android.graphics.Paint.Join.MITER;
+import static android.graphics.Paint.Style;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link VectorDrawable}
+ * <p>
+ * Through the layoutlib_create tool, the original methods of VectorDrawable have been replaced by
+ * calls to methods of the same name in this delegate class.
+ */
+@SuppressWarnings("unused")
+public class VectorDrawable_Delegate {
+ private static final String LOGTAG = VectorDrawable_Delegate.class.getSimpleName();
+ private static final boolean DBG_VECTOR_DRAWABLE = false;
+
+ private static final DelegateManager<VNativeObject> sPathManager =
+ new DelegateManager<>(VNativeObject.class);
+
+ /**
+ * Obtains styled attributes from the theme, if available, or unstyled resources if the theme is
+ * null.
+ */
+ private static TypedArray obtainAttributes(
+ Resources res, Theme theme, AttributeSet set, int[] attrs) {
+ if (theme == null) {
+ return res.obtainAttributes(set, attrs);
+ }
+ return theme.obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ private static int applyAlpha(int color, float alpha) {
+ int alphaBytes = Color.alpha(color);
+ color &= 0x00FFFFFF;
+ color |= ((int) (alphaBytes * alpha)) << 24;
+ return color;
+ }
+
+ @LayoutlibDelegate
+ static long nCreateTree(long rootGroupPtr) {
+ VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr);
+ return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroup));
+ }
+
+ @LayoutlibDelegate
+ static void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
+ float viewportHeight) {
+ VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
+ nativePathRenderer.mViewportWidth = viewportWidth;
+ nativePathRenderer.mViewportHeight = viewportHeight;
+ }
+
+ @LayoutlibDelegate
+ static boolean nSetRootAlpha(long rendererPtr, float alpha) {
+ VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
+ nativePathRenderer.setRootAlpha(alpha);
+
+ return true;
+ }
+
+ @LayoutlibDelegate
+ static float nGetRootAlpha(long rendererPtr) {
+ VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
+
+ return nativePathRenderer.getRootAlpha();
+ }
+
+ @LayoutlibDelegate
+ static void nSetAllowCaching(long rendererPtr, boolean allowCaching) {
+ // ignored
+ }
+
+ @LayoutlibDelegate
+ static int nDraw(long rendererPtr, long canvasWrapperPtr,
+ long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache) {
+ VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
+
+ Canvas_Delegate.native_save(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+ Canvas_Delegate.native_translate(canvasWrapperPtr, bounds.left, bounds.top);
+
+ if (needsMirroring) {
+ Canvas_Delegate.native_translate(canvasWrapperPtr, bounds.width(), 0);
+ Canvas_Delegate.native_scale(canvasWrapperPtr, -1.0f, 1.0f);
+ }
+
+ // At this point, canvas has been translated to the right position.
+ // And we use this bound for the destination rect for the drawBitmap, so
+ // we offset to (0, 0);
+ bounds.offsetTo(0, 0);
+ nativePathRenderer.draw(canvasWrapperPtr, colorFilterPtr, bounds.width(), bounds.height());
+
+ Canvas_Delegate.native_restore(canvasWrapperPtr, true);
+
+ return bounds.width() * bounds.height();
+ }
+
+ @LayoutlibDelegate
+ static long nCreateFullPath() {
+ return sPathManager.addNewDelegate(new VFullPath_Delegate());
+ }
+
+ @LayoutlibDelegate
+ static long nCreateFullPath(long nativeFullPathPtr) {
+ VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr);
+
+ return sPathManager.addNewDelegate(new VFullPath_Delegate(original));
+ }
+
+ @LayoutlibDelegate
+ static boolean nGetFullPathProperties(long pathPtr, byte[] propertiesData,
+ int length) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+
+ ByteBuffer properties = ByteBuffer.wrap(propertiesData);
+ properties.order(ByteOrder.nativeOrder());
+
+ properties.putFloat(VFullPath_Delegate.STROKE_WIDTH_INDEX * 4, path.getStrokeWidth());
+ properties.putInt(VFullPath_Delegate.STROKE_COLOR_INDEX * 4, path.getStrokeColor());
+ properties.putFloat(VFullPath_Delegate.STROKE_ALPHA_INDEX * 4, path.getStrokeAlpha());
+ properties.putInt(VFullPath_Delegate.FILL_COLOR_INDEX * 4, path.getFillColor());
+ properties.putFloat(VFullPath_Delegate.FILL_ALPHA_INDEX * 4, path.getStrokeAlpha());
+ properties.putFloat(VFullPath_Delegate.TRIM_PATH_START_INDEX * 4, path.getTrimPathStart());
+ properties.putFloat(VFullPath_Delegate.TRIM_PATH_END_INDEX * 4, path.getTrimPathEnd());
+ properties.putFloat(VFullPath_Delegate.TRIM_PATH_OFFSET_INDEX * 4,
+ path.getTrimPathOffset());
+ properties.putInt(VFullPath_Delegate.STROKE_LINE_CAP_INDEX * 4, path.getStrokeLineCap());
+ properties.putInt(VFullPath_Delegate.STROKE_LINE_JOIN_INDEX * 4, path.getStrokeLineJoin());
+ properties.putFloat(VFullPath_Delegate.STROKE_MITER_LIMIT_INDEX * 4,
+ path.getStrokeMiterlimit());
+ properties.putInt(VFullPath_Delegate.FILL_TYPE_INDEX * 4, path.getFillType());
+
+ return true;
+ }
+
+ @LayoutlibDelegate
+ static void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
+ int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
+ float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
+ int strokeLineJoin, int fillType) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+
+ path.setStrokeWidth(strokeWidth);
+ path.setStrokeColor(strokeColor);
+ path.setStrokeAlpha(strokeAlpha);
+ path.setFillColor(fillColor);
+ path.setFillAlpha(fillAlpha);
+ path.setTrimPathStart(trimPathStart);
+ path.setTrimPathEnd(trimPathEnd);
+ path.setTrimPathOffset(trimPathOffset);
+ path.setStrokeMiterlimit(strokeMiterLimit);
+ path.setStrokeLineCap(strokeLineCap);
+ path.setStrokeLineJoin(strokeLineJoin);
+ path.setFillType(fillType);
+ }
+
+ @LayoutlibDelegate
+ static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+
+ path.setFillGradient(fillGradientPtr);
+ }
+
+ @LayoutlibDelegate
+ static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+
+ path.setStrokeGradient(strokeGradientPtr);
+ }
+
+ @LayoutlibDelegate
+ static long nCreateClipPath() {
+ return sPathManager.addNewDelegate(new VClipPath_Delegate());
+ }
+
+ @LayoutlibDelegate
+ static long nCreateClipPath(long clipPathPtr) {
+ VClipPath_Delegate original = VNativeObject.getDelegate(clipPathPtr);
+ return sPathManager.addNewDelegate(new VClipPath_Delegate(original));
+ }
+
+ @LayoutlibDelegate
+ static long nCreateGroup() {
+ return sPathManager.addNewDelegate(new VGroup_Delegate());
+ }
+
+ @LayoutlibDelegate
+ static long nCreateGroup(long groupPtr) {
+ VGroup_Delegate original = VNativeObject.getDelegate(groupPtr);
+ return sPathManager.addNewDelegate(
+ new VGroup_Delegate(original, new ArrayMap<String, Object>()));
+ }
+
+ @LayoutlibDelegate
+ static void nSetName(long nodePtr, String name) {
+ VNativeObject group = VNativeObject.getDelegate(nodePtr);
+ group.setName(name);
+ }
+
+ @LayoutlibDelegate
+ static boolean nGetGroupProperties(long groupPtr, float[] propertiesData,
+ int length) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+
+ FloatBuffer properties = FloatBuffer.wrap(propertiesData);
+
+ properties.put(VGroup_Delegate.ROTATE_INDEX, group.getRotation());
+ properties.put(VGroup_Delegate.PIVOT_X_INDEX, group.getPivotX());
+ properties.put(VGroup_Delegate.PIVOT_Y_INDEX, group.getPivotY());
+ properties.put(VGroup_Delegate.SCALE_X_INDEX, group.getScaleX());
+ properties.put(VGroup_Delegate.SCALE_Y_INDEX, group.getScaleY());
+ properties.put(VGroup_Delegate.TRANSLATE_X_INDEX, group.getTranslateX());
+ properties.put(VGroup_Delegate.TRANSLATE_Y_INDEX, group.getTranslateY());
+
+ return true;
+ }
+ @LayoutlibDelegate
+ static void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
+ float pivotY, float scaleX, float scaleY, float translateX, float translateY) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+
+ group.setRotation(rotate);
+ group.setPivotX(pivotX);
+ group.setPivotY(pivotY);
+ group.setScaleX(scaleX);
+ group.setScaleY(scaleY);
+ group.setTranslateX(translateX);
+ group.setTranslateY(translateY);
+ }
+
+ @LayoutlibDelegate
+ static void nAddChild(long groupPtr, long nodePtr) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ group.mChildren.add(VNativeObject.getDelegate(nodePtr));
+ }
+
+ @LayoutlibDelegate
+ static void nSetPathString(long pathPtr, String pathString, int length) {
+ VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setPathData(PathParser_Delegate.createNodesFromPathData(pathString));
+ }
+
+ /**
+ * The setters and getters below for paths and groups are here temporarily, and will be removed
+ * once the animation in AVD is replaced with RenderNodeAnimator, in which case the animation
+ * will modify these properties in native. By then no JNI hopping would be necessary for VD
+ * during animation, and these setters and getters will be obsolete.
+ */
+ // Setters and getters during animation.
+ @LayoutlibDelegate
+ static float nGetRotation(long groupPtr) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ return group.getRotation();
+ }
+
+ @LayoutlibDelegate
+ static void nSetRotation(long groupPtr, float rotation) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ group.setRotation(rotation);
+ }
+
+ @LayoutlibDelegate
+ static float nGetPivotX(long groupPtr) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ return group.getPivotX();
+ }
+
+ @LayoutlibDelegate
+ static void nSetPivotX(long groupPtr, float pivotX) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ group.setPivotX(pivotX);
+ }
+
+ @LayoutlibDelegate
+ static float nGetPivotY(long groupPtr) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ return group.getPivotY();
+ }
+
+ @LayoutlibDelegate
+ static void nSetPivotY(long groupPtr, float pivotY) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ group.setPivotY(pivotY);
+ }
+
+ @LayoutlibDelegate
+ static float nGetScaleX(long groupPtr) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ return group.getScaleX();
+ }
+
+ @LayoutlibDelegate
+ static void nSetScaleX(long groupPtr, float scaleX) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ group.setScaleX(scaleX);
+ }
+
+ @LayoutlibDelegate
+ static float nGetScaleY(long groupPtr) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ return group.getScaleY();
+ }
+
+ @LayoutlibDelegate
+ static void nSetScaleY(long groupPtr, float scaleY) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ group.setScaleY(scaleY);
+ }
+
+ @LayoutlibDelegate
+ static float nGetTranslateX(long groupPtr) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ return group.getTranslateX();
+ }
+
+ @LayoutlibDelegate
+ static void nSetTranslateX(long groupPtr, float translateX) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ group.setTranslateX(translateX);
+ }
+
+ @LayoutlibDelegate
+ static float nGetTranslateY(long groupPtr) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ return group.getTranslateY();
+ }
+
+ @LayoutlibDelegate
+ static void nSetTranslateY(long groupPtr, float translateY) {
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ group.setTranslateY(translateY);
+ }
+
+ @LayoutlibDelegate
+ static void nSetPathData(long pathPtr, long pathDataPtr) {
+ VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setPathData(PathParser_Delegate.getDelegate(pathDataPtr).getPathDataNodes());
+ }
+
+ @LayoutlibDelegate
+ static float nGetStrokeWidth(long pathPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ return path.getStrokeWidth();
+ }
+
+ @LayoutlibDelegate
+ static void nSetStrokeWidth(long pathPtr, float width) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setStrokeWidth(width);
+ }
+
+ @LayoutlibDelegate
+ static int nGetStrokeColor(long pathPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ return path.getStrokeColor();
+ }
+
+ @LayoutlibDelegate
+ static void nSetStrokeColor(long pathPtr, int strokeColor) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setStrokeColor(strokeColor);
+ }
+
+ @LayoutlibDelegate
+ static float nGetStrokeAlpha(long pathPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ return path.getStrokeAlpha();
+ }
+
+ @LayoutlibDelegate
+ static void nSetStrokeAlpha(long pathPtr, float alpha) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setStrokeAlpha(alpha);
+ }
+
+ @LayoutlibDelegate
+ static int nGetFillColor(long pathPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ return path.getFillColor();
+ }
+
+ @LayoutlibDelegate
+ static void nSetFillColor(long pathPtr, int fillColor) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setFillColor(fillColor);
+ }
+
+ @LayoutlibDelegate
+ static float nGetFillAlpha(long pathPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ return path.getFillAlpha();
+ }
+
+ @LayoutlibDelegate
+ static void nSetFillAlpha(long pathPtr, float fillAlpha) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setFillAlpha(fillAlpha);
+ }
+
+ @LayoutlibDelegate
+ static float nGetTrimPathStart(long pathPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ return path.getTrimPathStart();
+ }
+
+ @LayoutlibDelegate
+ static void nSetTrimPathStart(long pathPtr, float trimPathStart) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setTrimPathStart(trimPathStart);
+ }
+
+ @LayoutlibDelegate
+ static float nGetTrimPathEnd(long pathPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ return path.getTrimPathEnd();
+ }
+
+ @LayoutlibDelegate
+ static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setTrimPathEnd(trimPathEnd);
+ }
+
+ @LayoutlibDelegate
+ static float nGetTrimPathOffset(long pathPtr) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ return path.getTrimPathOffset();
+ }
+
+ @LayoutlibDelegate
+ static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+ path.setTrimPathOffset(trimPathOffset);
+ }
+
+ /**
+ * Base class for all the internal Delegates that does two functions:
+ * <ol>
+ * <li>Serves as base class to store all the delegates in one {@link DelegateManager}
+ * <li>Provides setName for all the classes. {@link VPathRenderer_Delegate} does actually
+ * not need it
+ * </ol>
+ */
+ interface VNativeObject {
+ @NonNull
+ static <T> T getDelegate(long nativePtr) {
+ //noinspection unchecked
+ T vNativeObject = (T) sPathManager.getDelegate(nativePtr);
+
+ assert vNativeObject != null;
+ return vNativeObject;
+ }
+
+ void setName(String name);
+ }
+
+ private static class VClipPath_Delegate extends VPath_Delegate {
+ private VClipPath_Delegate() {
+ // Empty constructor.
+ }
+
+ private VClipPath_Delegate(VClipPath_Delegate copy) {
+ super(copy);
+ }
+
+ @Override
+ public boolean isClipPath() {
+ return true;
+ }
+ }
+
+ static class VFullPath_Delegate extends VPath_Delegate {
+ // These constants need to be kept in sync with their values in VectorDrawable.VFullPath
+ private static final int STROKE_WIDTH_INDEX = 0;
+ private static final int STROKE_COLOR_INDEX = 1;
+ private static final int STROKE_ALPHA_INDEX = 2;
+ private static final int FILL_COLOR_INDEX = 3;
+ private static final int FILL_ALPHA_INDEX = 4;
+ private static final int TRIM_PATH_START_INDEX = 5;
+ private static final int TRIM_PATH_END_INDEX = 6;
+ private static final int TRIM_PATH_OFFSET_INDEX = 7;
+ private static final int STROKE_LINE_CAP_INDEX = 8;
+ private static final int STROKE_LINE_JOIN_INDEX = 9;
+ private static final int STROKE_MITER_LIMIT_INDEX = 10;
+ private static final int FILL_TYPE_INDEX = 11;
+
+ private static final int LINECAP_BUTT = 0;
+ private static final int LINECAP_ROUND = 1;
+ private static final int LINECAP_SQUARE = 2;
+
+ private static final int LINEJOIN_MITER = 0;
+ private static final int LINEJOIN_ROUND = 1;
+ private static final int LINEJOIN_BEVEL = 2;
+
+ @NonNull
+ public Consumer<Float> getFloatPropertySetter(int propertyIdx) {
+ switch (propertyIdx) {
+ case STROKE_ALPHA_INDEX:
+ return this::setStrokeAlpha;
+ case FILL_ALPHA_INDEX:
+ return this::setFillAlpha;
+ case TRIM_PATH_START_INDEX:
+ return this::setTrimPathStart;
+ case TRIM_PATH_END_INDEX:
+ return this::setTrimPathEnd;
+ case TRIM_PATH_OFFSET_INDEX:
+ return this::setTrimPathOffset;
+ }
+
+ throw new IllegalArgumentException("Invalid VFullPath_Delegate property index "
+ + propertyIdx);
+ }
+
+ @NonNull
+ public Consumer<Integer> getIntPropertySetter(int propertyIdx) {
+ switch (propertyIdx) {
+ case STROKE_COLOR_INDEX:
+ return this::setStrokeColor;
+ case FILL_COLOR_INDEX:
+ return this::setFillColor;
+ }
+
+ throw new IllegalArgumentException("Invalid VFullPath_Delegate property index "
+ + propertyIdx);
+ }
+
+ /////////////////////////////////////////////////////
+ // Variables below need to be copied (deep copy if applicable) for mutation.
+
+ int mStrokeColor = Color.TRANSPARENT;
+ float mStrokeWidth = 0;
+
+ int mFillColor = Color.TRANSPARENT;
+ long mStrokeGradient = 0;
+ long mFillGradient = 0;
+ float mStrokeAlpha = 1.0f;
+ float mFillAlpha = 1.0f;
+ float mTrimPathStart = 0;
+ float mTrimPathEnd = 1;
+ float mTrimPathOffset = 0;
+
+ Cap mStrokeLineCap = BUTT;
+ Join mStrokeLineJoin = MITER;
+ float mStrokeMiterlimit = 4;
+
+ int mFillType = 0; // WINDING(0) is the default value. See Path.FillType
+
+ private VFullPath_Delegate() {
+ // Empty constructor.
+ }
+
+ private VFullPath_Delegate(VFullPath_Delegate copy) {
+ super(copy);
+
+ mStrokeColor = copy.mStrokeColor;
+ mStrokeWidth = copy.mStrokeWidth;
+ mStrokeAlpha = copy.mStrokeAlpha;
+ mFillColor = copy.mFillColor;
+ mFillAlpha = copy.mFillAlpha;
+ mTrimPathStart = copy.mTrimPathStart;
+ mTrimPathEnd = copy.mTrimPathEnd;
+ mTrimPathOffset = copy.mTrimPathOffset;
+
+ mStrokeLineCap = copy.mStrokeLineCap;
+ mStrokeLineJoin = copy.mStrokeLineJoin;
+ mStrokeMiterlimit = copy.mStrokeMiterlimit;
+
+ mStrokeGradient = copy.mStrokeGradient;
+ mFillGradient = copy.mFillGradient;
+ mFillType = copy.mFillType;
+ }
+
+ private int getStrokeLineCap() {
+ switch (mStrokeLineCap) {
+ case BUTT:
+ return LINECAP_BUTT;
+ case ROUND:
+ return LINECAP_ROUND;
+ case SQUARE:
+ return LINECAP_SQUARE;
+ default:
+ assert false;
+ }
+
+ return -1;
+ }
+
+ private void setStrokeLineCap(int cap) {
+ switch (cap) {
+ case LINECAP_BUTT:
+ mStrokeLineCap = BUTT;
+ break;
+ case LINECAP_ROUND:
+ mStrokeLineCap = ROUND;
+ break;
+ case LINECAP_SQUARE:
+ mStrokeLineCap = SQUARE;
+ break;
+ default:
+ assert false;
+ }
+ }
+
+ private int getStrokeLineJoin() {
+ switch (mStrokeLineJoin) {
+ case MITER:
+ return LINEJOIN_MITER;
+ case ROUND:
+ return LINEJOIN_ROUND;
+ case BEVEL:
+ return LINEJOIN_BEVEL;
+ default:
+ assert false;
+ }
+
+ return -1;
+ }
+
+ private void setStrokeLineJoin(int join) {
+ switch (join) {
+ case LINEJOIN_BEVEL:
+ mStrokeLineJoin = BEVEL;
+ break;
+ case LINEJOIN_MITER:
+ mStrokeLineJoin = MITER;
+ break;
+ case LINEJOIN_ROUND:
+ mStrokeLineJoin = Join.ROUND;
+ break;
+ default:
+ assert false;
+ }
+ }
+
+ private int getStrokeColor() {
+ return mStrokeColor;
+ }
+
+ private void setStrokeColor(int strokeColor) {
+ mStrokeColor = strokeColor;
+ }
+
+ private float getStrokeWidth() {
+ return mStrokeWidth;
+ }
+
+ private void setStrokeWidth(float strokeWidth) {
+ mStrokeWidth = strokeWidth;
+ }
+
+ private float getStrokeAlpha() {
+ return mStrokeAlpha;
+ }
+
+ private void setStrokeAlpha(float strokeAlpha) {
+ mStrokeAlpha = strokeAlpha;
+ }
+
+ private int getFillColor() {
+ return mFillColor;
+ }
+
+ private void setFillColor(int fillColor) {
+ mFillColor = fillColor;
+ }
+
+ private float getFillAlpha() {
+ return mFillAlpha;
+ }
+
+ private void setFillAlpha(float fillAlpha) {
+ mFillAlpha = fillAlpha;
+ }
+
+ private float getTrimPathStart() {
+ return mTrimPathStart;
+ }
+
+ private void setTrimPathStart(float trimPathStart) {
+ mTrimPathStart = trimPathStart;
+ }
+
+ private float getTrimPathEnd() {
+ return mTrimPathEnd;
+ }
+
+ private void setTrimPathEnd(float trimPathEnd) {
+ mTrimPathEnd = trimPathEnd;
+ }
+
+ private float getTrimPathOffset() {
+ return mTrimPathOffset;
+ }
+
+ private void setTrimPathOffset(float trimPathOffset) {
+ mTrimPathOffset = trimPathOffset;
+ }
+
+ private void setStrokeMiterlimit(float limit) {
+ mStrokeMiterlimit = limit;
+ }
+
+ private float getStrokeMiterlimit() {
+ return mStrokeMiterlimit;
+ }
+
+ private void setStrokeGradient(long gradientPtr) {
+ mStrokeGradient = gradientPtr;
+ }
+
+ private void setFillGradient(long gradientPtr) {
+ mFillGradient = gradientPtr;
+ }
+
+ private void setFillType(int fillType) {
+ mFillType = fillType;
+ }
+
+ private int getFillType() {
+ return mFillType;
+ }
+ }
+
+ static class VGroup_Delegate implements VNativeObject {
+ // This constants need to be kept in sync with their definitions in VectorDrawable.Group
+ private static final int ROTATE_INDEX = 0;
+ private static final int PIVOT_X_INDEX = 1;
+ private static final int PIVOT_Y_INDEX = 2;
+ private static final int SCALE_X_INDEX = 3;
+ private static final int SCALE_Y_INDEX = 4;
+ private static final int TRANSLATE_X_INDEX = 5;
+ private static final int TRANSLATE_Y_INDEX = 6;
+
+ public Consumer<Float> getPropertySetter(int propertyIdx) {
+ switch (propertyIdx) {
+ case ROTATE_INDEX:
+ return this::setRotation;
+ case PIVOT_X_INDEX:
+ return this::setPivotX;
+ case PIVOT_Y_INDEX:
+ return this::setPivotY;
+ case SCALE_X_INDEX:
+ return this::setScaleX;
+ case SCALE_Y_INDEX:
+ return this::setScaleY;
+ case TRANSLATE_X_INDEX:
+ return this::setTranslateX;
+ case TRANSLATE_Y_INDEX:
+ return this::setTranslateY;
+ }
+
+ throw new IllegalArgumentException("Invalid VGroup_Delegate property index "
+ + propertyIdx);
+ }
+
+ /////////////////////////////////////////////////////
+ // Variables below need to be copied (deep copy if applicable) for mutation.
+ final ArrayList<Object> mChildren = new ArrayList<>();
+ // mStackedMatrix is only used temporarily when drawing, it combines all
+ // the parents' local matrices with the current one.
+ private final Matrix mStackedMatrix = new Matrix();
+ // mLocalMatrix is updated based on the update of transformation information,
+ // either parsed from the XML or by animation.
+ private final Matrix mLocalMatrix = new Matrix();
+ private float mRotate = 0;
+ private float mPivotX = 0;
+ private float mPivotY = 0;
+ private float mScaleX = 1;
+ private float mScaleY = 1;
+ private float mTranslateX = 0;
+ private float mTranslateY = 0;
+ private int mChangingConfigurations;
+ private String mGroupName = null;
+
+ private VGroup_Delegate(VGroup_Delegate copy, ArrayMap<String, Object> targetsMap) {
+ mRotate = copy.mRotate;
+ mPivotX = copy.mPivotX;
+ mPivotY = copy.mPivotY;
+ mScaleX = copy.mScaleX;
+ mScaleY = copy.mScaleY;
+ mTranslateX = copy.mTranslateX;
+ mTranslateY = copy.mTranslateY;
+ mGroupName = copy.mGroupName;
+ mChangingConfigurations = copy.mChangingConfigurations;
+ if (mGroupName != null) {
+ targetsMap.put(mGroupName, this);
+ }
+
+ mLocalMatrix.set(copy.mLocalMatrix);
+
+ final ArrayList<Object> children = copy.mChildren;
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < children.size(); i++) {
+ Object copyChild = children.get(i);
+ if (copyChild instanceof VGroup_Delegate) {
+ VGroup_Delegate copyGroup = (VGroup_Delegate) copyChild;
+ mChildren.add(new VGroup_Delegate(copyGroup, targetsMap));
+ } else {
+ VPath_Delegate newPath;
+ if (copyChild instanceof VFullPath_Delegate) {
+ newPath = new VFullPath_Delegate((VFullPath_Delegate) copyChild);
+ } else if (copyChild instanceof VClipPath_Delegate) {
+ newPath = new VClipPath_Delegate((VClipPath_Delegate) copyChild);
+ } else {
+ throw new IllegalStateException("Unknown object in the tree!");
+ }
+ mChildren.add(newPath);
+ if (newPath.mPathName != null) {
+ targetsMap.put(newPath.mPathName, newPath);
+ }
+ }
+ }
+ }
+
+ private VGroup_Delegate() {
+ }
+
+ private void updateLocalMatrix() {
+ // The order we apply is the same as the
+ // RenderNode.cpp::applyViewPropertyTransforms().
+ mLocalMatrix.reset();
+ mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
+ mLocalMatrix.postScale(mScaleX, mScaleY);
+ mLocalMatrix.postRotate(mRotate, 0, 0);
+ mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
+ }
+
+ /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
+ private float getRotation() {
+ return mRotate;
+ }
+
+ private void setRotation(float rotation) {
+ if (rotation != mRotate) {
+ mRotate = rotation;
+ updateLocalMatrix();
+ }
+ }
+
+ private float getPivotX() {
+ return mPivotX;
+ }
+
+ private void setPivotX(float pivotX) {
+ if (pivotX != mPivotX) {
+ mPivotX = pivotX;
+ updateLocalMatrix();
+ }
+ }
+
+ private float getPivotY() {
+ return mPivotY;
+ }
+
+ private void setPivotY(float pivotY) {
+ if (pivotY != mPivotY) {
+ mPivotY = pivotY;
+ updateLocalMatrix();
+ }
+ }
+
+ private float getScaleX() {
+ return mScaleX;
+ }
+
+ private void setScaleX(float scaleX) {
+ if (scaleX != mScaleX) {
+ mScaleX = scaleX;
+ updateLocalMatrix();
+ }
+ }
+
+ private float getScaleY() {
+ return mScaleY;
+ }
+
+ private void setScaleY(float scaleY) {
+ if (scaleY != mScaleY) {
+ mScaleY = scaleY;
+ updateLocalMatrix();
+ }
+ }
+
+ private float getTranslateX() {
+ return mTranslateX;
+ }
+
+ private void setTranslateX(float translateX) {
+ if (translateX != mTranslateX) {
+ mTranslateX = translateX;
+ updateLocalMatrix();
+ }
+ }
+
+ private float getTranslateY() {
+ return mTranslateY;
+ }
+
+ private void setTranslateY(float translateY) {
+ if (translateY != mTranslateY) {
+ mTranslateY = translateY;
+ updateLocalMatrix();
+ }
+ }
+
+ @Override
+ public void setName(String name) {
+ mGroupName = name;
+ }
+ }
+
+ public static class VPath_Delegate implements VNativeObject {
+ protected PathParser_Delegate.PathDataNode[] mNodes = null;
+ String mPathName;
+ int mChangingConfigurations;
+
+ public VPath_Delegate() {
+ // Empty constructor.
+ }
+
+ public VPath_Delegate(VPath_Delegate copy) {
+ mPathName = copy.mPathName;
+ mChangingConfigurations = copy.mChangingConfigurations;
+ mNodes = PathParser_Delegate.deepCopyNodes(copy.mNodes);
+ }
+
+ public void toPath(Path path) {
+ path.reset();
+ if (mNodes != null) {
+ PathParser_Delegate.PathDataNode.nodesToPath(mNodes,
+ Path_Delegate.getDelegate(path.mNativePath));
+ }
+ }
+
+ @Override
+ public void setName(String name) {
+ mPathName = name;
+ }
+
+ public boolean isClipPath() {
+ return false;
+ }
+
+ private void setPathData(PathParser_Delegate.PathDataNode[] nodes) {
+ if (!PathParser_Delegate.canMorph(mNodes, nodes)) {
+ // This should not happen in the middle of animation.
+ mNodes = PathParser_Delegate.deepCopyNodes(nodes);
+ } else {
+ PathParser_Delegate.updateNodes(mNodes, nodes);
+ }
+ }
+ }
+
+ static class VPathRenderer_Delegate implements VNativeObject {
+ /* Right now the internal data structure is organized as a tree.
+ * Each node can be a group node, or a path.
+ * A group node can have groups or paths as children, but a path node has
+ * no children.
+ * One example can be:
+ * Root Group
+ * / | \
+ * Group Path Group
+ * / \ |
+ * Path Path Path
+ *
+ */
+ // Variables that only used temporarily inside the draw() call, so there
+ // is no need for deep copying.
+ private final Path mPath;
+ private final Path mRenderPath;
+ private final Matrix mFinalPathMatrix = new Matrix();
+ private final VGroup_Delegate mRootGroup;
+ private float mViewportWidth = 0;
+ private float mViewportHeight = 0;
+ private float mRootAlpha = 1.0f;
+ private Paint mStrokePaint;
+ private Paint mFillPaint;
+ private PathMeasure mPathMeasure;
+
+ private VPathRenderer_Delegate(VGroup_Delegate rootGroup) {
+ mRootGroup = rootGroup;
+ mPath = new Path();
+ mRenderPath = new Path();
+ }
+
+ private float getRootAlpha() {
+ return mRootAlpha;
+ }
+
+ void setRootAlpha(float alpha) {
+ mRootAlpha = alpha;
+ }
+
+ private void drawGroupTree(VGroup_Delegate currentGroup, Matrix currentMatrix,
+ long canvasPtr, int w, int h, long filterPtr) {
+ // Calculate current group's matrix by preConcat the parent's and
+ // and the current one on the top of the stack.
+ // Basically the Mfinal = Mviewport * M0 * M1 * M2;
+ // Mi the local matrix at level i of the group tree.
+ currentGroup.mStackedMatrix.set(currentMatrix);
+ currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
+
+ // Save the current clip information, which is local to this group.
+ Canvas_Delegate.native_save(canvasPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+ // Draw the group tree in the same order as the XML file.
+ for (int i = 0; i < currentGroup.mChildren.size(); i++) {
+ Object child = currentGroup.mChildren.get(i);
+ if (child instanceof VGroup_Delegate) {
+ VGroup_Delegate childGroup = (VGroup_Delegate) child;
+ drawGroupTree(childGroup, currentGroup.mStackedMatrix,
+ canvasPtr, w, h, filterPtr);
+ } else if (child instanceof VPath_Delegate) {
+ VPath_Delegate childPath = (VPath_Delegate) child;
+ drawPath(currentGroup, childPath, canvasPtr, w, h, filterPtr);
+ }
+ }
+ Canvas_Delegate.native_restore(canvasPtr, true);
+ }
+
+ public void draw(long canvasPtr, long filterPtr, int w, int h) {
+ // Traverse the tree in pre-order to draw.
+ drawGroupTree(mRootGroup, Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr);
+ }
+
+ private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr,
+ int w,
+ int h,
+ long filterPtr) {
+ final float scaleX = w / mViewportWidth;
+ final float scaleY = h / mViewportHeight;
+ final float minScale = Math.min(scaleX, scaleY);
+ final Matrix groupStackedMatrix = VGroup.mStackedMatrix;
+
+ mFinalPathMatrix.set(groupStackedMatrix);
+ mFinalPathMatrix.postScale(scaleX, scaleY);
+
+ final float matrixScale = getMatrixScale(groupStackedMatrix);
+ if (matrixScale == 0) {
+ // When either x or y is scaled to 0, we don't need to draw anything.
+ return;
+ }
+ VPath.toPath(mPath);
+ final Path path = mPath;
+
+ mRenderPath.reset();
+
+ if (VPath.isClipPath()) {
+ mRenderPath.addPath(path, mFinalPathMatrix);
+ Canvas_Delegate.native_clipPath(canvasPtr, mRenderPath.mNativePath, Op
+ .INTERSECT.nativeInt);
+ } else {
+ VFullPath_Delegate fullPath = (VFullPath_Delegate) VPath;
+ if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
+ float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
+ float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
+
+ if (mPathMeasure == null) {
+ mPathMeasure = new PathMeasure();
+ }
+ mPathMeasure.setPath(mPath, false);
+
+ float len = mPathMeasure.getLength();
+ start = start * len;
+ end = end * len;
+ path.reset();
+ if (start > end) {
+ mPathMeasure.getSegment(start, len, path, true);
+ mPathMeasure.getSegment(0f, end, path, true);
+ } else {
+ mPathMeasure.getSegment(start, end, path, true);
+ }
+ path.rLineTo(0, 0); // fix bug in measure
+ }
+ mRenderPath.addPath(path, mFinalPathMatrix);
+
+ if (fullPath.mFillColor != Color.TRANSPARENT) {
+ if (mFillPaint == null) {
+ mFillPaint = new Paint();
+ mFillPaint.setStyle(Style.FILL);
+ mFillPaint.setAntiAlias(true);
+ }
+
+ final Paint fillPaint = mFillPaint;
+ fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
+ Paint_Delegate fillPaintDelegate = Paint_Delegate.getDelegate(fillPaint
+ .getNativeInstance());
+ // mFillPaint can not be null at this point so we will have a delegate
+ assert fillPaintDelegate != null;
+ fillPaintDelegate.setColorFilter(filterPtr);
+ fillPaintDelegate.setShader(fullPath.mFillGradient);
+ Path_Delegate.native_setFillType(mRenderPath.mNativePath, fullPath.mFillType);
+ Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
+ .getNativeInstance());
+ }
+
+ if (fullPath.mStrokeColor != Color.TRANSPARENT) {
+ if (mStrokePaint == null) {
+ mStrokePaint = new Paint();
+ mStrokePaint.setStyle(Style.STROKE);
+ mStrokePaint.setAntiAlias(true);
+ }
+
+ final Paint strokePaint = mStrokePaint;
+ if (fullPath.mStrokeLineJoin != null) {
+ strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
+ }
+
+ if (fullPath.mStrokeLineCap != null) {
+ strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
+ }
+
+ strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
+ strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
+ Paint_Delegate strokePaintDelegate = Paint_Delegate.getDelegate(strokePaint
+ .getNativeInstance());
+ // mStrokePaint can not be null at this point so we will have a delegate
+ assert strokePaintDelegate != null;
+ strokePaintDelegate.setColorFilter(filterPtr);
+ final float finalStrokeScale = minScale * matrixScale;
+ strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
+ strokePaintDelegate.setShader(fullPath.mStrokeGradient);
+ Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
+ .getNativeInstance());
+ }
+ }
+ }
+
+ private float getMatrixScale(Matrix groupStackedMatrix) {
+ // Given unit vectors A = (0, 1) and B = (1, 0).
+ // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
+ // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
+ // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
+ // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
+ //
+ // For non-skew case, which is most of the cases, matrix scale is computing exactly the
+ // scale on x and y axis, and take the minimal of these two.
+ // For skew case, an unit square will mapped to a parallelogram. And this function will
+ // return the minimal height of the 2 bases.
+ float[] unitVectors = new float[]{0, 1, 1, 0};
+ groupStackedMatrix.mapVectors(unitVectors);
+ float scaleX = MathUtils.mag(unitVectors[0], unitVectors[1]);
+ float scaleY = MathUtils.mag(unitVectors[2], unitVectors[3]);
+ float crossProduct = MathUtils.cross(unitVectors[0], unitVectors[1],
+ unitVectors[2], unitVectors[3]);
+ float maxScale = MathUtils.max(scaleX, scaleY);
+
+ float matrixScale = 0;
+ if (maxScale > 0) {
+ matrixScale = MathUtils.abs(crossProduct) / maxScale;
+ }
+ if (DBG_VECTOR_DRAWABLE) {
+ Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
+ }
+ return matrixScale;
+ }
+
+ @Override
+ public void setName(String name) {
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
index 6a68ee29c9cb..549074d15757 100644
--- a/tools/layoutlib/bridge/src/android/os/ServiceManager.java
+++ b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
@@ -51,8 +51,10 @@ public final class ServiceManager {
/**
* Return a list of all currently running services.
+ * @return an array of all currently running services, or <code>null</code> in
+ * case of an exception
*/
- public static String[] listServices() throws RemoteException {
+ public static String[] listServices() {
// actual implementation returns null sometimes, so it's ok
// to return null instead of an empty list.
return null;
diff --git a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
index 44ce7311a95c..fcd63ea993c8 100644
--- a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
@@ -25,7 +25,7 @@ import java.nio.ByteBuffer;
/**
* Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator}
* <p/>
- * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
+ * Through the layoutlib_create tool, selected methods of Hyphenator have been replaced
* by calls to methods of the same name in this delegate class.
*/
public class Hyphenator_Delegate {
@@ -39,7 +39,8 @@ public class Hyphenator_Delegate {
return null;
}
- /*package*/ static long loadHyphenator(ByteBuffer buf, int offset) {
+ /*package*/ @SuppressWarnings("UnusedParameters") // TODO implement this.
+ static long loadHyphenator(ByteBuffer buffer, int offset) {
return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
}
}
diff --git a/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java
new file mode 100644
index 000000000000..6d3bb4ca9115
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java
@@ -0,0 +1,843 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.graphics.Path_Delegate;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Delegate that provides implementation for native methods in {@link android.util.PathParser}
+ * <p/>
+ * Through the layoutlib_create tool, selected methods of PathParser have been replaced by calls to
+ * methods of the same name in this delegate class.
+ *
+ * Most of the code has been taken from the implementation in
+ * {@code tools/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathParser.java}
+ * revision be6fe89a3b686db5a75e7e692a148699973957f3
+ */
+public class PathParser_Delegate {
+
+ private static final Logger LOGGER = Logger.getLogger("PathParser");
+
+ // ---- Builder delegate manager ----
+ private static final DelegateManager<PathParser_Delegate> sManager =
+ new DelegateManager<PathParser_Delegate>(PathParser_Delegate.class);
+
+ // ---- delegate data ----
+ @NonNull
+ private PathDataNode[] mPathDataNodes;
+
+ public static PathParser_Delegate getDelegate(long nativePtr) {
+ return sManager.getDelegate(nativePtr);
+ }
+
+ private PathParser_Delegate(@NonNull PathDataNode[] nodes) {
+ mPathDataNodes = nodes;
+ }
+
+ public PathDataNode[] getPathDataNodes() {
+ return mPathDataNodes;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nParseStringForPath(long pathPtr, @NonNull String pathString, int
+ stringLength) {
+ Path_Delegate path_delegate = Path_Delegate.getDelegate(pathPtr);
+ if (path_delegate == null) {
+ return;
+ }
+ assert pathString.length() == stringLength;
+ PathDataNode.nodesToPath(createNodesFromPathData(pathString), path_delegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nCreatePathFromPathData(long outPathPtr, long pathData) {
+ Path_Delegate path_delegate = Path_Delegate.getDelegate(outPathPtr);
+ PathParser_Delegate source = sManager.getDelegate(outPathPtr);
+ if (source == null || path_delegate == null) {
+ return;
+ }
+ PathDataNode.nodesToPath(source.mPathDataNodes, path_delegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreateEmptyPathData() {
+ PathParser_Delegate newDelegate = new PathParser_Delegate(new PathDataNode[0]);
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathData(long nativePtr) {
+ PathParser_Delegate source = sManager.getDelegate(nativePtr);
+ if (source == null) {
+ return 0;
+ }
+ PathParser_Delegate dest = new PathParser_Delegate(deepCopyNodes(source.mPathDataNodes));
+ return sManager.addNewDelegate(dest);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathDataFromString(@NonNull String pathString,
+ int stringLength) {
+ assert pathString.length() == stringLength : "Inconsistent path string length.";
+ PathDataNode[] nodes = createNodesFromPathData(pathString);
+ PathParser_Delegate delegate = new PathParser_Delegate(nodes);
+ return sManager.addNewDelegate(delegate);
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
+ long toDataPtr, float fraction) {
+ PathParser_Delegate out = sManager.getDelegate(outDataPtr);
+ PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
+ PathParser_Delegate to = sManager.getDelegate(toDataPtr);
+ if (out == null || from == null || to == null) {
+ return false;
+ }
+ int length = from.mPathDataNodes.length;
+ if (length != to.mPathDataNodes.length) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Cannot interpolate path data with different lengths (from " + length + " to " +
+ to.mPathDataNodes.length + ").", null);
+ return false;
+ }
+ if (out.mPathDataNodes.length != length) {
+ out.mPathDataNodes = new PathDataNode[length];
+ }
+ for (int i = 0; i < length; i++) {
+ if (out.mPathDataNodes[i] == null) {
+ out.mPathDataNodes[i] = new PathDataNode(from.mPathDataNodes[i]);
+ }
+ out.mPathDataNodes[i].interpolatePathDataNode(from.mPathDataNodes[i],
+ to.mPathDataNodes[i], fraction);
+ }
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nFinalize(long nativePtr) {
+ sManager.removeJavaReferenceFor(nativePtr);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nCanMorph(long fromDataPtr, long toDataPtr) {
+ PathParser_Delegate fromPath = PathParser_Delegate.getDelegate(fromDataPtr);
+ PathParser_Delegate toPath = PathParser_Delegate.getDelegate(toDataPtr);
+ if (fromPath == null || toPath == null || fromPath.getPathDataNodes() == null || toPath
+ .getPathDataNodes() == null) {
+ return true;
+ }
+ return PathParser_Delegate.canMorph(fromPath.getPathDataNodes(), toPath.getPathDataNodes());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetPathData(long outDataPtr, long fromDataPtr) {
+ PathParser_Delegate out = sManager.getDelegate(outDataPtr);
+ PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
+ if (from == null || out == null) {
+ return;
+ }
+ out.mPathDataNodes = deepCopyNodes(from.mPathDataNodes);
+ }
+
+ /**
+ * @param pathData The string representing a path, the same as "d" string in svg file.
+ *
+ * @return an array of the PathDataNode.
+ */
+ @NonNull
+ public static PathDataNode[] createNodesFromPathData(@NonNull String pathData) {
+ int start = 0;
+ int end = 1;
+
+ ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
+ while (end < pathData.length()) {
+ end = nextStart(pathData, end);
+ String s = pathData.substring(start, end).trim();
+ if (s.length() > 0) {
+ float[] val = getFloats(s);
+ addNode(list, s.charAt(0), val);
+ }
+
+ start = end;
+ end++;
+ }
+ if ((end - start) == 1 && start < pathData.length()) {
+ addNode(list, pathData.charAt(start), new float[0]);
+ }
+ return list.toArray(new PathDataNode[list.size()]);
+ }
+
+ /**
+ * @param source The array of PathDataNode to be duplicated.
+ *
+ * @return a deep copy of the <code>source</code>.
+ */
+ @NonNull
+ public static PathDataNode[] deepCopyNodes(@NonNull PathDataNode[] source) {
+ PathDataNode[] copy = new PathDataNode[source.length];
+ for (int i = 0; i < source.length; i++) {
+ copy[i] = new PathDataNode(source[i]);
+ }
+ return copy;
+ }
+
+ /**
+ * @param nodesFrom The source path represented in an array of PathDataNode
+ * @param nodesTo The target path represented in an array of PathDataNode
+ * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
+ */
+ public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
+ if (nodesFrom == null || nodesTo == null) {
+ return false;
+ }
+
+ if (nodesFrom.length != nodesTo.length) {
+ return false;
+ }
+
+ for (int i = 0; i < nodesFrom.length; i ++) {
+ if (nodesFrom[i].mType != nodesTo[i].mType
+ || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Update the target's data to match the source.
+ * Before calling this, make sure canMorph(target, source) is true.
+ *
+ * @param target The target path represented in an array of PathDataNode
+ * @param source The source path represented in an array of PathDataNode
+ */
+ public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
+ for (int i = 0; i < source.length; i ++) {
+ target[i].mType = source[i].mType;
+ for (int j = 0; j < source[i].mParams.length; j ++) {
+ target[i].mParams[j] = source[i].mParams[j];
+ }
+ }
+ }
+
+ private static int nextStart(@NonNull String s, int end) {
+ char c;
+
+ while (end < s.length()) {
+ c = s.charAt(end);
+ // Note that 'e' or 'E' are not valid path commands, but could be
+ // used for floating point numbers' scientific notation.
+ // Therefore, when searching for next command, we should ignore 'e'
+ // and 'E'.
+ if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
+ && c != 'e' && c != 'E') {
+ return end;
+ }
+ end++;
+ }
+ return end;
+ }
+
+ /**
+ * Calculate the position of the next comma or space or negative sign
+ *
+ * @param s the string to search
+ * @param start the position to start searching
+ * @param result the result of the extraction, including the position of the the starting
+ * position of next number, whether it is ending with a '-'.
+ */
+ private static void extract(@NonNull String s, int start, @NonNull ExtractFloatResult result) {
+ // Now looking for ' ', ',', '.' or '-' from the start.
+ int currentIndex = start;
+ boolean foundSeparator = false;
+ result.mEndWithNegOrDot = false;
+ boolean secondDot = false;
+ boolean isExponential = false;
+ for (; currentIndex < s.length(); currentIndex++) {
+ boolean isPrevExponential = isExponential;
+ isExponential = false;
+ char currentChar = s.charAt(currentIndex);
+ switch (currentChar) {
+ case ' ':
+ case ',':
+ foundSeparator = true;
+ break;
+ case '-':
+ // The negative sign following a 'e' or 'E' is not a separator.
+ if (currentIndex != start && !isPrevExponential) {
+ foundSeparator = true;
+ result.mEndWithNegOrDot = true;
+ }
+ break;
+ case '.':
+ if (!secondDot) {
+ secondDot = true;
+ } else {
+ // This is the second dot, and it is considered as a separator.
+ foundSeparator = true;
+ result.mEndWithNegOrDot = true;
+ }
+ break;
+ case 'e':
+ case 'E':
+ isExponential = true;
+ break;
+ }
+ if (foundSeparator) {
+ break;
+ }
+ }
+ // When there is nothing found, then we put the end position to the end
+ // of the string.
+ result.mEndPosition = currentIndex;
+ }
+
+ /**
+ * Parse the floats in the string. This is an optimized version of
+ * parseFloat(s.split(",|\\s"));
+ *
+ * @param s the string containing a command and list of floats
+ *
+ * @return array of floats
+ */
+ @NonNull
+ private static float[] getFloats(@NonNull String s) {
+ if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
+ return new float[0];
+ }
+ try {
+ float[] results = new float[s.length()];
+ int count = 0;
+ int startPosition = 1;
+ int endPosition;
+
+ ExtractFloatResult result = new ExtractFloatResult();
+ int totalLength = s.length();
+
+ // The startPosition should always be the first character of the
+ // current number, and endPosition is the character after the current
+ // number.
+ while (startPosition < totalLength) {
+ extract(s, startPosition, result);
+ endPosition = result.mEndPosition;
+
+ if (startPosition < endPosition) {
+ results[count++] = Float.parseFloat(
+ s.substring(startPosition, endPosition));
+ }
+
+ if (result.mEndWithNegOrDot) {
+ // Keep the '-' or '.' sign with next number.
+ startPosition = endPosition;
+ } else {
+ startPosition = endPosition + 1;
+ }
+ }
+ return Arrays.copyOf(results, count);
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("error in parsing \"" + s + "\"", e);
+ }
+ }
+
+
+ private static void addNode(@NonNull ArrayList<PathDataNode> list, char cmd,
+ @NonNull float[] val) {
+ list.add(new PathDataNode(cmd, val));
+ }
+
+ private static class ExtractFloatResult {
+ // We need to return the position of the next separator and whether the
+ // next float starts with a '-' or a '.'.
+ private int mEndPosition;
+ private boolean mEndWithNegOrDot;
+ }
+
+ /**
+ * Each PathDataNode represents one command in the "d" attribute of the svg file. An array of
+ * PathDataNode can represent the whole "d" attribute.
+ */
+ public static class PathDataNode {
+ private char mType;
+ @NonNull
+ private float[] mParams;
+
+ private PathDataNode(char type, @NonNull float[] params) {
+ mType = type;
+ mParams = params;
+ }
+
+ public char getType() {
+ return mType;
+ }
+
+ @NonNull
+ public float[] getParams() {
+ return mParams;
+ }
+
+ private PathDataNode(@NonNull PathDataNode n) {
+ mType = n.mType;
+ mParams = Arrays.copyOf(n.mParams, n.mParams.length);
+ }
+
+ /**
+ * Convert an array of PathDataNode to Path. Reset the passed path as needed before
+ * calling this method.
+ *
+ * @param node The source array of PathDataNode.
+ * @param path The target Path object.
+ */
+ public static void nodesToPath(@NonNull PathDataNode[] node, @NonNull Path_Delegate path) {
+ float[] current = new float[6];
+ char previousCommand = 'm';
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < node.length; i++) {
+ addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
+ previousCommand = node[i].mType;
+ }
+ }
+
+ /**
+ * The current PathDataNode will be interpolated between the <code>nodeFrom</code> and
+ * <code>nodeTo</code> according to the <code>fraction</code>.
+ *
+ * @param nodeFrom The start value as a PathDataNode.
+ * @param nodeTo The end value as a PathDataNode
+ * @param fraction The fraction to interpolate.
+ */
+ private void interpolatePathDataNode(@NonNull PathDataNode nodeFrom,
+ @NonNull PathDataNode nodeTo, float fraction) {
+ for (int i = 0; i < nodeFrom.mParams.length; i++) {
+ mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
+ + nodeTo.mParams[i] * fraction;
+ }
+ }
+
+ @SuppressWarnings("PointlessArithmeticExpression")
+ private static void addCommand(@NonNull Path_Delegate path, float[] current,
+ char previousCmd, char cmd, @NonNull float[] val) {
+
+ int incr = 2;
+ float currentX = current[0];
+ float currentY = current[1];
+ float ctrlPointX = current[2];
+ float ctrlPointY = current[3];
+ float currentSegmentStartX = current[4];
+ float currentSegmentStartY = current[5];
+ float reflectiveCtrlPointX;
+ float reflectiveCtrlPointY;
+
+ switch (cmd) {
+ case 'z':
+ case 'Z':
+ path.close();
+ // Path is closed here, but we need to move the pen to the
+ // closed position. So we cache the segment's starting position,
+ // and restore it here.
+ currentX = currentSegmentStartX;
+ currentY = currentSegmentStartY;
+ ctrlPointX = currentSegmentStartX;
+ ctrlPointY = currentSegmentStartY;
+ path.moveTo(currentX, currentY);
+ break;
+ case 'm':
+ case 'M':
+ case 'l':
+ case 'L':
+ case 't':
+ case 'T':
+ incr = 2;
+ break;
+ case 'h':
+ case 'H':
+ case 'v':
+ case 'V':
+ incr = 1;
+ break;
+ case 'c':
+ case 'C':
+ incr = 6;
+ break;
+ case 's':
+ case 'S':
+ case 'q':
+ case 'Q':
+ incr = 4;
+ break;
+ case 'a':
+ case 'A':
+ incr = 7;
+ break;
+ }
+
+ for (int k = 0; k < val.length; k += incr) {
+ switch (cmd) {
+ case 'm': // moveto - Start a new sub-path (relative)
+ currentX += val[k + 0];
+ currentY += val[k + 1];
+
+ if (k > 0) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ path.rLineTo(val[k + 0], val[k + 1]);
+ } else {
+ path.rMoveTo(val[k + 0], val[k + 1]);
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+ }
+ break;
+ case 'M': // moveto - Start a new sub-path
+ currentX = val[k + 0];
+ currentY = val[k + 1];
+
+ if (k > 0) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ path.lineTo(val[k + 0], val[k + 1]);
+ } else {
+ path.moveTo(val[k + 0], val[k + 1]);
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+ }
+ break;
+ case 'l': // lineto - Draw a line from the current point (relative)
+ path.rLineTo(val[k + 0], val[k + 1]);
+ currentX += val[k + 0];
+ currentY += val[k + 1];
+ break;
+ case 'L': // lineto - Draw a line from the current point
+ path.lineTo(val[k + 0], val[k + 1]);
+ currentX = val[k + 0];
+ currentY = val[k + 1];
+ break;
+ case 'h': // horizontal lineto - Draws a horizontal line (relative)
+ path.rLineTo(val[k + 0], 0);
+ currentX += val[k + 0];
+ break;
+ case 'H': // horizontal lineto - Draws a horizontal line
+ path.lineTo(val[k + 0], currentY);
+ currentX = val[k + 0];
+ break;
+ case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+ path.rLineTo(0, val[k + 0]);
+ currentY += val[k + 0];
+ break;
+ case 'V': // vertical lineto - Draws a vertical line from the current point
+ path.lineTo(currentX, val[k + 0]);
+ currentY = val[k + 0];
+ break;
+ case 'c': // curveto - Draws a cubic Bézier curve (relative)
+ path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+ val[k + 4], val[k + 5]);
+
+ ctrlPointX = currentX + val[k + 2];
+ ctrlPointY = currentY + val[k + 3];
+ currentX += val[k + 4];
+ currentY += val[k + 5];
+
+ break;
+ case 'C': // curveto - Draws a cubic Bézier curve
+ path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+ val[k + 4], val[k + 5]);
+ currentX = val[k + 4];
+ currentY = val[k + 5];
+ ctrlPointX = val[k + 2];
+ ctrlPointY = val[k + 3];
+ break;
+ case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1],
+ val[k + 2], val[k + 3]);
+
+ ctrlPointX = currentX + val[k + 0];
+ ctrlPointY = currentY + val[k + 1];
+ currentX += val[k + 2];
+ currentY += val[k + 3];
+ break;
+ case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ ctrlPointX = val[k + 0];
+ ctrlPointY = val[k + 1];
+ currentX = val[k + 2];
+ currentY = val[k + 3];
+ break;
+ case 'q': // Draws a quadratic Bézier (relative)
+ path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ ctrlPointX = currentX + val[k + 0];
+ ctrlPointY = currentY + val[k + 1];
+ currentX += val[k + 2];
+ currentY += val[k + 3];
+ break;
+ case 'Q': // Draws a quadratic Bézier
+ path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ ctrlPointX = val[k + 0];
+ ctrlPointY = val[k + 1];
+ currentX = val[k + 2];
+ currentY = val[k + 3];
+ break;
+ case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1]);
+ ctrlPointX = currentX + reflectiveCtrlPointX;
+ ctrlPointY = currentY + reflectiveCtrlPointY;
+ currentX += val[k + 0];
+ currentY += val[k + 1];
+ break;
+ case 'T': // Draws a quadratic Bézier curve (reflective control point)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1]);
+ ctrlPointX = reflectiveCtrlPointX;
+ ctrlPointY = reflectiveCtrlPointY;
+ currentX = val[k + 0];
+ currentY = val[k + 1];
+ break;
+ case 'a': // Draws an elliptical arc
+ // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+ drawArc(path,
+ currentX,
+ currentY,
+ val[k + 5] + currentX,
+ val[k + 6] + currentY,
+ val[k + 0],
+ val[k + 1],
+ val[k + 2],
+ val[k + 3] != 0,
+ val[k + 4] != 0);
+ currentX += val[k + 5];
+ currentY += val[k + 6];
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ case 'A': // Draws an elliptical arc
+ drawArc(path,
+ currentX,
+ currentY,
+ val[k + 5],
+ val[k + 6],
+ val[k + 0],
+ val[k + 1],
+ val[k + 2],
+ val[k + 3] != 0,
+ val[k + 4] != 0);
+ currentX = val[k + 5];
+ currentY = val[k + 6];
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ }
+ previousCmd = cmd;
+ }
+ current[0] = currentX;
+ current[1] = currentY;
+ current[2] = ctrlPointX;
+ current[3] = ctrlPointY;
+ current[4] = currentSegmentStartX;
+ current[5] = currentSegmentStartY;
+ }
+
+ private static void drawArc(@NonNull Path_Delegate p, float x0, float y0, float x1,
+ float y1, float a, float b, float theta, boolean isMoreThanHalf,
+ boolean isPositiveArc) {
+
+ LOGGER.log(Level.FINE, "(" + x0 + "," + y0 + ")-(" + x1 + "," + y1
+ + ") {" + a + " " + b + "}");
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = theta * Math.PI / 180.0f;
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = Math.cos(thetaD);
+ double sinTheta = Math.sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+ LOGGER.log(Level.FINE, "unit space (" + x0p + "," + y0p + ")-(" + x1p
+ + "," + y1p + ")");
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ LOGGER.log(Level.FINE, " Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ LOGGER.log(Level.FINE, "Points are too far apart " + dsq);
+ float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta,
+ isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = Math.sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+
+ double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+ LOGGER.log(Level.FINE, "eta0 = Math.atan2( " + (y0p - cy) + " , "
+ + (x0p - cx) + ") = " + Math.toDegrees(eta0));
+
+ double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+ LOGGER.log(Level.FINE, "eta1 = Math.atan2( " + (y1p - cy) + " , "
+ + (x1p - cx) + ") = " + Math.toDegrees(eta1));
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * Math.PI;
+ } else {
+ sweep += 2 * Math.PI;
+ }
+ }
+
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+ LOGGER.log(
+ Level.FINE,
+ "cx, cy, a, b, x0, y0, thetaD, eta0, sweep = " + cx + " , "
+ + cy + " , " + a + " , " + b + " , " + x0 + " , " + y0
+ + " , " + Math.toDegrees(thetaD) + " , "
+ + Math.toDegrees(eta0) + " , " + Math.toDegrees(sweep));
+
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+ }
+
+ /**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with the horizontal
+ * plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+ private static void arcToBezier(@NonNull Path_Delegate p, double cx, double cy, double a,
+ double b, double e1x, double e1y, double theta, double start,
+ double sweep) {
+ // Taken from equations at:
+ // http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+
+
+ double eta1 = start;
+ double cosTheta = Math.cos(theta);
+ double sinTheta = Math.sin(theta);
+ double cosEta1 = Math.cos(eta1);
+ double sinEta1 = Math.sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = Math.sin(eta2);
+ double cosEta2 = Math.cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2)
+ - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2)
+ + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+ double alpha = Math.sin(eta2 - eta1)
+ * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+
+ p.cubicTo((float) q1x,
+ (float) q1y,
+ (float) q2x,
+ (float) q2y,
+ (float) e2x,
+ (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 723e8278bcc0..bdddfd8d8ba3 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -265,12 +265,15 @@ public final class BridgeInflater extends LayoutInflater {
if (viewKey != null) {
bc.addViewKey(view, viewKey);
}
- String scrollPos = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
- if (scrollPos != null) {
- if (scrollPos.endsWith("px")) {
- int value = Integer.parseInt(scrollPos.substring(0, scrollPos.length() - 2));
- bc.setScrollYPos(view, value);
- }
+ String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX");
+ if (scrollPosX != null && scrollPosX.endsWith("px")) {
+ int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2));
+ bc.setScrollXPos(view, value);
+ }
+ String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
+ if (scrollPosY != null && scrollPosY.endsWith("px")) {
+ int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2));
+ bc.setScrollYPos(view, value);
}
if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
Integer resourceId = null;
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
index 01af669e39d3..381eb1f50973 100644
--- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -15,8 +15,11 @@
*/
package android.view;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -64,4 +67,18 @@ public class Choreographer_Delegate {
thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
+
+ public static void dispose() {
+ try {
+ Field threadInstanceField = Choreographer.class.getDeclaredField("sThreadInstance");
+ threadInstanceField.setAccessible(true);
+ @SuppressWarnings("unchecked") ThreadLocal<Choreographer> threadInstance =
+ (ThreadLocal<Choreographer>) threadInstanceField.get(null);
+ threadInstance.remove();
+ } catch (ReflectiveOperationException e) {
+ assert false;
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Unable to clear Choreographer memory.", e, null);
+ }
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_RunQueue_Delegate.java b/tools/layoutlib/bridge/src/android/view/HandlerActionQueue_Delegate.java
index 51b42a626a2e..e580ed0e14f7 100644
--- a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_RunQueue_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/HandlerActionQueue_Delegate.java
@@ -20,18 +20,18 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
/**
* Delegate used to provide new implementation of a select few methods of
- * {@link ViewRootImpl.RunQueue}
+ * {@link HandlerActionQueue}
*
* Through the layoutlib_create tool, the original methods of ViewRootImpl.RunQueue have been
* replaced by calls to methods of the same name in this delegate class.
*
*/
-public class ViewRootImpl_RunQueue_Delegate {
+public class HandlerActionQueue_Delegate {
@LayoutlibDelegate
- /*package*/ static void postDelayed(ViewRootImpl.RunQueue thisQueue, Runnable action, long
+ /*package*/ static void postDelayed(HandlerActionQueue thisQueue, Runnable action, long
delayMillis) {
- // The actual RunQueue is never run and therefore never cleared. This method avoids
- // runnables to be added to the RunQueue so they do not leak resources.
+ // The actual HandlerActionQueue is never run and therefore never cleared. This method
+ // avoids runnables to be added to the RunQueue so they do not leak resources.
}
}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 82012c1e2767..04a59bcfb5ae 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -17,7 +17,10 @@
package android.view;
import android.graphics.Point;
+import android.graphics.Rect;
import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.policy.IShortcutService;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -29,6 +32,7 @@ import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.util.DisplayMetrics;
+import android.view.AppTransitionAnimationSpec;
import java.lang.Override;
@@ -73,10 +77,10 @@ public class IWindowManagerImpl implements IWindowManager {
@Override
public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
- boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10)
+ boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10,
+ Rect arg11, Configuration arg12, int arg13, boolean arg14, boolean arg15, int arg16)
throws RemoteException {
// TODO Auto-generated method stub
-
}
@Override
@@ -240,6 +244,19 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
+ public void overridePendingAppTransitionMultiThumbFuture(
+ IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback,
+ boolean scaleUp) throws RemoteException {
+
+ }
+
+ @Override
+ public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+ IRemoteCallback callback0, IRemoteCallback callback1, boolean scaleUp) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
public void pauseKeyDispatching(IBinder arg0) throws RemoteException {
// TODO Auto-generated method stub
@@ -284,7 +301,7 @@ public class IWindowManagerImpl implements IWindowManager {
@Override
public Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth,
- int maxHeight) throws RemoteException {
+ int maxHeight, float frameScale) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -307,9 +324,10 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void setAppTask(IBinder arg0, int arg1) throws RemoteException {
+ public void setAppTask(IBinder arg0, int arg1, int arg2, Rect arg3, Configuration arg4,
+ int arg5, boolean arg6)
+ throws RemoteException {
// TODO Auto-generated method stub
-
}
@Override
@@ -318,10 +336,11 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void setAppStartingWindow(IBinder arg0, String arg1, int arg2, CompatibilityInfo arg3,
+ public boolean setAppStartingWindow(IBinder arg0, String arg1, int arg2, CompatibilityInfo arg3,
CharSequence arg4, int arg5, int arg6, int arg7, int arg8, IBinder arg9, boolean arg10)
throws RemoteException {
// TODO Auto-generated method stub
+ return false;
}
@Override
@@ -331,7 +350,7 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void setAppWillBeHidden(IBinder arg0) throws RemoteException {
+ public void notifyAppStopped(IBinder token, boolean stopped) throws RemoteException {
// TODO Auto-generated method stub
}
@@ -385,8 +404,15 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void setNewConfiguration(Configuration arg0) throws RemoteException {
+ public int[] setNewConfiguration(Configuration arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Rect getBoundsForNewConfiguration(int stackId) throws RemoteException {
// TODO Auto-generated method stub
+ return null;
}
@Override
@@ -474,8 +500,7 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void keyguardGoingAway(boolean disableWindowAnimations,
- boolean keyguardGoingToNotificationShade) throws RemoteException {
+ public void keyguardGoingAway(int flags) throws RemoteException {
}
@Override
@@ -511,4 +536,57 @@ public class IWindowManagerImpl implements IWindowManager {
// TODO Auto-generated method stub
return null;
}
+
+ @Override
+ public int getDockedStackSide() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void setDockedStackResizing(boolean resizing) throws RemoteException {
+ }
+
+ @Override
+ public void cancelTaskWindowTransition(int taskId) {
+ }
+
+ @Override
+ public void cancelTaskThumbnailTransition(int taskId) {
+ }
+
+ @Override
+ public void endProlongedAnimations() {
+ }
+
+ @Override
+ public void registerDockedStackListener(IDockedStackListener listener) throws RemoteException {
+ }
+
+ @Override
+ public void setResizeDimLayer(boolean visible, int targetStackId, float alpha)
+ throws RemoteException {
+ }
+
+ @Override
+ public void setDockedStackDividerTouchRegion(Rect touchableRegion) throws RemoteException {
+ }
+
+ @Override
+ public void requestAppKeyboardShortcuts(
+ IResultReceiver receiver, int deviceId) throws RemoteException {
+ }
+
+ @Override
+ public void getStableInsets(Rect outInsets) throws RemoteException {
+ }
+
+ @Override
+ public void registerShortcutKey(long shortcutCode, IShortcutService service)
+ throws RemoteException {}
+
+ @Override
+ public void createWallpaperInputConsumer(InputChannel inputChannel) throws RemoteException {}
+
+ @Override
+ public void removeWallpaperInputConsumer() throws RemoteException {}
}
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index 30512aad4509..ea9a255e8561 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -44,6 +44,11 @@ public class RectShadowPainter {
private static final float PERPENDICULAR_ANGLE = 90f;
public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+ Rect outline = new Rect();
+ if (!viewOutline.getRect(outline)) {
+ throw new IllegalArgumentException("Outline is not a rect shadow");
+ }
+
float shadowSize = elevationToShadow(elevation);
int saved = modifyCanvas(canvas, shadowSize);
if (saved == -1) {
@@ -54,8 +59,7 @@ public class RectShadowPainter {
cornerPaint.setStyle(Style.FILL);
Paint edgePaint = new Paint(cornerPaint);
edgePaint.setAntiAlias(false);
- Rect outline = viewOutline.mRect;
- float radius = viewOutline.mRadius;
+ float radius = viewOutline.getRadius();
float outerArcRadius = radius + shadowSize;
int[] colors = {START_COLOR, START_COLOR, END_COLOR};
cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
index 1465f5089599..24f788766cc9 100644
--- a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
@@ -55,7 +55,7 @@ public class RenderNode_Delegate {
private String mName;
@LayoutlibDelegate
- /*package*/ static long nCreate(String name) {
+ /*package*/ static long nCreate(RenderNode thisRenderNode, String name) {
RenderNode_Delegate renderNodeDelegate = new RenderNode_Delegate();
renderNodeDelegate.mName = name;
return sManager.addNewDelegate(renderNodeDelegate);
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
index 51d32e351eb8..23caaf85eb8e 100644
--- a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
@@ -64,7 +64,7 @@ public class ViewGroup_Delegate {
private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
Outline outline) {
float elevation = getElevation(child, parent);
- if(outline.mRect != null) {
+ if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
RectShadowPainter.paintShadow(outline, elevation, canvas);
return;
}
diff --git a/tools/layoutlib/bridge/src/android/view/WindowCallback.java b/tools/layoutlib/bridge/src/android/view/WindowCallback.java
index d691c8ea71a5..1ea8a9f2294e 100644
--- a/tools/layoutlib/bridge/src/android/view/WindowCallback.java
+++ b/tools/layoutlib/bridge/src/android/view/WindowCallback.java
@@ -16,10 +16,13 @@
package android.view;
+import android.annotation.Nullable;
import android.view.ActionMode.Callback;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
+import java.util.List;
+
/**
* An empty implementation of {@link Window.Callback} that always returns null/false.
*/
diff --git a/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java b/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java
deleted file mode 100644
index 1bd98301dc42..000000000000
--- a/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 android.widget;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.view.KeyEvent;
-
-/**
- * Delegate used to provide new implementation of few methods in {@link TimePickerClockDelegate}.
- */
-public class TimePickerClockDelegate_Delegate {
-
- // Copied from TimePickerClockDelegate.
- private static final int AM = 0;
- private static final int PM = 1;
-
- @LayoutlibDelegate
- static int getAmOrPmKeyCode(TimePickerClockDelegate tpcd, int amOrPm) {
- // We don't care about locales here.
- if (amOrPm == AM) {
- return KeyEvent.KEYCODE_A;
- } else if (amOrPm == PM) {
- return KeyEvent.KEYCODE_P;
- } else {
- assert false : "amOrPm value in TimePickerSpinnerDelegate can only be 0 or 1";
- return -1;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java
new file mode 100644
index 000000000000..01fe45d2644c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.LongSparseLongArray;
+
+/**
+ * Delegate used to provide new implementation the native methods of {@link VirtualRefBasePtr}
+ *
+ * Through the layoutlib_create tool, the original native methods of VirtualRefBasePtr have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+@SuppressWarnings("unused")
+public class VirtualRefBasePtr_Delegate {
+ private static final DelegateManager<Object> sManager = new DelegateManager<>(Object.class);
+ private static final LongSparseLongArray sRefCount = new LongSparseLongArray();
+
+ @LayoutlibDelegate
+ /*package*/ static synchronized void nIncStrong(long ptr) {
+ long counter = sRefCount.get(ptr);
+ sRefCount.put(ptr, ++counter);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static synchronized void nDecStrong(long ptr) {
+ long counter = sRefCount.get(ptr);
+
+ if (counter > 1) {
+ sRefCount.put(ptr, --counter);
+ } else {
+ sRefCount.delete(ptr);
+ sManager.removeJavaReferenceFor(ptr);
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java
new file mode 100644
index 000000000000..0f39e8042883
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.animation;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.MathUtils;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnticipateInterpolator;
+import android.view.animation.AnticipateOvershootInterpolator;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.BounceInterpolator;
+import android.view.animation.CycleInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.OvershootInterpolator;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link
+ * NativeInterpolatorFactoryHelper}
+ * <p>
+ * Through the layoutlib_create tool, the original methods of NativeInterpolatorFactoryHelper have
+ * been replaced by calls to methods of the same name in this delegate class.
+ */
+@SuppressWarnings("unused")
+public class NativeInterpolatorFactoryHelper_Delegate {
+ private static final DelegateManager<Interpolator> sManager = new DelegateManager<>
+ (Interpolator.class);
+
+ public static Interpolator getDelegate(long nativePtr) {
+ return sManager.getDelegate(nativePtr);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createAccelerateDecelerateInterpolator() {
+ return sManager.addNewDelegate(new AccelerateDecelerateInterpolator());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createAccelerateInterpolator(float factor) {
+ return sManager.addNewDelegate(new AccelerateInterpolator(factor));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createAnticipateInterpolator(float tension) {
+ return sManager.addNewDelegate(new AnticipateInterpolator(tension));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createAnticipateOvershootInterpolator(float tension) {
+ return sManager.addNewDelegate(new AnticipateOvershootInterpolator(tension));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createBounceInterpolator() {
+ return sManager.addNewDelegate(new BounceInterpolator());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createCycleInterpolator(float cycles) {
+ return sManager.addNewDelegate(new CycleInterpolator(cycles));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createDecelerateInterpolator(float factor) {
+ return sManager.addNewDelegate(new DecelerateInterpolator(factor));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createLinearInterpolator() {
+ return sManager.addNewDelegate(new LinearInterpolator());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createOvershootInterpolator(float tension) {
+ return sManager.addNewDelegate(new OvershootInterpolator(tension));
+ }
+
+ private static class LutInterpolator extends BaseInterpolator {
+ private final float[] mValues;
+ private final int mSize;
+
+ private LutInterpolator(float[] values) {
+ mValues = values;
+ mSize = mValues.length;
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ float lutpos = input * mSize;
+ if (lutpos >= (mSize - 1)) {
+ return mValues[mSize - 1];
+ }
+
+ int ipart = (int) lutpos;
+ float weight = lutpos - ipart;
+
+ int i1 = ipart;
+ int i2 = Math.min(i1 + 1, mSize - 1);
+
+ assert i1 >= 0 && i2 >= 0 : "Negatives in the interpolation";
+
+ return MathUtils.lerp(mValues[i1], mValues[i2], weight);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createLutInterpolator(float[] values) {
+ return sManager.addNewDelegate(new LutInterpolator(values));
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index c8e3d03169e8..3b882909698d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -24,6 +24,7 @@ import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.Result.Status;
import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.impl.RenderDrawable;
import com.android.layoutlib.bridge.impl.RenderSessionImpl;
import com.android.layoutlib.bridge.util.DynamicIdMap;
@@ -408,7 +409,9 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
/**
* Starts a layout session by inflating and rendering it. The method returns a
* {@link RenderSession} on which further actions can be taken.
- *
+ * <p/>
+ * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE},
+ * this method will only inflate the layout but will NOT render it.
* @param params the {@link SessionParams} object with all the information necessary to create
* the scene.
* @return a new {@link RenderSession} object that contains the result of the layout.
@@ -424,7 +427,10 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
lastResult = scene.init(params.getTimeout());
if (lastResult.isSuccess()) {
lastResult = scene.inflate();
- if (lastResult.isSuccess()) {
+
+ boolean doNotRenderOnCreate = Boolean.TRUE.equals(
+ params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE));
+ if (lastResult.isSuccess() && !doNotRenderOnCreate) {
lastResult = scene.render(true /*freshRender*/);
}
}
@@ -531,7 +537,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
* Note that while this can be called several time, the first call to {@link #cleanupThread()}
* will do the clean-up, and make the thread unable to do further scene actions.
*/
- public static void prepareThread() {
+ public synchronized static void prepareThread() {
// we need to make sure the Looper has been initialized for this thread.
// this is required for View that creates Handler objects.
if (Looper.myLooper() == null) {
@@ -545,7 +551,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
* Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
* call to this will prevent the thread from doing further scene actions
*/
- public static void cleanupThread() {
+ public synchronized static void cleanupThread() {
// clean up the looper
Looper_Accessor.cleanupThread();
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index fea633e7036d..3ac1889dbe72 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -24,6 +24,7 @@ import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.layoutlib.bridge.impl.RenderSessionImpl;
import com.android.tools.layoutlib.java.System_Delegate;
+import com.android.util.PropertiesMap;
import android.view.View;
import android.view.ViewGroup;
@@ -70,20 +71,8 @@ public class BridgeRenderSession extends RenderSession {
}
@Override
- public Map<String, String> getDefaultProperties(Object viewObject) {
- return mSession.getDefaultProperties(viewObject);
- }
-
- @Override
- public Result getProperty(Object objectView, String propertyName) {
- // pass
- return super.getProperty(objectView, propertyName);
- }
-
- @Override
- public Result setProperty(Object objectView, String propertyName, String propertyValue) {
- // pass
- return super.setProperty(objectView, propertyName, propertyValue);
+ public Map<Object, PropertiesMap> getDefaultProperties() {
+ return mSession.getDefaultProperties();
}
@Override
@@ -203,7 +192,9 @@ public class BridgeRenderSession extends RenderSession {
@Override
public void setElapsedFrameTimeNanos(long nanos) {
- mSession.setElapsedFrameTimeNanos(nanos);
+ if (mSession != null) {
+ mSession.setElapsedFrameTimeNanos(nanos);
+ }
}
@Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 2fd1f18a369f..616cb5761402 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -32,6 +32,8 @@ import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.Stack;
import com.android.resources.ResourceType;
import com.android.util.Pair;
+import com.android.util.PropertiesMap;
+import com.android.util.PropertiesMap.Property;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -73,6 +75,7 @@ import android.os.Looper;
import android.os.Parcel;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -109,13 +112,13 @@ import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_AP
public final class BridgeContext extends Context {
/** The map adds cookies to each view so that IDE can link xml tags to views. */
- private final HashMap<View, Object> mViewKeyMap = new HashMap<View, Object>();
+ private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
/**
* In some cases, when inflating an xml, some objects are created. Then later, the objects are
* converted to views. This map stores the mapping from objects to cookies which can then be
* used to populate the mViewKeyMap.
*/
- private final HashMap<Object, Object> mViewKeyHelpMap = new HashMap<Object, Object>();
+ private final HashMap<Object, Object> mViewKeyHelpMap = new HashMap<>();
private final BridgeAssetManager mAssets;
private Resources mSystemResources;
private final Object mProjectKey;
@@ -126,12 +129,12 @@ public final class BridgeContext extends Context {
private final LayoutlibCallback mLayoutlibCallback;
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
- private final HashMap<View, Integer> mScrollYPos = new HashMap<View, Integer>();
+ private final HashMap<View, Integer> mScrollYPos = new HashMap<>();
+ private final HashMap<View, Integer> mScrollXPos = new HashMap<>();
private Resources.Theme mTheme;
- private final Map<Object, Map<String, String>> mDefaultPropMaps =
- new IdentityHashMap<Object, Map<String,String>>();
+ private final Map<Object, PropertiesMap> mDefaultPropMaps = new IdentityHashMap<>();
// maps for dynamically generated id representing style objects (StyleResourceValue)
@Nullable
@@ -140,19 +143,17 @@ public final class BridgeContext extends Context {
private int mDynamicIdGenerator = 0x02030000; // Base id for R.style in custom namespace
// cache for TypedArray generated from StyleResourceValue object
- private Map<int[], Map<List<StyleResourceValue>, Map<Integer, BridgeTypedArray>>>
- mTypedArrayCache;
+ private TypedArrayCache mTypedArrayCache;
private BridgeInflater mBridgeInflater;
private BridgeContentResolver mContentResolver;
- private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>();
+ private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<>();
private SharedPreferences mSharedPreferences;
private ClassLoader mClassLoader;
private IBinder mBinder;
private PackageManager mPackageManager;
-
/**
* Some applications that target both pre API 17 and post API 17, set the newer attrs to
* reference the older ones. For example, android:paddingStart will resolve to
@@ -160,7 +161,7 @@ public final class BridgeContext extends Context {
* This a map from value to attribute name. Warning for missing references shouldn't be logged
* if value and attr name pair is the same as an entry in this map.
*/
- private static Map<String, String> RTL_ATTRS = new HashMap<String, String>(10);
+ private static Map<String, String> RTL_ATTRS = new HashMap<>(10);
static {
RTL_ATTRS.put("?android:attr/paddingLeft", "paddingStart");
@@ -275,8 +276,8 @@ public final class BridgeContext extends Context {
return mRenderResources;
}
- public Map<String, String> getDefaultPropMap(Object key) {
- return mDefaultPropMaps.get(key);
+ public Map<Object, PropertiesMap> getDefaultProperties() {
+ return mDefaultPropMaps;
}
public Configuration getConfiguration() {
@@ -323,11 +324,11 @@ public final class BridgeContext extends Context {
return mParserStack.get(mParserStack.size() - 2);
}
- public boolean resolveThemeAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
- Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resid);
+ public boolean resolveThemeAttribute(int resId, TypedValue outValue, boolean resolveRefs) {
+ Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resId);
boolean isFrameworkRes = true;
if (resourceInfo == null) {
- resourceInfo = mLayoutlibCallback.resolveResourceId(resid);
+ resourceInfo = mLayoutlibCallback.resolveResourceId(resId);
isFrameworkRes = false;
}
@@ -600,23 +601,20 @@ public final class BridgeContext extends Context {
@Override
public final BridgeTypedArray obtainStyledAttributes(int[] attrs) {
- // No style is specified here, so create the typed array based on the default theme
- // and the styles already applied to it. A null value of style indicates that the default
- // theme should be used.
- return createStyleBasedTypedArray(null, attrs);
+ return obtainStyledAttributes(0, attrs);
}
@Override
- public final BridgeTypedArray obtainStyledAttributes(int resid, int[] attrs)
+ public final BridgeTypedArray obtainStyledAttributes(int resId, int[] attrs)
throws Resources.NotFoundException {
StyleResourceValue style = null;
// get the StyleResourceValue based on the resId;
- if (resid != 0) {
- style = getStyleByDynamicId(resid);
+ if (resId != 0) {
+ style = getStyleByDynamicId(resId);
if (style == null) {
// In some cases, style may not be a dynamic id, so we do a full search.
- ResourceReference ref = resolveId(resid);
+ ResourceReference ref = resolveId(resId);
if (ref != null) {
style = mRenderResources.getStyle(ref.getName(), ref.isFramework());
}
@@ -627,41 +625,34 @@ public final class BridgeContext extends Context {
}
}
- // The map is from
- // attrs (int[]) -> context's current themes (List<StyleRV>) -> resid (int) -> typed array.
if (mTypedArrayCache == null) {
- mTypedArrayCache = new IdentityHashMap<int[],
- Map<List<StyleResourceValue>, Map<Integer, BridgeTypedArray>>>();
- }
-
- // get the 2nd map
- Map<List<StyleResourceValue>, Map<Integer, BridgeTypedArray>> map2 =
- mTypedArrayCache.get(attrs);
- if (map2 == null) {
- map2 = new HashMap<List<StyleResourceValue>, Map<Integer, BridgeTypedArray>>();
- mTypedArrayCache.put(attrs, map2);
+ mTypedArrayCache = new TypedArrayCache();
}
- // get the 3rd map
List<StyleResourceValue> currentThemes = mRenderResources.getAllThemes();
- Map<Integer, BridgeTypedArray> map3 = map2.get(currentThemes);
- if (map3 == null) {
- map3 = new HashMap<Integer, BridgeTypedArray>();
- // Create a copy of the list before adding it to the map. This allows reusing the
- // existing list.
- currentThemes = new ArrayList<StyleResourceValue>(currentThemes);
- map2.put(currentThemes, map3);
- }
- // get the array from the 3rd map
- BridgeTypedArray ta = map3.get(resid);
+ Pair<BridgeTypedArray, PropertiesMap> typeArrayAndPropertiesPair =
+ mTypedArrayCache.get(attrs, currentThemes, resId);
- if (ta == null) {
- ta = createStyleBasedTypedArray(style, attrs);
- map3.put(resid, ta);
+ if (typeArrayAndPropertiesPair == null) {
+ typeArrayAndPropertiesPair = createStyleBasedTypedArray(style, attrs);
+ mTypedArrayCache.put(attrs, currentThemes, resId, typeArrayAndPropertiesPair);
}
-
- return ta;
+ // Add value to defaultPropsMap if needed
+ if (typeArrayAndPropertiesPair.getSecond() != null) {
+ BridgeXmlBlockParser parser = getCurrentParser();
+ Object key = parser != null ? parser.getViewCookie() : null;
+ if (key != null) {
+ PropertiesMap defaultPropMap = mDefaultPropMaps.get(key);
+ if (defaultPropMap == null) {
+ defaultPropMap = typeArrayAndPropertiesPair.getSecond();
+ mDefaultPropMaps.put(key, defaultPropMap);
+ } else {
+ defaultPropMap.putAll(typeArrayAndPropertiesPair.getSecond());
+ }
+ }
+ }
+ return typeArrayAndPropertiesPair.getFirst();
}
@Override
@@ -673,7 +664,7 @@ public final class BridgeContext extends Context {
public BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
int defStyleAttr, int defStyleRes) {
- Map<String, String> defaultPropMap = null;
+ PropertiesMap defaultPropMap = null;
boolean isPlatformFile = true;
// Hint: for XmlPullParser, attach source //DEVICE_SRC/dalvik/libcore/xml/src/java
@@ -687,7 +678,7 @@ public final class BridgeContext extends Context {
if (key != null) {
defaultPropMap = mDefaultPropMaps.get(key);
if (defaultPropMap == null) {
- defaultPropMap = new HashMap<String, String>();
+ defaultPropMap = new PropertiesMap();
mDefaultPropMaps.put(key, defaultPropMap);
}
}
@@ -741,16 +732,10 @@ public final class BridgeContext extends Context {
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
"Failed to find the style corresponding to the id " + defStyleAttr, null);
} else {
- if (defaultPropMap != null) {
- String defStyleName = defStyleAttribute.getFirst();
- if (defStyleAttribute.getSecond()) {
- defStyleName = "android:" + defStyleName;
- }
- defaultPropMap.put("style", defStyleName);
- }
+ String defStyleName = defStyleAttribute.getFirst();
// look for the style in the current theme, and its parent:
- ResourceValue item = mRenderResources.findItemInTheme(defStyleAttribute.getFirst(),
+ ResourceValue item = mRenderResources.findItemInTheme(defStyleName,
defStyleAttribute.getSecond());
if (item != null) {
@@ -760,6 +745,12 @@ public final class BridgeContext extends Context {
if (item instanceof StyleResourceValue) {
defStyleValues = (StyleResourceValue) item;
}
+ if (defaultPropMap != null) {
+ if (defStyleAttribute.getSecond()) {
+ defStyleName = "android:" + defStyleName;
+ }
+ defaultPropMap.put("style", new Property(defStyleName, item.getValue()));
+ }
} else {
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
String.format(
@@ -786,7 +777,8 @@ public final class BridgeContext extends Context {
item = mRenderResources.getStyle(value.getSecond(), isFrameworkRes);
if (item != null) {
if (defaultPropMap != null) {
- defaultPropMap.put("style", item.getName());
+ String name = item.getName();
+ defaultPropMap.put("style", new Property(name, name));
}
defStyleValues = item;
@@ -865,13 +857,16 @@ public final class BridgeContext extends Context {
// if we found a value, we make sure this doesn't reference another value.
// So we resolve it.
if (resValue != null) {
- // put the first default value, before the resolution.
+ String preResolve = resValue.getValue();
+ resValue = mRenderResources.resolveResValue(resValue);
+
if (defaultPropMap != null) {
- defaultPropMap.put(attrName, resValue.getValue());
+ defaultPropMap.put(
+ frameworkAttr ? SdkConstants.PREFIX_ANDROID + attrName :
+ attrName,
+ new Property(preResolve, resValue.getValue()));
}
- resValue = mRenderResources.resolveResValue(resValue);
-
// If the value is a reference to another theme attribute that doesn't
// exist, we should log a warning and omit it.
String val = resValue.getValue();
@@ -935,40 +930,45 @@ public final class BridgeContext extends Context {
*
* @see #obtainStyledAttributes(int, int[])
*/
- private BridgeTypedArray createStyleBasedTypedArray(@Nullable StyleResourceValue style,
- int[] attrs) throws Resources.NotFoundException {
-
+ private Pair<BridgeTypedArray, PropertiesMap> createStyleBasedTypedArray(
+ @Nullable StyleResourceValue style, int[] attrs) throws Resources.NotFoundException {
List<Pair<String, Boolean>> attributes = searchAttrs(attrs);
- BridgeTypedArray ta = Resources_Delegate.newTypeArray(mSystemResources, attrs.length,
- false);
+ BridgeTypedArray ta =
+ Resources_Delegate.newTypeArray(mSystemResources, attrs.length, false);
+ PropertiesMap defaultPropMap = new PropertiesMap();
// for each attribute, get its name so that we can search it in the style
- for (int i = 0 ; i < attrs.length ; i++) {
+ for (int i = 0; i < attrs.length; i++) {
Pair<String, Boolean> attribute = attributes.get(i);
if (attribute != null) {
// look for the value in the given style
ResourceValue resValue;
+ String attrName = attribute.getFirst();
+ boolean frameworkAttr = attribute.getSecond();
if (style != null) {
- resValue = mRenderResources.findItemInStyle(style, attribute.getFirst(),
- attribute.getSecond());
+ resValue = mRenderResources.findItemInStyle(style, attrName, frameworkAttr);
} else {
- resValue = mRenderResources.findItemInTheme(attribute.getFirst(),
- attribute.getSecond());
+ resValue = mRenderResources.findItemInTheme(attrName, frameworkAttr);
}
if (resValue != null) {
+ // Add it to defaultPropMap before resolving
+ String preResolve = resValue.getValue();
// resolve it to make sure there are no references left.
- ta.bridgeSetValue(i, attribute.getFirst(), attribute.getSecond(),
- mRenderResources.resolveResValue(resValue));
+ resValue = mRenderResources.resolveResValue(resValue);
+ ta.bridgeSetValue(i, attrName, frameworkAttr, resValue);
+ defaultPropMap.put(
+ frameworkAttr ? SdkConstants.ANDROID_PREFIX + attrName : attrName,
+ new Property(preResolve, resValue.getValue()));
}
}
}
ta.sealArray();
- return ta;
+ return Pair.of(ta, defaultPropMap);
}
/**
@@ -980,7 +980,7 @@ public final class BridgeContext extends Context {
* @return List of attribute information.
*/
private List<Pair<String, Boolean>> searchAttrs(int[] attrs) {
- List<Pair<String, Boolean>> results = new ArrayList<Pair<String, Boolean>>(attrs.length);
+ List<Pair<String, Boolean>> results = new ArrayList<>(attrs.length);
// for each attribute, get its name so that we can search it in the style
for (int attr : attrs) {
@@ -1009,7 +1009,7 @@ public final class BridgeContext extends Context {
* @return A (name, isFramework) pair describing the attribute if found. Returns null
* if nothing is found.
*/
- public Pair<String, Boolean> searchAttr(int attr) {
+ private Pair<String, Boolean> searchAttr(int attr) {
Pair<ResourceType, String> info = Bridge.resolveResourceId(attr);
if (info != null) {
return Pair.of(info.getSecond(), Boolean.TRUE);
@@ -1026,8 +1026,8 @@ public final class BridgeContext extends Context {
public int getDynamicIdByStyle(StyleResourceValue resValue) {
if (mDynamicIdToStyleMap == null) {
// create the maps.
- mDynamicIdToStyleMap = new HashMap<Integer, StyleResourceValue>();
- mStyleToDynamicIdMap = new HashMap<StyleResourceValue, Integer>();
+ mDynamicIdToStyleMap = new HashMap<>();
+ mStyleToDynamicIdMap = new HashMap<>();
}
// look for an existing id
@@ -1135,6 +1135,11 @@ public final class BridgeContext extends Context {
public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
return false;
}
+
+ @Override
+ public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ResultReceiver resultReceiver) {
+ }
};
}
return mBinder;
@@ -1252,6 +1257,12 @@ public final class BridgeContext extends Context {
}
@Override
+ public boolean moveDatabaseFrom(Context sourceContext, String name) {
+ // pass
+ return false;
+ }
+
+ @Override
public boolean deleteDatabase(String arg0) {
// pass
return false;
@@ -1364,6 +1375,18 @@ public final class BridgeContext extends Context {
}
@Override
+ public File getSharedPreferencesPath(String name) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getDataDir() {
+ // pass
+ return null;
+ }
+
+ @Override
public File getFilesDir() {
// pass
return null;
@@ -1411,13 +1434,15 @@ public final class BridgeContext extends Context {
}
@Override
- public File getSharedPrefsFile(String name) {
- // pass
- return null;
+ public SharedPreferences getSharedPreferences(String arg0, int arg1) {
+ if (mSharedPreferences == null) {
+ mSharedPreferences = new BridgeSharedPreferences();
+ }
+ return mSharedPreferences;
}
@Override
- public SharedPreferences getSharedPreferences(String arg0, int arg1) {
+ public SharedPreferences getSharedPreferences(File arg0, int arg1) {
if (mSharedPreferences == null) {
mSharedPreferences = new BridgeSharedPreferences();
}
@@ -1425,6 +1450,18 @@ public final class BridgeContext extends Context {
}
@Override
+ public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public boolean deleteSharedPreferences(String name) {
+ // pass
+ return false;
+ }
+
+ @Override
public Drawable getWallpaper() {
// pass
return null;
@@ -1625,6 +1662,11 @@ public final class BridgeContext extends Context {
}
@Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
+ // pass
+ }
+
+ @Override
public void sendStickyOrderedBroadcastAsUser(Intent intent,
UserHandle user, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
@@ -1757,6 +1799,12 @@ public final class BridgeContext extends Context {
}
@Override
+ public Display getDisplay() {
+ // pass
+ return null;
+ }
+
+ @Override
public int getUserId() {
return 0; // not used
}
@@ -1793,4 +1841,92 @@ public final class BridgeContext extends Context {
Integer pos = mScrollYPos.get(view);
return pos != null ? pos : 0;
}
+
+ public void setScrollXPos(@NonNull View view, int scrollPos) {
+ mScrollXPos.put(view, scrollPos);
+ }
+
+ public int getScrollXPos(@NonNull View view) {
+ Integer pos = mScrollXPos.get(view);
+ return pos != null ? pos : 0;
+ }
+
+ @Override
+ public Context createDeviceProtectedStorageContext() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Context createCredentialProtectedStorageContext() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public boolean isDeviceProtectedStorage() {
+ return false;
+ }
+
+ @Override
+ public boolean isCredentialProtectedStorage() {
+ return false;
+ }
+
+ /**
+ * The cached value depends on
+ * <ol>
+ * <li>{@code int[]}: the attributes for which TypedArray is created </li>
+ * <li>{@code List<StyleResourceValue>}: the themes set on the context at the time of
+ * creation of the TypedArray</li>
+ * <li>{@code Integer}: the default style used at the time of creation</li>
+ * </ol>
+ *
+ * The class is created by using nested maps resolving one dependency at a time.
+ * <p/>
+ * The final value of the nested maps is a pair of the typed array and a map of properties
+ * that should be added to {@link #mDefaultPropMaps}, if needed.
+ */
+ private static class TypedArrayCache {
+
+ private Map<int[],
+ Map<List<StyleResourceValue>,
+ Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>> mCache;
+
+ public TypedArrayCache() {
+ mCache = new IdentityHashMap<>();
+ }
+
+ public Pair<BridgeTypedArray, PropertiesMap> get(int[] attrs,
+ List<StyleResourceValue> themes, int resId) {
+ Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>
+ cacheFromThemes = mCache.get(attrs);
+ if (cacheFromThemes != null) {
+ Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId =
+ cacheFromThemes.get(themes);
+ if (cacheFromResId != null) {
+ return cacheFromResId.get(resId);
+ }
+ }
+ return null;
+ }
+
+ public void put(int[] attrs, List<StyleResourceValue> themes, int resId,
+ Pair<BridgeTypedArray, PropertiesMap> value) {
+ Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>
+ cacheFromThemes = mCache.get(attrs);
+ if (cacheFromThemes == null) {
+ cacheFromThemes = new HashMap<>();
+ mCache.put(attrs, cacheFromThemes);
+ }
+ Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId =
+ cacheFromThemes.get(themes);
+ if (cacheFromResId == null) {
+ cacheFromResId = new HashMap<>();
+ cacheFromThemes.put(themes, cacheFromResId);
+ }
+ cacheFromResId.put(resId, value);
+ }
+
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 8899e53648d2..3f276c9375ba 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -184,13 +184,6 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
}
@Override
- public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext,
- EditorInfo attribute, int controlFlags) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
public boolean switchToLastInputMethod(IBinder arg0) throws RemoteException {
// TODO Auto-generated method stub
return false;
@@ -226,9 +219,17 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
}
@Override
- public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
- int controlFlags, int softInputMode, int windowFlags, EditorInfo attribute,
- IInputContext inputContext) throws RemoteException {
+ public void clearLastInputMethodWindowForTransition(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public InputBindResult startInputOrWindowGainedFocus(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
+ int windowFlags, EditorInfo attribute, IInputContext inputContext,
+ /* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags)
+ throws RemoteException {
// TODO Auto-generated method stub
return null;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index f04654eded0f..b3ed9e1a0164 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -16,6 +16,7 @@
package com.android.layoutlib.bridge.android;
+import android.annotation.NonNull;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
@@ -23,7 +24,7 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ContainerEncryptionParams;
+import android.content.pm.EphemeralApplicationInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
@@ -32,7 +33,6 @@ import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
-import android.content.pm.ManifestDigest;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageItemInfo;
@@ -42,7 +42,6 @@ import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.pm.VerificationParams;
import android.content.pm.VerifierDeviceIdentity;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -50,6 +49,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
import java.util.List;
@@ -65,6 +65,12 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
public String[] currentToCanonicalPackageNames(String[] names) {
return new String[0];
}
@@ -90,7 +96,22 @@ public class BridgePackageManager extends PackageManager {
}
@Override
- public int getPackageUid(String packageName, int userHandle) throws NameNotFoundException {
+ public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException {
+ return new int[0];
+ }
+
+ @Override
+ public int getPackageUid(String packageName, int flags) throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int getPackageUidAsUser(String packageName, int userHandle) throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int getPackageUidAsUser(String packageName, int flags, int userHandle) throws NameNotFoundException {
return 0;
}
@@ -123,6 +144,12 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
public ActivityInfo getActivityInfo(ComponentName component, int flags)
throws NameNotFoundException {
return null;
@@ -157,7 +184,7 @@ public class BridgePackageManager extends PackageManager {
}
@Override
- public List<PackageInfo> getInstalledPackages(int flags, int userId) {
+ public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
return null;
}
@@ -245,11 +272,51 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public List<EphemeralApplicationInfo> getEphemeralApplications() {
+ return null;
+ }
+
+ @Override
+ public Drawable getEphemeralApplicationIcon(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public byte[] getEphemeralCookie() {
+ return new byte[0];
+ }
+
+ @Override
+ public boolean isEphemeralApplication() {
+ return false;
+ }
+
+ @Override
+ public int getEphemeralCookieMaxSizeBytes() {
+ return 0;
+ }
+
+ @Override
+ public boolean setEphemeralCookie(@NonNull byte[] cookie) {
+ return false;
+ }
+
+ @Override
public String[] getSystemSharedLibraryNames() {
return new String[0];
}
@Override
+ public String getServicesSystemSharedLibraryPackageName() {
+ return null;
+ }
+
+ @Override
+ public @NonNull String getSharedSystemSharedLibraryPackageName() {
+ return null;
+ }
+
+ @Override
public FeatureInfo[] getSystemAvailableFeatures() {
return new FeatureInfo[0];
}
@@ -260,6 +327,11 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public boolean hasSystemFeature(String name, int version) {
+ return false;
+ }
+
+ @Override
public ResolveInfo resolveActivity(Intent intent, int flags) {
return null;
}
@@ -291,7 +363,7 @@ public class BridgePackageManager extends PackageManager {
}
@Override
- public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags, int userId) {
+ public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, int flags, int userId) {
return null;
}
@@ -418,6 +490,12 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public Drawable getManagedUserBadgedDrawable(Drawable drawable, Rect badgeLocation,
+ int badgeDensity) {
+ return null;
+ }
+
+ @Override
public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
return null;
}
@@ -434,6 +512,11 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
+ return null;
+ }
+
+ @Override
public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
return null;
}
@@ -482,36 +565,18 @@ public class BridgePackageManager extends PackageManager {
}
@Override
- public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
- int flags, String installerPackageName, Uri verificationURI,
- ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
- }
-
- @Override
- public void installPackageWithVerificationAndEncryption(Uri packageURI,
- IPackageInstallObserver observer, int flags, String installerPackageName,
- VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
- }
-
- @Override
public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
String installerPackageName) {
}
@Override
- public void installPackageWithVerification(Uri packageURI, PackageInstallObserver observer,
- int flags, String installerPackageName, Uri verificationURI,
- ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
- }
-
- @Override
- public void installPackageWithVerificationAndEncryption(Uri packageURI,
- PackageInstallObserver observer, int flags, String installerPackageName,
- VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
+ public int installExistingPackage(String packageName) throws NameNotFoundException {
+ return 0;
}
@Override
- public int installExistingPackage(String packageName) throws NameNotFoundException {
+ public int installExistingPackageAsUser(String packageName, int userId)
+ throws NameNotFoundException {
return 0;
}
@@ -530,12 +595,12 @@ public class BridgePackageManager extends PackageManager {
}
@Override
- public int getIntentVerificationStatus(String packageName, int userId) {
+ public int getIntentVerificationStatusAsUser(String packageName, int userId) {
return 0;
}
@Override
- public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
+ public boolean updateIntentVerificationStatusAsUser(String packageName, int status, int userId) {
return false;
}
@@ -550,12 +615,12 @@ public class BridgePackageManager extends PackageManager {
}
@Override
- public String getDefaultBrowserPackageName(int userId) {
+ public String getDefaultBrowserPackageNameAsUser(int userId) {
return null;
}
@Override
- public boolean setDefaultBrowserPackageName(String packageName, int userId) {
+ public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) {
return false;
}
@@ -568,6 +633,11 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer, int flags,
+ int userId) {
+ }
+
+ @Override
public String getInstallerPackageName(String packageName) {
return null;
}
@@ -581,6 +651,11 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public void deleteApplicationCacheFilesAsUser(String packageName, int userId,
+ IPackageDataObserver observer) {
+ }
+
+ @Override
public void freeStorageAndNotify(String volumeUuid, long freeStorageSize,
IPackageDataObserver observer) {
}
@@ -590,7 +665,7 @@ public class BridgePackageManager extends PackageManager {
}
@Override
- public void getPackageSizeInfo(String packageName, int userHandle,
+ public void getPackageSizeInfoAsUser(String packageName, int userHandle,
IPackageStatsObserver observer) {
}
@@ -651,6 +726,10 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public void flushPackageRestrictionsAsUser(int userId) {
+ }
+
+ @Override
public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
UserHandle userHandle) {
return false;
@@ -695,6 +774,17 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
+ int userId) {
+ return new String[]{};
+ }
+
+ @Override
+ public boolean isPackageSuspendedForUser(String packageName, int userId) {
+ return false;
+ }
+
+ @Override
public int getMoveStatus(int moveId) {
return 0;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
index 895f9c9eaeac..9f73d79ff142 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -86,7 +86,12 @@ public class BridgePowerManager implements IPowerManager {
}
@Override
- public void shutdown(boolean confirm, boolean wait) {
+ public void rebootSafeMode(boolean confirm, boolean wait) {
+ // pass for now.
+ }
+
+ @Override
+ public void shutdown(boolean confirm, String reason, boolean wait) {
// pass for now.
}
@@ -152,6 +157,11 @@ public class BridgePowerManager implements IPowerManager {
}
@Override
+ public boolean isLightDeviceIdleMode() throws RemoteException {
+ return false;
+ }
+
+ @Override
public boolean isScreenBrightnessBoosted() throws RemoteException {
return false;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index 771c3c85d2f5..a83f10087649 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -16,6 +16,8 @@
package com.android.layoutlib.bridge.android;
+import com.android.internal.os.IResultReceiver;
+
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
@@ -48,7 +50,8 @@ public final class BridgeWindow implements IWindow {
@Override
public void resized(Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, Rect rect6,
- boolean b, Configuration configuration) throws RemoteException {
+ boolean b, Configuration configuration, Rect rect7, boolean b2, boolean b3)
+ throws RemoteException {
// pass for now.
}
@@ -85,21 +88,23 @@ public final class BridgeWindow implements IWindow {
}
@Override
- public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
- int localValue, int localChanges) {
- // pass for now.
+ public void updatePointerIcon(float x, float y) {
+ // pass for now
}
@Override
- public void onAnimationStarted(int remainingFrameCount) {
+ public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
+ int localValue, int localChanges) {
+ // pass for now.
}
@Override
- public void onAnimationStopped() {
+ public void dispatchWindowShown() {
}
@Override
- public void dispatchWindowShown() {
+ public void requestAppKeyboardShortcuts(
+ IResultReceiver receiver, int deviceId) throws RemoteException {
}
@Override
@@ -107,4 +112,5 @@ public final class BridgeWindow implements IWindow {
// pass for now.
return null;
}
+
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index bea1f86907e0..7582fda6a0fb 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -89,12 +89,20 @@ public final class BridgeWindowSession implements IWindowSession {
@Override
public int relayout(IWindow iWindow, int i, LayoutParams layoutParams, int i2,
int i3, int i4, int i5, Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5,
- Rect rect6, Configuration configuration, Surface surface) throws RemoteException {
+ Rect rect6, Rect rect7, Configuration configuration, Surface surface)
+ throws RemoteException {
// pass for now.
return 0;
}
@Override
+ public void repositionChild(IWindow window, int left, int top, int right, int bottom,
+ long deferTransactionUntilFrame, Rect outFrame) {
+ // pass for now.
+ return;
+ }
+
+ @Override
public void performDeferredDestroy(IWindow window) {
// pass for now.
}
@@ -140,7 +148,7 @@ public final class BridgeWindowSession implements IWindowSession {
@Override
public boolean performDrag(IWindow window, IBinder dragToken,
- float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
ClipData data)
throws RemoteException {
// pass for now
@@ -148,11 +156,23 @@ public final class BridgeWindowSession implements IWindowSession {
}
@Override
+ public boolean startMovingTask(IWindow window, float startX, float startY)
+ throws RemoteException {
+ // pass for now
+ return false;
+ }
+
+ @Override
public void reportDropResult(IWindow window, boolean consumed) throws RemoteException {
// pass for now
}
@Override
+ public void cancelDragAndDrop(IBinder dragToken) throws RemoteException {
+ // pass for now
+ }
+
+ @Override
public void dragRecipientEntered(IWindow window) throws RemoteException {
// pass for now
}
@@ -211,4 +231,14 @@ public final class BridgeWindowSession implements IWindowSession {
public void pokeDrawLock(IBinder window) {
// pass for now.
}
+
+ @Override
+ public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
+ // pass for now.
+ }
+
+ @Override
+ public void updatePointerIcon(IWindow window) {
+ // pass for now.
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
index bd17a2fe6ca2..051de9055042 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
@@ -53,6 +53,12 @@ public final class RenderParamsFlags {
*/
public static final Key<Boolean> FLAG_KEY_XML_FILE_PARSER_SUPPORT =
new Key<Boolean>("xmlFileParser", Boolean.class);
+ /**
+ * To tell LayoutLib to not render when creating a new session. This allows controlling when the first
+ * layout rendering will happen.
+ */
+ public static final Key<Boolean> FLAG_DO_NOT_RENDER_ON_CREATE =
+ new Key<Boolean>("doNotRenderOnCreate", Boolean.class);
// Disallow instances.
private RenderParamsFlags() {}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java
new file mode 100644
index 000000000000..2b4661b08210
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.android.graphics;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.NinePatch;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Picture;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * Canvas implementation that does not do any rendering
+ */
+public class NopCanvas extends Canvas {
+ public NopCanvas() {
+ super();
+ }
+
+ @Override
+ public boolean isHardwareAccelerated() {
+ // Return true so there is no allocations for the software renderer in the constructor
+ return true;
+ }
+
+ @Override
+ public int save() {
+ return 0;
+ }
+
+ @Override
+ public int save(int saveFlags) {
+ return 0;
+ }
+
+ @Override
+ public int saveLayer(RectF bounds, Paint paint, int saveFlags) {
+ return 0;
+ }
+
+ @Override
+ public int saveLayer(RectF bounds, Paint paint) {
+ return 0;
+ }
+
+ @Override
+ public int saveLayer(float left, float top, float right, float bottom, Paint paint,
+ int saveFlags) {
+ return 0;
+ }
+
+ @Override
+ public int saveLayer(float left, float top, float right, float bottom, Paint paint) {
+ return 0;
+ }
+
+ @Override
+ public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) {
+ return 0;
+ }
+
+ @Override
+ public int saveLayerAlpha(RectF bounds, int alpha) {
+ return 0;
+ }
+
+ @Override
+ public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
+ int saveFlags) {
+ return 0;
+ }
+
+ @Override
+ public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha) {
+ return 0;
+ }
+
+ @Override
+ public void restore() {
+ }
+
+ @Override
+ public int getSaveCount() {
+ return 0;
+ }
+
+ @Override
+ public void restoreToCount(int saveCount) {
+ }
+
+ @Override
+ public void drawRGB(int r, int g, int b) {
+ }
+
+ @Override
+ public void drawARGB(int a, int r, int g, int b) {
+ }
+
+ @Override
+ public void drawColor(int color) {
+ }
+
+ @Override
+ public void drawColor(int color, Mode mode) {
+ }
+
+ @Override
+ public void drawPaint(Paint paint) {
+ }
+
+ @Override
+ public void drawPoints(float[] pts, int offset, int count, Paint paint) {
+ }
+
+ @Override
+ public void drawPoints(float[] pts, Paint paint) {
+ }
+
+ @Override
+ public void drawPoint(float x, float y, Paint paint) {
+ }
+
+ @Override
+ public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
+ }
+
+ @Override
+ public void drawLines(float[] pts, int offset, int count, Paint paint) {
+ }
+
+ @Override
+ public void drawLines(float[] pts, Paint paint) {
+ }
+
+ @Override
+ public void drawRect(RectF rect, Paint paint) {
+ }
+
+ @Override
+ public void drawRect(Rect r, Paint paint) {
+ }
+
+ @Override
+ public void drawRect(float left, float top, float right, float bottom, Paint paint) {
+ }
+
+ @Override
+ public void drawOval(RectF oval, Paint paint) {
+ }
+
+ @Override
+ public void drawOval(float left, float top, float right, float bottom, Paint paint) {
+ }
+
+ @Override
+ public void drawCircle(float cx, float cy, float radius, Paint paint) {
+ }
+
+ @Override
+ public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
+ Paint paint) {
+ }
+
+ @Override
+ public void drawArc(float left, float top, float right, float bottom, float startAngle,
+ float sweepAngle, boolean useCenter, Paint paint) {
+ }
+
+ @Override
+ public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
+ }
+
+ @Override
+ public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
+ Paint paint) {
+ }
+
+ @Override
+ public void drawPath(Path path, Paint paint) {
+ }
+
+ @Override
+ protected void throwIfCannotDraw(Bitmap bitmap) {
+ }
+
+ @Override
+ public void drawPatch(NinePatch patch, Rect dst, Paint paint) {
+ }
+
+ @Override
+ public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
+ }
+
+ @Override
+ public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
+ }
+
+ @Override
+ public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
+ }
+
+ @Override
+ public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
+ }
+
+ @Override
+ public void drawBitmap(int[] colors, int offset, int stride, float x, float y, int width,
+ int height, boolean hasAlpha, Paint paint) {
+ }
+
+ @Override
+ public void drawBitmap(int[] colors, int offset, int stride, int x, int y, int width,
+ int height, boolean hasAlpha, Paint paint) {
+ }
+
+ @Override
+ public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
+ }
+
+ @Override
+ public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts,
+ int vertOffset, int[] colors, int colorOffset, Paint paint) {
+ }
+
+ @Override
+ public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
+ float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices,
+ int indexOffset, int indexCount, Paint paint) {
+ }
+
+ @Override
+ public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
+ }
+
+ @Override
+ public void drawText(String text, float x, float y, Paint paint) {
+ }
+
+ @Override
+ public void drawText(String text, int start, int end, float x, float y, Paint paint) {
+ }
+
+ @Override
+ public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
+ }
+
+ @Override
+ public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
+ float x, float y, boolean isRtl, Paint paint) {
+ }
+
+ @Override
+ public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
+ float x, float y, boolean isRtl, Paint paint) {
+ }
+
+ @Override
+ public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
+ }
+
+ @Override
+ public void drawPosText(String text, float[] pos, Paint paint) {
+ }
+
+ @Override
+ public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset,
+ float vOffset, Paint paint) {
+ }
+
+ @Override
+ public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
+ }
+
+ @Override
+ public void drawPicture(Picture picture) {
+ }
+
+ @Override
+ public void drawPicture(Picture picture, RectF dst) {
+ }
+
+ @Override
+ public void drawPicture(Picture picture, Rect dst) {
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
index 7e5ae8d8c756..30317012fb52 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
@@ -63,4 +63,9 @@ public class WindowManagerImpl implements WindowManager {
public void removeViewImmediate(View arg0) {
// pass
}
+
+ @Override
+ public void requestAppKeyboardShortcuts(
+ KeyboardShortcutsReceiver receiver, int deviceId) {
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
index af6ba2445309..2cdc647b7996 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
@@ -43,6 +43,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowCallback;
import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
import android.widget.Toolbar;
import android.widget.Toolbar_Accessor;
@@ -196,11 +197,16 @@ public abstract class FrameworkActionBarWrapper {
@Override
protected void inflateMenus() {
super.inflateMenus();
- // Inflating the menus doesn't initialize the ActionMenuPresenter. Setting a fake menu
- // and then setting it back does the trick.
+ // Inflating the menus isn't enough. ActionMenuPresenter needs to be initialized too.
MenuBuilder menu = getMenuBuilder();
DecorToolbar decorToolbar = getDecorToolbar();
+ // Setting a menu different from the above initializes the presenter.
decorToolbar.setMenu(new MenuBuilder(getActionMenuContext()), null);
+ // ActionMenuView needs to be recreated to be able to set the menu back.
+ ActionMenuPresenter presenter = getActionMenuPresenter();
+ if (presenter != null) {
+ presenter.setMenuView(new ActionMenuView(getPopupContext()));
+ }
decorToolbar.setMenu(menu, null);
}
@@ -255,7 +261,7 @@ public abstract class FrameworkActionBarWrapper {
@NonNull ActionBarView actionBarView) {
super(context, callback, new WindowDecorActionBar(decorContentRoot));
mActionBarView = actionBarView;
- mActionBar = ((WindowDecorActionBar) super.mActionBar);
+ mActionBar = (WindowDecorActionBar) super.mActionBar;
mDecorContentRoot = decorContentRoot;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
index baf2e2e11564..c59b1a66bb02 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
@@ -22,9 +22,11 @@ import com.android.layoutlib.bridge.util.SparseWeakArray;
import android.annotation.Nullable;
import android.util.SparseArray;
+import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Manages native delegates.
@@ -73,14 +75,14 @@ import java.util.List;
public final class DelegateManager<T> {
@SuppressWarnings("FieldCanBeLocal")
private final Class<T> mClass;
- private final SparseWeakArray<T> mDelegates = new SparseWeakArray<T>();
+ private static final SparseWeakArray<Object> sDelegates = new SparseWeakArray<>();
/** list used to store delegates when their main object holds a reference to them.
* This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed
* @see #addNewDelegate(Object)
* @see #removeJavaReferenceFor(long)
*/
- private final List<T> mJavaReferences = new ArrayList<T>();
- private int mDelegateCounter = 0;
+ private static final List<Object> sJavaReferences = new ArrayList<>();
+ private static final AtomicLong sDelegateCounter = new AtomicLong(1);
public DelegateManager(Class<T> theClass) {
mClass = theClass;
@@ -97,9 +99,12 @@ public final class DelegateManager<T> {
* @return the delegate or null if not found.
*/
@Nullable
- public synchronized T getDelegate(long native_object) {
+ public T getDelegate(long native_object) {
if (native_object > 0) {
- T delegate = mDelegates.get(native_object);
+ Object delegate;
+ synchronized (DelegateManager.class) {
+ delegate = sDelegates.get(native_object);
+ }
if (Debug.DEBUG) {
if (delegate == null) {
@@ -109,7 +114,8 @@ public final class DelegateManager<T> {
}
assert delegate != null;
- return delegate;
+ //noinspection unchecked
+ return (T)delegate;
}
return null;
}
@@ -119,12 +125,13 @@ public final class DelegateManager<T> {
* @param newDelegate the delegate to add
* @return a unique native int to identify the delegate
*/
- public synchronized long addNewDelegate(T newDelegate) {
- long native_object = ++mDelegateCounter;
-
- mDelegates.put(native_object, newDelegate);
- assert !mJavaReferences.contains(newDelegate);
- mJavaReferences.add(newDelegate);
+ public long addNewDelegate(T newDelegate) {
+ long native_object = sDelegateCounter.getAndIncrement();
+ synchronized (DelegateManager.class) {
+ sDelegates.put(native_object, newDelegate);
+ assert !sJavaReferences.contains(newDelegate);
+ sJavaReferences.add(newDelegate);
+ }
if (Debug.DEBUG) {
System.out.println(
@@ -140,14 +147,23 @@ public final class DelegateManager<T> {
* Removes the main reference on the given delegate.
* @param native_object the native integer representing the delegate.
*/
- public synchronized void removeJavaReferenceFor(long native_object) {
- T delegate = getDelegate(native_object);
+ public void removeJavaReferenceFor(long native_object) {
+ synchronized (DelegateManager.class) {
+ T delegate = getDelegate(native_object);
- if (Debug.DEBUG) {
- System.out.println("Removing main Java ref on " + mClass.getSimpleName() +
- " with int " + native_object);
+ if (Debug.DEBUG) {
+ System.out.println("Removing main Java ref on " + mClass.getSimpleName() +
+ " with int " + native_object);
+ }
+
+ sJavaReferences.remove(delegate);
}
+ }
- mJavaReferences.remove(delegate);
+ public synchronized static void dump(PrintStream out) {
+ for (Object reference : sJavaReferences) {
+ int idx = sDelegates.indexOfValue(reference);
+ out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName());
+ }
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
index cbd041593d95..1afd90d39f31 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
@@ -23,6 +23,7 @@ import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.bars.AppCompatActionBar;
import com.android.layoutlib.bridge.bars.BridgeActionBar;
import com.android.layoutlib.bridge.bars.Config;
@@ -232,8 +233,10 @@ class Layout extends RelativeLayout {
private BridgeActionBar createActionBar(@NonNull BridgeContext context,
@NonNull SessionParams params) {
+ boolean isMenu = "menu".equals(params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG));
+
BridgeActionBar actionBar;
- if (mBuilder.isThemeAppCompat()) {
+ if (mBuilder.isThemeAppCompat() && !isMenu) {
actionBar = new AppCompatActionBar(context, params);
} else {
actionBar = new FrameworkActionBar(context, params);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index 4e4fcd0aff2d..0c537533479e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -122,7 +122,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
// build the context
mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
- mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(),
+ mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(mParams),
mParams.getTargetSdkVersion(), mParams.isRtlSupported());
setUp();
@@ -130,7 +130,6 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
return SUCCESS.createResult();
}
-
/**
* Prepares the scene for action.
* <p>
@@ -320,10 +319,11 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
}
}
- private Configuration getConfiguration() {
+ // VisibleForTesting
+ public static Configuration getConfiguration(RenderParams params) {
Configuration config = new Configuration();
- HardwareConfig hardwareConfig = mParams.getHardwareConfig();
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
ScreenSize screenSize = hardwareConfig.getScreenSize();
if (screenSize != null) {
@@ -392,7 +392,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
} else {
config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_UNDEFINED;
}
- String locale = getParams().getLocale();
+ String locale = params.getLocale();
if (locale != null && !locale.isEmpty()) config.locale = new Locale(locale);
// TODO: fill in more config info.
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 99af226e4c4c..a8077ccae01a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -42,12 +42,14 @@ import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.android.graphics.NopCanvas;
import com.android.layoutlib.bridge.android.support.DesignLibUtil;
import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
import com.android.resources.ResourceType;
import com.android.tools.layoutlib.java.System_Delegate;
import com.android.util.Pair;
+import com.android.util.PropertiesMap;
import android.animation.AnimationThread;
import android.animation.Animator;
@@ -60,6 +62,7 @@ import android.app.Fragment_Delegate;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
import android.graphics.Canvas;
+import android.os.Looper;
import android.preference.Preference_Delegate;
import android.view.AttachInfo_Accessor;
import android.view.BridgeInflater;
@@ -112,6 +115,8 @@ import static com.android.layoutlib.bridge.util.ReflectionUtils.isInstanceOf;
*/
public class RenderSessionImpl extends RenderAction<SessionParams> {
+ private static final Canvas NOP_CANVAS = new NopCanvas();
+
// scene state
private RenderSession mScene;
private BridgeXmlBlockParser mBlockParser;
@@ -132,6 +137,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
private List<ViewInfo> mViewInfoList;
private List<ViewInfo> mSystemViewInfoList;
private Layout.Builder mLayoutBuilder;
+ private boolean mNewRenderSize;
private static final class PostInflateException extends Exception {
private static final long serialVersionUID = 1L;
@@ -198,6 +204,88 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
/**
+ * Measures the the current layout if needed (see {@link #invalidateRenderingSize}).
+ */
+ private void measure(@NonNull SessionParams params) {
+ // only do the screen measure when needed.
+ if (mMeasuredScreenWidth != -1) {
+ return;
+ }
+
+ RenderingMode renderingMode = params.getRenderingMode();
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
+
+ mNewRenderSize = true;
+ mMeasuredScreenWidth = hardwareConfig.getScreenWidth();
+ mMeasuredScreenHeight = hardwareConfig.getScreenHeight();
+
+ if (renderingMode != RenderingMode.NORMAL) {
+ int widthMeasureSpecMode = renderingMode.isHorizExpand() ?
+ MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
+ : MeasureSpec.EXACTLY;
+ int heightMeasureSpecMode = renderingMode.isVertExpand() ?
+ MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
+ : MeasureSpec.EXACTLY;
+
+ // We used to compare the measured size of the content to the screen size but
+ // this does not work anymore due to the 2 following issues:
+ // - If the content is in a decor (system bar, title/action bar), the root view
+ // will not resize even with the UNSPECIFIED because of the embedded layout.
+ // - If there is no decor, but a dialog frame, then the dialog padding prevents
+ // comparing the size of the content to the screen frame (as it would not
+ // take into account the dialog padding).
+
+ // The solution is to first get the content size in a normal rendering, inside
+ // the decor or the dialog padding.
+ // Then measure only the content with UNSPECIFIED to see the size difference
+ // and apply this to the screen size.
+
+ // first measure the full layout, with EXACTLY to get the size of the
+ // content as it is inside the decor/dialog
+ @SuppressWarnings("deprecation")
+ Pair<Integer, Integer> exactMeasure = measureView(
+ mViewRoot, mContentRoot.getChildAt(0),
+ mMeasuredScreenWidth, MeasureSpec.EXACTLY,
+ mMeasuredScreenHeight, MeasureSpec.EXACTLY);
+
+ // now measure the content only using UNSPECIFIED (where applicable, based on
+ // the rendering mode). This will give us the size the content needs.
+ @SuppressWarnings("deprecation")
+ Pair<Integer, Integer> result = measureView(
+ mContentRoot, mContentRoot.getChildAt(0),
+ mMeasuredScreenWidth, widthMeasureSpecMode,
+ mMeasuredScreenHeight, heightMeasureSpecMode);
+
+ // now look at the difference and add what is needed.
+ if (renderingMode.isHorizExpand()) {
+ int measuredWidth = exactMeasure.getFirst();
+ int neededWidth = result.getFirst();
+ if (neededWidth > measuredWidth) {
+ mMeasuredScreenWidth += neededWidth - measuredWidth;
+ }
+ if (mMeasuredScreenWidth < measuredWidth) {
+ // If the screen width is less than the exact measured width,
+ // expand to match.
+ mMeasuredScreenWidth = measuredWidth;
+ }
+ }
+
+ if (renderingMode.isVertExpand()) {
+ int measuredHeight = exactMeasure.getSecond();
+ int neededHeight = result.getSecond();
+ if (neededHeight > measuredHeight) {
+ mMeasuredScreenHeight += neededHeight - measuredHeight;
+ }
+ if (mMeasuredScreenHeight < measuredHeight) {
+ // If the screen height is less than the exact measured height,
+ // expand to match.
+ mMeasuredScreenHeight = measuredHeight;
+ }
+ }
+ }
+ }
+
+ /**
* Inflates the layout.
* <p>
* {@link #acquire(long)} must have been called before this.
@@ -243,6 +331,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
setActiveToolbar(view, context, params);
+ measure(params);
+ measureView(mViewRoot, null /*measuredView*/,
+ mMeasuredScreenWidth, MeasureSpec.EXACTLY,
+ mMeasuredScreenHeight, MeasureSpec.EXACTLY);
+ mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
+ mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
+ false);
+
return SUCCESS.createResult();
} catch (PostInflateException e) {
return ERROR_INFLATION.createResult(e.getMessage(), e);
@@ -266,6 +362,34 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
/**
+ * Renders the given view hierarchy to the passed canvas and returns the result of the render
+ * operation.
+ * @param canvas an optional canvas to render the views to. If null, only the measure and
+ * layout steps will be executed.
+ */
+ private static Result render(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
+ @Nullable Canvas canvas, int width, int height) {
+ // measure again with the size we need
+ // This must always be done before the call to layout
+ measureView(viewRoot, null /*measuredView*/,
+ width, MeasureSpec.EXACTLY,
+ height, MeasureSpec.EXACTLY);
+
+ // now do the layout.
+ viewRoot.layout(0, 0, width, height);
+ handleScrolling(context, viewRoot);
+
+ if (canvas == null) {
+ return SUCCESS.createResult();
+ }
+
+ AttachInfo_Accessor.dispatchOnPreDraw(viewRoot);
+ viewRoot.draw(canvas);
+
+ return SUCCESS.createResult();
+ }
+
+ /**
* Renders the scene.
* <p>
* {@link #acquire(long)} must have been called before this.
@@ -290,100 +414,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
return ERROR_NOT_INFLATED.createResult();
}
- RenderingMode renderingMode = params.getRenderingMode();
- HardwareConfig hardwareConfig = params.getHardwareConfig();
-
- // only do the screen measure when needed.
- boolean newRenderSize = false;
- if (mMeasuredScreenWidth == -1) {
- newRenderSize = true;
- mMeasuredScreenWidth = hardwareConfig.getScreenWidth();
- mMeasuredScreenHeight = hardwareConfig.getScreenHeight();
-
- if (renderingMode != RenderingMode.NORMAL) {
- int widthMeasureSpecMode = renderingMode.isHorizExpand() ?
- MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
- : MeasureSpec.EXACTLY;
- int heightMeasureSpecMode = renderingMode.isVertExpand() ?
- MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
- : MeasureSpec.EXACTLY;
-
- // We used to compare the measured size of the content to the screen size but
- // this does not work anymore due to the 2 following issues:
- // - If the content is in a decor (system bar, title/action bar), the root view
- // will not resize even with the UNSPECIFIED because of the embedded layout.
- // - If there is no decor, but a dialog frame, then the dialog padding prevents
- // comparing the size of the content to the screen frame (as it would not
- // take into account the dialog padding).
-
- // The solution is to first get the content size in a normal rendering, inside
- // the decor or the dialog padding.
- // Then measure only the content with UNSPECIFIED to see the size difference
- // and apply this to the screen size.
-
- // first measure the full layout, with EXACTLY to get the size of the
- // content as it is inside the decor/dialog
- @SuppressWarnings("deprecation")
- Pair<Integer, Integer> exactMeasure = measureView(
- mViewRoot, mContentRoot.getChildAt(0),
- mMeasuredScreenWidth, MeasureSpec.EXACTLY,
- mMeasuredScreenHeight, MeasureSpec.EXACTLY);
-
- // now measure the content only using UNSPECIFIED (where applicable, based on
- // the rendering mode). This will give us the size the content needs.
- @SuppressWarnings("deprecation")
- Pair<Integer, Integer> result = measureView(
- mContentRoot, mContentRoot.getChildAt(0),
- mMeasuredScreenWidth, widthMeasureSpecMode,
- mMeasuredScreenHeight, heightMeasureSpecMode);
-
- // now look at the difference and add what is needed.
- if (renderingMode.isHorizExpand()) {
- int measuredWidth = exactMeasure.getFirst();
- int neededWidth = result.getFirst();
- if (neededWidth > measuredWidth) {
- mMeasuredScreenWidth += neededWidth - measuredWidth;
- }
- if (mMeasuredScreenWidth < measuredWidth) {
- // If the screen width is less than the exact measured width,
- // expand to match.
- mMeasuredScreenWidth = measuredWidth;
- }
- }
-
- if (renderingMode.isVertExpand()) {
- int measuredHeight = exactMeasure.getSecond();
- int neededHeight = result.getSecond();
- if (neededHeight > measuredHeight) {
- mMeasuredScreenHeight += neededHeight - measuredHeight;
- }
- if (mMeasuredScreenHeight < measuredHeight) {
- // If the screen height is less than the exact measured height,
- // expand to match.
- mMeasuredScreenHeight = measuredHeight;
- }
- }
- }
- }
-
- // measure again with the size we need
- // This must always be done before the call to layout
- measureView(mViewRoot, null /*measuredView*/,
- mMeasuredScreenWidth, MeasureSpec.EXACTLY,
- mMeasuredScreenHeight, MeasureSpec.EXACTLY);
-
- // now do the layout.
- mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
-
- handleScrolling(mViewRoot);
+ measure(params);
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
+ Result renderResult = SUCCESS.createResult();
if (params.isLayoutOnly()) {
// delete the canvas and image to reset them on the next full rendering
mImage = null;
mCanvas = null;
} else {
- AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot);
-
// draw the views
// create the BufferedImage into which the layout will be rendered.
boolean newImage = false;
@@ -394,7 +433,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// it doesn't get cached.
boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag(
RenderParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING));
- if (newRenderSize || mCanvas == null || disableBitmapCaching) {
+ if (mNewRenderSize || mCanvas == null || disableBitmapCaching) {
+ mNewRenderSize = false;
if (params.getImageFactory() != null) {
mImage = params.getImageFactory().getImage(
mMeasuredScreenWidth,
@@ -445,6 +485,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
if (mElapsedFrameTimeNanos >= 0) {
long initialTime = System_Delegate.nanoTime();
if (!mFirstFrameExecuted) {
+ // We need to run an initial draw call to initialize the animations
+ render(getContext(), mViewRoot, NOP_CANVAS, mMeasuredScreenWidth, mMeasuredScreenHeight);
+
// The first frame will initialize the animations
Choreographer_Delegate.doFrame(initialTime);
mFirstFrameExecuted = true;
@@ -452,14 +495,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// Second frame will move the animations
Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
}
- mViewRoot.draw(mCanvas);
+ renderResult = render(getContext(), mViewRoot, mCanvas, mMeasuredScreenWidth,
+ mMeasuredScreenHeight);
}
mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
false);
// success!
- return SUCCESS.createResult();
+ return renderResult;
} catch (Throwable e) {
// get the real cause of the exception.
Throwable t = e;
@@ -487,7 +531,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
* @return the measured width/height if measuredView is non-null, null otherwise.
*/
@SuppressWarnings("deprecation") // For the use of Pair
- private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
+ private static Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
int width, int widthMode, int height, int heightMode) {
int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
@@ -1056,25 +1100,29 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
/**
- * Set the vertical scroll position on all the components with the "scrollY" attribute. If the
- * component supports nested scrolling attempt that first, then use the unconsumed scroll part
- * to scroll the content in the component.
+ * Set the scroll position on all the components with the "scrollX" and "scrollY" attribute. If
+ * the component supports nested scrolling attempt that first, then use the unconsumed scroll
+ * part to scroll the content in the component.
*/
- private void handleScrolling(View view) {
- BridgeContext context = getContext();
- int scrollPos = context.getScrollYPos(view);
- if (scrollPos != 0) {
+ private static void handleScrolling(BridgeContext context, View view) {
+ int scrollPosX = context.getScrollXPos(view);
+ int scrollPosY = context.getScrollYPos(view);
+ if (scrollPosX != 0 || scrollPosY != 0) {
if (view.isNestedScrollingEnabled()) {
int[] consumed = new int[2];
- if (view.startNestedScroll(DesignLibUtil.SCROLL_AXIS_VERTICAL)) {
- view.dispatchNestedPreScroll(0, scrollPos, consumed, null);
- view.dispatchNestedScroll(consumed[0], consumed[1], 0, scrollPos, null);
+ int axis = scrollPosX != 0 ? View.SCROLL_AXIS_HORIZONTAL : 0;
+ axis |= scrollPosY != 0 ? View.SCROLL_AXIS_VERTICAL : 0;
+ if (view.startNestedScroll(axis)) {
+ view.dispatchNestedPreScroll(scrollPosX, scrollPosY, consumed, null);
+ view.dispatchNestedScroll(consumed[0], consumed[1], scrollPosX, scrollPosY,
+ null);
view.stopNestedScroll();
- scrollPos -= consumed[1];
+ scrollPosX -= consumed[0];
+ scrollPosY -= consumed[1];
}
}
- if (scrollPos != 0) {
- view.scrollBy(0, scrollPos);
+ if (scrollPosX != 0 || scrollPosY != 0) {
+ view.scrollTo(scrollPosX, scrollPosY);
}
}
@@ -1084,7 +1132,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
ViewGroup group = (ViewGroup) view;
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
- handleScrolling(child);
+ handleScrolling(context, child);
}
}
@@ -1275,14 +1323,20 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
return null;
}
+ ViewParent parent = view.getParent();
ViewInfo result;
if (isContentFrame) {
+ // Account for parent scroll values when calculating the bounding box
+ int scrollX = parent != null ? ((View)parent).getScrollX() : 0;
+ int scrollY = parent != null ? ((View)parent).getScrollY() : 0;
+
// The view is part of the layout added by the user. Hence,
// the ViewCookie may be obtained only through the Context.
result = new ViewInfo(view.getClass().getName(),
getContext().getViewKey(view),
- view.getLeft(), view.getTop() + offset, view.getRight(),
- view.getBottom() + offset, view, view.getLayoutParams());
+ -scrollX + view.getLeft(), -scrollY + view.getTop() + offset,
+ -scrollX + view.getRight(), -scrollY + view.getBottom() + offset,
+ view, view.getLayoutParams());
} else {
// We are part of the system decor.
SystemViewInfo r = new SystemViewInfo(view.getClass().getName(),
@@ -1310,7 +1364,6 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// its parent is of type ActionMenuView. We can also check if the view is
// instanceof ActionMenuItemView but that will fail for menus using
// actionProviderClass.
- ViewParent parent = view.getParent();
while (parent != mViewRoot && parent instanceof ViewGroup) {
if (parent instanceof ActionMenuView) {
r.setViewType(ViewType.ACTION_BAR_MENU);
@@ -1385,8 +1438,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
return mSystemViewInfoList;
}
- public Map<String, String> getDefaultProperties(Object viewObject) {
- return getContext().getDefaultPropMap(viewObject);
+ public Map<Object, PropertiesMap> getDefaultProperties() {
+ return getContext().getDefaultProperties();
}
public void setScene(RenderSession session) {
@@ -1398,6 +1451,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
public void dispose() {
+ boolean createdLooper = false;
+ if (Looper.myLooper() == null) {
+ // Detaching the root view from the window will try to stop any running animations.
+ // The stop method checks that it can run in the looper so, if there is no current
+ // looper, we create a temporary one to complete the shutdown.
+ Bridge.prepareThread();
+ createdLooper = true;
+ }
AttachInfo_Accessor.detachFromWindow(mViewRoot);
if (mCanvas != null) {
mCanvas.release();
@@ -1412,5 +1473,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
mImage = null;
mViewRoot = null;
mContentRoot = null;
+
+ if (createdLooper) {
+ Bridge.cleanupThread();
+ Choreographer_Delegate.dispose();
+ }
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index c72eeb11c62d..a21de56066cb 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -25,6 +25,7 @@ import com.android.internal.util.XmlUtils;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.ninepatch.NinePatch;
import com.android.ninepatch.NinePatchChunk;
import com.android.resources.Density;
@@ -33,7 +34,11 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.ColorStateList;
+import android.content.res.ComplexColor;
+import android.content.res.ComplexColor_Accessor;
+import android.content.res.GradientColor;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
@@ -47,6 +52,7 @@ import android.util.TypedValue;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -119,47 +125,99 @@ public final class ResourceHelper {
throw new NumberFormatException();
}
- public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) {
+ /**
+ * Returns a {@link ComplexColor} from the given {@link ResourceValue}
+ *
+ * @param resValue the value containing a color value or a file path to a complex color
+ * definition
+ * @param context the current context
+ * @param theme the theme to use when resolving the complex color
+ * @param allowGradients when false, only {@link ColorStateList} will be returned. If a {@link
+ * GradientColor} is found, null will be returned.
+ */
+ @Nullable
+ private static ComplexColor getInternalComplexColor(@NonNull ResourceValue resValue,
+ @NonNull BridgeContext context, @Nullable Theme theme, boolean allowGradients) {
String value = resValue.getValue();
- if (value != null && !RenderResources.REFERENCE_NULL.equals(value)) {
- // first check if the value is a file (xml most likely)
+ if (value == null || RenderResources.REFERENCE_NULL.equals(value)) {
+ return null;
+ }
+
+ XmlPullParser parser = null;
+ // first check if the value is a file (xml most likely)
+ Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
+ RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
+ if (psiParserSupport != null && psiParserSupport) {
+ parser = context.getLayoutlibCallback().getXmlFileParser(value);
+ }
+ if (parser == null) {
File f = new File(value);
if (f.isFile()) {
+ // let the framework inflate the color from the XML file, by
+ // providing an XmlPullParser
try {
- // let the framework inflate the ColorStateList from the XML file, by
- // providing an XmlPullParser
- XmlPullParser parser = ParserFactory.create(f);
-
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- parser, context, resValue.isFramework());
- try {
- return ColorStateList.createFromXml(context.getResources(), blockParser);
- } finally {
- blockParser.ensurePopped();
- }
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value, e, null /*data*/);
- // we'll return null below.
- } catch (Exception e) {
- // this is an error and not warning since the file existence is
- // checked before attempting to parse it.
+ parser = ParserFactory.create(f);
+ } catch (XmlPullParserException | FileNotFoundException e) {
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
"Failed to parse file " + value, e, null /*data*/);
-
- return null;
}
- } else {
- // try to load the color state list from an int
+ }
+ }
+
+ if (parser != null) {
+ try {
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
+ parser, context, resValue.isFramework());
try {
- int color = ResourceHelper.getColor(value);
- return ColorStateList.valueOf(color);
- } catch (NumberFormatException e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Failed to convert " + value + " into a ColorStateList", e,
- null /*data*/);
- return null;
+ // Advance the parser to the first element so we can detect if it's a
+ // color list or a gradient color
+ int type;
+ //noinspection StatementWithEmptyBody
+ while ((type = blockParser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ final String name = blockParser.getName();
+ if (allowGradients && "gradient".equals(name)) {
+ return ComplexColor_Accessor.createGradientColorFromXmlInner(
+ context.getResources(),
+ blockParser, blockParser,
+ theme);
+ } else if ("selector".equals(name)) {
+ return ComplexColor_Accessor.createColorStateListFromXmlInner(
+ context.getResources(),
+ blockParser, blockParser,
+ theme);
+ }
+ } finally {
+ blockParser.ensurePopped();
}
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + value, e, null /*data*/);
+ // we'll return null below.
+ } catch (Exception e) {
+ // this is an error and not warning since the file existence is
+ // checked before attempting to parse it.
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed to parse file " + value, e, null /*data*/);
+
+ return null;
+ }
+ } else {
+ // try to load the color state list from an int
+ try {
+ int color = getColor(value);
+ return ColorStateList.valueOf(color);
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Failed to convert " + value + " into a ColorStateList", e,
+ null /*data*/);
}
}
@@ -167,6 +225,33 @@ public final class ResourceHelper {
}
/**
+ * Returns a {@link ColorStateList} from the given {@link ResourceValue}
+ *
+ * @param resValue the value containing a color value or a file path to a complex color
+ * definition
+ * @param context the current context
+ */
+ @Nullable
+ public static ColorStateList getColorStateList(@NonNull ResourceValue resValue,
+ @NonNull BridgeContext context) {
+ return (ColorStateList) getInternalComplexColor(resValue, context, context.getTheme(),
+ false);
+ }
+
+ /**
+ * Returns a {@link ComplexColor} from the given {@link ResourceValue}
+ *
+ * @param resValue the value containing a color value or a file path to a complex color
+ * definition
+ * @param context the current context
+ */
+ @Nullable
+ public static ComplexColor getComplexColor(@NonNull ResourceValue resValue,
+ @NonNull BridgeContext context) {
+ return getInternalComplexColor(resValue, context, context.getTheme(), true);
+ }
+
+ /**
* Returns a drawable from the given value.
* @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
* or an hexadecimal color
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
index 9aab340909fd..c0ca1d5916e9 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
@@ -36,9 +36,9 @@ import android.widget.TextView;
/**
* A Helper class to do fake data binding in {@link AdapterView} objects.
*/
-@SuppressWarnings("deprecation")
public class AdapterHelper {
+ @SuppressWarnings("deprecation")
static Pair<View, Boolean> getView(AdapterItem item, AdapterItem parentItem, ViewGroup parent,
LayoutlibCallback callback, ResourceReference adapterRef, boolean skipCallbackParser) {
// we don't care about recycling here because we never scroll.
@@ -114,7 +114,7 @@ public class AdapterHelper {
if (value != null) {
if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
- "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+ "Wrong Adapter Item value class for IS_CHECKED. Expected Boolean, got %s",
value.getClass().getName()), null);
} else {
cb.setChecked((Boolean) value);
@@ -134,7 +134,7 @@ public class AdapterHelper {
if (value != null) {
if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
- "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+ "Wrong Adapter Item value class for SRC. Expected Boolean, got %s",
value.getClass().getName()), null);
} else {
// FIXME
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
index 08a8faf0bbfb..161bf4155a1a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
@@ -27,8 +27,8 @@ import java.util.Map;
public class DynamicIdMap {
- private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<Pair<ResourceType, String>, Integer>();
- private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<Pair<ResourceType, String>>();
+ private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<>();
+ private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<>();
private int mDynamicSeed;
public DynamicIdMap(int seed) {
diff --git a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
new file mode 100644
index 000000000000..6246ec1b8661
--- /dev/null
+++ b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.util;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of {@link NativeAllocationRegistry}
+ *
+ * Through the layoutlib_create tool, the original native methods of NativeAllocationRegistry have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original NativeAllocationRegistry class.
+ *
+ * @see DelegateManager
+ */
+public class NativeAllocationRegistry_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<NativeAllocationRegistry_Delegate> sManager =
+ new DelegateManager<>(NativeAllocationRegistry_Delegate.class);
+
+ private final FreeFunction mFinalizer;
+
+ private NativeAllocationRegistry_Delegate(FreeFunction finalizer) {
+ mFinalizer = finalizer;
+ }
+
+ /**
+ * The result of this method should be cached by the class and reused.
+ */
+ public static long createFinalizer(FreeFunction finalizer) {
+ return sManager.addNewDelegate(new NativeAllocationRegistry_Delegate(finalizer));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void applyFreeFunction(long freeFunction, long nativePtr) {
+ NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction);
+ if (delegate != null) {
+ delegate.mFinalizer.free(nativePtr);
+ }
+ }
+
+ public interface FreeFunction {
+ void free(long nativePtr);
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
index 5eef24adbdef..8a81d0b684db 100644
--- a/tools/layoutlib/bridge/tests/Android.mk
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -19,7 +19,6 @@ include $(CLEAR_VARS)
# Only compile source java files in this lib.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_RESOURCE_DIRS := res
-LOCAL_JAVACFLAGS := -source 6 -target 6
LOCAL_MODULE := layoutlib-tests
LOCAL_MODULE_TAGS := optional
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
index d8ead233b4ec..0e788e0c8264 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
index 65d1dc5b1edb..bad296bf4a66 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/scrolled.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/scrolled.png
new file mode 100644
index 000000000000..87bd5020bcd4
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/scrolled.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
index 72b87abfb917..55d6a20949a2 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
new file mode 100644
index 000000000000..fc0afa661751
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+ android:startX="10"
+ android:startY="10"
+ android:endX="50"
+ android:endY="50"
+ android:startColor="#ffff0000"
+ android:endColor="#ff00ff00" />
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
index ffc70dc1e519..32e6e732bb5a 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
@@ -53,6 +53,35 @@
android:trimPathStart="0.2"
android:trimPathEnd="0.8"
/>
+
+ <!--
+ Draw a line with gradient stroke color
+ -->
+ <path
+ android:strokeWidth="1"
+ android:strokeColor="#FF00FF"
+ android:fillColor="@color/gradient"
+ android:pathData="M-20,-20 l0, 10 l10, 0 l0, -10 l-10,0 "
+ />
+
+ <!--
+ Draw squares with different fill types
+ -->
+ <path
+ android:fillType="evenOdd"
+ android:strokeWidth="1"
+ android:strokeColor="#AABBCC"
+ android:fillColor="#AAEFCC"
+ android:pathData="M-20,-40 l0, 10 l10, 0 l0, -10 l-10,0 m5,0 l0, 10 l10, 0 l0, -10 l-10,0"
+ />
+
+ <path
+ android:fillType="nonZero"
+ android:strokeWidth="1"
+ android:strokeColor="#AABBCC"
+ android:fillColor="#AAEFCC"
+ android:pathData="M0,-40 l0, 10 l10, 0 l0, -10 l-10,0 m5,0 l0, 10 l10, 0 l0, -10 l-10,0"
+ />
</group>
</vector> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
index 2da2cb983a6c..adb58a322abb 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
@@ -197,6 +197,14 @@
android:inputType="numberPassword"
android:text="numeric password" />
+ <ToggleButton
+ android:id="@+id/toggleButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@id/editText4"
+ android:layout_toEndOf="@id/editText4"
+ android:text="New ToggleButton" />
+
<EditText
android:id="@id/editText5"
android:layout_width="wrap_content"
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml
new file mode 100644
index 000000000000..a07498cd07b1
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml
@@ -0,0 +1,57 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:scrollX="30px"
+ android:scrollY="90px">
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:background="#FF0000" />
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="30dp"
+ android:background="#00FF00" />
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:background="#0000FF" />
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="30dp"
+ android:background="#FF00FF" />
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:background="#00FFFF" />
+
+ <LinearLayout
+ android:layout_width="200dp"
+ android:layout_height="400dp"
+ android:orientation="vertical"
+ android:scrollX="-90px"
+ android:scrollY="450px">
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:background="#FF0000" />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="30dp"
+ android:background="#00FF00" />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:background="#0000FF" />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="30dp"
+ android:background="#FF00FF" />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:background="#00FFFF" />
+ </LinearLayout>
+
+
+</LinearLayout>
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
index 16911bdd7c2a..8f9fa8a2bcf6 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
@@ -75,8 +75,12 @@ public class ImageUtils {
}
}
else {
- BufferedImage goldenImage = ImageIO.read(is);
- assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE);
+ try {
+ BufferedImage goldenImage = ImageIO.read(is);
+ assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE);
+ } finally {
+ is.close();
+ }
}
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 6b23da71861a..8f570aee96b7 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.common.resources.FrameworkResources;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
@@ -28,29 +29,46 @@ import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.io.FolderWrapper;
import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
import com.android.resources.Density;
import com.android.resources.Navigation;
+import com.android.resources.ResourceType;
import com.android.utils.ILogger;
import org.junit.AfterClass;
+import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
import java.io.File;
-import java.io.FileFilter;
import java.io.IOException;
+import java.lang.ref.WeakReference;
import java.net.URL;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Map;
import java.util.concurrent.TimeUnit;
+import com.google.android.collect.Lists;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
@@ -94,6 +112,21 @@ public class Main {
private static ILogger sLogger;
private static Bridge sBridge;
+ /** List of log messages generated by a render call. It can be used to find specific errors */
+ private static ArrayList<String> sRenderMessages = Lists.newArrayList();
+
+ @Rule
+ public static TestWatcher sRenderMessageWatcher = new TestWatcher() {
+ @Override
+ protected void succeeded(Description description) {
+ // We only check error messages if the rest of the test case was successful.
+ if (!sRenderMessages.isEmpty()) {
+ fail(description.getMethodName() + " render error message: " + sRenderMessages.get
+ (0));
+ }
+ }
+ };
+
static {
// Test that System Properties are properly set.
PLATFORM_DIR = getPlatformDir();
@@ -157,13 +190,8 @@ public class Main {
if (!host.isDirectory()) {
return null;
}
- File[] hosts = host.listFiles(new FileFilter() {
- @Override
- public boolean accept(File path) {
- return path.isDirectory() && (path.getName().startsWith("linux-") || path.getName()
- .startsWith("darwin-"));
- }
- });
+ File[] hosts = host.listFiles(path -> path.isDirectory() &&
+ (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
for (File hostOut : hosts) {
String platformDir = getPlatformDirFromHostOut(hostOut);
if (platformDir != null) {
@@ -181,12 +209,9 @@ public class Main {
if (!sdkDir.isDirectory()) {
return null;
}
- File[] sdkDirs = sdkDir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File path) {
- // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
- return path.isDirectory() && path.getName().startsWith("sdk");
- }
+ File[] sdkDirs = sdkDir.listFiles(path -> {
+ // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
+ return path.isDirectory() && path.getName().startsWith("sdk");
});
for (File dir : sdkDirs) {
String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
@@ -198,46 +223,34 @@ public class Main {
}
private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
- File[] possibleSdks = sdkDir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File path) {
- return path.isDirectory() && path.getName().contains("android-sdk");
- }
- });
+ File[] possibleSdks = sdkDir.listFiles(
+ path -> path.isDirectory() && path.getName().contains("android-sdk"));
for (File possibleSdk : possibleSdks) {
File platformsDir = new File(possibleSdk, "platforms");
- File[] platforms = platformsDir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File path) {
- return path.isDirectory() && path.getName().startsWith("android-");
- }
- });
+ File[] platforms = platformsDir.listFiles(
+ path -> path.isDirectory() && path.getName().startsWith("android-"));
if (platforms == null || platforms.length == 0) {
continue;
}
- Arrays.sort(platforms, new Comparator<File>() {
- // Codenames before ints. Higher APIs precede lower.
- @Override
- public int compare(File o1, File o2) {
- final int MAX_VALUE = 1000;
- String suffix1 = o1.getName().substring("android-".length());
- String suffix2 = o2.getName().substring("android-".length());
- int suff1, suff2;
- try {
- suff1 = Integer.parseInt(suffix1);
- } catch (NumberFormatException e) {
- suff1 = MAX_VALUE;
- }
- try {
- suff2 = Integer.parseInt(suffix2);
- } catch (NumberFormatException e) {
- suff2 = MAX_VALUE;
- }
- if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
- return suff2 - suff1;
- }
- return suffix2.compareTo(suffix1);
+ Arrays.sort(platforms, (o1, o2) -> {
+ final int MAX_VALUE = 1000;
+ String suffix1 = o1.getName().substring("android-".length());
+ String suffix2 = o2.getName().substring("android-".length());
+ int suff1, suff2;
+ try {
+ suff1 = Integer.parseInt(suffix1);
+ } catch (NumberFormatException e) {
+ suff1 = MAX_VALUE;
+ }
+ try {
+ suff2 = Integer.parseInt(suffix2);
+ } catch (NumberFormatException e) {
+ suff2 = MAX_VALUE;
+ }
+ if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
+ return suff2 - suff1;
}
+ return suffix2.compareTo(suffix1);
});
return platforms[0].getAbsolutePath();
}
@@ -258,6 +271,7 @@ public class Main {
return null;
}
}
+
/**
* Initialize the bridge and the resource maps.
*/
@@ -287,6 +301,11 @@ public class Main {
ConfigGenerator.getEnumMap(attrs), getLayoutLog());
}
+ @Before
+ public void beforeTestCase() {
+ sRenderMessages.clear();
+ }
+
/** Test activity.xml */
@Test
public void testActivity() throws ClassNotFoundException {
@@ -297,6 +316,9 @@ public class Main {
@Test
public void testAllWidgets() throws ClassNotFoundException {
renderAndVerify("allwidgets.xml", "allwidgets.png");
+
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
}
@Test
@@ -307,6 +329,19 @@ public class Main {
@Test
public void testAllWidgetsTablet() throws ClassNotFoundException {
renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
+
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+ }
+
+ private static void gc() {
+ // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
+ Object obj = new Object();
+ WeakReference ref = new WeakReference<Object>(obj);
+ obj = null;
+ while(ref.get() != null) {
+ System.gc();
+ }
}
@AfterClass
@@ -316,14 +351,18 @@ public class Main {
sProjectResources = null;
sLogger = null;
sBridge = null;
+
+ gc();
+
+ System.out.println("Objects still linked from the DelegateManager:");
+ DelegateManager.dump(System.out);
}
/** Test expand_layout.xml */
@Test
public void testExpand() throws ClassNotFoundException {
// Create the layout pull parser.
- LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
- "expand_vert_layout.xml");
+ LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
layoutLibCallback.initResources();
@@ -335,7 +374,7 @@ public class Main {
.setNavigation(Navigation.NONAV);
SessionParams params = getSessionParams(parser, customConfigGenerator,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
RenderingMode.V_SCROLL, 22);
renderAndVerify(params, "expand_vert_layout.png");
@@ -345,10 +384,9 @@ public class Main {
.setScreenHeight(300)
.setDensity(Density.XHIGH)
.setNavigation(Navigation.NONAV);
- parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
- "expand_horz_layout.xml");
+ parser = createLayoutPullParser("expand_horz_layout.xml");
params = getSessionParams(parser, customConfigGenerator,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
RenderingMode.H_SCROLL, 22);
renderAndVerify(params, "expand_horz_layout.png");
@@ -358,8 +396,7 @@ public class Main {
@Test
public void testVectorAnimation() throws ClassNotFoundException {
// Create the layout pull parser.
- LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
- "indeterminate_progressbar.xml");
+ LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
layoutLibCallback.initResources();
@@ -370,8 +407,7 @@ public class Main {
renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
- parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
- "indeterminate_progressbar.xml");
+ parser = createLayoutPullParser("indeterminate_progressbar.xml");
params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
RenderingMode.V_SCROLL, 22);
@@ -385,8 +421,7 @@ public class Main {
@Test
public void testVectorDrawable() throws ClassNotFoundException {
// Create the layout pull parser.
- LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
- "vector_drawable.xml");
+ LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
layoutLibCallback.initResources();
@@ -398,6 +433,72 @@ public class Main {
renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
}
+ /** Test activity.xml */
+ @Test
+ public void testScrolling() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = createLayoutPullParser("scrolled.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+ params.setForceNoDecor();
+ params.setExtendedViewInfoMode(true);
+
+ RenderResult result = renderAndVerify(params, "scrolled.png");
+ assertNotNull(result);
+ assertTrue(result.getResult().isSuccess());
+
+ ViewInfo rootLayout = result.getRootViews().get(0);
+ // Check the first box in the main LinearLayout
+ assertEquals(-90, rootLayout.getChildren().get(0).getTop());
+ assertEquals(-30, rootLayout.getChildren().get(0).getLeft());
+ assertEquals(90, rootLayout.getChildren().get(0).getBottom());
+ assertEquals(150, rootLayout.getChildren().get(0).getRight());
+
+ // Check the first box within the nested LinearLayout
+ assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop());
+ assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft());
+ assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom());
+ assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight());
+ }
+
+ @Test
+ public void testGetResourceNameVariants() throws Exception {
+ // Setup
+ SessionParams params = createSessionParams("", ConfigGenerator.NEXUS_4);
+ AssetManager assetManager = AssetManager.getSystem();
+ DisplayMetrics metrics = new DisplayMetrics();
+ Configuration configuration = RenderAction.getConfiguration(params);
+ Resources resources = new Resources(assetManager, metrics, configuration);
+ resources.mLayoutlibCallback = params.getLayoutlibCallback();
+ resources.mContext =
+ new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+ params.getAssets(), params.getLayoutlibCallback(), configuration,
+ params.getTargetSdkVersion(), params.isRtlSupported());
+ // Test
+ assertEquals("android:style/ButtonBar",
+ resources.getResourceName(android.R.style.ButtonBar));
+ assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar));
+ assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar));
+ assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar));
+ int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name");
+ assertEquals("com.android.layoutlib.test.myapplication:string/app_name",
+ resources.getResourceName(id));
+ assertEquals("com.android.layoutlib.test.myapplication",
+ resources.getResourcePackageName(id));
+ assertEquals("string", resources.getResourceTypeName(id));
+ assertEquals("app_name", resources.getResourceEntryName(id));
+ }
+
+ @NonNull
+ private LayoutPullParser createLayoutPullParser(String layoutPath) {
+ return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath);
+ }
+
/**
* Create a new rendering session and test that rendering the given layout doesn't throw any
* exceptions and matches the provided image.
@@ -405,7 +506,8 @@ public class Main {
* If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
* how far in the future is.
*/
- private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)
+ @Nullable
+ private RenderResult renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)
throws ClassNotFoundException {
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
@@ -428,48 +530,60 @@ public class Main {
try {
String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
ImageUtils.requireSimilar(goldenImagePath, session.getImage());
+
+ return RenderResult.getFromSession(session);
} catch (IOException e) {
getLogger().error(e, e.getMessage());
} finally {
session.dispose();
}
+
+ return null;
}
/**
* Create a new rendering session and test that rendering the given layout doesn't throw any
* exceptions and matches the provided image.
*/
- private void renderAndVerify(SessionParams params, String goldenFileName)
+ @Nullable
+ private RenderResult renderAndVerify(SessionParams params, String goldenFileName)
throws ClassNotFoundException {
- renderAndVerify(params, goldenFileName, -1);
+ return renderAndVerify(params, goldenFileName, -1);
}
/**
* Create a new rendering session and test that rendering the given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
*/
- private void renderAndVerify(String layoutFileName, String goldenFileName)
+ @Nullable
+ private RenderResult renderAndVerify(String layoutFileName, String goldenFileName)
throws ClassNotFoundException {
- renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
+ return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
}
/**
* Create a new rendering session and test that rendering the given layout on given device
* doesn't throw any exceptions and matches the provided image.
*/
- private void renderAndVerify(String layoutFileName, String goldenFileName,
+ @Nullable
+ private RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
ConfigGenerator deviceConfig)
throws ClassNotFoundException {
+ SessionParams params = createSessionParams(layoutFileName, deviceConfig);
+ return renderAndVerify(params, goldenFileName);
+ }
+
+ private SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
+ throws ClassNotFoundException {
// Create the layout pull parser.
- LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutFileName);
+ LayoutPullParser parser = createLayoutPullParser(layoutFileName);
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
layoutLibCallback.initResources();
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
- SessionParams params = getSessionParams(parser, deviceConfig,
+ return getSessionParams(parser, deviceConfig,
layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
- renderAndVerify(params, goldenFileName);
}
/**
@@ -484,7 +598,7 @@ public class Main {
sFrameworkRepo.getConfiguredResources(config),
themeName, isProjectTheme);
- return new SessionParams(
+ SessionParams sessionParams = new SessionParams(
layoutParser,
renderingMode,
null /*used for caching*/,
@@ -494,6 +608,8 @@ public class Main {
0,
targetSdk,
getLayoutLog());
+ sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
+ return sessionParams;
}
private static LayoutLog getLayoutLog() {
@@ -566,6 +682,6 @@ public class Main {
}
private static void failWithMsg(@NonNull String msgFormat, Object... args) {
- fail(args == null ? "" : String.format(msgFormat, args));
+ sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args));
}
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
new file mode 100644
index 000000000000..17b20f76d2cd
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive;
+
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.ViewInfo;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class RenderResult {
+ private final List<ViewInfo> mRootViews;
+ private final List<ViewInfo> mSystemViews;
+ private final Result mRenderResult;
+
+ private RenderResult(@Nullable Result result, @Nullable List<ViewInfo> systemViewInfoList,
+ @Nullable List<ViewInfo> rootViewInfoList) {
+ mSystemViews = systemViewInfoList == null ? Collections.emptyList() : systemViewInfoList;
+ mRootViews = rootViewInfoList == null ? Collections.emptyList() : rootViewInfoList;
+ mRenderResult = result;
+ }
+
+ @NonNull
+ static RenderResult getFromSession(@NonNull RenderSession session) {
+ return new RenderResult(session.getResult(),
+ new ArrayList<>(session.getSystemRootViews()),
+ new ArrayList<>(session.getRootViews()));
+ }
+
+ @Nullable
+ Result getResult() {
+ return mRenderResult;
+ }
+
+ @NonNull
+ public List<ViewInfo> getRootViews() {
+ return mRootViews;
+ }
+
+ @NonNull
+ public List<ViewInfo> getSystemViews() {
+ return mSystemViews;
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
index 6c16ed01ca54..96ae523006b3 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
@@ -24,7 +24,9 @@ import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.ParserFactory;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.SessionParams.Key;
import com.android.ide.common.resources.IntArrayWrapper;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.resources.ResourceType;
import com.android.util.Pair;
import com.android.utils.ILogger;
@@ -176,4 +178,12 @@ public class LayoutLibTestCallback extends LayoutlibCallback {
}
};
}
+
+ @Override
+ public <T> T getFlag(Key<T> key) {
+ if (key.equals(RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE)) {
+ return (T) PACKAGE_NAME;
+ }
+ return null;
+ }
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
index c79b66281efc..111049474461 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
@@ -56,9 +56,7 @@ public class LayoutPullParser extends KXmlParser implements ILayoutPullParser{
public LayoutPullParser(File layoutFile) {
try {
init(new FileInputStream(layoutFile));
- } catch (XmlPullParserException e) {
- throw new IOError(e);
- } catch (FileNotFoundException e) {
+ } catch (XmlPullParserException | FileNotFoundException e) {
throw new IOError(e);
}
}
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
index e6f0bc306b07..c7f2c4137687 100644
--- a/tools/layoutlib/create/Android.mk
+++ b/tools/layoutlib/create/Android.mk
@@ -20,7 +20,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_JAR_MANIFEST := manifest.txt
LOCAL_STATIC_JAVA_LIBRARIES := \
- asm-4.0
+ asm-5.0
LOCAL_MODULE := layoutlib_create
diff --git a/tools/layoutlib/create/create.iml b/tools/layoutlib/create/create.iml
index 9b18e73aae90..368b46bc92dc 100644
--- a/tools/layoutlib/create/create.iml
+++ b/tools/layoutlib/create/create.iml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
@@ -9,12 +9,12 @@
<sourceFolder url="file://$MODULE_DIR$/tests/mock_data" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/.settings" />
</content>
- <orderEntry type="inheritedJdk" />
+ <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
- <library name="asm-4.0">
+ <library name="asm-5.0">
<CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-4.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-5.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
@@ -22,6 +22,6 @@
</SOURCES>
</library>
</orderEntry>
- <orderEntry type="library" scope="TEST" name="JUnit4" level="application" />
+ <orderEntry type="library" scope="TEST" name="junit" level="project" />
</component>
</module> \ No newline at end of file
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
index a6902a40b1a3..01c940ad665c 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
@@ -21,7 +21,6 @@ import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
@@ -44,7 +43,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor {
abstract String renameInternalType(String name);
public AbstractClassAdapter(ClassVisitor cv) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
}
/**
@@ -177,17 +176,6 @@ public abstract class AbstractClassAdapter extends ClassVisitor {
}
}
- /* Java 7 verifies the StackMapTable of a class if its version number is greater than 50.0.
- * However, the check is disabled if the class version number is 50.0 or less. Generation
- * of the StackMapTable requires a rewrite using the tree API of ASM. As a workaround,
- * we rewrite the version number of the class to be 50.0
- *
- * http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6693236
- */
- if (version > 50) {
- version = 50;
- }
-
super.visit(version, access, name, signature, superName, interfaces);
}
@@ -239,7 +227,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor {
* The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
*/
public RenameMethodAdapter(MethodVisitor mv) {
- super(Opcodes.ASM4, mv);
+ super(Main.ASM_VERSION, mv);
}
@Override
@@ -276,7 +264,8 @@ public abstract class AbstractClassAdapter extends ClassVisitor {
}
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
// The owner sometimes turns out to be a type descriptor. We try to detect it and fix.
if (owner.indexOf(';') > 0) {
owner = renameTypeDesc(owner);
@@ -285,7 +274,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor {
}
desc = renameMethodDesc(desc);
- super.visitMethodInsn(opcode, owner, name, desc);
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
@@ -330,7 +319,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor {
private final SignatureVisitor mSv;
public RenameSignatureAdapter(SignatureVisitor sv) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mSv = sv;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index c8b2b8448e21..11d4c81c6dc7 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -23,7 +23,6 @@ import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
@@ -65,7 +64,7 @@ public class AsmAnalyzer {
/** Glob patterns of files to keep as is. */
private final String[] mIncludeFileGlobs;
/** Internal names of classes that contain method calls that need to be rewritten. */
- private final Set<String> mReplaceMethodCallClasses = new HashSet<String>();
+ private final Set<String> mReplaceMethodCallClasses = new HashSet<>();
/**
* Creates a new analyzer.
@@ -97,8 +96,8 @@ public class AsmAnalyzer {
*/
public void analyze() throws IOException, LogAbortException {
- TreeMap<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ TreeMap<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
parseZip(mOsSourceJar, zipClasses, filesFound);
mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
@@ -189,7 +188,7 @@ public class AsmAnalyzer {
*/
Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses)
throws LogAbortException {
- TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> found = new TreeMap<>();
mLog.debug("Find classes to include.");
@@ -318,10 +317,10 @@ public class AsmAnalyzer {
Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inOutKeepClasses) {
- TreeMap<String, ClassReader> deps = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> new_deps = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> temp = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> deps = new TreeMap<>();
+ TreeMap<String, ClassReader> new_deps = new TreeMap<>();
+ TreeMap<String, ClassReader> new_keep = new TreeMap<>();
+ TreeMap<String, ClassReader> temp = new TreeMap<>();
DependencyVisitor visitor = getVisitor(zipClasses,
inOutKeepClasses, new_keep,
@@ -399,7 +398,7 @@ public class AsmAnalyzer {
Map<String, ClassReader> outKeep,
Map<String,ClassReader> inDeps,
Map<String,ClassReader> outDeps) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mZipClasses = zipClasses;
mInKeep = inKeep;
mOutKeep = outKeep;
@@ -439,7 +438,8 @@ public class AsmAnalyzer {
try {
// exclude classes that are part of the default JRE (the one executing this program)
- if (getClass().getClassLoader().loadClass(className) != null) {
+ if (className.startsWith("java.") ||
+ getClass().getClassLoader().loadClass(className) != null) {
return;
}
} catch (ClassNotFoundException e) {
@@ -557,7 +557,7 @@ public class AsmAnalyzer {
private class MyFieldVisitor extends FieldVisitor {
public MyFieldVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
@Override
@@ -630,7 +630,7 @@ public class AsmAnalyzer {
private String mOwnerClass;
public MyMethodVisitor(String ownerClass) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mOwnerClass = ownerClass;
}
@@ -719,7 +719,8 @@ public class AsmAnalyzer {
// instruction that invokes a method
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
// owner is the internal name of the method's owner class
considerName(owner);
@@ -779,7 +780,7 @@ public class AsmAnalyzer {
private class MySignatureVisitor extends SignatureVisitor {
public MySignatureVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
// ---------------------------------------------------
@@ -878,7 +879,7 @@ public class AsmAnalyzer {
private class MyAnnotationVisitor extends AnnotationVisitor {
public MyAnnotationVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
// Visits a primitive value of an annotation
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 414b255be0b6..3b376123daa4 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -91,7 +91,7 @@ public class AsmGenerator {
mLog = log;
mOsDestJar = osDestJar;
ArrayList<Class<?>> injectedClasses =
- new ArrayList<Class<?>>(Arrays.asList(createInfo.getInjectedClasses()));
+ new ArrayList<>(Arrays.asList(createInfo.getInjectedClasses()));
// Search for and add anonymous inner classes also.
ListIterator<Class<?>> iter = injectedClasses.listIterator();
while (iter.hasNext()) {
@@ -107,25 +107,25 @@ public class AsmGenerator {
}
}
mInjectClasses = injectedClasses.toArray(new Class<?>[0]);
- mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
+ mStubMethods = new HashSet<>(Arrays.asList(createInfo.getOverriddenMethods()));
// Create the map/set of methods to change to delegates
- mDelegateMethods = new HashMap<String, Set<String>>();
+ mDelegateMethods = new HashMap<>();
addToMap(createInfo.getDelegateMethods(), mDelegateMethods);
for (String className : createInfo.getDelegateClassNatives()) {
className = binaryToInternalClassName(className);
Set<String> methods = mDelegateMethods.get(className);
if (methods == null) {
- methods = new HashSet<String>();
+ methods = new HashSet<>();
mDelegateMethods.put(className, methods);
}
methods.add(DelegateClassAdapter.ALL_NATIVES);
}
// Create the map of classes to rename.
- mRenameClasses = new HashMap<String, String>();
- mClassesNotRenamed = new HashSet<String>();
+ mRenameClasses = new HashMap<>();
+ mClassesNotRenamed = new HashSet<>();
String[] renameClasses = createInfo.getRenamedClasses();
int n = renameClasses.length;
for (int i = 0; i < n; i += 2) {
@@ -138,7 +138,7 @@ public class AsmGenerator {
}
// Create a map of classes to be refactored.
- mRefactorClasses = new HashMap<String, String>();
+ mRefactorClasses = new HashMap<>();
String[] refactorClasses = createInfo.getJavaPkgClasses();
n = refactorClasses.length;
for (int i = 0; i < n; i += 2) {
@@ -149,7 +149,7 @@ public class AsmGenerator {
}
// create the map of renamed class -> return type of method to delete.
- mDeleteReturns = new HashMap<String, Set<String>>();
+ mDeleteReturns = new HashMap<>();
String[] deleteReturns = createInfo.getDeleteReturns();
Set<String> returnTypes = null;
String renamedClass = null;
@@ -172,12 +172,12 @@ public class AsmGenerator {
// just a standard return type, we add it to the list.
if (returnTypes == null) {
- returnTypes = new HashSet<String>();
+ returnTypes = new HashSet<>();
}
returnTypes.add(binaryToInternalClassName(className));
}
- mPromotedFields = new HashMap<String, Set<String>>();
+ mPromotedFields = new HashMap<>();
addToMap(createInfo.getPromotedFields(), mPromotedFields);
mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
@@ -197,7 +197,7 @@ public class AsmGenerator {
String methodOrFieldName = entry.substring(pos + 1);
Set<String> set = map.get(className);
if (set == null) {
- set = new HashSet<String>();
+ set = new HashSet<>();
map.put(className, set);
}
set.add(methodOrFieldName);
@@ -247,7 +247,7 @@ public class AsmGenerator {
/** Generates the final JAR */
public void generate() throws IOException {
- TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
+ TreeMap<String, byte[]> all = new TreeMap<>();
for (Class<?> clazz : mInjectClasses) {
String name = classToEntryPath(clazz);
@@ -314,7 +314,7 @@ public class AsmGenerator {
* e.g. for the input "android.view.View" it returns "android/view/View.class"
*/
String classNameToEntryPath(String className) {
- return className.replaceAll("\\.", "/").concat(".class");
+ return className.replace('.', '/').concat(".class");
}
/**
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
index 2c955fd9d9bb..4748a7c7b2f1 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
@@ -31,7 +31,7 @@ import org.objectweb.asm.Opcodes;
*/
public class ClassHasNativeVisitor extends ClassVisitor {
public ClassHasNativeVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
private boolean mHasNativeMethods = false;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index e17f1f891ddd..061bed7b7740 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -111,7 +111,7 @@ public final class CreateInfo implements ICreateInfo {
public Set<String> getExcludedClasses() {
String[] refactoredClasses = getJavaPkgClasses();
int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
- Set<String> excludedClasses = new HashSet<String>(count);
+ Set<String> excludedClasses = new HashSet<>(count);
for (int i = 0; i < refactoredClasses.length; i+=2) {
excludedClasses.add(refactoredClasses[i]);
}
@@ -144,11 +144,8 @@ public final class CreateInfo implements ICreateInfo {
InjectMethodRunnable.class,
InjectMethodRunnables.class,
/* Java package classes */
- AutoCloseable.class,
- Objects.class,
IntegralToString.class,
UnsafeByteSequence.class,
- Charsets.class,
System_Delegate.class,
LinkedHashMap_Delegate.class,
};
@@ -171,6 +168,7 @@ public final class CreateInfo implements ICreateInfo {
"android.content.res.Resources#getLayout",
"android.content.res.Resources#getResourceEntryName",
"android.content.res.Resources#getResourceName",
+ "android.content.res.Resources#getResourcePackageName",
"android.content.res.Resources#getResourceTypeName",
"android.content.res.Resources#getString",
"android.content.res.Resources#getStringArray",
@@ -192,8 +190,11 @@ public final class CreateInfo implements ICreateInfo {
"android.content.res.TypedArray#obtain",
"android.graphics.BitmapFactory#finishDecode",
"android.graphics.BitmapFactory#setDensityFromOptions",
+ "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget",
"android.graphics.drawable.GradientDrawable#buildRing",
+ "android.graphics.FontFamily#addFont",
"android.graphics.Typeface#getSystemFontConfigLocation",
+ "android.graphics.Typeface#makeFamilyFromParsed",
"android.os.Handler#sendMessageAtTime",
"android.os.HandlerThread#run",
"android.preference.Preference#getView",
@@ -205,15 +206,16 @@ public final class CreateInfo implements ICreateInfo {
"android.view.Choreographer#scheduleVsyncLocked",
"android.view.Display#updateDisplayInfoLocked",
"android.view.Display#getWindowManager",
+ "android.view.HandlerActionQueue#postDelayed",
"android.view.LayoutInflater#rInflate",
"android.view.LayoutInflater#parseInclude",
"android.view.View#getWindowToken",
"android.view.View#isInEditMode",
"android.view.ViewRootImpl#isInTouchMode",
- "android.view.ViewRootImpl$RunQueue#postDelayed",
"android.view.WindowManagerGlobal#getWindowManagerService",
"android.view.inputmethod.InputMethodManager#getInstance",
"android.view.MenuInflater#registerMenu",
+ "android.view.RenderNode#getMatrix",
"android.view.RenderNode#nCreate",
"android.view.RenderNode#nDestroyRenderNode",
"android.view.RenderNode#nSetElevation",
@@ -226,7 +228,6 @@ public final class CreateInfo implements ICreateInfo {
"android.view.RenderNode#nGetTranslationZ",
"android.view.RenderNode#nSetRotation",
"android.view.RenderNode#nGetRotation",
- "android.view.RenderNode#getMatrix",
"android.view.RenderNode#nSetLeft",
"android.view.RenderNode#nSetTop",
"android.view.RenderNode#nSetRight",
@@ -242,7 +243,6 @@ public final class CreateInfo implements ICreateInfo {
"android.view.RenderNode#nGetScaleY",
"android.view.RenderNode#nIsPivotExplicitlySet",
"android.view.ViewGroup#drawChild",
- "android.widget.TimePickerClockDelegate#getAmOrPmKeyCode",
"com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
"com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
@@ -250,7 +250,7 @@ public final class CreateInfo implements ICreateInfo {
"libcore.io.MemoryMappedFile#mmapRO",
"libcore.io.MemoryMappedFile#close",
"libcore.io.MemoryMappedFile#bigEndianIterator",
- "libcore.util.ZoneInfo$WallTime#createGregorianCalendar",
+ "libcore.util.NativeAllocationRegistry#applyFreeFunction",
};
/**
@@ -258,7 +258,6 @@ public final class CreateInfo implements ICreateInfo {
*/
public final static String[] DELEGATE_CLASS_NATIVES = new String[] {
"android.animation.PropertyValuesHolder",
- "android.graphics.AvoidXfermode",
"android.graphics.Bitmap",
"android.graphics.BitmapFactory",
"android.graphics.BitmapShader",
@@ -286,7 +285,6 @@ public final class CreateInfo implements ICreateInfo {
"android.graphics.PathDashPathEffect",
"android.graphics.PathEffect",
"android.graphics.PathMeasure",
- "android.graphics.PixelXorXfermode",
"android.graphics.PorterDuffColorFilter",
"android.graphics.PorterDuffXfermode",
"android.graphics.RadialGradient",
@@ -297,11 +295,16 @@ public final class CreateInfo implements ICreateInfo {
"android.graphics.SweepGradient",
"android.graphics.Typeface",
"android.graphics.Xfermode",
+ "android.graphics.drawable.AnimatedVectorDrawable",
+ "android.graphics.drawable.VectorDrawable",
"android.os.SystemClock",
"android.os.SystemProperties",
"android.text.AndroidBidi",
"android.text.StaticLayout",
+ "android.util.PathParser",
"android.view.Display",
+ "com.android.internal.util.VirtualRefBasePtr",
+ "com.android.internal.view.animation.NativeInterpolatorFactoryHelper",
"libcore.icu.ICU",
};
@@ -333,12 +336,9 @@ public final class CreateInfo implements ICreateInfo {
*/
private final static String[] JAVA_PKG_CLASSES =
new String[] {
- "java.lang.AutoCloseable", "com.android.tools.layoutlib.java.AutoCloseable",
- "java.util.Objects", "com.android.tools.layoutlib.java.Objects",
- "java.nio.charset.Charsets", "com.android.tools.layoutlib.java.Charsets",
+ "java.nio.charset.Charsets", "java.nio.charset.StandardCharsets",
"java.lang.IntegralToString", "com.android.tools.layoutlib.java.IntegralToString",
"java.lang.UnsafeByteSequence", "com.android.tools.layoutlib.java.UnsafeByteSequence",
- "java.nio.charset.StandardCharsets", "com.android.tools.layoutlib.java.Charsets",
// Use android.icu.text versions of DateFormat and SimpleDateFormat since the
// original ones do not match the Android implementation
"java.text.DateFormat", "android.icu.text.DateFormat",
@@ -351,11 +351,13 @@ public final class CreateInfo implements ICreateInfo {
"org.kxml2.io.KXmlParser"
};
+ /**
+ * List of fields for which we will update the visibility to be public. This is sometimes
+ * needed when access from the delegate classes is needed.
+ */
private final static String[] PROMOTED_FIELDS = new String[] {
- "android.view.Choreographer#mLastFrameTimeNanos",
- "android.widget.SimpleMonthView#mTitle",
- "android.widget.SimpleMonthView#mCalendar",
- "android.widget.SimpleMonthView#mDayOfWeekLabelCalendar"
+ "android.graphics.drawable.VectorDrawable#mVectorState",
+ "android.view.Choreographer#mLastFrameTimeNanos"
};
/**
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index 7ef75662aad4..cbb3a8abd692 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -60,7 +60,7 @@ public class DelegateClassAdapter extends ClassVisitor {
ClassVisitor cv,
String className,
Set<String> delegateMethods) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mLog = log;
mClassName = className;
mDelegateMethods = delegateMethods;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index cca9e574b7ea..da8babcbca83 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -124,7 +124,7 @@ class DelegateMethodAdapter extends MethodVisitor {
String desc,
boolean isStatic,
boolean isStaticClass) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mLog = log;
mOrgWriter = mvOriginal;
mDelWriter = mvDelegate;
@@ -188,7 +188,7 @@ class DelegateMethodAdapter extends MethodVisitor {
mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]);
}
- ArrayList<Type> paramTypes = new ArrayList<Type>();
+ ArrayList<Type> paramTypes = new ArrayList<>();
String delegateClassName = mClassName + DELEGATE_SUFFIX;
boolean pushedArg0 = false;
int maxStack = 0;
@@ -253,7 +253,8 @@ class DelegateMethodAdapter extends MethodVisitor {
mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC,
delegateClassName,
mMethodName,
- desc);
+ desc,
+ false);
Type returnType = Type.getReturnType(mDesc);
mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
@@ -371,9 +372,9 @@ class DelegateMethodAdapter extends MethodVisitor {
}
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (mOrgWriter != null) {
- mOrgWriter.visitMethodInsn(opcode, owner, name, desc);
+ mOrgWriter.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
index 61b64a2e8e38..aa68ea099844 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
@@ -26,7 +26,6 @@ import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
@@ -82,7 +81,7 @@ public class DependencyFinder {
Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet());
- List<Map<String, Set<String>>> result = new ArrayList<Map<String,Set<String>>>(2);
+ List<Map<String, Set<String>>> result = new ArrayList<>(2);
result.add(deps);
result.add(missing);
return result;
@@ -151,7 +150,7 @@ public class DependencyFinder {
* class name => ASM ClassReader. Class names are in the form "android.view.View".
*/
Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
- TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> classes = new TreeMap<>();
for (String jarPath : jarPathList) {
ZipFile zip = new ZipFile(jarPath);
@@ -202,7 +201,7 @@ public class DependencyFinder {
// The dependencies that we'll collect.
// It's a map Class name => uses class names.
- Map<String, Set<String>> dependencyMap = new TreeMap<String, Set<String>>();
+ Map<String, Set<String>> dependencyMap = new TreeMap<>();
DependencyVisitor visitor = getVisitor();
@@ -211,7 +210,7 @@ public class DependencyFinder {
for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
String name = entry.getKey();
- TreeSet<String> set = new TreeSet<String>();
+ TreeSet<String> set = new TreeSet<>();
dependencyMap.put(name, set);
visitor.setDependencySet(set);
@@ -240,7 +239,7 @@ public class DependencyFinder {
private Map<String, Set<String>> findMissingClasses(
Map<String, Set<String>> deps,
Set<String> zipClasses) {
- Map<String, Set<String>> missing = new TreeMap<String, Set<String>>();
+ Map<String, Set<String>> missing = new TreeMap<>();
for (Entry<String, Set<String>> entry : deps.entrySet()) {
String name = entry.getKey();
@@ -250,7 +249,7 @@ public class DependencyFinder {
// This dependency doesn't exist in the zip classes.
Set<String> set = missing.get(dep);
if (set == null) {
- set = new TreeSet<String>();
+ set = new TreeSet<>();
missing.put(dep, set);
}
set.add(name);
@@ -284,7 +283,7 @@ public class DependencyFinder {
* Creates a new visitor that will find all the dependencies for the visited class.
*/
public DependencyVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
/**
@@ -435,7 +434,7 @@ public class DependencyFinder {
private class MyFieldVisitor extends FieldVisitor {
public MyFieldVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
@Override
@@ -510,7 +509,7 @@ public class DependencyFinder {
private class MyMethodVisitor extends MethodVisitor {
public MyMethodVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
@@ -598,7 +597,8 @@ public class DependencyFinder {
// instruction that invokes a method
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
// owner is the internal name of the method's owner class
if (!considerDesc(owner) && owner.indexOf('/') != -1) {
@@ -654,7 +654,7 @@ public class DependencyFinder {
private class MySignatureVisitor extends SignatureVisitor {
public MySignatureVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
// ---------------------------------------------------
@@ -753,7 +753,7 @@ public class DependencyFinder {
private class MyAnnotationVisitor extends AnnotationVisitor {
public MyAnnotationVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
// Visits a primitive value of an annotation
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
index 37fc096acb04..1941ab4e78d8 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
@@ -42,9 +42,9 @@ public class InjectMethodRunnables {
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass",
- "()Ljava/lang/Class;");
+ "()Ljava/lang/Class;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
- "()Ljava/lang/ClassLoader;");
+ "()Ljava/lang/ClassLoader;", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
index ea2b9c900ad0..c834808d950d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
@@ -19,7 +19,6 @@ package com.android.tools.layoutlib.create;
import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Opcodes;
/**
* Injects methods into some classes.
@@ -29,7 +28,7 @@ public class InjectMethodsAdapter extends ClassVisitor {
private final ICreateInfo.InjectMethodRunnable mRunnable;
public InjectMethodsAdapter(ClassVisitor cv, InjectMethodRunnable runnable) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mRunnable = runnable;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index 383168face86..9bb91e599d77 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -16,6 +16,8 @@
package com.android.tools.layoutlib.create;
+import org.objectweb.asm.Opcodes;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -52,13 +54,15 @@ public class Main {
public boolean listOnlyMissingDeps = false;
}
+ public static final int ASM_VERSION = Opcodes.ASM5;
+
public static final Options sOptions = new Options();
public static void main(String[] args) {
Log log = new Log();
- ArrayList<String> osJarPath = new ArrayList<String>();
+ ArrayList<String> osJarPath = new ArrayList<>();
String[] osDestJar = { null };
if (!processArgs(log, args, osJarPath, osDestJar)) {
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
index 6fc2b240b84f..faba4d727a06 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
@@ -36,41 +36,40 @@ public interface MethodListener {
* @param isNative True if the method was a native method.
* @param caller The calling object. Null for static methods, "this" for instance methods.
*/
- public void onInvokeV(String signature, boolean isNative, Object caller);
+ void onInvokeV(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
* @see #onInvokeV(String, boolean, Object)
* @return an integer, or a boolean, or a short or a byte.
*/
- public int onInvokeI(String signature, boolean isNative, Object caller);
+ int onInvokeI(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
* @see #onInvokeV(String, boolean, Object)
* @return a long.
*/
- public long onInvokeL(String signature, boolean isNative, Object caller);
+ long onInvokeL(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
* @see #onInvokeV(String, boolean, Object)
* @return a float.
*/
- public float onInvokeF(String signature, boolean isNative, Object caller);
+ float onInvokeF(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
* @see #onInvokeV(String, boolean, Object)
* @return a double.
*/
- public double onInvokeD(String signature, boolean isNative, Object caller);
+ double onInvokeD(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
* @see #onInvokeV(String, boolean, Object)
* @return an object.
*/
- public Object onInvokeA(String signature, boolean isNative, Object caller);
+ Object onInvokeA(String signature, boolean isNative, Object caller);
}
-
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
index 4c87b3c3562d..7ccafc3867e7 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
@@ -28,7 +28,7 @@ import java.util.HashMap;
public final class OverrideMethod {
/** Map of method overridden. */
- private static HashMap<String, MethodListener> sMethods = new HashMap<String, MethodListener>();
+ private static HashMap<String, MethodListener> sMethods = new HashMap<>();
/** Default listener for all method not listed in sMethods. Nothing if null. */
private static MethodListener sDefaultListener = null;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
index e4b70da2504f..05af0337a397 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
@@ -24,7 +24,6 @@ import java.util.Set;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
-import static org.objectweb.asm.Opcodes.ASM4;
/**
* Promotes given fields to public visibility.
@@ -35,7 +34,7 @@ public class PromoteFieldClassAdapter extends ClassVisitor {
private static final int ACC_NOT_PUBLIC = ~(ACC_PRIVATE | ACC_PROTECTED);
public PromoteFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) {
- super(ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mFieldNames = fieldNames;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java
index 91161f573b33..024e32f2ddbc 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java
@@ -16,9 +16,11 @@
package com.android.tools.layoutlib.create;
+import java.util.Arrays;
import java.util.HashMap;
import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
public class RefactorClassAdapter extends AbstractClassAdapter {
@@ -30,6 +32,14 @@ public class RefactorClassAdapter extends AbstractClassAdapter {
}
@Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+
+ return new RefactorStackMapAdapter(mw);
+ }
+
+ @Override
protected String renameInternalType(String oldClassName) {
if (oldClassName != null) {
String newName = mRefactorClasses.get(oldClassName);
@@ -46,4 +56,49 @@ public class RefactorClassAdapter extends AbstractClassAdapter {
}
return oldClassName;
}
+
+ /**
+ * A method visitor that renames all references from an old class name to a new class name in
+ * the stackmap of the method.
+ */
+ private class RefactorStackMapAdapter extends MethodVisitor {
+
+ private RefactorStackMapAdapter(MethodVisitor mv) {
+ super(Main.ASM_VERSION, mv);
+ }
+
+
+ private Object[] renameFrame(Object[] elements) {
+ if (elements == null) {
+ return null;
+ }
+
+ // The input array cannot be modified. We only copy the source array on write
+ boolean copied = false;
+ for (int i = 0; i < elements.length; i++) {
+ if (!(elements[i] instanceof String)) {
+ continue;
+ }
+
+ if (!copied) {
+ elements = Arrays.copyOf(elements, elements.length);
+ copied = true;
+ }
+
+ String type = (String)elements[i];
+ if (type.indexOf(';') > 0) {
+ elements[i] = renameTypeDesc(type);
+ } else {
+ elements[i] = renameInternalType(type);
+ }
+ }
+
+ return elements;
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ super.visitFrame(type, nLocal, renameFrame(local), nStack, renameFrame(stack));
+ }
+ }
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 5e47261ea89c..bf94415a4c6f 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -43,11 +43,11 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
* Descriptors for specialized versions {@link System#arraycopy} that are not present on the
* Desktop VM.
*/
- private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<String>(Arrays.asList(
+ private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<>(Arrays.asList(
"([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
"([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
- private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(5);
+ private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<>(5);
private static final String ANDROID_LOCALE_CLASS =
"com/android/layoutlib/bridge/android/AndroidLocale";
@@ -232,7 +232,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
private final String mOriginalClassName;
public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mOriginalClassName = originalClassName;
}
@@ -245,11 +245,12 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
private class MyMethodVisitor extends MethodVisitor {
public MyMethodVisitor(MethodVisitor mv) {
- super(Opcodes.ASM4, mv);
+ super(Main.ASM_VERSION, mv);
}
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
for (MethodReplacer replacer : METHOD_REPLACERS) {
if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) {
MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
@@ -261,7 +262,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
break;
}
}
- super.visitMethodInsn(opcode, owner, name, desc);
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
index 416b73a43c11..4ba7237d7991 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
@@ -50,7 +50,7 @@ class StubMethodAdapter extends MethodVisitor {
public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
String invokeSignature, boolean isStatic, boolean isNative) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mParentVisitor = mv;
mReturnType = returnType;
mInvokeSignature = invokeSignature;
@@ -82,7 +82,8 @@ class StubMethodAdapter extends MethodVisitor {
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeV",
- "(Ljava/lang/String;ZLjava/lang/Object;)V");
+ "(Ljava/lang/String;ZLjava/lang/Object;)V",
+ false);
mParentVisitor.visitInsn(Opcodes.RETURN);
break;
case Type.BOOLEAN:
@@ -93,7 +94,8 @@ class StubMethodAdapter extends MethodVisitor {
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeI",
- "(Ljava/lang/String;ZLjava/lang/Object;)I");
+ "(Ljava/lang/String;ZLjava/lang/Object;)I",
+ false);
switch(sort) {
case Type.BOOLEAN:
Label l1 = new Label();
@@ -101,6 +103,7 @@ class StubMethodAdapter extends MethodVisitor {
mParentVisitor.visitInsn(Opcodes.ICONST_1);
mParentVisitor.visitInsn(Opcodes.IRETURN);
mParentVisitor.visitLabel(l1);
+ mParentVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mParentVisitor.visitInsn(Opcodes.ICONST_0);
break;
case Type.CHAR:
@@ -119,21 +122,24 @@ class StubMethodAdapter extends MethodVisitor {
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeL",
- "(Ljava/lang/String;ZLjava/lang/Object;)J");
+ "(Ljava/lang/String;ZLjava/lang/Object;)J",
+ false);
mParentVisitor.visitInsn(Opcodes.LRETURN);
break;
case Type.FLOAT:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeF",
- "(Ljava/lang/String;ZLjava/lang/Object;)F");
+ "(Ljava/lang/String;ZLjava/lang/Object;)F",
+ false);
mParentVisitor.visitInsn(Opcodes.FRETURN);
break;
case Type.DOUBLE:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeD",
- "(Ljava/lang/String;ZLjava/lang/Object;)D");
+ "(Ljava/lang/String;ZLjava/lang/Object;)D",
+ false);
mParentVisitor.visitInsn(Opcodes.DRETURN);
break;
case Type.ARRAY:
@@ -141,7 +147,8 @@ class StubMethodAdapter extends MethodVisitor {
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeA",
- "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;");
+ "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;",
+ false);
mParentVisitor.visitTypeInsn(Opcodes.CHECKCAST, mReturnType.getInternalName());
mParentVisitor.visitInsn(Opcodes.ARETURN);
break;
@@ -282,9 +289,9 @@ class StubMethodAdapter extends MethodVisitor {
}
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (mIsInitMethod) {
- mParentVisitor.visitMethodInsn(opcode, owner, name, desc);
+ mParentVisitor.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index d9ecf980658c..a28ae694246d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -49,7 +49,7 @@ class TransformClassAdapter extends ClassVisitor {
public TransformClassAdapter(Log logger, Set<String> stubMethods,
Set<String> deleteReturns, String className, ClassVisitor cv,
boolean stubNativesOnly) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mLog = logger;
mStubMethods = stubMethods;
mClassName = className;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
index ed2c128e1900..7d6c4ec1ad9e 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
@@ -28,4 +28,5 @@ public interface AutoCloseable {
/**
* Closes the object and release any system resources it holds.
*/
- void close() throws Exception; }
+ void close() throws Exception;
+}
diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk
index c197d5733658..dafb9c6f9402 100644
--- a/tools/layoutlib/create/tests/Android.mk
+++ b/tools/layoutlib/create/tests/Android.mk
@@ -24,7 +24,7 @@ LOCAL_MODULE := layoutlib-create-tests
LOCAL_MODULE_TAGS := optional
LOCAL_JAVA_LIBRARIES := layoutlib_create junit
-LOCAL_STATIC_JAVA_LIBRARIES := asm-4.0
+LOCAL_STATIC_JAVA_LIBRARIES := asm-5.0
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index 78e2c4899c1b..f86917a8139c 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -17,13 +17,8 @@
package com.android.tools.layoutlib.create;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
@@ -32,11 +27,15 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
/**
* Unit tests for some methods of {@link AsmAnalyzer}.
*/
@@ -51,26 +50,22 @@ public class AsmAnalyzerTest {
mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
- mOsJarPath = new ArrayList<String>();
+ mOsJarPath = new ArrayList<>();
+ //noinspection ConstantConditions
mOsJarPath.add(url.getFile());
- Set<String> excludeClasses = new HashSet<String>(1);
- excludeClasses.add("java.lang.JavaClass");
+ Set<String> excludeClasses = Collections.singleton("java.lang.JavaClass");
String[] includeFiles = new String[]{"mock_android/data/data*"};
mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, null /* deriveFrom */,
null /* includeGlobs */, excludeClasses, includeFiles);
}
- @After
- public void tearDown() throws Exception {
- }
-
@Test
public void testParseZip() throws IOException {
- Map<String, ClassReader> map = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> map = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, map, filesFound);
@@ -101,11 +96,11 @@ public class AsmAnalyzerTest {
@Test
public void testFindClass() throws IOException, LogAbortException {
- Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> found = new TreeMap<>();
ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
zipClasses, found);
@@ -120,11 +115,11 @@ public class AsmAnalyzerTest {
@Test
public void testFindGlobs() throws IOException, LogAbortException {
- Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> found = new TreeMap<>();
// this matches classes, a package match returns nothing
found.clear();
@@ -183,11 +178,11 @@ public class AsmAnalyzerTest {
@Test
public void testFindClassesDerivingFrom() throws LogAbortException, IOException {
- Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> found = new TreeMap<>();
mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
@@ -209,14 +204,14 @@ public class AsmAnalyzerTest {
@Test
public void testDependencyVisitor() throws IOException, LogAbortException {
- Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> keep = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> out_deps = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> keep = new TreeMap<>();
+ TreeMap<String, ClassReader> new_keep = new TreeMap<>();
+ TreeMap<String, ClassReader> in_deps = new TreeMap<>();
+ TreeMap<String, ClassReader> out_deps = new TreeMap<>();
ClassReader cr = mAa.findClass("mock_android.widget.LinearLayout", zipClasses, keep);
DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 8a2235b8526c..c4dd7eeafbba 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -18,12 +18,6 @@
package com.android.tools.layoutlib.create;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -31,7 +25,6 @@ import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.io.ByteArrayOutputStream;
@@ -44,7 +37,6 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -52,11 +44,18 @@ import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
/**
* Unit tests for some methods of {@link AsmGenerator}.
*/
public class AsmGeneratorTest {
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
private MockLog mLog;
private ArrayList<String> mOsJarPath;
private String mOsDestJar;
@@ -70,7 +69,8 @@ public class AsmGeneratorTest {
mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
- mOsJarPath = new ArrayList<String>();
+ mOsJarPath = new ArrayList<>();
+ //noinspection ConstantConditions
mOsJarPath.add(url.getFile());
mTempFile = File.createTempFile("mock", ".jar");
@@ -98,18 +98,18 @@ public class AsmGeneratorTest {
@Override
public String[] getDelegateMethods() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getDelegateClassNatives() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getOverriddenMethods() {
// methods to force override
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
@@ -123,7 +123,7 @@ public class AsmGeneratorTest {
@Override
public String[] getJavaPkgClasses() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
@@ -134,17 +134,17 @@ public class AsmGeneratorTest {
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedFields() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- return new HashMap<String, InjectMethodRunnable>(0);
+ return Collections.emptyMap();
}
};
@@ -155,7 +155,7 @@ public class AsmGeneratorTest {
new String[] { // include classes
"**"
},
- new HashSet<String>(0) /* excluded classes */,
+ Collections.<String>emptySet() /* excluded classes */,
new String[]{} /* include files */);
aa.analyze();
agen.generate();
@@ -178,24 +178,24 @@ public class AsmGeneratorTest {
@Override
public String[] getDelegateMethods() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getDelegateClassNatives() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getOverriddenMethods() {
// methods to force override
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
@@ -214,17 +214,17 @@ public class AsmGeneratorTest {
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedFields() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- return new HashMap<String, InjectMethodRunnable>(0);
+ return Collections.emptyMap();
}
};
@@ -235,14 +235,14 @@ public class AsmGeneratorTest {
new String[] { // include classes
"**"
},
- new HashSet<String>(1),
+ Collections.<String>emptySet(),
new String[] { /* include files */
"mock_android/data/data*"
});
aa.analyze();
agen.generate();
- Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> output = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
parseZip(mOsDestJar, output, filesFound);
boolean injectedClassFound = false;
for (ClassReader cr: output.values()) {
@@ -265,35 +265,35 @@ public class AsmGeneratorTest {
@Override
public String[] getDelegateMethods() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getDelegateClassNatives() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getOverriddenMethods() {
// methods to force override
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getJavaPkgClasses() {
// classes to refactor (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Set<String> getExcludedClasses() {
- Set<String> set = new HashSet<String>(2);
+ Set<String> set = new HashSet<>(2);
set.add("mock_android.dummy.InnerTest");
set.add("java.lang.JavaClass");
return set;
@@ -302,17 +302,17 @@ public class AsmGeneratorTest {
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedFields() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- return new HashMap<String, InjectMethodRunnable>(0);
+ return Collections.emptyMap();
}
};
@@ -329,8 +329,8 @@ public class AsmGeneratorTest {
});
aa.analyze();
agen.generate();
- Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> output = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
parseZip(mOsDestJar, output, filesFound);
for (String s : output.keySet()) {
assertFalse(excludedClasses.contains(s));
@@ -351,55 +351,52 @@ public class AsmGeneratorTest {
@Override
public String[] getDelegateMethods() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getDelegateClassNatives() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getOverriddenMethods() {
// methods to force override
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getJavaPkgClasses() {
// classes to refactor (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Set<String> getExcludedClasses() {
- return new HashSet<String>(0);
+ return Collections.emptySet();
}
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedFields() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- HashMap<String, InjectMethodRunnable> map =
- new HashMap<String, InjectMethodRunnable>(1);
- map.put("mock_android.util.EmptyArray",
+ return Collections.singletonMap("mock_android.util.EmptyArray",
InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
- return map;
}
};
@@ -415,8 +412,8 @@ public class AsmGeneratorTest {
});
aa.analyze();
agen.generate();
- Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> output = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
parseZip(mOsDestJar, output, filesFound);
final String modifiedClass = "mock_android.util.EmptyArray";
final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class");
@@ -424,11 +421,8 @@ public class AsmGeneratorTest {
ZipEntry entry = zipFile.getEntry(modifiedClassPath);
assertNotNull(entry);
final byte[] bytes;
- final InputStream inputStream = zipFile.getInputStream(entry);
- try {
+ try (InputStream inputStream = zipFile.getInputStream(entry)) {
bytes = getByteArray(inputStream);
- } finally {
- inputStream.close();
}
ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
@Override
@@ -489,7 +483,7 @@ public class AsmGeneratorTest {
boolean mInjectedClassFound = false;
TestClassVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
@Override
@@ -514,7 +508,7 @@ public class AsmGeneratorTest {
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
- return new MethodVisitor(Opcodes.ASM4, mv) {
+ return new MethodVisitor(Main.ASM_VERSION, mv) {
@Override
public void visitFieldInsn(int opcode, String owner, String name,
@@ -540,10 +534,10 @@ public class AsmGeneratorTest {
@Override
public void visitMethodInsn(int opcode, String owner, String name,
- String desc) {
+ String desc, boolean itf) {
assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
assertTrue(testType(Type.getType(desc)));
- super.visitMethodInsn(opcode, owner, name, desc);
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
}
};
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
index 0135c40e71ab..0cdcdc0c1235 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -60,7 +60,7 @@ public class ClassHasNativeVisitorTest {
* Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
*/
private static class MockClassHasNativeVisitor extends ClassHasNativeVisitor {
- private ArrayList<String> mMethodsFound = new ArrayList<String>();
+ private ArrayList<String> mMethodsFound = new ArrayList<>();
public String[] getMethodsFound() {
return mMethodsFound.toArray(new String[mMethodsFound.size()]);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index e37a09b348b8..afaa3997adf7 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -53,12 +53,10 @@ public class DelegateClassAdapterTest {
private MockLog mLog;
- private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName();
- private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
- private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
- InnerClass.class.getSimpleName();
- private static final String STATIC_INNER_CLASS_NAME =
- OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName();
+ private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getName();
+ private static final String OUTER_CLASS_NAME = OuterClass.class.getName();
+ private static final String INNER_CLASS_NAME = InnerClass.class.getName();
+ private static final String STATIC_INNER_CLASS_NAME = StaticInnerClass.class.getName();
@Before
public void setUp() throws Exception {
@@ -69,12 +67,12 @@ public class DelegateClassAdapterTest {
/**
* Tests that a class not being modified still works.
*/
- @SuppressWarnings("unchecked")
@Test
public void testNoOp() throws Throwable {
// create an instance of the class that will be modified
// (load the class in a distinct class loader so that we can trash its definition later)
ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
+ @SuppressWarnings("unchecked")
Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME);
ClassWithNative instance1 = clazz1.newInstance();
assertEquals(42, instance1.add(20, 22));
@@ -88,7 +86,7 @@ public class DelegateClassAdapterTest {
// Now process it but tell the delegate to not modify any method
ClassWriter cw = new ClassWriter(0 /*flags*/);
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
@@ -152,7 +150,7 @@ public class DelegateClassAdapterTest {
String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
delegateMethods.add("<init>");
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
@@ -166,7 +164,7 @@ public class DelegateClassAdapterTest {
ClassWriter cw = new ClassWriter(0 /*flags*/);
String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
@@ -217,7 +215,7 @@ public class DelegateClassAdapterTest {
@Test
public void testDelegateInner() throws Throwable {
// We'll delegate the "get" method of both the inner and outer class.
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
delegateMethods.add("get");
delegateMethods.add("privateMethod");
@@ -300,7 +298,7 @@ public class DelegateClassAdapterTest {
@Test
public void testDelegateStaticInner() throws Throwable {
// We'll delegate the "get" method of both the inner and outer class.
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
delegateMethods.add("get");
// Generate the delegate for the outer class.
@@ -367,7 +365,7 @@ public class DelegateClassAdapterTest {
*/
private abstract class ClassLoader2 extends ClassLoader {
- private final Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>();
+ private final Map<String, byte[]> mClassDefs = new HashMap<>();
public ClassLoader2() {
super(null);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java
new file mode 100644
index 000000000000..3db3e2364eec
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.layoutlib.create;
+
+import com.android.tools.layoutlib.create.dataclass.StubClass;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.reflect.Method;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+
+import static org.junit.Assert.*;
+
+public class StubMethodAdapterTest {
+
+ private static final String STUB_CLASS_NAME = StubClass.class.getName();
+
+ /**
+ * Load a dummy class, stub one of its method and ensure that the modified class works as
+ * intended.
+ */
+ @Test
+ public void testBoolean() throws Exception {
+ final String methodName = "returnTrue";
+ // First don't change the method and assert that it returns true
+ testBoolean((name, type) -> false, Assert::assertTrue, methodName);
+ // Change the method now and assert that it returns false.
+ testBoolean((name, type) -> methodName.equals(name) &&
+ Type.BOOLEAN_TYPE.equals(type.getReturnType()), Assert::assertFalse, methodName);
+ }
+
+ /**
+ * @param methodPredicate tests if the method should be replaced
+ */
+ private void testBoolean(BiPredicate<String, Type> methodPredicate, Consumer<Boolean> assertion,
+ String methodName) throws Exception {
+ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ // Always rename the class to avoid conflict with the original class.
+ String newClassName = STUB_CLASS_NAME + '_';
+ new ClassReader(STUB_CLASS_NAME).accept(
+ new ClassAdapter(newClassName, writer, methodPredicate), 0);
+ MyClassLoader myClassLoader = new MyClassLoader(newClassName, writer.toByteArray());
+ Class<?> aClass = myClassLoader.loadClass(newClassName);
+ assertTrue("StubClass not loaded by the classloader. Likely a bug in the test.",
+ myClassLoader.findClassCalled);
+ Method method = aClass.getMethod(methodName);
+ Object o = aClass.newInstance();
+ assertion.accept((Boolean) method.invoke(o));
+ }
+
+ private static class ClassAdapter extends ClassVisitor {
+
+ private final String mClassName;
+ private final BiPredicate<String, Type> mMethodPredicate;
+
+ private ClassAdapter(String className, ClassVisitor cv,
+ BiPredicate<String, Type> methodPredicate) {
+ super(Main.ASM_VERSION, cv);
+ mClassName = className.replace('.', '/');
+ mMethodPredicate = methodPredicate;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ super.visit(version, access, mClassName, signature, superName,
+ interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+ // Copied partly from
+ // com.android.tools.layoutlib.create.DelegateClassAdapter.visitMethod()
+ // but not generating the _Original method.
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+ MethodVisitor originalMethod =
+ super.visitMethod(access, name, desc, signature, exceptions);
+ Type descriptor = Type.getMethodType(desc);
+ if (mMethodPredicate.test(name, descriptor)) {
+ String methodSignature = mClassName + "#" + name;
+ String invokeSignature = methodSignature + desc;
+ return new StubMethodAdapter(originalMethod, name, descriptor.getReturnType(),
+ invokeSignature, isStatic, isNative);
+ }
+ return originalMethod;
+ }
+ }
+
+ private static class MyClassLoader extends ClassLoader {
+ private final String mName;
+ private final byte[] mBytes;
+ private boolean findClassCalled;
+
+ private MyClassLoader(String name, byte[] bytes) {
+ mName = name;
+ mBytes = bytes;
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.equals(mName)) {
+ findClassCalled = true;
+ return defineClass(name, mBytes, 0, mBytes.length);
+ }
+ return super.findClass(name);
+ }
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java
new file mode 100644
index 000000000000..3ae8e47de6e3
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.StubMethodAdapterTest;
+
+/**
+ * Used by {@link StubMethodAdapterTest}
+ */
+@SuppressWarnings("unused")
+public class StubClass {
+
+ public boolean returnTrue() {
+ return true;
+ }
+}
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
new file mode 100755
index 000000000000..b071093a5615
--- /dev/null
+++ b/tools/localedata/extract_icu_data.py
@@ -0,0 +1,286 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 The Android Open Source Project. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Generate a C++ data table containing locale data."""
+
+import collections
+import glob
+import os.path
+import sys
+
+
+def get_locale_parts(locale):
+ """Split a locale into three parts, for langauge, script, and region."""
+ parts = locale.split('_')
+ if len(parts) == 1:
+ return (parts[0], None, None)
+ elif len(parts) == 2:
+ if len(parts[1]) == 4: # parts[1] is a script
+ return (parts[0], parts[1], None)
+ else:
+ return (parts[0], None, parts[1])
+ else:
+ assert len(parts) == 3
+ return tuple(parts)
+
+
+def read_likely_subtags(input_file_name):
+ """Read and parse ICU's likelySubtags.txt."""
+ with open(input_file_name) as input_file:
+ likely_script_dict = {
+ # Android's additions for pseudo-locales. These internal codes make
+ # sure that the pseudo-locales would not match other English or
+ # Arabic locales. (We can't use private-use ISO 15924 codes, since
+ # they may be used by apps for other purposes.)
+ "en_XA": "~~~A",
+ "ar_XB": "~~~B",
+ }
+ representative_locales = {
+ # Android's additions
+ "en_Latn_GB", # representative for en_Latn_001
+ "es_Latn_MX", # representative for es_Latn_419
+ "es_Latn_US", # representative for es_Latn_419 (not the best idea,
+ # but Android has been shipping with it for quite a
+ # while. Fortunately, MX < US, so if both exist, MX
+ # would be chosen.)
+ }
+ for line in input_file:
+ line = unicode(line, 'UTF-8').strip(u' \n\uFEFF').encode('UTF-8')
+ if line.startswith('//'):
+ continue
+ if '{' in line and '}' in line:
+ from_locale = line[:line.index('{')]
+ to_locale = line[line.index('"')+1:line.rindex('"')]
+ from_lang, from_scr, from_region = get_locale_parts(from_locale)
+ _, to_scr, to_region = get_locale_parts(to_locale)
+ if from_lang == 'und':
+ continue # not very useful for our purposes
+ if from_region is None and to_region != '001':
+ representative_locales.add(to_locale)
+ if from_scr is None:
+ likely_script_dict[from_locale] = to_scr
+ return likely_script_dict, frozenset(representative_locales)
+
+
+# From packLanguageOrRegion() in ResourceTypes.cpp
+def pack_language_or_region(inp, base):
+ """Pack langauge or region in a two-byte tuple."""
+ if inp is None:
+ return (0, 0)
+ elif len(inp) == 2:
+ return ord(inp[0]), ord(inp[1])
+ else:
+ assert len(inp) == 3
+ base = ord(base)
+ first = ord(inp[0]) - base
+ second = ord(inp[1]) - base
+ third = ord(inp[2]) - base
+
+ return (0x80 | (third << 2) | (second >>3),
+ ((second << 5) | first) & 0xFF)
+
+
+# From packLanguage() in ResourceTypes.cpp
+def pack_language(language):
+ """Pack language in a two-byte tuple."""
+ return pack_language_or_region(language, 'a')
+
+
+# From packRegion() in ResourceTypes.cpp
+def pack_region(region):
+ """Pack region in a two-byte tuple."""
+ return pack_language_or_region(region, '0')
+
+
+def pack_to_uint32(locale):
+ """Pack language+region of locale into a 32-bit unsigned integer."""
+ lang, _, region = get_locale_parts(locale)
+ plang = pack_language(lang)
+ pregion = pack_region(region)
+ return (plang[0] << 24) | (plang[1] << 16) | (pregion[0] << 8) | pregion[1]
+
+
+def dump_script_codes(all_scripts):
+ """Dump the SCRIPT_CODES table."""
+ print 'const char SCRIPT_CODES[][4] = {'
+ for index, script in enumerate(all_scripts):
+ print " /* %-2d */ {'%c', '%c', '%c', '%c'}," % (
+ index, script[0], script[1], script[2], script[3])
+ print '};'
+ print
+
+
+def dump_script_data(likely_script_dict, all_scripts):
+ """Dump the script data."""
+ print
+ print 'const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({'
+ for locale in sorted(likely_script_dict.keys()):
+ script = likely_script_dict[locale]
+ print ' {0x%08Xu, %2du}, // %s -> %s' % (
+ pack_to_uint32(locale),
+ all_scripts.index(script),
+ locale.replace('_', '-'),
+ script)
+ print '});'
+
+
+def pack_to_uint64(locale):
+ """Pack a full locale into a 64-bit unsigned integer."""
+ _, script, _ = get_locale_parts(locale)
+ return ((pack_to_uint32(locale) << 32) |
+ (ord(script[0]) << 24) |
+ (ord(script[1]) << 16) |
+ (ord(script[2]) << 8) |
+ ord(script[3]))
+
+
+def dump_representative_locales(representative_locales):
+ """Dump the set of representative locales."""
+ print
+ print 'std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({'
+ for locale in sorted(representative_locales):
+ print ' 0x%08Xllu, // %s' % (
+ pack_to_uint64(locale),
+ locale)
+ print '});'
+
+
+def read_and_dump_likely_data(icu_data_dir):
+ """Read and dump the likely-script data."""
+ likely_subtags_txt = os.path.join(icu_data_dir, 'misc', 'likelySubtags.txt')
+ likely_script_dict, representative_locales = read_likely_subtags(
+ likely_subtags_txt)
+
+ all_scripts = list(set(likely_script_dict.values()))
+ assert len(all_scripts) <= 256
+ all_scripts.sort()
+
+ dump_script_codes(all_scripts)
+ dump_script_data(likely_script_dict, all_scripts)
+ dump_representative_locales(representative_locales)
+ return likely_script_dict
+
+
+def read_parent_data(icu_data_dir):
+ """Read locale parent data from ICU data files."""
+ all_icu_data_files = glob.glob(os.path.join(icu_data_dir, '*', '*.txt'))
+ parent_dict = {}
+ for data_file in all_icu_data_files:
+ locale = os.path.splitext(os.path.basename(data_file))[0]
+ with open(data_file) as input_file:
+ for line in input_file:
+ if '%%Parent' in line:
+ parent = line[line.index('"')+1:line.rindex('"')]
+ if locale in parent_dict:
+ # Different files shouldn't have different parent info
+ assert parent_dict[locale] == parent
+ else:
+ parent_dict[locale] = parent
+ elif locale.startswith('ar_') and 'default{"latn"}' in line:
+ # Arabic parent overrides for ASCII digits. Since
+ # Unicode extensions are not supported in ResourceTypes,
+ # we will use ar-015 (Arabic, Northern Africa) instead
+ # of the more correct ar-u-nu-latn.
+ parent_dict[locale] = 'ar_015'
+ return parent_dict
+
+
+def get_likely_script(locale, likely_script_dict):
+ """Find the likely script for a locale, given the likely-script dictionary.
+ """
+ if locale.count('_') == 2:
+ # it already has a script
+ return locale.split('_')[1]
+ elif locale in likely_script_dict:
+ return likely_script_dict[locale]
+ else:
+ language = locale.split('_')[0]
+ return likely_script_dict[language]
+
+
+def dump_parent_data(script_organized_dict):
+ """Dump information for parents of locales."""
+ sorted_scripts = sorted(script_organized_dict.keys())
+ print
+ for script in sorted_scripts:
+ parent_dict = script_organized_dict[script]
+ print ('const std::unordered_map<uint32_t, uint32_t> %s_PARENTS({'
+ % script.upper())
+ for locale in sorted(parent_dict.keys()):
+ parent = parent_dict[locale]
+ print ' {0x%08Xu, 0x%08Xu}, // %s -> %s' % (
+ pack_to_uint32(locale),
+ pack_to_uint32(parent),
+ locale.replace('_', '-'),
+ parent.replace('_', '-'))
+ print '});'
+ print
+
+ print 'const struct {'
+ print ' const char script[4];'
+ print ' const std::unordered_map<uint32_t, uint32_t>* map;'
+ print '} SCRIPT_PARENTS[] = {'
+ for script in sorted_scripts:
+ print " {{'%c', '%c', '%c', '%c'}, &%s_PARENTS}," % (
+ script[0], script[1], script[2], script[3],
+ script.upper())
+ print '};'
+
+
+def dump_parent_tree_depth(parent_dict):
+ """Find and dump the depth of the parent tree."""
+ max_depth = 1
+ for locale, _ in parent_dict.items():
+ depth = 1
+ while locale in parent_dict:
+ locale = parent_dict[locale]
+ depth += 1
+ max_depth = max(max_depth, depth)
+ assert max_depth < 5 # Our algorithms assume small max_depth
+ print
+ print 'const size_t MAX_PARENT_DEPTH = %d;' % max_depth
+
+
+def read_and_dump_parent_data(icu_data_dir, likely_script_dict):
+ """Read parent data from ICU and dump it."""
+ parent_dict = read_parent_data(icu_data_dir)
+ script_organized_dict = collections.defaultdict(dict)
+ for locale in parent_dict:
+ parent = parent_dict[locale]
+ if parent == 'root':
+ continue
+ script = get_likely_script(locale, likely_script_dict)
+ script_organized_dict[script][locale] = parent_dict[locale]
+ dump_parent_data(script_organized_dict)
+ dump_parent_tree_depth(parent_dict)
+
+
+def main():
+ """Read the data files from ICU and dump the output to a C++ file."""
+ source_root = sys.argv[1]
+ icu_data_dir = os.path.join(
+ source_root,
+ 'external', 'icu', 'icu4c', 'source', 'data')
+
+ print '// Auto-generated by %s' % sys.argv[0]
+ print
+ likely_script_dict = read_and_dump_likely_data(icu_data_dir)
+ read_and_dump_parent_data(icu_data_dir, likely_script_dict)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk
new file mode 100644
index 000000000000..35d28fbfeba8
--- /dev/null
+++ b/tools/preload2/Android.mk
@@ -0,0 +1,32 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+# To connect to devices (and take hprof dumps).
+LOCAL_STATIC_JAVA_LIBRARIES := ddmlib-prebuilt
+
+# To process hprof dumps.
+LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib
+
+# For JDWP access we use the framework in the JDWP tests from Apache Harmony, for
+# convenience (and to not depend on internal JDK APIs).
+LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit
+
+LOCAL_MODULE:= preload2
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Copy the preload-tool shell script to the host's bin directory.
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := preload-tool
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/preload-tool $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
diff --git a/tools/preload2/preload-tool b/tools/preload2/preload-tool
new file mode 100644
index 000000000000..36dbc1c775b1
--- /dev/null
+++ b/tools/preload2/preload-tool
@@ -0,0 +1,37 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script is used on the host only. It uses a common subset
+# shell dialect that should work well. It is partially derived
+# from art/tools/art.
+
+function follow_links() {
+ if [ z"$BASH_SOURCE" != z ]; then
+ file="$BASH_SOURCE"
+ else
+ file="$0"
+ fi
+ while [ -h "$file" ]; do
+ # On Mac OS, readlink -f doesn't work.
+ file="$(readlink "$file")"
+ done
+ echo "$file"
+}
+
+
+PROG_NAME="$(follow_links)"
+PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
+ANDROID_ROOT=$PROG_DIR/..
+
+java -cp $ANDROID_ROOT/framework/preload2.jar com.android.preload.Main
diff --git a/tools/preload2/src/com/android/preload/ClientUtils.java b/tools/preload2/src/com/android/preload/ClientUtils.java
new file mode 100644
index 000000000000..71ef025d6587
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ClientUtils.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+
+/**
+ * Helper class for common communication with a Client (the ddms name for a running application).
+ *
+ * Instances take a default timeout parameter that's applied to all functions without explicit
+ * timeout. Timeouts are in milliseconds.
+ */
+public class ClientUtils {
+
+ private int defaultTimeout;
+
+ public ClientUtils() {
+ this(10000);
+ }
+
+ public ClientUtils(int defaultTimeout) {
+ this.defaultTimeout = defaultTimeout;
+ }
+
+ /**
+ * Shortcut for findClient with default timeout.
+ */
+ public Client findClient(IDevice device, String processName, int processPid) {
+ return findClient(device, processName, processPid, defaultTimeout);
+ }
+
+ /**
+ * Find the client with the given process name or process id. The name takes precedence over
+ * the process id (if valid). Stop looking after the given timeout.
+ *
+ * @param device The device to communicate with.
+ * @param processName The name of the process. May be null.
+ * @param processPid The pid of the process. Values less than or equal to zero are ignored.
+ * @param timeout The amount of milliseconds to wait, at most.
+ * @return The client, if found. Otherwise null.
+ */
+ public Client findClient(IDevice device, String processName, int processPid, int timeout) {
+ WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout);
+ return wfc.get();
+ }
+
+ /**
+ * Shortcut for findAllClients with default timeout.
+ */
+ public Client[] findAllClients(IDevice device) {
+ return findAllClients(device, defaultTimeout);
+ }
+
+ /**
+ * Retrieve all clients known to the given device. Wait at most the given timeout.
+ *
+ * @param device The device to investigate.
+ * @param timeout The amount of milliseconds to wait, at most.
+ * @return An array of clients running on the given device. May be null depending on the
+ * device implementation.
+ */
+ public Client[] findAllClients(IDevice device, int timeout) {
+ if (device.hasClients()) {
+ return device.getClients();
+ }
+ WaitForClients wfc = new WaitForClients(device, timeout);
+ return wfc.get();
+ }
+
+ private static class WaitForClient implements IClientChangeListener {
+
+ private IDevice device;
+ private String processName;
+ private int processPid;
+ private long timeout;
+ private Client result;
+
+ public WaitForClient(IDevice device, String processName, int processPid, long timeout) {
+ this.device = device;
+ this.processName = processName;
+ this.processPid = processPid;
+ this.timeout = timeout;
+ this.result = null;
+ }
+
+ public Client get() {
+ synchronized (this) {
+ AndroidDebugBridge.addClientChangeListener(this);
+
+ // Maybe it's already there.
+ if (result == null) {
+ result = searchForClient(device);
+ }
+
+ if (result == null) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Note: doesn't guard for spurious wakeup.
+ }
+ }
+ }
+
+ AndroidDebugBridge.removeClientChangeListener(this);
+ return result;
+ }
+
+ private Client searchForClient(IDevice device) {
+ if (processName != null) {
+ Client tmp = device.getClient(processName);
+ if (tmp != null) {
+ return tmp;
+ }
+ }
+ if (processPid > 0) {
+ String name = device.getClientName(processPid);
+ if (name != null && !name.isEmpty()) {
+ Client tmp = device.getClient(name);
+ if (tmp != null) {
+ return tmp;
+ }
+ }
+ }
+ if (processPid > 0) {
+ // Try manual search.
+ for (Client cl : device.getClients()) {
+ if (cl.getClientData().getPid() == processPid
+ && cl.getClientData().getClientDescription() != null) {
+ return cl;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean isTargetClient(Client c) {
+ if (processPid > 0 && c.getClientData().getPid() == processPid) {
+ return true;
+ }
+ if (processName != null
+ && processName.equals(c.getClientData().getClientDescription())) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void clientChanged(Client arg0, int arg1) {
+ synchronized (this) {
+ if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
+ if (isTargetClient(arg0)) {
+ result = arg0;
+ notifyAll();
+ }
+ }
+ }
+ }
+ }
+
+ private static class WaitForClients implements IClientChangeListener {
+
+ private IDevice device;
+ private long timeout;
+
+ public WaitForClients(IDevice device, long timeout) {
+ this.device = device;
+ this.timeout = timeout;
+ }
+
+ public Client[] get() {
+ synchronized (this) {
+ AndroidDebugBridge.addClientChangeListener(this);
+
+ if (device.hasClients()) {
+ return device.getClients();
+ }
+
+ try {
+ wait(timeout); // Note: doesn't guard for spurious wakeup.
+ } catch (InterruptedException exc) {
+ }
+
+ // We will be woken up when the first client data arrives. Sleep a little longer
+ // to give (hopefully all of) the rest of the clients a chance to become available.
+ // Note: a loop with timeout is brittle as well and complicated, just accept this
+ // for now.
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException exc) {
+ }
+ }
+
+ AndroidDebugBridge.removeClientChangeListener(this);
+
+ return device.getClients();
+ }
+
+ @Override
+ public void clientChanged(Client arg0, int arg1) {
+ synchronized (this) {
+ if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
+ notifyAll();
+ }
+ }
+ }
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java
new file mode 100644
index 000000000000..72de7b58b0a7
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DeviceUtils.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
+import com.android.preload.classdataretrieval.hprof.Hprof;
+import com.android.ddmlib.DdmPreferences;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+
+import java.util.Date;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class for some device routines.
+ */
+public class DeviceUtils {
+
+ public static void init(int debugPort) {
+ DdmPreferences.setSelectedDebugPort(debugPort);
+
+ Hprof.init();
+
+ AndroidDebugBridge.init(true);
+
+ AndroidDebugBridge.createBridge();
+ }
+
+ /**
+ * Run a command in the shell on the device.
+ */
+ public static void doShell(IDevice device, String cmdline, long timeout, TimeUnit unit) {
+ doShell(device, cmdline, new NullShellOutputReceiver(), timeout, unit);
+ }
+
+ /**
+ * Run a command in the shell on the device. Collects and returns the console output.
+ */
+ public static String doShellReturnString(IDevice device, String cmdline, long timeout,
+ TimeUnit unit) {
+ CollectStringShellOutputReceiver rec = new CollectStringShellOutputReceiver();
+ doShell(device, cmdline, rec, timeout, unit);
+ return rec.toString();
+ }
+
+ /**
+ * Run a command in the shell on the device, directing all output to the given receiver.
+ */
+ public static void doShell(IDevice device, String cmdline, IShellOutputReceiver receiver,
+ long timeout, TimeUnit unit) {
+ try {
+ device.executeShellCommand(cmdline, receiver, timeout, unit);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Run am start on the device.
+ */
+ public static void doAMStart(IDevice device, String name, String activity) {
+ doShell(device, "am start -n " + name + " /." + activity, 30, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Find the device with the given serial. Give up after the given timeout (in milliseconds).
+ */
+ public static IDevice findDevice(String serial, int timeout) {
+ WaitForDevice wfd = new WaitForDevice(serial, timeout);
+ return wfd.get();
+ }
+
+ /**
+ * Get all devices ddms knows about. Wait at most for the given timeout.
+ */
+ public static IDevice[] findDevices(int timeout) {
+ WaitForDevice wfd = new WaitForDevice(null, timeout);
+ wfd.get();
+ return AndroidDebugBridge.getBridge().getDevices();
+ }
+
+ /**
+ * Return the build type of the given device. This is the value of the "ro.build.type"
+ * system property.
+ */
+ public static String getBuildType(IDevice device) {
+ try {
+ Future<String> buildType = device.getSystemProperty("ro.build.type");
+ return buildType.get(500, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ }
+ return null;
+ }
+
+ /**
+ * Check whether the given device has a pre-optimized boot image. More precisely, checks
+ * whether /system/framework/ * /boot.art exists.
+ */
+ public static boolean hasPrebuiltBootImage(IDevice device) {
+ String ret =
+ doShellReturnString(device, "ls /system/framework/*/boot.art", 500, TimeUnit.MILLISECONDS);
+
+ return !ret.contains("No such file or directory");
+ }
+
+ /**
+ * Remove files involved in a standard build that interfere with collecting data. This will
+ * remove /etc/preloaded-classes, which determines which classes are allocated already in the
+ * boot image. It also deletes any compiled boot image on the device. Then it restarts the
+ * device.
+ *
+ * This is a potentially long-running operation, as the boot after the deletion may take a while.
+ * The method will abort after the given timeout.
+ */
+ public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) {
+ String oldContent =
+ DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS);
+ if (oldContent.trim().equals("")) {
+ System.out.println("Preloaded-classes already empty.");
+ return true;
+ }
+
+ // Stop the system server etc.
+ doShell(device, "stop", 100, TimeUnit.MILLISECONDS);
+
+ // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount,"
+ // but AndroidDebugBridge doesn't expose it.
+ doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS);
+ doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
+ // We do need an empty file.
+ doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
+
+ // Delete the files in the dalvik cache.
+ doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS);
+
+ // We'll try to use dev.bootcomplete to know when the system server is back up. But stop
+ // doesn't reset it, so do it manually.
+ doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS);
+
+ // Start the system server.
+ doShell(device, "start", 100, TimeUnit.MILLISECONDS);
+
+ // Do a loop checking each second whether bootcomplete. Wait for at most the given
+ // threshold.
+ Date startDate = new Date();
+ for (;;) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore spurious wakeup.
+ }
+ // Check whether bootcomplete.
+ String ret =
+ doShellReturnString(device, "getprop dev.bootcomplete", 500, TimeUnit.MILLISECONDS);
+ if (ret.trim().equals("1")) {
+ break;
+ }
+ System.out.println("Still not booted: " + ret);
+
+ // Check whether we timed out. This is a simplistic check that doesn't take into account
+ // things like switches in time.
+ Date endDate = new Date();
+ long seconds =
+ TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS);
+ if (seconds > preloadedWaitTimeInSeconds) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Enable method-tracing on device. The system should be restarted after this.
+ */
+ public static void enableTracing(IDevice device) {
+ // Disable selinux.
+ doShell(device, "setenforce 0", 100, TimeUnit.MILLISECONDS);
+
+ // Make the profile directory world-writable.
+ doShell(device, "chmod 777 /data/dalvik-cache/profiles", 100, TimeUnit.MILLISECONDS);
+
+ // Enable streaming method tracing with a small 1K buffer.
+ doShell(device, "setprop dalvik.vm.method-trace true", 100, TimeUnit.MILLISECONDS);
+ doShell(device, "setprop dalvik.vm.method-trace-file "
+ + "/data/dalvik-cache/profiles/zygote.trace.bin", 100, TimeUnit.MILLISECONDS);
+ doShell(device, "setprop dalvik.vm.method-trace-file-siz 1024", 100, TimeUnit.MILLISECONDS);
+ doShell(device, "setprop dalvik.vm.method-trace-stream true", 100, TimeUnit.MILLISECONDS);
+ }
+
+ private static class NullShellOutputReceiver implements IShellOutputReceiver {
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public void addOutput(byte[] arg0, int arg1, int arg2) {}
+ }
+
+ private static class CollectStringShellOutputReceiver implements IShellOutputReceiver {
+
+ private StringBuilder builder = new StringBuilder();
+
+ @Override
+ public String toString() {
+ String ret = builder.toString();
+ // Strip trailing newlines. They are especially ugly because adb uses DOS line endings.
+ while (ret.endsWith("\r") || ret.endsWith("\n")) {
+ ret = ret.substring(0, ret.length() - 1);
+ }
+ return ret;
+ }
+
+ @Override
+ public void addOutput(byte[] arg0, int arg1, int arg2) {
+ builder.append(new String(arg0, arg1, arg2));
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ }
+
+ private static class WaitForDevice {
+
+ private String serial;
+ private long timeout;
+ private IDevice device;
+
+ public WaitForDevice(String serial, long timeout) {
+ this.serial = serial;
+ this.timeout = timeout;
+ device = null;
+ }
+
+ public IDevice get() {
+ if (device == null) {
+ WaitForDeviceListener wfdl = new WaitForDeviceListener(serial);
+ synchronized (wfdl) {
+ AndroidDebugBridge.addDeviceChangeListener(wfdl);
+
+ // Check whether we already know about this device.
+ IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
+ if (serial != null) {
+ for (IDevice d : devices) {
+ if (serial.equals(d.getSerialNumber())) {
+ // Only accept if there are clients already. Else wait for the callback informing
+ // us that we now have clients.
+ if (d.hasClients()) {
+ device = d;
+ }
+
+ break;
+ }
+ }
+ } else {
+ if (devices.length > 0) {
+ device = devices[0];
+ }
+ }
+
+ if (device == null) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Ignore spurious wakeups.
+ }
+ device = wfdl.getDevice();
+ }
+
+ AndroidDebugBridge.removeDeviceChangeListener(wfdl);
+ }
+ }
+
+ if (device != null) {
+ // Wait for clients.
+ WaitForClientsListener wfcl = new WaitForClientsListener(device);
+ synchronized (wfcl) {
+ AndroidDebugBridge.addDeviceChangeListener(wfcl);
+
+ if (!device.hasClients()) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Ignore spurious wakeups.
+ }
+ }
+
+ AndroidDebugBridge.removeDeviceChangeListener(wfcl);
+ }
+ }
+
+ return device;
+ }
+
+ private static class WaitForDeviceListener implements IDeviceChangeListener {
+
+ private String serial;
+ private IDevice device;
+
+ public WaitForDeviceListener(String serial) {
+ this.serial = serial;
+ }
+
+ public IDevice getDevice() {
+ return device;
+ }
+
+ @Override
+ public void deviceChanged(IDevice arg0, int arg1) {
+ // We may get a device changed instead of connected. Handle like a connection.
+ deviceConnected(arg0);
+ }
+
+ @Override
+ public void deviceConnected(IDevice arg0) {
+ if (device != null) {
+ // Ignore updates.
+ return;
+ }
+
+ if (serial == null || serial.equals(arg0.getSerialNumber())) {
+ device = arg0;
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void deviceDisconnected(IDevice arg0) {
+ // Ignore disconnects.
+ }
+
+ }
+
+ private static class WaitForClientsListener implements IDeviceChangeListener {
+
+ private IDevice myDevice;
+
+ public WaitForClientsListener(IDevice myDevice) {
+ this.myDevice = myDevice;
+ }
+
+ @Override
+ public void deviceChanged(IDevice arg0, int arg1) {
+ if (arg0 == myDevice && (arg1 & IDevice.CHANGE_CLIENT_LIST) != 0) {
+ // Got a client list, done here.
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void deviceConnected(IDevice arg0) {
+ }
+
+ @Override
+ public void deviceDisconnected(IDevice arg0) {
+ }
+
+ }
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/DumpData.java b/tools/preload2/src/com/android/preload/DumpData.java
new file mode 100644
index 000000000000..d99722416a1d
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DumpData.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Holds the collected data for a process.
+ */
+public class DumpData {
+ /**
+ * Name of the package (=application).
+ */
+ String packageName;
+
+ /**
+ * A map of class name to a string for the classloader. This may be a toString equivalent,
+ * or just a unique ID.
+ */
+ Map<String, String> dumpData;
+
+ /**
+ * The Date when this data was captured. Mostly for display purposes.
+ */
+ Date date;
+
+ /**
+ * A cached value for the number of boot classpath classes (classloader value in dumpData is
+ * null).
+ */
+ int bcpClasses;
+
+ public DumpData(String packageName, Map<String, String> dumpData, Date date) {
+ this.packageName = packageName;
+ this.dumpData = dumpData;
+ this.date = date;
+
+ countBootClassPath();
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public Map<String, String> getDumpData() {
+ return dumpData;
+ }
+
+ public void countBootClassPath() {
+ bcpClasses = 0;
+ for (Map.Entry<String, String> e : dumpData.entrySet()) {
+ if (e.getValue() == null) {
+ bcpClasses++;
+ }
+ }
+ }
+
+ // Return an inverted mapping.
+ public Map<String, Set<String>> invertData() {
+ Map<String, Set<String>> ret = new HashMap<>();
+ for (Map.Entry<String, String> e : dumpData.entrySet()) {
+ if (!ret.containsKey(e.getValue())) {
+ ret.put(e.getValue(), new HashSet<String>());
+ }
+ ret.get(e.getValue()).add(e.getKey());
+ }
+ return ret;
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/DumpDataIO.java b/tools/preload2/src/com/android/preload/DumpDataIO.java
new file mode 100644
index 000000000000..28625c5531e4
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DumpDataIO.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.io.FileReader;
+import java.text.DateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Helper class for serialization and deserialization of a collection of DumpData objects to XML.
+ */
+public class DumpDataIO {
+
+ /**
+ * Serialize the given collection to an XML document. Returns the produced string.
+ */
+ public static String serialize(Collection<DumpData> data) {
+ // We'll do this by hand, constructing a DOM or similar is too complicated for our simple
+ // use case.
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("<preloaded-classes-data>\n");
+
+ for (DumpData d : data) {
+ serialize(d, sb);
+ }
+
+ sb.append("</preloaded-classes-data>\n");
+ return sb.toString();
+ }
+
+ private static void serialize(DumpData d, StringBuilder sb) {
+ sb.append("<data package=\"" + d.packageName + "\" date=\"" +
+ DateFormat.getDateTimeInstance().format(d.date) +"\">\n");
+
+ for (Map.Entry<String, String> e : d.dumpData.entrySet()) {
+ sb.append("<class name=\"" + e.getKey() + "\" classloader=\"" + e.getValue() + "\"/>\n");
+ }
+
+ sb.append("</data>\n");
+ }
+
+ /**
+ * Load a collection of DumpData objects from the given file.
+ */
+ public static Collection<DumpData> deserialize(File f) throws Exception {
+ // Use SAX parsing. Our format is very simple. Don't do any schema validation or such.
+
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ spf.setNamespaceAware(false);
+ SAXParser saxParser = spf.newSAXParser();
+
+ XMLReader xmlReader = saxParser.getXMLReader();
+ DumpDataContentHandler ddch = new DumpDataContentHandler();
+ xmlReader.setContentHandler(ddch);
+ xmlReader.parse(new InputSource(new FileReader(f)));
+
+ return ddch.data;
+ }
+
+ private static class DumpDataContentHandler extends DefaultHandler {
+ Collection<DumpData> data = new LinkedList<DumpData>();
+ DumpData openData = null;
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ if (qName.equals("data")) {
+ if (openData != null) {
+ throw new IllegalStateException();
+ }
+ String pkg = attributes.getValue("package");
+ String dateString = attributes.getValue("date");
+
+ if (pkg == null || dateString == null) {
+ throw new IllegalArgumentException();
+ }
+
+ try {
+ Date date = DateFormat.getDateTimeInstance().parse(dateString);
+ openData = new DumpData(pkg, new HashMap<String, String>(), date);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ } else if (qName.equals("class")) {
+ if (openData == null) {
+ throw new IllegalStateException();
+ }
+ String className = attributes.getValue("name");
+ String classLoader = attributes.getValue("classloader");
+
+ if (className == null || classLoader == null) {
+ throw new IllegalArgumentException();
+ }
+
+ openData.dumpData.put(className, classLoader.equals("null") ? null : classLoader);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (qName.equals("data")) {
+ if (openData == null) {
+ throw new IllegalStateException();
+ }
+ openData.countBootClassPath();
+
+ data.add(openData);
+ openData = null;
+ }
+ }
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/DumpTableModel.java b/tools/preload2/src/com/android/preload/DumpTableModel.java
new file mode 100644
index 000000000000..d97cbf0df5e5
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DumpTableModel.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * A table model for collected DumpData. This is both the internal storage as well as the model
+ * for display.
+ */
+public class DumpTableModel extends AbstractTableModel {
+
+ private List<DumpData> data = new ArrayList<DumpData>();
+
+ public void addData(DumpData d) {
+ data.add(d);
+ fireTableRowsInserted(data.size() - 1, data.size() - 1);
+ }
+
+ public void clear() {
+ int size = data.size();
+ if (size > 0) {
+ data.clear();
+ fireTableRowsDeleted(0, size - 1);
+ }
+ }
+
+ public List<DumpData> getData() {
+ return data;
+ }
+
+ @Override
+ public int getRowCount() {
+ return data.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 4;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0:
+ return "Package";
+ case 1:
+ return "Date";
+ case 2:
+ return "# All Classes";
+ case 3:
+ return "# Boot Classpath Classes";
+
+ default:
+ throw new IndexOutOfBoundsException(String.valueOf(column));
+ }
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ DumpData d = data.get(rowIndex);
+ switch (columnIndex) {
+ case 0:
+ return d.packageName;
+ case 1:
+ return d.date;
+ case 2:
+ return d.dumpData.size();
+ case 3:
+ return d.bcpClasses;
+
+ default:
+ throw new IndexOutOfBoundsException(String.valueOf(columnIndex));
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java
new file mode 100644
index 000000000000..ca5b0e005a1d
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/Main.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.actions.ClearTableAction;
+import com.android.preload.actions.ComputeThresholdAction;
+import com.android.preload.actions.ComputeThresholdXAction;
+import com.android.preload.actions.DeviceSpecific;
+import com.android.preload.actions.ExportAction;
+import com.android.preload.actions.ImportAction;
+import com.android.preload.actions.ReloadListAction;
+import com.android.preload.actions.RunMonkeyAction;
+import com.android.preload.actions.ScanAllPackagesAction;
+import com.android.preload.actions.ScanPackageAction;
+import com.android.preload.actions.ShowDataAction;
+import com.android.preload.classdataretrieval.ClassDataRetriever;
+import com.android.preload.classdataretrieval.hprof.Hprof;
+import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever;
+import com.android.preload.ui.UI;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.Action;
+import javax.swing.DefaultListModel;
+
+public class Main {
+
+ /**
+ * Enable tracing mode. This is a work-in-progress to derive compiled-methods data, so it is
+ * off for now.
+ */
+ public final static boolean ENABLE_TRACING = false;
+
+ /**
+ * Ten-second timeout.
+ */
+ public final static int DEFAULT_TIMEOUT_MILLIS = 10 * 1000;
+
+ /**
+ * Hprof timeout. Two minutes.
+ */
+ public final static int HPROF_TIMEOUT_MILLIS = 120 * 1000;
+
+ private IDevice device;
+ private static ClientUtils clientUtils;
+
+ private DumpTableModel dataTableModel;
+ private DefaultListModel<Client> clientListModel;
+
+ private UI ui;
+
+ // Actions that need to be updated once a device is selected.
+ private Collection<DeviceSpecific> deviceSpecificActions;
+
+ // Current main instance.
+ private static Main top;
+ private static boolean useJdwpClassDataRetriever = false;
+
+ public final static String CLASS_PRELOAD_BLACKLIST = "android.app.AlarmManager$" + "|"
+ + "android.app.SearchManager$" + "|" + "android.os.FileObserver$" + "|"
+ + "com.android.server.PackageManagerService\\$AppDirObserver$" + "|" +
+
+
+ // Threads
+ "android.os.AsyncTask$" + "|" + "android.pim.ContactsAsyncHelper$" + "|"
+ + "android.webkit.WebViewClassic\\$1$" + "|" + "java.lang.ProcessManager$" + "|"
+ + "(.*\\$NoPreloadHolder$)";
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ Main m = new Main();
+ top = m;
+
+ m.startUp();
+ }
+
+ public Main() {
+ clientListModel = new DefaultListModel<Client>();
+ dataTableModel = new DumpTableModel();
+
+ clientUtils = new ClientUtils(DEFAULT_TIMEOUT_MILLIS); // Client utils with 10s timeout.
+
+ List<Action> actions = new ArrayList<Action>();
+ actions.add(new ReloadListAction(clientUtils, null, clientListModel));
+ actions.add(new ClearTableAction(dataTableModel));
+ actions.add(new RunMonkeyAction(null, dataTableModel));
+ actions.add(new ScanPackageAction(clientUtils, null, dataTableModel));
+ actions.add(new ScanAllPackagesAction(clientUtils, null, dataTableModel));
+ actions.add(new ComputeThresholdAction("Compute preloaded-classes", dataTableModel, 2,
+ CLASS_PRELOAD_BLACKLIST));
+ actions.add(new ComputeThresholdAction("Compute compiled-classes", dataTableModel, 1,
+ null));
+ actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel,
+ CLASS_PRELOAD_BLACKLIST));
+ actions.add(new ShowDataAction(dataTableModel));
+ actions.add(new ImportAction(dataTableModel));
+ actions.add(new ExportAction(dataTableModel));
+
+ deviceSpecificActions = new ArrayList<DeviceSpecific>();
+ for (Action a : actions) {
+ if (a instanceof DeviceSpecific) {
+ deviceSpecificActions.add((DeviceSpecific)a);
+ }
+ }
+
+ ui = new UI(clientListModel, dataTableModel, actions);
+ ui.setVisible(true);
+ }
+
+ public static UI getUI() {
+ return top.ui;
+ }
+
+ public static ClassDataRetriever getClassDataRetriever() {
+ if (useJdwpClassDataRetriever) {
+ return new JDWPClassDataRetriever();
+ } else {
+ return new Hprof(HPROF_TIMEOUT_MILLIS);
+ }
+ }
+
+ public IDevice getDevice() {
+ return device;
+ }
+
+ public void setDevice(IDevice device) {
+ this.device = device;
+ for (DeviceSpecific ds : deviceSpecificActions) {
+ ds.setDevice(device);
+ }
+ }
+
+ public DefaultListModel<Client> getClientListModel() {
+ return clientListModel;
+ }
+
+ static class DeviceWrapper {
+ IDevice device;
+
+ public DeviceWrapper(IDevice d) {
+ device = d;
+ }
+
+ @Override
+ public String toString() {
+ return device.getName() + " (#" + device.getSerialNumber() + ")";
+ }
+ }
+
+ private void startUp() {
+ getUI().showWaitDialog();
+ initDevice();
+
+ // Load clients.
+ new ReloadListAction(clientUtils, getDevice(), clientListModel).run();
+
+ getUI().hideWaitDialog();
+ }
+
+ private void initDevice() {
+ DeviceUtils.init(DEFAULT_TIMEOUT_MILLIS);
+
+ IDevice devices[] = DeviceUtils.findDevices(DEFAULT_TIMEOUT_MILLIS);
+ if (devices == null || devices.length == 0) {
+ throw new RuntimeException("Could not find any devices...");
+ }
+
+ getUI().hideWaitDialog();
+
+ DeviceWrapper deviceWrappers[] = new DeviceWrapper[devices.length];
+ for (int i = 0; i < devices.length; i++) {
+ deviceWrappers[i] = new DeviceWrapper(devices[i]);
+ }
+
+ DeviceWrapper ret = Main.getUI().showChoiceDialog("Choose a device", "Choose device",
+ deviceWrappers);
+ if (ret != null) {
+ setDevice(ret.device);
+ } else {
+ System.exit(0);
+ }
+
+ boolean prepare = Main.getUI().showConfirmDialog("Prepare device?",
+ "Do you want to prepare the device? This is highly recommended.");
+ if (prepare) {
+ String buildType = DeviceUtils.getBuildType(device);
+ if (buildType == null || (!buildType.equals("userdebug") && !buildType.equals("eng"))) {
+ Main.getUI().showMessageDialog("Need a userdebug or eng build! (Found " + buildType
+ + ")");
+ return;
+ }
+ if (DeviceUtils.hasPrebuiltBootImage(device)) {
+ Main.getUI().showMessageDialog("Cannot prepare a device with pre-optimized boot "
+ + "image!");
+ return;
+ }
+
+ if (ENABLE_TRACING) {
+ DeviceUtils.enableTracing(device);
+ }
+
+ Main.getUI().showMessageDialog("The device will reboot. This will potentially take a "
+ + "long time. Please be patient.");
+ if (!DeviceUtils.removePreloaded(device, 15 * 60) /* 15m timeout */) {
+ Main.getUI().showMessageDialog("Removing preloaded-classes failed unexpectedly!");
+ }
+ }
+ }
+
+ public static Map<String, String> findAndGetClassData(IDevice device, String packageName)
+ throws Exception {
+ Client client = clientUtils.findClient(device, packageName, -1);
+ if (client == null) {
+ throw new RuntimeException("Could not find client...");
+ }
+ System.out.println("Found client: " + client);
+
+ return getClassDataRetriever().getClassData(client);
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
new file mode 100644
index 000000000000..fbf83d2e2339
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+public abstract class AbstractThreadedAction extends AbstractAction implements Runnable {
+
+ protected AbstractThreadedAction(String title) {
+ super(title);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ new Thread(this).start();
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
new file mode 100644
index 000000000000..7906417b7a8d
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.ddmlib.IDevice;
+
+import java.awt.event.ActionEvent;
+
+public abstract class AbstractThreadedDeviceSpecificAction extends AbstractThreadedAction
+ implements DeviceSpecific {
+
+ protected IDevice device;
+
+ protected AbstractThreadedDeviceSpecificAction(String title, IDevice device) {
+ super(title);
+ this.device = device;
+ }
+
+ @Override
+ public void setDevice(IDevice device) {
+ this.device = device;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (device == null) {
+ return;
+ }
+ super.actionPerformed(e);
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/actions/ClearTableAction.java b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java
new file mode 100644
index 000000000000..c0e4795b6d90
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.preload.DumpTableModel;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+public class ClearTableAction extends AbstractAction {
+ private final DumpTableModel dataTableModel;
+
+ public ClearTableAction(DumpTableModel dataTableModel) {
+ super("Clear");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dataTableModel.clear();
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
new file mode 100644
index 000000000000..b524716fc2cb
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+
+/**
+ * Compute an intersection of classes from the given data. A class is in the intersection if it
+ * appears in at least the number of threshold given packages. An optional blacklist can be
+ * used to filter classes from the intersection.
+ */
+public class ComputeThresholdAction extends AbstractAction implements Runnable {
+ protected int threshold;
+ private Pattern blacklist;
+ private DumpTableModel dataTableModel;
+
+ /**
+ * Create an action with the given parameters. The blacklist is a regular expression
+ * that filters classes.
+ */
+ public ComputeThresholdAction(String name, DumpTableModel dataTableModel, int threshold,
+ String blacklist) {
+ super(name);
+ this.dataTableModel = dataTableModel;
+ this.threshold = threshold;
+ if (blacklist != null) {
+ this.blacklist = Pattern.compile(blacklist);
+ }
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ List<DumpData> data = dataTableModel.getData();
+ if (data.size() == 0) {
+ Main.getUI().showMessageDialog("No data available, please scan packages or run "
+ + "monkeys.");
+ return;
+ }
+ if (data.size() == 1) {
+ Main.getUI().showMessageDialog("Cannot compute list from only one data set, please "
+ + "scan packages or run monkeys.");
+ return;
+ }
+
+ new Thread(this).start();
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ Map<String, Set<String>> uses = new HashMap<String, Set<String>>();
+ for (DumpData d : dataTableModel.getData()) {
+ Main.getUI().updateWaitDialog("Merging " + d.getPackageName());
+ updateClassUse(d.getPackageName(), uses, getBootClassPathClasses(d.getDumpData()));
+ }
+
+ Main.getUI().updateWaitDialog("Computing thresholded set");
+ Set<String> result = fromThreshold(uses, blacklist, threshold);
+ Main.getUI().hideWaitDialog();
+
+ boolean ret = Main.getUI().showConfirmDialog("Computed a set with " + result.size()
+ + " classes, would you like to save to disk?", "Save?");
+ if (ret) {
+ JFileChooser jfc = new JFileChooser();
+ int ret2 = jfc.showSaveDialog(Main.getUI());
+ if (ret2 == JFileChooser.APPROVE_OPTION) {
+ File f = jfc.getSelectedFile();
+ saveSet(result, f);
+ }
+ }
+ }
+
+ private Set<String> fromThreshold(Map<String, Set<String>> classUses, Pattern blacklist,
+ int threshold) {
+ TreeSet<String> ret = new TreeSet<>(); // TreeSet so it's nicely ordered by name.
+
+ for (Map.Entry<String, Set<String>> e : classUses.entrySet()) {
+ if (e.getValue().size() >= threshold) {
+ if (blacklist == null || !blacklist.matcher(e.getKey()).matches()) {
+ ret.add(e.getKey());
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ private static void updateClassUse(String pkg, Map<String, Set<String>> classUses,
+ Set<String> classes) {
+ for (String className : classes) {
+ Set<String> old = classUses.get(className);
+ if (old == null) {
+ classUses.put(className, new HashSet<String>());
+ }
+ classUses.get(className).add(pkg);
+ }
+ }
+
+ private static Set<String> getBootClassPathClasses(Map<String, String> source) {
+ Set<String> ret = new HashSet<>();
+ for (Map.Entry<String, String> e : source.entrySet()) {
+ if (e.getValue() == null) {
+ ret.add(e.getKey());
+ }
+ }
+ return ret;
+ }
+
+ private static void saveSet(Set<String> result, File f) {
+ try {
+ PrintWriter out = new PrintWriter(f);
+ for (String s : result) {
+ out.println(s);
+ }
+ out.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
new file mode 100644
index 000000000000..3ec0a4c18db1
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+public class ComputeThresholdXAction extends ComputeThresholdAction {
+
+ public ComputeThresholdXAction(String name, DumpTableModel dataTableModel,
+ String blacklist) {
+ super(name, dataTableModel, 1, blacklist);
+ }
+
+ @Override
+ public void run() {
+ String value = Main.getUI().showInputDialog("Threshold?");
+
+ if (value != null) {
+ try {
+ threshold = Integer.parseInt(value);
+ super.run();
+ } catch (Exception exc) {
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
new file mode 100644
index 000000000000..35a8f26a99fe
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.ddmlib.IDevice;
+
+/**
+ * Marks an action as being device-specific. The user must set the device through the specified
+ * method if the device selection changes.
+ *
+ * Implementors must tolerate a null device (for example, with a no-op). This includes calling
+ * any methods before setDevice has been called.
+ */
+public interface DeviceSpecific {
+
+ /**
+ * Set the device that should be used. Note that there is no restriction on calling other
+ * methods of the implementor before a setDevice call. Neither is device guaranteed to be
+ * non-null.
+ *
+ * @param device The device to use going forward.
+ */
+ public void setDevice(IDevice device);
+}
diff --git a/tools/preload2/src/com/android/preload/actions/ExportAction.java b/tools/preload2/src/com/android/preload/actions/ExportAction.java
new file mode 100644
index 000000000000..cb8b3df75b18
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ExportAction.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.preload.DumpDataIO;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.PrintWriter;
+
+import javax.swing.AbstractAction;
+
+public class ExportAction extends AbstractAction implements Runnable {
+ private File lastSaveFile;
+ private DumpTableModel dataTableModel;
+
+ public ExportAction(DumpTableModel dataTableModel) {
+ super("Export data");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ lastSaveFile = Main.getUI().showSaveDialog();
+ if (lastSaveFile != null) {
+ new Thread(this).start();
+ }
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ String serialized = DumpDataIO.serialize(dataTableModel.getData());
+
+ if (serialized != null) {
+ try {
+ PrintWriter out = new PrintWriter(lastSaveFile);
+ out.println(serialized);
+ out.close();
+
+ Main.getUI().hideWaitDialog();
+ } catch (Exception e) {
+ Main.getUI().hideWaitDialog();
+ Main.getUI().showMessageDialog("Failed writing: " + e.getMessage());
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ImportAction.java b/tools/preload2/src/com/android/preload/actions/ImportAction.java
new file mode 100644
index 000000000000..5c1976580f94
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ImportAction.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.preload.DumpData;
+import com.android.preload.DumpDataIO;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.Collection;
+
+import javax.swing.AbstractAction;
+
+public class ImportAction extends AbstractAction implements Runnable {
+ private File[] lastOpenFiles;
+ private DumpTableModel dataTableModel;
+
+ public ImportAction(DumpTableModel dataTableModel) {
+ super("Import data");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ lastOpenFiles = Main.getUI().showOpenDialog(true);
+ if (lastOpenFiles != null) {
+ new Thread(this).start();
+ }
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ for (File f : lastOpenFiles) {
+ try {
+ Collection<DumpData> data = DumpDataIO.deserialize(f);
+
+ for (DumpData d : data) {
+ dataTableModel.addData(d);
+ }
+ } catch (Exception e) {
+ Main.getUI().showMessageDialog("Failed reading: " + e.getMessage());
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ReloadListAction.java b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
new file mode 100644
index 000000000000..29f055700dfa
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import javax.swing.DefaultListModel;
+
+public class ReloadListAction extends AbstractThreadedDeviceSpecificAction {
+
+ private ClientUtils clientUtils;
+ private final DefaultListModel<Client> clientListModel;
+
+ public ReloadListAction(ClientUtils utils, IDevice device,
+ DefaultListModel<Client> clientListModel) {
+ super("Reload", device);
+ this.clientUtils = utils;
+ this.clientListModel = clientListModel;
+ }
+
+ @Override
+ public void run() {
+ Client[] clients = clientUtils.findAllClients(device);
+ if (clients != null) {
+ Arrays.sort(clients, new ClientComparator());
+ }
+ clientListModel.removeAllElements();
+ for (Client c : clients) {
+ clientListModel.addElement(c);
+ }
+ }
+
+ private static class ClientComparator implements Comparator<Client> {
+
+ @Override
+ public int compare(Client o1, Client o2) {
+ String s1 = o1.getClientData().getClientDescription();
+ String s2 = o2.getClientData().getClientDescription();
+
+ if (s1 == null || s2 == null) {
+ // Not good, didn't get all data?
+ return (s1 == null) ? -1 : 1;
+ }
+
+ return s1.compareTo(s2);
+ }
+
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
new file mode 100644
index 000000000000..385e8577b1c8
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.ddmlib.IDevice;
+import com.android.preload.DeviceUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.AbstractAction;
+
+public class RunMonkeyAction extends AbstractAction implements DeviceSpecific {
+
+ private final static String DEFAULT_MONKEY_PACKAGES =
+ "com.android.calendar,com.android.gallery3d";
+
+ private IDevice device;
+ private DumpTableModel dataTableModel;
+
+ public RunMonkeyAction(IDevice device, DumpTableModel dataTableModel) {
+ super("Run monkey");
+ this.device = device;
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void setDevice(IDevice device) {
+ this.device = device;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String packages = Main.getUI().showInputDialog("Please enter packages name to run with"
+ + " the monkey, or leave empty for default.");
+ if (packages == null) {
+ return;
+ }
+ if (packages.isEmpty()) {
+ packages = DEFAULT_MONKEY_PACKAGES;
+ }
+ new Thread(new RunMonkeyRunnable(packages)).start();
+ }
+
+ private class RunMonkeyRunnable implements Runnable {
+
+ private String packages;
+ private final static int ITERATIONS = 1000;
+
+ public RunMonkeyRunnable(String packages) {
+ this.packages = packages;
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ String pkgs[] = packages.split(",");
+
+ for (String pkg : pkgs) {
+ Main.getUI().updateWaitDialog("Running monkey on " + pkg);
+
+ try {
+ // Stop running app.
+ forceStop(pkg);
+
+ // Little bit of breather here.
+ try {
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ }
+
+ DeviceUtils.doShell(device, "monkey -p " + pkg + " " + ITERATIONS, 1,
+ TimeUnit.MINUTES);
+
+ Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
+ Map<String, String> data = Main.findAndGetClassData(device, pkg);
+ DumpData dumpData = new DumpData(pkg, data, new Date());
+ dataTableModel.addData(dumpData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ // Stop running app.
+ forceStop(pkg);
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+
+ private void forceStop(String packageName) {
+ // Stop running app.
+ DeviceUtils.doShell(device, "force-stop " + packageName, 5, TimeUnit.SECONDS);
+ DeviceUtils.doShell(device, "kill " + packageName, 5, TimeUnit.SECONDS);
+ DeviceUtils.doShell(device, "kill `pid " + packageName + "`", 5, TimeUnit.SECONDS);
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
new file mode 100644
index 000000000000..d74b8a3f6cfb
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.util.Date;
+import java.util.Map;
+
+public class ScanAllPackagesAction extends AbstractThreadedDeviceSpecificAction {
+
+ private ClientUtils clientUtils;
+ private DumpTableModel dataTableModel;
+
+ public ScanAllPackagesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
+ super("Scan all packages", device);
+ this.clientUtils = utils;
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ Client[] clients = clientUtils.findAllClients(device);
+ for (Client c : clients) {
+ String pkg = c.getClientData().getClientDescription();
+ Main.getUI().showWaitDialog();
+ Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
+
+ try {
+ Map<String, String> data = Main.getClassDataRetriever().getClassData(c);
+ DumpData dumpData = new DumpData(pkg, data, new Date());
+ dataTableModel.addData(dumpData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
new file mode 100644
index 000000000000..98492bd951bf
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.util.Date;
+import java.util.Map;
+
+public class ScanPackageAction extends AbstractThreadedDeviceSpecificAction {
+
+ private ClientUtils clientUtils;
+ private DumpTableModel dataTableModel;
+
+ public ScanPackageAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
+ super("Scan package", device);
+ this.clientUtils = utils;
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ Client client = Main.getUI().getSelectedClient();
+ if (client != null) {
+ work(client);
+ } else {
+ Client[] clients = clientUtils.findAllClients(device);
+ if (clients.length > 0) {
+ ClientWrapper[] clientWrappers = new ClientWrapper[clients.length];
+ for (int i = 0; i < clientWrappers.length; i++) {
+ clientWrappers[i] = new ClientWrapper(clients[i]);
+ }
+ Main.getUI().hideWaitDialog();
+
+ ClientWrapper ret = Main.getUI().showChoiceDialog("Choose a package to scan",
+ "Choose package",
+ clientWrappers);
+ if (ret != null) {
+ work(ret.client);
+ }
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+
+ private void work(Client c) {
+ String pkg = c.getClientData().getClientDescription();
+ Main.getUI().showWaitDialog();
+ Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
+
+ try {
+ Map<String, String> data = Main.findAndGetClassData(device, pkg);
+ DumpData dumpData = new DumpData(pkg, data, new Date());
+ dataTableModel.addData(dumpData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static class ClientWrapper {
+ private Client client;
+
+ public ClientWrapper(Client c) {
+ client = c;
+ }
+
+ @Override
+ public String toString() {
+ return client.getClientData().getClientDescription() + " (pid "
+ + client.getClientData().getPid() + ")";
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ShowDataAction.java b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
new file mode 100644
index 000000000000..2bb175f60772
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+public class ShowDataAction extends AbstractAction {
+ private DumpTableModel dataTableModel;
+
+ public ShowDataAction(DumpTableModel dataTableModel) {
+ super("Show data");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // TODO(agampe): Auto-generated method stub
+ int selRow = Main.getUI().getSelectedDataTableRow();
+ if (selRow != -1) {
+ DumpData data = dataTableModel.getData().get(selRow);
+ Map<String, Set<String>> inv = data.invertData();
+
+ StringBuilder builder = new StringBuilder();
+
+ // First bootclasspath.
+ add(builder, "Boot classpath:", inv.get(null));
+
+ // Now everything else.
+ for (String k : inv.keySet()) {
+ if (k != null) {
+ builder.append("==================\n\n");
+ add(builder, k, inv.get(k));
+ }
+ }
+
+ JFrame newFrame = new JFrame(data.getPackageName() + " " + data.getDate());
+ newFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+ newFrame.getContentPane().add(new JScrollPane(new JTextArea(builder.toString())),
+ BorderLayout.CENTER);
+ newFrame.setSize(800, 600);
+ newFrame.setLocationRelativeTo(null);
+ newFrame.setVisible(true);
+ }
+ }
+
+ private void add(StringBuilder builder, String head, Set<String> set) {
+ builder.append(head);
+ builder.append('\n');
+ addSet(builder, set);
+ builder.append('\n');
+ }
+
+ private void addSet(StringBuilder builder, Set<String> set) {
+ if (set == null) {
+ builder.append(" NONE\n");
+ return;
+ }
+ List<String> sorted = new ArrayList<>(set);
+ Collections.sort(sorted);
+ for (String s : sorted) {
+ builder.append(s);
+ builder.append('\n');
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java
new file mode 100644
index 000000000000..f04360fc1942
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.classdataretrieval;
+
+import com.android.ddmlib.Client;
+
+import java.util.Map;
+
+/**
+ * Retrieve a class-to-classloader map for loaded classes from the client.
+ */
+public interface ClassDataRetriever {
+
+ public Map<String, String> getClassData(Client client);
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
new file mode 100644
index 000000000000..8d797ee00128
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.classdataretrieval.hprof;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GeneralHprofDumpHandler implements IHprofDumpHandler {
+
+ private List<IHprofDumpHandler> handlers = new ArrayList<>();
+
+ public void addHandler(IHprofDumpHandler h) {
+ synchronized (handlers) {
+ handlers.add(h);
+ }
+ }
+
+ public void removeHandler(IHprofDumpHandler h) {
+ synchronized (handlers) {
+ handlers.remove(h);
+ }
+ }
+
+ private List<IHprofDumpHandler> getIterationList() {
+ synchronized (handlers) {
+ return new ArrayList<>(handlers);
+ }
+ }
+
+ @Override
+ public void onEndFailure(Client arg0, String arg1) {
+ List<IHprofDumpHandler> iterList = getIterationList();
+ for (IHprofDumpHandler h : iterList) {
+ h.onEndFailure(arg0, arg1);
+ }
+ }
+
+ @Override
+ public void onSuccess(String arg0, Client arg1) {
+ List<IHprofDumpHandler> iterList = getIterationList();
+ for (IHprofDumpHandler h : iterList) {
+ h.onSuccess(arg0, arg1);
+ }
+ }
+
+ @Override
+ public void onSuccess(byte[] arg0, Client arg1) {
+ List<IHprofDumpHandler> iterList = getIterationList();
+ for (IHprofDumpHandler h : iterList) {
+ h.onSuccess(arg0, arg1);
+ }
+ }
+ } \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
new file mode 100644
index 000000000000..21b7a04e07dc
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.classdataretrieval.hprof;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
+import com.android.preload.classdataretrieval.ClassDataRetriever;
+import com.android.preload.ui.NullProgressMonitor;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Queries;
+import com.android.tools.perflib.heap.Snapshot;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class Hprof implements ClassDataRetriever {
+
+ private static GeneralHprofDumpHandler hprofHandler;
+
+ public static void init() {
+ synchronized(Hprof.class) {
+ if (hprofHandler == null) {
+ ClientData.setHprofDumpHandler(hprofHandler = new GeneralHprofDumpHandler());
+ }
+ }
+ }
+
+ public static File doHprof(Client client, int timeout) {
+ GetHprof gh = new GetHprof(client, timeout);
+ return gh.get();
+ }
+
+ /**
+ * Return a map of class names to class-loader names derived from the hprof dump.
+ *
+ * @param hprofLocalFile
+ */
+ public static Map<String, String> analyzeHprof(File hprofLocalFile) throws Exception {
+ Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprofLocalFile));
+
+ Map<String, Set<ClassObj>> classes = Queries.classes(snapshot, null);
+ Map<String, String> retValue = new HashMap<String, String>();
+ for (Map.Entry<String, Set<ClassObj>> e : classes.entrySet()) {
+ for (ClassObj c : e.getValue()) {
+ String cl = c.getClassLoader() == null ? null : c.getClassLoader().toString();
+ String cName = c.getClassName();
+ int aDepth = 0;
+ while (cName.endsWith("[]")) {
+ cName = cName.substring(0, cName.length()-2);
+ aDepth++;
+ }
+ String newName = transformPrimitiveClass(cName);
+ if (aDepth > 0) {
+ // Need to use kind-a descriptor syntax. If it was transformed, it is primitive.
+ if (newName.equals(cName)) {
+ newName = "L" + newName + ";";
+ }
+ for (int i = 0; i < aDepth; i++) {
+ newName = "[" + newName;
+ }
+ }
+ retValue.put(newName, cl);
+ }
+ }
+
+ // Free up memory.
+ snapshot.dispose();
+
+ return retValue;
+ }
+
+ private static Map<String, String> primitiveMapping;
+
+ static {
+ primitiveMapping = new HashMap<>();
+ primitiveMapping.put("boolean", "Z");
+ primitiveMapping.put("byte", "B");
+ primitiveMapping.put("char", "C");
+ primitiveMapping.put("double", "D");
+ primitiveMapping.put("float", "F");
+ primitiveMapping.put("int", "I");
+ primitiveMapping.put("long", "J");
+ primitiveMapping.put("short", "S");
+ primitiveMapping.put("void", "V");
+ }
+
+ private static String transformPrimitiveClass(String name) {
+ String rep = primitiveMapping.get(name);
+ if (rep != null) {
+ return rep;
+ }
+ return name;
+ }
+
+ private static class GetHprof implements IHprofDumpHandler {
+
+ private File target;
+ private long timeout;
+ private Client client;
+
+ public GetHprof(Client client, long timeout) {
+ this.client = client;
+ this.timeout = timeout;
+ }
+
+ public File get() {
+ synchronized (this) {
+ hprofHandler.addHandler(this);
+ client.dumpHprof();
+ if (target == null) {
+ try {
+ wait(timeout);
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ }
+ }
+
+ hprofHandler.removeHandler(this);
+ return target;
+ }
+
+ private void wakeUp() {
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onEndFailure(Client arg0, String arg1) {
+ System.out.println("GetHprof.onEndFailure");
+ if (client == arg0) {
+ wakeUp();
+ }
+ }
+
+ private static File createTargetFile() {
+ try {
+ return File.createTempFile("ddms", ".hprof");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void onSuccess(String arg0, Client arg1) {
+ System.out.println("GetHprof.onSuccess");
+ if (client == arg1) {
+ try {
+ target = createTargetFile();
+ arg1.getDevice().getSyncService().pullFile(arg0,
+ target.getAbsoluteFile().toString(), new NullProgressMonitor());
+ } catch (Exception e) {
+ e.printStackTrace();
+ target = null;
+ }
+ wakeUp();
+ }
+ }
+
+ @Override
+ public void onSuccess(byte[] arg0, Client arg1) {
+ System.out.println("GetHprof.onSuccess");
+ if (client == arg1) {
+ try {
+ target = createTargetFile();
+ BufferedOutputStream out =
+ new BufferedOutputStream(new FileOutputStream(target));
+ out.write(arg0);
+ out.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ target = null;
+ }
+ wakeUp();
+ }
+ }
+ }
+
+ private int timeout;
+
+ public Hprof(int timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public Map<String, String> getClassData(Client client) {
+ File hprofLocalFile = Hprof.doHprof(client, timeout);
+ if (hprofLocalFile == null) {
+ throw new RuntimeException("Failed getting dump...");
+ }
+ System.out.println("Dump file is " + hprofLocalFile);
+
+ try {
+ return analyzeHprof(hprofLocalFile);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
new file mode 100644
index 000000000000..dbd4c89b27cf
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.classdataretrieval.jdwp;
+
+import com.android.ddmlib.Client;
+import com.android.preload.classdataretrieval.ClassDataRetriever;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
+import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPTestCase;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPUnitDebuggeeWrapper;
+import org.apache.harmony.jpda.tests.share.JPDALogWriter;
+import org.apache.harmony.jpda.tests.share.JPDATestOptions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class JDWPClassDataRetriever extends JDWPTestCase implements ClassDataRetriever {
+
+ private final Client client;
+
+ public JDWPClassDataRetriever() {
+ this(null);
+ }
+
+ public JDWPClassDataRetriever(Client client) {
+ this.client = client;
+ }
+
+
+ @Override
+ protected String getDebuggeeClassName() {
+ return "<unset>";
+ }
+
+ @Override
+ public Map<String, String> getClassData(Client client) {
+ return new JDWPClassDataRetriever(client).retrieve();
+ }
+
+ private Map<String, String> retrieve() {
+ if (client == null) {
+ throw new IllegalStateException();
+ }
+
+ settings = createTestOptions("localhost:" + String.valueOf(client.getDebuggerListenPort()));
+ settings.setDebuggeeSuspend("n");
+
+ logWriter = new JPDALogWriter(System.out, "", false);
+
+ try {
+ internalSetUp();
+
+ return retrieveImpl();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ internalTearDown();
+ }
+ }
+
+ private Map<String, String> retrieveImpl() {
+ try {
+ // Suspend the app.
+ {
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.VirtualMachineCommandSet.CommandSetID,
+ JDWPCommands.VirtualMachineCommandSet.SuspendCommand);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return null;
+ }
+ }
+
+ // List all classes.
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.VirtualMachineCommandSet.CommandSetID,
+ JDWPCommands.VirtualMachineCommandSet.AllClassesCommand);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return null;
+ }
+
+ int classCount = reply.getNextValueAsInt();
+ System.out.println("Runtime reported " + classCount + " classes.");
+
+ Map<Long, String> classes = new HashMap<Long, String>();
+ Map<Long, String> arrayClasses = new HashMap<Long, String>();
+
+ for (int i = 0; i < classCount; i++) {
+ byte refTypeTag = reply.getNextValueAsByte();
+ long typeID = reply.getNextValueAsReferenceTypeID();
+ String signature = reply.getNextValueAsString();
+ /* int status = */ reply.getNextValueAsInt();
+
+ switch (refTypeTag) {
+ case JDWPConstants.TypeTag.CLASS:
+ case JDWPConstants.TypeTag.INTERFACE:
+ classes.put(typeID, signature);
+ break;
+
+ case JDWPConstants.TypeTag.ARRAY:
+ arrayClasses.put(typeID, signature);
+ break;
+ }
+ }
+
+ Map<String, String> result = new HashMap<String, String>();
+
+ // Parse all classes.
+ for (Map.Entry<Long, String> entry : classes.entrySet()) {
+ long typeID = entry.getKey();
+ String signature = entry.getValue();
+
+ if (!checkClass(typeID, signature, result)) {
+ System.err.println("Issue investigating " + signature);
+ }
+ }
+
+ // For arrays, look at the leaf component type.
+ for (Map.Entry<Long, String> entry : arrayClasses.entrySet()) {
+ long typeID = entry.getKey();
+ String signature = entry.getValue();
+
+ if (!checkArrayClass(typeID, signature, result)) {
+ System.err.println("Issue investigating " + signature);
+ }
+ }
+
+ return result;
+ } finally {
+ // Resume the app.
+ {
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.VirtualMachineCommandSet.CommandSetID,
+ JDWPCommands.VirtualMachineCommandSet.ResumeCommand);
+ /* ReplyPacket reply = */ debuggeeWrapper.vmMirror.performCommand(packet);
+ }
+ }
+ }
+
+ private boolean checkClass(long typeID, String signature, Map<String, String> result) {
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
+ JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
+ packet.setNextValueAsReferenceTypeID(typeID);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return false;
+ }
+
+ long classLoaderID = reply.getNextValueAsObjectID();
+
+ // TODO: Investigate the classloader to have a better string?
+ String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
+
+ result.put(getClassName(signature), classLoaderString);
+
+ return true;
+ }
+
+ private boolean checkArrayClass(long typeID, String signature, Map<String, String> result) {
+ // Classloaders of array classes are the same as the component class'.
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
+ JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
+ packet.setNextValueAsReferenceTypeID(typeID);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return false;
+ }
+
+ long classLoaderID = reply.getNextValueAsObjectID();
+
+ // TODO: Investigate the classloader to have a better string?
+ String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
+
+ // For array classes, we *need* the signature directly.
+ result.put(signature, classLoaderString);
+
+ return true;
+ }
+
+ private static String getClassName(String signature) {
+ String withoutLAndSemicolon = signature.substring(1, signature.length() - 1);
+ return withoutLAndSemicolon.replace('/', '.');
+ }
+
+
+ private static JPDATestOptions createTestOptions(String address) {
+ JPDATestOptions options = new JPDATestOptions();
+ options.setAttachConnectorKind();
+ options.setTimeout(1000);
+ options.setWaitingTime(1000);
+ options.setTransportAddress(address);
+ return options;
+ }
+
+ @Override
+ protected JDWPUnitDebuggeeWrapper createDebuggeeWrapper() {
+ return new PreloadDebugeeWrapper(settings, logWriter);
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
new file mode 100644
index 000000000000..b9df6d0aeb93
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.classdataretrieval.jdwp;
+
+import org.apache.harmony.jpda.tests.framework.LogWriter;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPManualDebuggeeWrapper;
+import org.apache.harmony.jpda.tests.share.JPDATestOptions;
+
+import java.io.IOException;
+
+public class PreloadDebugeeWrapper extends JDWPManualDebuggeeWrapper {
+
+ public PreloadDebugeeWrapper(JPDATestOptions options, LogWriter writer) {
+ super(options, writer);
+ }
+
+ @Override
+ protected Process launchProcess(String cmdLine) throws IOException {
+ return null;
+ }
+
+ @Override
+ protected void WaitForProcessExit(Process process) {
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java
new file mode 100644
index 000000000000..f45aad06ac6b
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.ui;
+
+import com.android.ddmlib.SyncService.ISyncProgressMonitor;
+
+public class NullProgressMonitor implements ISyncProgressMonitor {
+
+ @Override
+ public void advance(int arg0) {}
+
+ @Override
+ public boolean isCanceled() {
+ return false;
+ }
+
+ @Override
+ public void start(int arg0) {}
+
+ @Override
+ public void startSubTask(String arg0) {}
+
+ @Override
+ public void stop() {}
+} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/ui/UI.java b/tools/preload2/src/com/android/preload/ui/UI.java
new file mode 100644
index 000000000000..47174ddd0e07
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ui/UI.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.ui;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.io.File;
+import java.util.List;
+
+import javax.swing.Action;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JToolBar;
+import javax.swing.ListModel;
+import javax.swing.SwingUtilities;
+import javax.swing.table.TableModel;
+
+public class UI extends JFrame {
+
+ private JList<Client> clientList;
+ private JTable dataTable;
+
+ // Shared file chooser, means the directory is retained.
+ private JFileChooser jfc;
+
+ public UI(ListModel<Client> clientListModel,
+ TableModel dataTableModel,
+ List<Action> actions) {
+ super("Preloaded-classes computation");
+
+ getContentPane().add(new JScrollPane(clientList = new JList<Client>(clientListModel)),
+ BorderLayout.WEST);
+ clientList.setCellRenderer(new ClientListCellRenderer());
+ // clientList.addListSelectionListener(listener);
+
+ dataTable = new JTable(dataTableModel);
+ getContentPane().add(new JScrollPane(dataTable), BorderLayout.CENTER);
+
+ JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL);
+ for (Action a : actions) {
+ if (a == null) {
+ toolbar.addSeparator();
+ } else {
+ toolbar.add(a);
+ }
+ }
+ getContentPane().add(toolbar, BorderLayout.PAGE_START);
+
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ setBounds(100, 100, 800, 600);
+ }
+
+ public Client getSelectedClient() {
+ return clientList.getSelectedValue();
+ }
+
+ public int getSelectedDataTableRow() {
+ return dataTable.getSelectedRow();
+ }
+
+ private JDialog currentWaitDialog = null;
+
+ public void showWaitDialog() {
+ if (currentWaitDialog == null) {
+ currentWaitDialog = new JDialog(this, "Please wait...", true);
+ currentWaitDialog.getContentPane().add(new JLabel("Please be patient."),
+ BorderLayout.CENTER);
+ JProgressBar progress = new JProgressBar(JProgressBar.HORIZONTAL);
+ progress.setIndeterminate(true);
+ currentWaitDialog.getContentPane().add(progress, BorderLayout.SOUTH);
+ currentWaitDialog.setSize(200, 100);
+ currentWaitDialog.setLocationRelativeTo(null);
+ showWaitDialogLater();
+ }
+ }
+
+ private void showWaitDialogLater() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(true); // This is blocking.
+ }
+ }
+ });
+ }
+
+ public void updateWaitDialog(String s) {
+ if (currentWaitDialog != null) {
+ ((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s);
+ Dimension prefSize = currentWaitDialog.getPreferredSize();
+ Dimension curSize = currentWaitDialog.getSize();
+ if (prefSize.width > curSize.width || prefSize.height > curSize.height) {
+ currentWaitDialog.setSize(Math.max(prefSize.width, curSize.width),
+ Math.max(prefSize.height, curSize.height));
+ currentWaitDialog.invalidate();
+ }
+ }
+ }
+
+ public void hideWaitDialog() {
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ currentWaitDialog = null;
+ }
+ }
+
+ public void showMessageDialog(String s) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try {
+ JOptionPane.showMessageDialog(this, s);
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public boolean showConfirmDialog(String title, String message) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try {
+ return JOptionPane.showConfirmDialog(this, title, message, JOptionPane.YES_NO_OPTION)
+ == JOptionPane.YES_OPTION;
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public String showInputDialog(String message) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try {
+ return JOptionPane.showInputDialog(message);
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T showChoiceDialog(String title, String message, T[] choices) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try{
+ return (T)JOptionPane.showInputDialog(this,
+ title,
+ message,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ choices,
+ choices[0]);
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public File showSaveDialog() {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try{
+ if (jfc == null) {
+ jfc = new JFileChooser();
+ }
+
+ int ret = jfc.showSaveDialog(this);
+ if (ret == JFileChooser.APPROVE_OPTION) {
+ return jfc.getSelectedFile();
+ } else {
+ return null;
+ }
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public File[] showOpenDialog(boolean multi) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try{
+ if (jfc == null) {
+ jfc = new JFileChooser();
+ }
+
+ jfc.setMultiSelectionEnabled(multi);
+ int ret = jfc.showOpenDialog(this);
+ if (ret == JFileChooser.APPROVE_OPTION) {
+ return jfc.getSelectedFiles();
+ } else {
+ return null;
+ }
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ private class ClientListCellRenderer extends DefaultListCellRenderer {
+
+ @Override
+ public Component getListCellRendererComponent(JList<?> list, Object value, int index,
+ boolean isSelected, boolean cellHasFocus) {
+ ClientData cd = ((Client) value).getClientData();
+ String s = cd.getClientDescription() + " (pid " + cd.getPid() + ")";
+ return super.getListCellRendererComponent(list, s, index, isSelected, cellHasFocus);
+ }
+ }
+}
diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk
index d9ddf08862b3..863abae1e0fb 100644
--- a/tools/split-select/Android.mk
+++ b/tools/split-select/Android.mk
@@ -43,13 +43,12 @@ cIncludes := \
external/zlib \
frameworks/base/tools
-hostLdLibs :=
hostStaticLibs := \
libaapt \
libandroidfw \
libpng \
- liblog \
libutils \
+ liblog \
libcutils \
libexpat \
libziparchive-host \
@@ -57,17 +56,13 @@ hostStaticLibs := \
cFlags := -Wall -Werror
-ifeq ($(HOST_OS),linux)
- hostLdLibs += -lrt -ldl -lpthread
-endif
+hostLdLibs_linux := -lrt -ldl -lpthread
# Statically link libz for MinGW (Win SDK under Linux),
# and dynamically link for all others.
-ifneq ($(strip $(USE_MINGW)),)
- hostStaticLibs += libz
-else
- hostLdLibs += -lz
-endif
+hostStaticLibs_windows := libz
+hostLdLibs_darwin := -lz
+hostLdLibs_linux += -lz
# ==========================================================
@@ -75,11 +70,12 @@ endif
# ==========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := libsplit-select
+LOCAL_MODULE_HOST_OS := darwin linux windows
LOCAL_SRC_FILES := $(sources)
-LOCAL_C_INCLUDES += $(cIncludes)
-LOCAL_CFLAGS += $(cFlags) -D_DARWIN_UNLIMITED_STREAMS
+LOCAL_C_INCLUDES := $(cIncludes)
+LOCAL_CFLAGS := $(cFlags) -D_DARWIN_UNLIMITED_STREAMS
include $(BUILD_HOST_STATIC_LIBRARY)
@@ -93,10 +89,12 @@ LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(testSources)
-LOCAL_C_INCLUDES += $(cIncludes)
-LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs)
-LOCAL_LDLIBS += $(hostLdLibs)
-LOCAL_CFLAGS += $(cFlags)
+LOCAL_C_INCLUDES := $(cIncludes)
+LOCAL_STATIC_LIBRARIES := libsplit-select $(hostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
+LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin)
+LOCAL_LDLIBS_linux := $(hostLdLibs_linux)
+LOCAL_CFLAGS := $(cFlags)
include $(BUILD_HOST_NATIVE_TEST)
@@ -105,13 +103,16 @@ include $(BUILD_HOST_NATIVE_TEST)
# ==========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := split-select
+LOCAL_MODULE_HOST_OS := darwin linux windows
LOCAL_SRC_FILES := $(main)
-LOCAL_C_INCLUDES += $(cIncludes)
-LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs)
-LOCAL_LDLIBS += $(hostLdLibs)
-LOCAL_CFLAGS += $(cFlags)
+LOCAL_C_INCLUDES := $(cIncludes)
+LOCAL_STATIC_LIBRARIES := libsplit-select $(hostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
+LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin)
+LOCAL_LDLIBS_linux := $(hostLdLibs_linux)
+LOCAL_CFLAGS := $(cFlags)
include $(BUILD_HOST_EXECUTABLE)