summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/Command.cpp200
-rw-r--r--tools/aapt/Resource.cpp26
-rw-r--r--tools/aapt2/.clang-format3
-rw-r--r--tools/aapt2/Android.mk43
-rw-r--r--tools/aapt2/AppInfo.h29
-rw-r--r--tools/aapt2/ConfigDescription.cpp1295
-rw-r--r--tools/aapt2/ConfigDescription.h175
-rw-r--r--tools/aapt2/ConfigDescription_test.cpp103
-rw-r--r--tools/aapt2/Debug.cpp430
-rw-r--r--tools/aapt2/Debug.h23
-rw-r--r--tools/aapt2/Diagnostics.h166
-rw-r--r--tools/aapt2/DominatorTree.cpp94
-rw-r--r--tools/aapt2/DominatorTree.h121
-rw-r--r--tools/aapt2/DominatorTree_test.cpp163
-rw-r--r--tools/aapt2/Flags.cpp238
-rw-r--r--tools/aapt2/Flags.h68
-rw-r--r--tools/aapt2/Format.proto2
-rw-r--r--tools/aapt2/Locale.cpp416
-rw-r--r--tools/aapt2/Locale.h115
-rw-r--r--tools/aapt2/Locale_test.cpp116
-rw-r--r--tools/aapt2/Main.cpp77
-rw-r--r--tools/aapt2/NameMangler.h120
-rw-r--r--tools/aapt2/NameMangler_test.cpp30
-rw-r--r--tools/aapt2/Resource.cpp147
-rw-r--r--tools/aapt2/Resource.h364
-rw-r--r--tools/aapt2/ResourceParser.cpp2274
-rw-r--r--tools/aapt2/ResourceParser.h154
-rw-r--r--tools/aapt2/ResourceParser_test.cpp915
-rw-r--r--tools/aapt2/ResourceTable.cpp842
-rw-r--r--tools/aapt2/ResourceTable.h458
-rw-r--r--tools/aapt2/ResourceTable_test.cpp198
-rw-r--r--tools/aapt2/ResourceUtils.cpp1015
-rw-r--r--tools/aapt2/ResourceUtils.h175
-rw-r--r--tools/aapt2/ResourceUtils_test.cpp297
-rw-r--r--tools/aapt2/ResourceValues.cpp1119
-rw-r--r--tools/aapt2/ResourceValues.h427
-rw-r--r--tools/aapt2/Resource_test.cpp146
-rw-r--r--tools/aapt2/SdkConstants.cpp1413
-rw-r--r--tools/aapt2/SdkConstants.h53
-rw-r--r--tools/aapt2/SdkConstants_test.cpp18
-rw-r--r--tools/aapt2/Source.h59
-rw-r--r--tools/aapt2/StringPool.cpp622
-rw-r--r--tools/aapt2/StringPool.h319
-rw-r--r--tools/aapt2/StringPool_test.cpp341
-rw-r--r--tools/aapt2/ValueVisitor.h180
-rw-r--r--tools/aapt2/ValueVisitor_test.cpp80
-rw-r--r--tools/aapt2/compile/Compile.cpp1132
-rw-r--r--tools/aapt2/compile/IdAssigner.cpp252
-rw-r--r--tools/aapt2/compile/IdAssigner.h23
-rw-r--r--tools/aapt2/compile/IdAssigner_test.cpp226
-rw-r--r--tools/aapt2/compile/Image.h209
-rw-r--r--tools/aapt2/compile/InlineXmlFormatParser.cpp207
-rw-r--r--tools/aapt2/compile/InlineXmlFormatParser.h69
-rw-r--r--tools/aapt2/compile/InlineXmlFormatParser_test.cpp143
-rw-r--r--tools/aapt2/compile/NinePatch.cpp698
-rw-r--r--tools/aapt2/compile/NinePatch_test.cpp377
-rw-r--r--tools/aapt2/compile/Png.cpp2245
-rw-r--r--tools/aapt2/compile/Png.h78
-rw-r--r--tools/aapt2/compile/PngChunkFilter.cpp174
-rw-r--r--tools/aapt2/compile/PngCrunch.cpp767
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.cpp392
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.h9
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator_test.cpp178
-rw-r--r--tools/aapt2/compile/Pseudolocalizer.cpp709
-rw-r--r--tools/aapt2/compile/Pseudolocalizer.h52
-rw-r--r--tools/aapt2/compile/Pseudolocalizer_test.cpp333
-rw-r--r--tools/aapt2/compile/XmlIdCollector.cpp68
-rw-r--r--tools/aapt2/compile/XmlIdCollector.h4
-rw-r--r--tools/aapt2/compile/XmlIdCollector_test.cpp44
-rw-r--r--tools/aapt2/diff/Diff.cpp681
-rw-r--r--tools/aapt2/dump/Dump.cpp248
-rw-r--r--tools/aapt2/filter/ConfigFilter.cpp97
-rw-r--r--tools/aapt2/filter/ConfigFilter.h39
-rw-r--r--tools/aapt2/filter/ConfigFilter_test.cpp96
-rw-r--r--tools/aapt2/flatten/Archive.cpp280
-rw-r--r--tools/aapt2/flatten/Archive.h58
-rw-r--r--tools/aapt2/flatten/ChunkWriter.h103
-rw-r--r--tools/aapt2/flatten/ResourceTypeExtensions.h15
-rw-r--r--tools/aapt2/flatten/TableFlattener.cpp804
-rw-r--r--tools/aapt2/flatten/TableFlattener.h22
-rw-r--r--tools/aapt2/flatten/TableFlattener_test.cpp379
-rw-r--r--tools/aapt2/flatten/XmlFlattener.cpp533
-rw-r--r--tools/aapt2/flatten/XmlFlattener.h41
-rw-r--r--tools/aapt2/flatten/XmlFlattener_test.cpp301
-rw-r--r--tools/aapt2/integration-tests/AppOne/AndroidManifest.xml5
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.pngbin0 -> 397 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.pngbin0 -> 409 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.pngbin0 -> 191 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.pngbin0 -> 196 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.pngbin0 -> 181 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.pngbin0 -> 105 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.pngbin0 -> 103 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.pngbin0 -> 103 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.pngbin0 -> 105 bytes
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf0
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf0
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/font/myfont.xml5
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/layout/special.xml10
-rw-r--r--tools/aapt2/integration-tests/AppOne/res/values/test.xml7
-rw-r--r--tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml1
-rw-r--r--tools/aapt2/io/Data.h109
-rw-r--r--tools/aapt2/io/File.cpp43
-rw-r--r--tools/aapt2/io/File.h110
-rw-r--r--tools/aapt2/io/FileSystem.cpp73
-rw-r--r--tools/aapt2/io/FileSystem.h54
-rw-r--r--tools/aapt2/io/Io.cpp44
-rw-r--r--tools/aapt2/io/Io.h63
-rw-r--r--tools/aapt2/io/ZipArchive.cpp193
-rw-r--r--tools/aapt2/io/ZipArchive.h67
-rw-r--r--tools/aapt2/java/AnnotationProcessor.cpp102
-rw-r--r--tools/aapt2/java/AnnotationProcessor.h51
-rw-r--r--tools/aapt2/java/AnnotationProcessor_test.cpp41
-rw-r--r--tools/aapt2/java/ClassDefinition.cpp72
-rw-r--r--tools/aapt2/java/ClassDefinition.h201
-rw-r--r--tools/aapt2/java/JavaClassGenerator.cpp933
-rw-r--r--tools/aapt2/java/JavaClassGenerator.h122
-rw-r--r--tools/aapt2/java/JavaClassGenerator_test.cpp520
-rw-r--r--tools/aapt2/java/ManifestClassGenerator.cpp195
-rw-r--r--tools/aapt2/java/ManifestClassGenerator.h8
-rw-r--r--tools/aapt2/java/ManifestClassGenerator_test.cpp143
-rw-r--r--tools/aapt2/java/ProguardRules.cpp396
-rw-r--r--tools/aapt2/java/ProguardRules.h45
-rw-r--r--tools/aapt2/jni/ScopedUtfChars.h84
-rw-r--r--tools/aapt2/jni/aapt2_jni.cpp99
-rw-r--r--tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h37
-rw-r--r--tools/aapt2/link/AutoVersioner.cpp222
-rw-r--r--tools/aapt2/link/AutoVersioner_test.cpp202
-rw-r--r--tools/aapt2/link/Link.cpp3129
-rw-r--r--tools/aapt2/link/Linkers.h185
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp510
-rw-r--r--tools/aapt2/link/ManifestFixer.h36
-rw-r--r--tools/aapt2/link/ManifestFixer_test.cpp445
-rw-r--r--tools/aapt2/link/PrivateAttributeMover.cpp95
-rw-r--r--tools/aapt2/link/PrivateAttributeMover_test.cpp96
-rw-r--r--tools/aapt2/link/ProductFilter.cpp177
-rw-r--r--tools/aapt2/link/ProductFilter.h49
-rw-r--r--tools/aapt2/link/ProductFilter_test.cpp202
-rw-r--r--tools/aapt2/link/ReferenceLinker.cpp553
-rw-r--r--tools/aapt2/link/ReferenceLinker.h151
-rw-r--r--tools/aapt2/link/ReferenceLinker_test.cpp403
-rw-r--r--tools/aapt2/link/ResourceDeduper.cpp119
-rw-r--r--tools/aapt2/link/ResourceDeduper_test.cpp87
-rw-r--r--tools/aapt2/link/TableMerger.cpp585
-rw-r--r--tools/aapt2/link/TableMerger.h181
-rw-r--r--tools/aapt2/link/TableMerger_test.cpp463
-rw-r--r--tools/aapt2/link/VersionCollapser.cpp162
-rw-r--r--tools/aapt2/link/VersionCollapser_test.cpp117
-rw-r--r--tools/aapt2/link/XmlNamespaceRemover.cpp88
-rw-r--r--tools/aapt2/link/XmlNamespaceRemover_test.cpp122
-rw-r--r--tools/aapt2/link/XmlReferenceLinker.cpp257
-rw-r--r--tools/aapt2/link/XmlReferenceLinker_test.cpp405
-rw-r--r--tools/aapt2/process/IResourceTableConsumer.h35
-rw-r--r--tools/aapt2/process/SymbolTable.cpp481
-rw-r--r--tools/aapt2/process/SymbolTable.h191
-rw-r--r--tools/aapt2/process/SymbolTable_test.cpp57
-rw-r--r--tools/aapt2/proto/ProtoHelpers.cpp219
-rw-r--r--tools/aapt2/proto/ProtoHelpers.h43
-rw-r--r--tools/aapt2/proto/ProtoSerialize.h72
-rw-r--r--tools/aapt2/proto/TableProtoDeserializer.cpp843
-rw-r--r--tools/aapt2/proto/TableProtoSerializer.cpp581
-rw-r--r--tools/aapt2/proto/TableProtoSerializer_test.cpp310
-rw-r--r--tools/aapt2/readme.md44
-rw-r--r--tools/aapt2/split/TableSplitter.cpp414
-rw-r--r--tools/aapt2/split/TableSplitter.h64
-rw-r--r--tools/aapt2/split/TableSplitter_test.cpp259
-rw-r--r--tools/aapt2/test/Builders.h433
-rw-r--r--tools/aapt2/test/Common.h123
-rw-r--r--tools/aapt2/test/Context.h249
-rw-r--r--tools/aapt2/test/Test.h12
-rw-r--r--tools/aapt2/tools/consumers/__init__.py0
-rw-r--r--tools/aapt2/tools/consumers/duplicates.py132
-rw-r--r--tools/aapt2/tools/consumers/positional_arguments.py77
-rw-r--r--tools/aapt2/tools/fix_resources.py63
-rw-r--r--tools/aapt2/tools/public_attr_map.py (renamed from tools/aapt2/public_attr_map.py)0
-rw-r--r--tools/aapt2/unflatten/BinaryResourceParser.cpp1010
-rw-r--r--tools/aapt2/unflatten/BinaryResourceParser.h150
-rw-r--r--tools/aapt2/unflatten/ResChunkPullParser.cpp83
-rw-r--r--tools/aapt2/unflatten/ResChunkPullParser.h134
-rw-r--r--tools/aapt2/util/BigBuffer.cpp67
-rw-r--r--tools/aapt2/util/BigBuffer.h221
-rw-r--r--tools/aapt2/util/BigBuffer_test.cpp100
-rw-r--r--tools/aapt2/util/Files.cpp381
-rw-r--r--tools/aapt2/util/Files.h124
-rw-r--r--tools/aapt2/util/Files_test.cpp53
-rw-r--r--tools/aapt2/util/ImmutableMap.h94
-rw-r--r--tools/aapt2/util/Maybe.h343
-rw-r--r--tools/aapt2/util/Maybe_test.cpp168
-rw-r--r--tools/aapt2/util/StringPiece.h306
-rw-r--r--tools/aapt2/util/StringPiece_test.cpp107
-rw-r--r--tools/aapt2/util/TypeTraits.h32
-rw-r--r--tools/aapt2/util/Util.cpp868
-rw-r--r--tools/aapt2/util/Util.h365
-rw-r--r--tools/aapt2/util/Util_test.cpp243
-rw-r--r--tools/aapt2/xml/XmlActionExecutor.cpp138
-rw-r--r--tools/aapt2/xml/XmlActionExecutor.h133
-rw-r--r--tools/aapt2/xml/XmlActionExecutor_test.cpp72
-rw-r--r--tools/aapt2/xml/XmlDom.cpp735
-rw-r--r--tools/aapt2/xml/XmlDom.h208
-rw-r--r--tools/aapt2/xml/XmlDom_test.cpp28
-rw-r--r--tools/aapt2/xml/XmlPullParser.cpp436
-rw-r--r--tools/aapt2/xml/XmlPullParser.h483
-rw-r--r--tools/aapt2/xml/XmlPullParser_test.cpp49
-rw-r--r--tools/aapt2/xml/XmlUtil.cpp85
-rw-r--r--tools/aapt2/xml/XmlUtil.h86
-rw-r--r--tools/aapt2/xml/XmlUtil_test.cpp65
-rw-r--r--tools/apilint/apilint.py2
-rw-r--r--tools/bit/Android.mk46
-rw-r--r--tools/bit/aapt.cpp270
-rw-r--r--tools/bit/aapt.h39
-rw-r--r--tools/bit/adb.cpp463
-rw-r--r--tools/bit/adb.h47
-rw-r--r--tools/bit/command.cpp217
-rw-r--r--tools/bit/command.h61
-rw-r--r--tools/bit/main.cpp994
-rw-r--r--tools/bit/make.cpp210
-rw-r--r--tools/bit/make.h50
-rw-r--r--tools/bit/print.cpp155
-rw-r--r--tools/bit/print.h39
-rw-r--r--tools/bit/util.cpp254
-rw-r--r--tools/bit/util.h83
-rwxr-xr-xtools/fonts/fontchain_lint.py22
-rw-r--r--tools/layoutlib/.gitignore1
-rw-r--r--tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml1
-rw-r--r--tools/layoutlib/.idea/libraries/junit.xml2
-rw-r--r--tools/layoutlib/bridge/bridge.iml1
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java6
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java18
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java23
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java727
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java25
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java845
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java12
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java98
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java113
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java4
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java87
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java69
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java16
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java6
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java134
-rw-r--r--tools/layoutlib/bridge/src/android/os/ServiceManager.java21
-rw-r--r--tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java5
-rw-r--r--tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java11
-rw-r--r--tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java2
-rw-r--r--tools/layoutlib/bridge/src/android/view/BridgeInflater.java149
-rw-r--r--tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java62
-rw-r--r--tools/layoutlib/bridge/src/android/view/RectShadowPainter.java24
-rw-r--r--tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java13
-rw-r--r--tools/layoutlib/bridge/src/android/view/SurfaceView.java14
-rw-r--r--tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java342
-rw-r--r--tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java111
-rw-r--r--tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java12
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java10
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java95
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java11
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java21
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java282
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java15
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java61
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java61
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java76
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java1
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java28
-rw-r--r--tools/layoutlib/bridge/tests/Android.mk4
-rw-r--r--tools/layoutlib/bridge/tests/res/empty.xml0
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle9
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.classbin2317 -> 2349 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomCalendar.classbin1174 -> 0 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomDate.classbin1300 -> 0 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.classbin1157 -> 1420 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.classbin0 -> 461 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.classbin527 -> 527 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.classbin473 -> 569 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.classbin1978 -> 2014 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.classbin492 -> 492 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.classbin675 -> 881 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.classbin452 -> 452 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.classbin538 -> 538 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.classbin461 -> 461 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.classbin1041 -> 1107 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.pngbin7991 -> 6077 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.pngbin10819 -> 10852 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.pngbin7305 -> 7358 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.pngbin3274 -> 3343 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.pngbin2816 -> 2846 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners.pngbin0 -> 4600 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.pngbin0 -> 4565 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.pngbin0 -> 4634 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.pngbin0 -> 1590 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.pngbin0 -> 1601 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.pngbin5966 -> 5881 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.pngbin0 -> 2449 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java16
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomCalendar.java (renamed from tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomCalendar.java)20
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomDate.java (renamed from tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomDate.java)18
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java6
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml65
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml25
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml5
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml3
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/four_corners.xml38
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml4
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml32
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml34
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml2
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java19
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java159
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java62
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java8
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java6
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java67
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java6
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java29
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java7
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java1
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java24
-rw-r--r--tools/streaming_proto/Android.mk41
-rw-r--r--tools/streaming_proto/Errors.cpp87
-rw-r--r--tools/streaming_proto/Errors.h48
-rw-r--r--tools/streaming_proto/main.cpp474
-rw-r--r--tools/streaming_proto/string_utils.cpp95
-rw-r--r--tools/streaming_proto/string_utils.h32
-rw-r--r--tools/streaming_proto/test/imported.proto26
-rw-r--r--tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java7
-rw-r--r--tools/streaming_proto/test/test.proto124
327 files changed, 36972 insertions, 23904 deletions
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 1dd43f79471d..2e34197e97aa 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -494,7 +494,7 @@ struct ImpliedFeature {
struct Feature {
Feature() : required(false), version(-1) {}
- Feature(bool required, int32_t version = -1) : required(required), version(version) {}
+ explicit Feature(bool required, int32_t version = -1) : required(required), version(version) {}
/**
* Whether the feature is required.
@@ -775,7 +775,7 @@ int doDump(Bundle* bundle)
}
// Source for AndroidManifest.xml
- const String8 manifestFile = String8::format("%s@AndroidManifest.xml", filename);
+ const String8 manifestFile("AndroidManifest.xml");
// The dynamicRefTable can be null if there are no resources for this asset cookie.
// This fine.
@@ -803,7 +803,7 @@ int doDump(Bundle* bundle)
ResXMLTree tree(dynamicRefTable);
asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER);
if (asset == NULL) {
- fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
+ fprintf(stderr, "ERROR: dump failed because resource %s not found\n", resname);
goto bail;
}
@@ -875,14 +875,16 @@ int doDump(Bundle* bundle)
depth++;
const char16_t* ctag16 = tree.getElementName(&len);
if (ctag16 == NULL) {
- fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR: failed to get XML element name (bad string pool)");
goto bail;
}
String8 tag(ctag16);
//printf("Depth %d tag %s\n", depth, tag.string());
if (depth == 1) {
if (tag != "manifest") {
- fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR: manifest does not start with <manifest> tag");
goto bail;
}
String8 pkg = AaptXml::getAttribute(tree, NULL, "package", NULL);
@@ -892,12 +894,14 @@ int doDump(Bundle* bundle)
String8 error;
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR: %s\n", error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name': %s", error.string());
goto bail;
}
if (name == "") {
- fprintf(stderr, "ERROR: missing 'android:name' for permission\n");
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR: missing 'android:name' for permission");
goto bail;
}
printf("permission: %s\n",
@@ -906,12 +910,14 @@ int doDump(Bundle* bundle)
String8 error;
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR: %s\n", error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s", error.string());
goto bail;
}
if (name == "") {
- fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n");
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR: missing 'android:name' for uses-permission");
goto bail;
}
printUsesPermission(name,
@@ -921,13 +927,14 @@ int doDump(Bundle* bundle)
String8 error;
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR: %s\n", error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s", error.string());
goto bail;
}
if (name == "") {
- fprintf(stderr, "ERROR: missing 'android:name' for "
- "uses-permission-sdk-23\n");
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR: missing 'android:name' for uses-permission-sdk-23");
goto bail;
}
printUsesPermissionSdk23(
@@ -1188,14 +1195,16 @@ int doDump(Bundle* bundle)
const char16_t* ctag16 = tree.getElementName(&len);
if (ctag16 == NULL) {
- fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR: failed to get XML element name (bad string pool)");
goto bail;
}
String8 tag(ctag16);
//printf("Depth %d, %s\n", depth, tag.string());
if (depth == 1) {
if (tag != "manifest") {
- fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR: manifest does not start with <manifest> tag");
goto bail;
}
pkg = AaptXml::getAttribute(tree, NULL, "package", NULL);
@@ -1204,7 +1213,8 @@ int doDump(Bundle* bundle)
int32_t versionCode = AaptXml::getIntegerAttribute(tree, VERSION_CODE_ATTR,
&error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:versionCode' attribute: %s",
error.string());
goto bail;
}
@@ -1216,7 +1226,8 @@ int doDump(Bundle* bundle)
String8 versionName = AaptXml::getResolvedAttribute(res, tree,
VERSION_NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:versionName' attribute: %s",
error.string());
goto bail;
}
@@ -1237,7 +1248,8 @@ int doDump(Bundle* bundle)
int32_t installLocation = AaptXml::getResolvedIntegerAttribute(res, tree,
INSTALL_LOCATION_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:installLocation' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:installLocation' attribute: %s",
error.string());
goto bail;
}
@@ -1269,7 +1281,7 @@ int doDump(Bundle* bundle)
const size_t NL = locales.size();
for (size_t i=0; i<NL; i++) {
const char* localeStr = locales[i].string();
- assets.setLocale(localeStr != NULL ? localeStr : "");
+ assets.setConfiguration(config, localeStr != NULL ? localeStr : "");
String8 llabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR,
&error);
if (llabel != "") {
@@ -1303,14 +1315,15 @@ int doDump(Bundle* bundle)
String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
- error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:icon' attribute: %s", error.string());
goto bail;
}
int32_t testOnly = AaptXml::getIntegerAttribute(tree, TEST_ONLY_ATTR, 0,
&error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:testOnly' attribute: %s",
error.string());
goto bail;
}
@@ -1318,8 +1331,8 @@ int doDump(Bundle* bundle)
String8 banner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR,
&error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
- error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:banner' attribute: %s", error.string());
goto bail;
}
printf("application: label='%s' ",
@@ -1337,8 +1350,8 @@ int doDump(Bundle* bundle)
int32_t isGame = AaptXml::getResolvedIntegerAttribute(res, tree,
ISGAME_ATTR, 0, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:isGame' attribute: %s\n",
- error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:isGame' attribute: %s", error.string());
goto bail;
}
if (isGame != 0) {
@@ -1348,7 +1361,8 @@ int doDump(Bundle* bundle)
int32_t debuggable = AaptXml::getResolvedIntegerAttribute(res, tree,
DEBUGGABLE_ATTR, 0, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:debuggable' attribute: %s",
error.string());
goto bail;
}
@@ -1377,8 +1391,8 @@ int doDump(Bundle* bundle)
String8 name = AaptXml::getResolvedAttribute(res, tree,
MIN_SDK_VERSION_ATTR, &error);
if (error != "") {
- fprintf(stderr,
- "ERROR getting 'android:minSdkVersion' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:minSdkVersion' attribute: %s",
error.string());
goto bail;
}
@@ -1399,8 +1413,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",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:targetSdkVersion' attribute: %s",
error.string());
goto bail;
}
@@ -1465,8 +1479,8 @@ int doDump(Bundle* bundle)
FeatureGroup group;
group.label = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:label' attribute:"
- " %s\n", error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:label' attribute: %s", error.string());
goto bail;
}
featureGroups.add(group);
@@ -1511,13 +1525,14 @@ int doDump(Bundle* bundle)
} else if (tag == "uses-permission") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
- error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s", error.string());
goto bail;
}
if (name == "") {
- fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n");
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR: missing 'android:name' for uses-permission");
goto bail;
}
@@ -1546,14 +1561,14 @@ int doDump(Bundle* bundle)
} 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());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s", error.string());
goto bail;
}
if (name == "") {
- fprintf(stderr, "ERROR: missing 'android:name' for "
- "uses-permission-sdk-23\n");
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR: missing 'android:name' for uses-permission-sdk-23");
goto bail;
}
@@ -1568,9 +1583,9 @@ int doDump(Bundle* bundle)
printf("uses-package:'%s'\n",
ResTable::normalizeForOutput(name.string()).string());
} else {
- fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
- error.string());
- goto bail;
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s", error.string());
+ goto bail;
}
} else if (tag == "original-package") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
@@ -1578,9 +1593,9 @@ int doDump(Bundle* bundle)
printf("original-package:'%s'\n",
ResTable::normalizeForOutput(name.string()).string());
} else {
- fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
- error.string());
- goto bail;
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s", error.string());
+ goto bail;
}
} else if (tag == "supports-gl-texture") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
@@ -1588,15 +1603,15 @@ int doDump(Bundle* bundle)
printf("supports-gl-texture:'%s'\n",
ResTable::normalizeForOutput(name.string()).string());
} else {
- fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
- error.string());
- goto bail;
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s", error.string());
+ goto bail;
}
} else if (tag == "compatible-screens") {
printCompatibleScreens(tree, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting compatible screens: %s\n",
- error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting compatible screens: %s", error.string());
goto bail;
}
depth--;
@@ -1633,7 +1648,8 @@ int doDump(Bundle* bundle)
withinActivity = true;
activityName = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s",
error.string());
goto bail;
}
@@ -1641,7 +1657,8 @@ int doDump(Bundle* bundle)
activityLabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR,
&error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:label' attribute: %s",
error.string());
goto bail;
}
@@ -1649,7 +1666,8 @@ int doDump(Bundle* bundle)
activityIcon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR,
&error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:icon' attribute: %s",
error.string());
goto bail;
}
@@ -1657,7 +1675,8 @@ int doDump(Bundle* bundle)
activityBanner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR,
&error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:banner' attribute: %s",
error.string());
goto bail;
}
@@ -1684,9 +1703,9 @@ int doDump(Bundle* bundle)
} else if (tag == "uses-library") {
String8 libraryName = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr,
+ SourcePos(manifestFile, tree.getLineNumber()).error(
"ERROR getting 'android:name' attribute for uses-library"
- " %s\n", error.string());
+ " %s", error.string());
goto bail;
}
int req = AaptXml::getIntegerAttribute(tree,
@@ -1699,9 +1718,9 @@ int doDump(Bundle* bundle)
receiverName = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr,
+ SourcePos(manifestFile, tree.getLineNumber()).error(
"ERROR getting 'android:name' attribute for receiver:"
- " %s\n", error.string());
+ " %s", error.string());
goto bail;
}
@@ -1712,9 +1731,9 @@ int doDump(Bundle* bundle)
hasBindDeviceAdminPermission = true;
}
} else {
- fprintf(stderr,
+ SourcePos(manifestFile, tree.getLineNumber()).error(
"ERROR getting 'android:permission' attribute for"
- " receiver '%s': %s\n",
+ " receiver '%s': %s",
receiverName.string(), error.string());
}
} else if (tag == "service") {
@@ -1722,8 +1741,9 @@ int doDump(Bundle* bundle)
serviceName = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:name' attribute for "
- "service:%s\n", error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute for "
+ "service:%s", error.string());
goto bail;
}
@@ -1748,8 +1768,9 @@ int doDump(Bundle* bundle)
hasBindDreamServicePermission = true;
}
} else {
- fprintf(stderr, "ERROR getting 'android:permission' attribute for "
- "service '%s': %s\n", serviceName.string(), error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:permission' attribute for "
+ "service '%s': %s", serviceName.string(), error.string());
}
} else if (tag == "provider") {
withinProvider = true;
@@ -1757,26 +1778,27 @@ int doDump(Bundle* bundle)
bool exported = AaptXml::getResolvedIntegerAttribute(res, tree,
EXPORTED_ATTR, &error);
if (error != "") {
- fprintf(stderr,
+ SourcePos(manifestFile, tree.getLineNumber()).error(
"ERROR getting 'android:exported' attribute for provider:"
- " %s\n", error.string());
+ " %s", error.string());
goto bail;
}
bool grantUriPermissions = AaptXml::getResolvedIntegerAttribute(
res, tree, GRANT_URI_PERMISSIONS_ATTR, &error);
if (error != "") {
- fprintf(stderr,
+ SourcePos(manifestFile, tree.getLineNumber()).error(
"ERROR getting 'android:grantUriPermissions' attribute for "
- "provider: %s\n", error.string());
+ "provider: %s", 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());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:permission' attribute for "
+ "provider: %s", error.string());
goto bail;
}
@@ -1787,8 +1809,9 @@ int doDump(Bundle* bundle)
String8 metaDataName = AaptXml::getResolvedAttribute(res, tree,
NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:name' attribute for "
- "meta-data:%s\n", error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute for "
+ "meta-data: %s", error.string());
goto bail;
}
printf("meta-data: name='%s' ",
@@ -1801,9 +1824,10 @@ int doDump(Bundle* bundle)
printResolvedResourceAttribute(res, tree, RESOURCE_ATTR,
String8("resource"), &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:value' or "
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:value' or "
"'android:resource' attribute for "
- "meta-data:%s\n", error.string());
+ "meta-data: %s", error.string());
goto bail;
}
}
@@ -1813,7 +1837,8 @@ int doDump(Bundle* bundle)
if (name != "" && error == "") {
supportedInput.add(name);
} else {
- fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s",
error.string());
goto bail;
}
@@ -1872,8 +1897,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(),
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute for "
+ "meta-data tag in service '%s': %s", serviceName.string(),
error.string());
goto bail;
}
@@ -1888,8 +1914,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",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:resource' attribute for "
+ "meta-data tag in service '%s': %s",
serviceName.string(), error.string());
goto bail;
}
@@ -1897,7 +1924,8 @@ int doDump(Bundle* bundle)
Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
offHost, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting AID category for service '%s'\n",
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting AID category for service '%s'",
serviceName.string());
goto bail;
}
@@ -1918,8 +1946,8 @@ int doDump(Bundle* bundle)
if (tag == "action") {
action = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
- error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'android:name' attribute: %s", error.string());
goto bail;
}
@@ -1974,8 +2002,8 @@ int doDump(Bundle* bundle)
if (tag == "category") {
String8 category = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
- error.string());
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "ERROR getting 'name' attribute: %s", error.string());
goto bail;
}
if (withinActivity) {
@@ -2290,6 +2318,10 @@ int doDump(Bundle* bundle)
result = NO_ERROR;
bail:
+ if (SourcePos::hasErrors()) {
+ SourcePos::printErrors(stderr);
+ }
+
if (asset) {
delete asset;
}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 3e8abb121c42..75a316092a02 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -2930,6 +2930,19 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass
if (!keepTag && inApplication && depth == 3) {
if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") {
keepTag = true;
+
+ if (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) {
@@ -2942,19 +2955,6 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass
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());
diff --git a/tools/aapt2/.clang-format b/tools/aapt2/.clang-format
new file mode 100644
index 000000000000..71c5ef2fcda0
--- /dev/null
+++ b/tools/aapt2/.clang-format
@@ -0,0 +1,3 @@
+BasedOnStyle: Google
+ColumnLimit: 100
+
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 53225da14569..1efd2ed6446c 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -23,7 +23,11 @@ LOCAL_PATH:= $(call my-dir)
main := Main.cpp
sources := \
compile/IdAssigner.cpp \
+ compile/InlineXmlFormatParser.cpp \
+ compile/NinePatch.cpp \
compile/Png.cpp \
+ compile/PngChunkFilter.cpp \
+ compile/PngCrunch.cpp \
compile/PseudolocaleGenerator.cpp \
compile/Pseudolocalizer.cpp \
compile/XmlIdCollector.cpp \
@@ -31,14 +35,19 @@ sources := \
flatten/Archive.cpp \
flatten/TableFlattener.cpp \
flatten/XmlFlattener.cpp \
+ io/File.cpp \
io/FileSystem.cpp \
+ io/Io.cpp \
io/ZipArchive.cpp \
link/AutoVersioner.cpp \
link/ManifestFixer.cpp \
link/ProductFilter.cpp \
link/PrivateAttributeMover.cpp \
link/ReferenceLinker.cpp \
+ link/ResourceDeduper.cpp \
link/TableMerger.cpp \
+ link/VersionCollapser.cpp \
+ link/XmlNamespaceRemover.cpp \
link/XmlReferenceLinker.cpp \
process/SymbolTable.cpp \
proto/ProtoHelpers.cpp \
@@ -52,6 +61,7 @@ sources := \
util/Util.cpp \
ConfigDescription.cpp \
Debug.cpp \
+ DominatorTree.cpp \
Flags.cpp \
java/AnnotationProcessor.cpp \
java/ClassDefinition.cpp \
@@ -73,8 +83,12 @@ sources := \
sources += Format.proto
+sourcesJni := jni/aapt2_jni.cpp
+
testSources := \
compile/IdAssigner_test.cpp \
+ compile/InlineXmlFormatParser_test.cpp \
+ compile/NinePatch_test.cpp \
compile/PseudolocaleGenerator_test.cpp \
compile/Pseudolocalizer_test.cpp \
compile/XmlIdCollector_test.cpp \
@@ -86,7 +100,10 @@ testSources := \
link/PrivateAttributeMover_test.cpp \
link/ProductFilter_test.cpp \
link/ReferenceLinker_test.cpp \
+ link/ResourceDeduper_test.cpp \
link/TableMerger_test.cpp \
+ link/VersionCollapser_test.cpp \
+ link/XmlNamespaceRemover_test.cpp \
link/XmlReferenceLinker_test.cpp \
process/SymbolTable_test.cpp \
proto/TableProtoSerializer_test.cpp \
@@ -97,10 +114,12 @@ testSources := \
util/StringPiece_test.cpp \
util/Util_test.cpp \
ConfigDescription_test.cpp \
+ DominatorTree_test.cpp \
java/AnnotationProcessor_test.cpp \
java/JavaClassGenerator_test.cpp \
java/ManifestClassGenerator_test.cpp \
Locale_test.cpp \
+ NameMangler_test.cpp \
Resource_test.cpp \
ResourceParser_test.cpp \
ResourceTable_test.cpp \
@@ -139,7 +158,7 @@ hostStaticLibs_windows := libz
hostLdLibs_linux := -lz
hostLdLibs_darwin := -lz
-cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
+cFlags := -Wall -Werror -Wno-unused-parameter
cFlags_darwin := -D_DARWIN_UNLIMITED_STREAMS
cFlags_windows := -Wno-maybe-uninitialized # Incorrectly marking use of Maybe.value() as error.
cppFlags := -Wno-missing-field-initializers -fno-exceptions -fno-rtti
@@ -168,6 +187,28 @@ LOCAL_STATIC_LIBRARIES := $(hostStaticLibs)
LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
include $(BUILD_HOST_STATIC_LIBRARY)
+
+# ==========================================================
+# Build the host shared library: libaapt2_jni
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libaapt2_jni
+LOCAL_MODULE_CLASS := SHARED_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 := $(toolSources) $(sourcesJni)
+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_SHARED_LIBRARY)
+
+
# ==========================================================
# Build the host tests: libaapt2_tests
# ==========================================================
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
index 30047f71cc04..1e488f70bd4d 100644
--- a/tools/aapt2/AppInfo.h
+++ b/tools/aapt2/AppInfo.h
@@ -19,6 +19,8 @@
#include <string>
+#include "util/Maybe.h"
+
namespace aapt {
/**
@@ -26,12 +28,27 @@ namespace aapt {
* will come from the app's AndroidManifest.
*/
struct AppInfo {
- /**
- * App's package name.
- */
- std::u16string package;
+ /**
+ * App's package name.
+ */
+ std::string package;
+
+ /**
+ * The App's minimum SDK version.
+ */
+ Maybe<std::string> min_sdk_version;
+
+ /**
+ * The Version code of the app.
+ */
+ Maybe<uint32_t> version_code;
+
+ /**
+ * The revision code of the app.
+ */
+ Maybe<uint32_t> revision_code;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_APP_INFO_H
+#endif // AAPT_APP_INFO_H
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 13f8b3b54f68..289919a39373 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -15,772 +15,887 @@
*/
#include "ConfigDescription.h"
+
+#include <string>
+#include <vector>
+
+#include "androidfw/ResourceTypes.h"
+
#include "Locale.h"
#include "SdkConstants.h"
#include "util/StringPiece.h"
#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-#include <string>
-#include <vector>
-
namespace aapt {
using android::ResTable_config;
static const char* kWildcardName = "any";
-const ConfigDescription& ConfigDescription::defaultConfig() {
- static ConfigDescription config = {};
- return config;
+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;
- return true;
- }
- const char* c = name;
- if (tolower(*c) != 'm') return false;
- c++;
- if (tolower(*c) != 'c') return false;
- c++;
- if (tolower(*c) != 'c') return false;
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->mcc = 0;
+ return true;
+ }
+ const char* c = name;
+ if (tolower(*c) != 'm') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+
+ const char* val = c;
+
+ while (*c >= '0' && *c <= '9') {
c++;
+ }
+ if (*c != 0) return false;
+ if (c - val != 3) return false;
- const char* val = c;
-
- while (*c >= '0' && *c <= '9') {
- c++;
- }
- if (*c != 0) return false;
- if (c-val != 3) return false;
-
- int d = atoi(val);
- if (d != 0) {
- if (out) out->mcc = d;
- return true;
- }
+ int d = atoi(val);
+ if (d != 0) {
+ if (out) out->mcc = d;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseMnc(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->mcc = 0;
- return true;
- }
- const char* c = name;
- if (tolower(*c) != 'm') return false;
- c++;
- if (tolower(*c) != 'n') return false;
- c++;
- if (tolower(*c) != 'c') return false;
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->mcc = 0;
+ return true;
+ }
+ const char* c = name;
+ if (tolower(*c) != 'm') return false;
+ c++;
+ if (tolower(*c) != 'n') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+
+ const char* val = c;
+
+ while (*c >= '0' && *c <= '9') {
c++;
+ }
+ if (*c != 0) return false;
+ if (c - val == 0 || c - val > 3) return false;
- const char* val = c;
-
- while (*c >= '0' && *c <= '9') {
- c++;
- }
- if (*c != 0) return false;
- if (c-val == 0 || c-val > 3) return false;
-
- if (out) {
- out->mnc = atoi(val);
- if (out->mnc == 0) {
- out->mnc = ACONFIGURATION_MNC_ZERO;
- }
+ if (out) {
+ out->mnc = atoi(val);
+ if (out->mnc == 0) {
+ out->mnc = ACONFIGURATION_MNC_ZERO;
}
+ }
- return true;
+ return true;
}
static bool parseLayoutDirection(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
- | ResTable_config::LAYOUTDIR_ANY;
- return true;
- } else if (strcmp(name, "ldltr") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
- | ResTable_config::LAYOUTDIR_LTR;
- return true;
- } else if (strcmp(name, "ldrtl") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
- | ResTable_config::LAYOUTDIR_RTL;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) |
+ ResTable_config::LAYOUTDIR_ANY;
+ return true;
+ } else if (strcmp(name, "ldltr") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) |
+ ResTable_config::LAYOUTDIR_LTR;
+ return true;
+ } else if (strcmp(name, "ldrtl") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) |
+ ResTable_config::LAYOUTDIR_RTL;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseScreenLayoutSize(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_ANY;
- return true;
- } else if (strcmp(name, "small") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_SMALL;
- return true;
- } else if (strcmp(name, "normal") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_NORMAL;
- return true;
- } else if (strcmp(name, "large") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_LARGE;
- return true;
- } else if (strcmp(name, "xlarge") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_XLARGE;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_ANY;
+ return true;
+ } else if (strcmp(name, "small") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_SMALL;
+ return true;
+ } else if (strcmp(name, "normal") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_NORMAL;
+ return true;
+ } else if (strcmp(name, "large") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_LARGE;
+ return true;
+ } else if (strcmp(name, "xlarge") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_XLARGE;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseScreenLayoutLong(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
- | ResTable_config::SCREENLONG_ANY;
- return true;
- } else if (strcmp(name, "long") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
- | ResTable_config::SCREENLONG_YES;
- return true;
- } else if (strcmp(name, "notlong") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
- | ResTable_config::SCREENLONG_NO;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) |
+ ResTable_config::SCREENLONG_ANY;
+ return true;
+ } else if (strcmp(name, "long") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) |
+ ResTable_config::SCREENLONG_YES;
+ return true;
+ } else if (strcmp(name, "notlong") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) |
+ ResTable_config::SCREENLONG_NO;
+ return true;
+ }
- return false;
+ 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;
+ 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;
- return true;
- } else if (strcmp(name, "port") == 0) {
- if (out) out->orientation = out->ORIENTATION_PORT;
- return true;
- } else if (strcmp(name, "land") == 0) {
- if (out) out->orientation = out->ORIENTATION_LAND;
- return true;
- } else if (strcmp(name, "square") == 0) {
- if (out) out->orientation = out->ORIENTATION_SQUARE;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->orientation = out->ORIENTATION_ANY;
+ return true;
+ } else if (strcmp(name, "port") == 0) {
+ if (out) out->orientation = out->ORIENTATION_PORT;
+ return true;
+ } else if (strcmp(name, "land") == 0) {
+ if (out) out->orientation = out->ORIENTATION_LAND;
+ return true;
+ } else if (strcmp(name, "square") == 0) {
+ if (out) out->orientation = out->ORIENTATION_SQUARE;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseUiModeType(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_ANY;
- return true;
- } else if (strcmp(name, "desk") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_DESK;
- return true;
- } else if (strcmp(name, "car") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_CAR;
- return true;
- } else if (strcmp(name, "television") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_TELEVISION;
- return true;
- } else if (strcmp(name, "appliance") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_APPLIANCE;
- return true;
- } else if (strcmp(name, "watch") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_WATCH;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_ANY;
+ return true;
+ } else if (strcmp(name, "desk") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_DESK;
+ return true;
+ } else if (strcmp(name, "car") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_CAR;
+ return true;
+ } else if (strcmp(name, "television") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_TELEVISION;
+ return true;
+ } else if (strcmp(name, "appliance") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_APPLIANCE;
+ return true;
+ } else if (strcmp(name, "watch") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_WATCH;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseUiModeNight(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
- | ResTable_config::UI_MODE_NIGHT_ANY;
- return true;
- } else if (strcmp(name, "night") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
- | ResTable_config::UI_MODE_NIGHT_YES;
- return true;
- } else if (strcmp(name, "notnight") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
- | ResTable_config::UI_MODE_NIGHT_NO;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) |
+ ResTable_config::UI_MODE_NIGHT_ANY;
+ return true;
+ } else if (strcmp(name, "night") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) |
+ ResTable_config::UI_MODE_NIGHT_YES;
+ return true;
+ } else if (strcmp(name, "notnight") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) |
+ ResTable_config::UI_MODE_NIGHT_NO;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseDensity(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->density = ResTable_config::DENSITY_DEFAULT;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->density = ResTable_config::DENSITY_DEFAULT;
+ return true;
+ }
- if (strcmp(name, "anydpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_ANY;
- return true;
- }
+ if (strcmp(name, "anydpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_ANY;
+ return true;
+ }
- if (strcmp(name, "nodpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_NONE;
- return true;
- }
+ if (strcmp(name, "nodpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_NONE;
+ return true;
+ }
- if (strcmp(name, "ldpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_LOW;
- return true;
- }
+ if (strcmp(name, "ldpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_LOW;
+ return true;
+ }
- if (strcmp(name, "mdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_MEDIUM;
- return true;
- }
+ if (strcmp(name, "mdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_MEDIUM;
+ return true;
+ }
- if (strcmp(name, "tvdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_TV;
- return true;
- }
+ if (strcmp(name, "tvdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_TV;
+ return true;
+ }
- if (strcmp(name, "hdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_HIGH;
- return true;
- }
+ if (strcmp(name, "hdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_HIGH;
+ return true;
+ }
- if (strcmp(name, "xhdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_XHIGH;
- return true;
- }
+ if (strcmp(name, "xhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XHIGH;
+ return true;
+ }
- if (strcmp(name, "xxhdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_XXHIGH;
- return true;
- }
+ if (strcmp(name, "xxhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XXHIGH;
+ return true;
+ }
- if (strcmp(name, "xxxhdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
- return true;
- }
+ if (strcmp(name, "xxxhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
+ return true;
+ }
- char* c = (char*)name;
- while (*c >= '0' && *c <= '9') {
- c++;
- }
+ char* c = (char*)name;
+ while (*c >= '0' && *c <= '9') {
+ c++;
+ }
- // check that we have 'dpi' after the last digit.
- if (toupper(c[0]) != 'D' ||
- toupper(c[1]) != 'P' ||
- toupper(c[2]) != 'I' ||
- c[3] != 0) {
- return false;
- }
+ // check that we have 'dpi' after the last digit.
+ if (toupper(c[0]) != 'D' || toupper(c[1]) != 'P' || toupper(c[2]) != 'I' ||
+ c[3] != 0) {
+ return false;
+ }
- // temporarily replace the first letter with \0 to
- // use atoi.
- char tmp = c[0];
- c[0] = '\0';
+ // temporarily replace the first letter with \0 to
+ // use atoi.
+ char tmp = c[0];
+ c[0] = '\0';
- int d = atoi(name);
- c[0] = tmp;
+ int d = atoi(name);
+ c[0] = tmp;
- if (d != 0) {
- if (out) out->density = d;
- return true;
- }
+ if (d != 0) {
+ if (out) out->density = d;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseTouchscreen(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
- return true;
- } else if (strcmp(name, "notouch") == 0) {
- if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
- return true;
- } else if (strcmp(name, "stylus") == 0) {
- if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
- return true;
- } else if (strcmp(name, "finger") == 0) {
- if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
+ return true;
+ } else if (strcmp(name, "notouch") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
+ return true;
+ } else if (strcmp(name, "stylus") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
+ return true;
+ } else if (strcmp(name, "finger") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseKeysHidden(const char* name, ResTable_config* out) {
- uint8_t mask = 0;
- uint8_t value = 0;
- if (strcmp(name, kWildcardName) == 0) {
- mask = ResTable_config::MASK_KEYSHIDDEN;
- value = ResTable_config::KEYSHIDDEN_ANY;
- } else if (strcmp(name, "keysexposed") == 0) {
- mask = ResTable_config::MASK_KEYSHIDDEN;
- value = ResTable_config::KEYSHIDDEN_NO;
- } else if (strcmp(name, "keyshidden") == 0) {
- mask = ResTable_config::MASK_KEYSHIDDEN;
- value = ResTable_config::KEYSHIDDEN_YES;
- } else if (strcmp(name, "keyssoft") == 0) {
- mask = ResTable_config::MASK_KEYSHIDDEN;
- value = ResTable_config::KEYSHIDDEN_SOFT;
- }
-
- if (mask != 0) {
- if (out) out->inputFlags = (out->inputFlags&~mask) | value;
- return true;
- }
+ uint8_t mask = 0;
+ uint8_t value = 0;
+ if (strcmp(name, kWildcardName) == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_ANY;
+ } else if (strcmp(name, "keysexposed") == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_NO;
+ } else if (strcmp(name, "keyshidden") == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_YES;
+ } else if (strcmp(name, "keyssoft") == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_SOFT;
+ }
+
+ if (mask != 0) {
+ if (out) out->inputFlags = (out->inputFlags & ~mask) | value;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseKeyboard(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->keyboard = out->KEYBOARD_ANY;
- return true;
- } else if (strcmp(name, "nokeys") == 0) {
- if (out) out->keyboard = out->KEYBOARD_NOKEYS;
- return true;
- } else if (strcmp(name, "qwerty") == 0) {
- if (out) out->keyboard = out->KEYBOARD_QWERTY;
- return true;
- } else if (strcmp(name, "12key") == 0) {
- if (out) out->keyboard = out->KEYBOARD_12KEY;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->keyboard = out->KEYBOARD_ANY;
+ return true;
+ } else if (strcmp(name, "nokeys") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_NOKEYS;
+ return true;
+ } else if (strcmp(name, "qwerty") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_QWERTY;
+ return true;
+ } else if (strcmp(name, "12key") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_12KEY;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseNavHidden(const char* name, ResTable_config* out) {
- uint8_t mask = 0;
- uint8_t value = 0;
- if (strcmp(name, kWildcardName) == 0) {
- mask = ResTable_config::MASK_NAVHIDDEN;
- value = ResTable_config::NAVHIDDEN_ANY;
- } else if (strcmp(name, "navexposed") == 0) {
- mask = ResTable_config::MASK_NAVHIDDEN;
- value = ResTable_config::NAVHIDDEN_NO;
- } else if (strcmp(name, "navhidden") == 0) {
- mask = ResTable_config::MASK_NAVHIDDEN;
- value = ResTable_config::NAVHIDDEN_YES;
- }
-
- if (mask != 0) {
- if (out) out->inputFlags = (out->inputFlags&~mask) | value;
- return true;
- }
+ uint8_t mask = 0;
+ uint8_t value = 0;
+ if (strcmp(name, kWildcardName) == 0) {
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_ANY;
+ } else if (strcmp(name, "navexposed") == 0) {
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_NO;
+ } else if (strcmp(name, "navhidden") == 0) {
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_YES;
+ }
+
+ if (mask != 0) {
+ if (out) out->inputFlags = (out->inputFlags & ~mask) | value;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseNavigation(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->navigation = out->NAVIGATION_ANY;
- return true;
- } else if (strcmp(name, "nonav") == 0) {
- if (out) out->navigation = out->NAVIGATION_NONAV;
- return true;
- } else if (strcmp(name, "dpad") == 0) {
- if (out) out->navigation = out->NAVIGATION_DPAD;
- return true;
- } else if (strcmp(name, "trackball") == 0) {
- if (out) out->navigation = out->NAVIGATION_TRACKBALL;
- return true;
- } else if (strcmp(name, "wheel") == 0) {
- if (out) out->navigation = out->NAVIGATION_WHEEL;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->navigation = out->NAVIGATION_ANY;
+ return true;
+ } else if (strcmp(name, "nonav") == 0) {
+ if (out) out->navigation = out->NAVIGATION_NONAV;
+ return true;
+ } else if (strcmp(name, "dpad") == 0) {
+ if (out) out->navigation = out->NAVIGATION_DPAD;
+ return true;
+ } else if (strcmp(name, "trackball") == 0) {
+ if (out) out->navigation = out->NAVIGATION_TRACKBALL;
+ return true;
+ } else if (strcmp(name, "wheel") == 0) {
+ if (out) out->navigation = out->NAVIGATION_WHEEL;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseScreenSize(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->screenWidth = out->SCREENWIDTH_ANY;
- out->screenHeight = out->SCREENHEIGHT_ANY;
- }
- return true;
- }
-
- const char* x = name;
- while (*x >= '0' && *x <= '9') x++;
- if (x == name || *x != 'x') return false;
- std::string xName(name, x-name);
- x++;
-
- const char* y = x;
- while (*y >= '0' && *y <= '9') y++;
- if (y == name || *y != 0) return false;
- std::string yName(x, y-x);
-
- uint16_t w = (uint16_t)atoi(xName.c_str());
- uint16_t h = (uint16_t)atoi(yName.c_str());
- if (w < h) {
- return false;
- }
-
+ if (strcmp(name, kWildcardName) == 0) {
if (out) {
- out->screenWidth = w;
- out->screenHeight = h;
+ out->screenWidth = out->SCREENWIDTH_ANY;
+ out->screenHeight = out->SCREENHEIGHT_ANY;
}
-
return true;
-}
+ }
+
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || *x != 'x') return false;
+ std::string xName(name, x - name);
+ x++;
+
+ const char* y = x;
+ while (*y >= '0' && *y <= '9') y++;
+ if (y == name || *y != 0) return false;
+ std::string yName(x, y - x);
+
+ uint16_t w = (uint16_t)atoi(xName.c_str());
+ uint16_t h = (uint16_t)atoi(yName.c_str());
+ if (w < h) {
+ return false;
+ }
-static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
- }
- return true;
- }
+ if (out) {
+ out->screenWidth = w;
+ out->screenHeight = h;
+ }
- if (*name != 's') return false;
- name++;
- if (*name != 'w') return false;
- name++;
- const char* x = name;
- while (*x >= '0' && *x <= '9') x++;
- if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
- std::string xName(name, x-name);
+ return true;
+}
+static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) {
+ if (strcmp(name, kWildcardName) == 0) {
if (out) {
- out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str());
+ out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
}
-
return true;
+ }
+
+ if (*name != 's') return false;
+ name++;
+ if (*name != 'w') return false;
+ name++;
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+ std::string xName(name, x - name);
+
+ if (out) {
+ out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str());
+ }
+
+ return true;
}
static bool parseScreenWidthDp(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->screenWidthDp = out->SCREENWIDTH_ANY;
- }
- return true;
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->screenWidthDp = out->SCREENWIDTH_ANY;
}
+ return true;
+ }
- if (*name != 'w') return false;
- name++;
- const char* x = name;
- while (*x >= '0' && *x <= '9') x++;
- if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
- std::string xName(name, x-name);
+ if (*name != 'w') return false;
+ name++;
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+ std::string xName(name, x - name);
- if (out) {
- out->screenWidthDp = (uint16_t)atoi(xName.c_str());
- }
+ if (out) {
+ out->screenWidthDp = (uint16_t)atoi(xName.c_str());
+ }
- return true;
+ return true;
}
static bool parseScreenHeightDp(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->screenHeightDp = out->SCREENWIDTH_ANY;
- }
- return true;
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->screenHeightDp = out->SCREENWIDTH_ANY;
}
+ return true;
+ }
- if (*name != 'h') return false;
- name++;
- const char* x = name;
- while (*x >= '0' && *x <= '9') x++;
- if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
- std::string xName(name, x-name);
+ if (*name != 'h') return false;
+ name++;
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+ std::string xName(name, x - name);
- if (out) {
- out->screenHeightDp = (uint16_t)atoi(xName.c_str());
- }
+ if (out) {
+ out->screenHeightDp = (uint16_t)atoi(xName.c_str());
+ }
- return true;
+ return true;
}
static bool parseVersion(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->sdkVersion = out->SDKVERSION_ANY;
- out->minorVersion = out->MINORVERSION_ANY;
- }
- return true;
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->sdkVersion = out->SDKVERSION_ANY;
+ out->minorVersion = out->MINORVERSION_ANY;
}
+ return true;
+ }
- if (*name != 'v') {
- return false;
- }
+ if (*name != 'v') {
+ return false;
+ }
- name++;
- const char* s = name;
- while (*s >= '0' && *s <= '9') s++;
- if (s == name || *s != 0) return false;
- std::string sdkName(name, s-name);
+ name++;
+ const char* s = name;
+ while (*s >= '0' && *s <= '9') s++;
+ if (s == name || *s != 0) return false;
+ std::string sdkName(name, s - name);
- if (out) {
- out->sdkVersion = (uint16_t)atoi(sdkName.c_str());
- out->minorVersion = 0;
- }
+ if (out) {
+ out->sdkVersion = (uint16_t)atoi(sdkName.c_str());
+ out->minorVersion = 0;
+ }
- return true;
+ return true;
}
-bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) {
- std::vector<std::string> parts = util::splitAndLowercase(str, '-');
+bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
+ std::vector<std::string> parts = util::SplitAndLowercase(str, '-');
- ConfigDescription config;
- ssize_t partsConsumed = 0;
- LocaleValue locale;
+ ConfigDescription config;
+ ssize_t parts_consumed = 0;
+ LocaleValue locale;
- const auto partsEnd = parts.end();
- auto partIter = parts.begin();
+ const auto parts_end = parts.end();
+ auto part_iter = parts.begin();
- if (str.size() == 0) {
- goto success;
- }
+ if (str.size() == 0) {
+ goto success;
+ }
- if (parseMcc(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseMcc(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseMnc(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseMnc(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- // Locale spans a few '-' separators, so we let it
- // control the index.
- partsConsumed = locale.initFromParts(partIter, partsEnd);
- if (partsConsumed < 0) {
- return false;
- } else {
- locale.writeTo(&config);
- partIter += partsConsumed;
- if (partIter == partsEnd) {
- goto success;
- }
+ // Locale spans a few '-' separators, so we let it
+ // control the index.
+ parts_consumed = locale.InitFromParts(part_iter, parts_end);
+ if (parts_consumed < 0) {
+ return false;
+ } else {
+ locale.WriteTo(&config);
+ part_iter += parts_consumed;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseLayoutDirection(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseLayoutDirection(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseSmallestScreenWidthDp(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseScreenWidthDp(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseScreenWidthDp(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseScreenHeightDp(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseScreenHeightDp(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseScreenLayoutSize(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseScreenLayoutSize(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseScreenLayoutLong(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseScreenLayoutLong(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseScreenRound(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseScreenRound(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseOrientation(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseOrientation(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseUiModeType(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseUiModeType(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseUiModeNight(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseUiModeNight(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseDensity(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseDensity(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseTouchscreen(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseTouchscreen(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseKeysHidden(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseKeysHidden(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseKeyboard(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseKeyboard(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseNavHidden(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseNavHidden(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseNavigation(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseNavigation(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseScreenSize(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseScreenSize(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseVersion(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseVersion(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- // Unrecognized.
- return false;
+ // Unrecognized.
+ return false;
success:
- if (out != NULL) {
- applyVersionForCompatibility(&config);
- *out = config;
- }
+ if (out != NULL) {
+ ApplyVersionForCompatibility(&config);
+ *out = config;
+ }
+ return true;
+}
+
+void ConfigDescription::ApplyVersionForCompatibility(
+ ConfigDescription* config) {
+ uint16_t min_sdk = 0;
+ if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
+ min_sdk = SDK_MARSHMALLOW;
+ } else if (config->density == ResTable_config::DENSITY_ANY) {
+ min_sdk = SDK_LOLLIPOP;
+ } else if (config->smallestScreenWidthDp !=
+ ResTable_config::SCREENWIDTH_ANY ||
+ config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY ||
+ config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
+ min_sdk = SDK_HONEYCOMB_MR2;
+ } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE) !=
+ ResTable_config::UI_MODE_TYPE_ANY ||
+ (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT) !=
+ ResTable_config::UI_MODE_NIGHT_ANY) {
+ min_sdk = SDK_FROYO;
+ } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE) !=
+ ResTable_config::SCREENSIZE_ANY ||
+ (config->screenLayout & ResTable_config::MASK_SCREENLONG) !=
+ ResTable_config::SCREENLONG_ANY ||
+ config->density != ResTable_config::DENSITY_DEFAULT) {
+ min_sdk = SDK_DONUT;
+ }
+
+ if (min_sdk > config->sdkVersion) {
+ config->sdkVersion = min_sdk;
+ }
+}
+
+ConfigDescription ConfigDescription::CopyWithoutSdkVersion() const {
+ ConfigDescription copy = *this;
+ copy.sdkVersion = 0;
+ return copy;
+}
+
+bool ConfigDescription::Dominates(const ConfigDescription& o) const {
+ if (*this == DefaultConfig() || *this == o) {
return true;
+ }
+ return MatchWithDensity(o) && !o.MatchWithDensity(*this) &&
+ !isMoreSpecificThan(o) && !o.HasHigherPrecedenceThan(*this);
}
-void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) {
- uint16_t minSdk = 0;
- 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
- || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
- minSdk = SDK_HONEYCOMB_MR2;
- } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
- != ResTable_config::UI_MODE_TYPE_ANY
- || (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT)
- != ResTable_config::UI_MODE_NIGHT_ANY) {
- minSdk = SDK_FROYO;
- } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE)
- != ResTable_config::SCREENSIZE_ANY
- || (config->screenLayout & ResTable_config::MASK_SCREENLONG)
- != ResTable_config::SCREENLONG_ANY
- || config->density != ResTable_config::DENSITY_DEFAULT) {
- minSdk = SDK_DONUT;
- }
+bool ConfigDescription::HasHigherPrecedenceThan(
+ const ConfigDescription& o) const {
+ // The order of the following tests defines the importance of one
+ // configuration parameter over another. Those tests first are more
+ // important, trumping any values in those following them.
+ // The ordering should be the same as ResTable_config#isBetterThan.
+ if (mcc || o.mcc) return (!o.mcc);
+ if (mnc || o.mnc) return (!o.mnc);
+ if (language[0] || o.language[0]) return (!o.language[0]);
+ if (country[0] || o.country[0]) return (!o.country[0]);
+ // Script and variant require either a language or country, both of which
+ // have higher precedence.
+ if ((screenLayout | o.screenLayout) & MASK_LAYOUTDIR) {
+ return !(o.screenLayout & MASK_LAYOUTDIR);
+ }
+ if (smallestScreenWidthDp || o.smallestScreenWidthDp)
+ return (!o.smallestScreenWidthDp);
+ if (screenWidthDp || o.screenWidthDp) return (!o.screenWidthDp);
+ if (screenHeightDp || o.screenHeightDp) return (!o.screenHeightDp);
+ if ((screenLayout | o.screenLayout) & MASK_SCREENSIZE) {
+ return !(o.screenLayout & MASK_SCREENSIZE);
+ }
+ if ((screenLayout | o.screenLayout) & MASK_SCREENLONG) {
+ return !(o.screenLayout & MASK_SCREENLONG);
+ }
+ if ((screenLayout2 | o.screenLayout2) & MASK_SCREENROUND) {
+ return !(o.screenLayout2 & MASK_SCREENROUND);
+ }
+ if (orientation || o.orientation) return (!o.orientation);
+ if ((uiMode | o.uiMode) & MASK_UI_MODE_TYPE) {
+ return !(o.uiMode & MASK_UI_MODE_TYPE);
+ }
+ if ((uiMode | o.uiMode) & MASK_UI_MODE_NIGHT) {
+ return !(o.uiMode & MASK_UI_MODE_NIGHT);
+ }
+ if (density || o.density) return (!o.density);
+ if (touchscreen || o.touchscreen) return (!o.touchscreen);
+ if ((inputFlags | o.inputFlags) & MASK_KEYSHIDDEN) {
+ return !(o.inputFlags & MASK_KEYSHIDDEN);
+ }
+ if ((inputFlags | o.inputFlags) & MASK_NAVHIDDEN) {
+ return !(o.inputFlags & MASK_NAVHIDDEN);
+ }
+ if (keyboard || o.keyboard) return (!o.keyboard);
+ if (navigation || o.navigation) return (!o.navigation);
+ if (screenWidth || o.screenWidth) return (!o.screenWidth);
+ if (screenHeight || o.screenHeight) return (!o.screenHeight);
+ if (sdkVersion || o.sdkVersion) return (!o.sdkVersion);
+ if (minorVersion || o.minorVersion) return (!o.minorVersion);
+ // Both configurations have nothing defined except some possible future
+ // value. Returning the comparison of the two configurations is a
+ // "best effort" at this point to protect against incorrect dominations.
+ return *this != o;
+}
- if (minSdk > config->sdkVersion) {
- config->sdkVersion = minSdk;
- }
+bool ConfigDescription::ConflictsWith(const ConfigDescription& o) const {
+ // This method should be updated as new configuration parameters are
+ // introduced (e.g. screenConfig2).
+ auto pred = [](const uint32_t a, const uint32_t b) -> bool {
+ return a == 0 || b == 0 || a == b;
+ };
+ // The values here can be found in ResTable_config#match. Density and range
+ // values can't lead to conflicts, and are ignored.
+ return !pred(mcc, o.mcc) || !pred(mnc, o.mnc) || !pred(locale, o.locale) ||
+ !pred(screenLayout & MASK_LAYOUTDIR,
+ o.screenLayout & MASK_LAYOUTDIR) ||
+ !pred(screenLayout & MASK_SCREENLONG,
+ o.screenLayout & MASK_SCREENLONG) ||
+ !pred(screenLayout & MASK_UI_MODE_TYPE,
+ o.screenLayout & MASK_UI_MODE_TYPE) ||
+ !pred(uiMode & MASK_UI_MODE_TYPE, o.uiMode & MASK_UI_MODE_TYPE) ||
+ !pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) ||
+ !pred(screenLayout2 & MASK_SCREENROUND,
+ o.screenLayout2 & MASK_SCREENROUND) ||
+ !pred(orientation, o.orientation) ||
+ !pred(touchscreen, o.touchscreen) ||
+ !pred(inputFlags & MASK_KEYSHIDDEN, o.inputFlags & MASK_KEYSHIDDEN) ||
+ !pred(inputFlags & MASK_NAVHIDDEN, o.inputFlags & MASK_NAVHIDDEN) ||
+ !pred(keyboard, o.keyboard) || !pred(navigation, o.navigation);
+}
+
+bool ConfigDescription::IsCompatibleWith(const ConfigDescription& o) const {
+ return !ConflictsWith(o) && !Dominates(o) && !o.Dominates(*this);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index b1397d2e2816..97d0f38a5af1 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -17,11 +17,12 @@
#ifndef AAPT_CONFIG_DESCRIPTION_H
#define AAPT_CONFIG_DESCRIPTION_H
-#include "util/StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
#include <ostream>
+#include "androidfw/ResourceTypes.h"
+
+#include "util/StringPiece.h"
+
namespace aapt {
/*
@@ -29,106 +30,152 @@ 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.
- *
- * The resulting configuration has the appropriate sdkVersion defined
- * for backwards compatibility.
- */
- static bool parse(const StringPiece& str, ConfigDescription* out = nullptr);
-
- /**
- * If the configuration uses an axis that was added after
- * the original Android release, make sure the SDK version
- * is set accordingly.
- */
- static void applyVersionForCompatibility(ConfigDescription* config);
-
- ConfigDescription();
- ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit)
- ConfigDescription(const ConfigDescription& o);
- ConfigDescription(ConfigDescription&& o);
-
- ConfigDescription& operator=(const android::ResTable_config& o);
- ConfigDescription& operator=(const ConfigDescription& o);
- ConfigDescription& operator=(ConfigDescription&& o);
-
- bool operator<(const ConfigDescription& o) const;
- bool operator<=(const ConfigDescription& o) const;
- bool operator==(const ConfigDescription& o) const;
- bool operator!=(const ConfigDescription& o) const;
- bool operator>=(const ConfigDescription& o) const;
- bool operator>(const ConfigDescription& o) const;
+ /**
+ * 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.
+ *
+ * The resulting configuration has the appropriate sdkVersion defined
+ * for backwards compatibility.
+ */
+ static bool Parse(const StringPiece& str, ConfigDescription* out = nullptr);
+
+ /**
+ * If the configuration uses an axis that was added after
+ * the original Android release, make sure the SDK version
+ * is set accordingly.
+ */
+ static void ApplyVersionForCompatibility(ConfigDescription* config);
+
+ ConfigDescription();
+ ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit)
+ ConfigDescription(const ConfigDescription& o);
+ ConfigDescription(ConfigDescription&& o);
+
+ ConfigDescription& operator=(const android::ResTable_config& o);
+ ConfigDescription& operator=(const ConfigDescription& o);
+ ConfigDescription& operator=(ConfigDescription&& o);
+
+ ConfigDescription CopyWithoutSdkVersion() const;
+
+ /**
+ * A configuration X dominates another configuration Y, if X has at least the
+ * precedence of Y and X is strictly more general than Y: for any type defined
+ * by X, the same type is defined by Y with a value equal to or, in the case
+ * of ranges, more specific than that of X.
+ *
+ * For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
+ * does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
+ */
+ bool Dominates(const ConfigDescription& o) const;
+
+ /**
+ * Returns true if this configuration defines a more important configuration
+ * parameter than o. For example, "en" has higher precedence than "v23",
+ * whereas "en" has the same precedence as "en-v23".
+ */
+ bool HasHigherPrecedenceThan(const ConfigDescription& o) const;
+
+ /**
+ * A configuration conflicts with another configuration if both
+ * configurations define an incompatible configuration parameter. An
+ * incompatible configuration parameter is a non-range, non-density parameter
+ * that is defined in both configurations as a different, non-default value.
+ */
+ bool ConflictsWith(const ConfigDescription& o) const;
+
+ /**
+ * A configuration is compatible with another configuration if both
+ * configurations can match a common concrete device configuration and are
+ * unrelated by domination. For example, land-v11 conflicts with port-v21
+ * but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
+ */
+ bool IsCompatibleWith(const ConfigDescription& o) const;
+
+ bool MatchWithDensity(const ConfigDescription& o) const;
+
+ bool operator<(const ConfigDescription& o) const;
+ bool operator<=(const ConfigDescription& o) const;
+ bool operator==(const ConfigDescription& o) const;
+ bool operator!=(const ConfigDescription& o) const;
+ bool operator>=(const ConfigDescription& o) const;
+ bool operator>(const ConfigDescription& o) const;
};
inline ConfigDescription::ConfigDescription() {
- memset(this, 0, sizeof(*this));
- size = sizeof(android::ResTable_config);
+ memset(this, 0, sizeof(*this));
+ size = sizeof(android::ResTable_config);
}
inline ConfigDescription::ConfigDescription(const android::ResTable_config& o) {
- *static_cast<android::ResTable_config*>(this) = o;
- size = sizeof(android::ResTable_config);
+ *static_cast<android::ResTable_config*>(this) = o;
+ size = sizeof(android::ResTable_config);
}
inline ConfigDescription::ConfigDescription(const ConfigDescription& o) {
- *static_cast<android::ResTable_config*>(this) = o;
+ *static_cast<android::ResTable_config*>(this) = o;
}
inline ConfigDescription::ConfigDescription(ConfigDescription&& o) {
- *this = o;
+ *this = o;
}
-inline ConfigDescription& ConfigDescription::operator=(const android::ResTable_config& o) {
- *static_cast<android::ResTable_config*>(this) = o;
- size = sizeof(android::ResTable_config);
- return *this;
+inline ConfigDescription& ConfigDescription::operator=(
+ const android::ResTable_config& o) {
+ *static_cast<android::ResTable_config*>(this) = o;
+ size = sizeof(android::ResTable_config);
+ return *this;
}
-inline ConfigDescription& ConfigDescription::operator=(const ConfigDescription& o) {
- *static_cast<android::ResTable_config*>(this) = o;
- return *this;
+inline ConfigDescription& ConfigDescription::operator=(
+ const ConfigDescription& o) {
+ *static_cast<android::ResTable_config*>(this) = o;
+ return *this;
}
inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) {
- *this = o;
- return *this;
+ *this = o;
+ return *this;
+}
+
+inline bool ConfigDescription::MatchWithDensity(
+ const ConfigDescription& o) const {
+ return match(o) && (density == 0 || density == o.density);
}
inline bool ConfigDescription::operator<(const ConfigDescription& o) const {
- return compare(o) < 0;
+ return compare(o) < 0;
}
inline bool ConfigDescription::operator<=(const ConfigDescription& o) const {
- return compare(o) <= 0;
+ return compare(o) <= 0;
}
inline bool ConfigDescription::operator==(const ConfigDescription& o) const {
- return compare(o) == 0;
+ return compare(o) == 0;
}
inline bool ConfigDescription::operator!=(const ConfigDescription& o) const {
- return compare(o) != 0;
+ return compare(o) != 0;
}
inline bool ConfigDescription::operator>=(const ConfigDescription& o) const {
- return compare(o) >= 0;
+ return compare(o) >= 0;
}
inline bool ConfigDescription::operator>(const ConfigDescription& o) const {
- return compare(o) > 0;
+ return compare(o) > 0;
}
-inline ::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) {
- return out << o.toString().string();
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const ConfigDescription& o) {
+ return out << o.toString().string();
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_CONFIG_DESCRIPTION_H
+#endif // AAPT_CONFIG_DESCRIPTION_H
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index e68d6be536df..c331dc0f6909 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -15,85 +15,88 @@
*/
#include "ConfigDescription.h"
-#include "SdkConstants.h"
-
-#include "util/StringPiece.h"
-#include <gtest/gtest.h>
#include <string>
+#include "SdkConstants.h"
+#include "test/Test.h"
+#include "util/StringPiece.h"
+
namespace aapt {
-static ::testing::AssertionResult TestParse(const StringPiece& input,
- ConfigDescription* config = nullptr) {
- if (ConfigDescription::parse(input, config)) {
- return ::testing::AssertionSuccess() << input << " was successfully parsed";
- }
- return ::testing::AssertionFailure() << input << " could not be parsed";
+static ::testing::AssertionResult TestParse(
+ const StringPiece& input, ConfigDescription* config = nullptr) {
+ if (ConfigDescription::Parse(input, config)) {
+ return ::testing::AssertionSuccess() << input << " was successfully parsed";
+ }
+ return ::testing::AssertionFailure() << input << " could not be parsed";
}
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreOutOfOrder) {
- EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
- EXPECT_FALSE(TestParse("land-en"));
- EXPECT_FALSE(TestParse("hdpi-320dpi"));
+ EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
+ EXPECT_FALSE(TestParse("land-en"));
+ EXPECT_FALSE(TestParse("hdpi-320dpi"));
}
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreNotMatched) {
- EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
+ EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
}
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) {
- EXPECT_FALSE(TestParse("en-sw600dp-land-"));
+ EXPECT_FALSE(TestParse("en-sw600dp-land-"));
}
TEST(ConfigDescriptionTest, ParseBasicQualifiers) {
- ConfigDescription config;
- EXPECT_TRUE(TestParse("", &config));
- EXPECT_EQ(std::string(""), config.toString().string());
-
- EXPECT_TRUE(TestParse("fr-land", &config));
- EXPECT_EQ(std::string("fr-land"), config.toString().string());
-
- EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
- "xhdpi-keyssoft-qwerty-navexposed-nonav", &config));
- EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-"
- "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString().string());
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("", &config));
+ EXPECT_EQ(std::string(""), config.toString().string());
+
+ EXPECT_TRUE(TestParse("fr-land", &config));
+ EXPECT_EQ(std::string("fr-land"), config.toString().string());
+
+ EXPECT_TRUE(
+ TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
+ "xhdpi-keyssoft-qwerty-navexposed-nonav",
+ &config));
+ EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-"
+ "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"),
+ config.toString().string());
}
TEST(ConfigDescriptionTest, ParseLocales) {
- ConfigDescription config;
- EXPECT_TRUE(TestParse("en-rUS", &config));
- EXPECT_EQ(std::string("en-rUS"), config.toString().string());
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("en-rUS", &config));
+ EXPECT_EQ(std::string("en-rUS"), config.toString().string());
}
TEST(ConfigDescriptionTest, ParseQualifierAddedInApi13) {
- ConfigDescription config;
- EXPECT_TRUE(TestParse("sw600dp", &config));
- EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("sw600dp", &config));
+ EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
- EXPECT_TRUE(TestParse("sw600dp-v8", &config));
- EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
+ EXPECT_TRUE(TestParse("sw600dp-v8", &config));
+ EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
}
TEST(ConfigDescriptionTest, ParseCarAttribute) {
- ConfigDescription config;
- EXPECT_TRUE(TestParse("car", &config));
- EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("car", &config));
+ 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());
+ 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
+} // namespace aapt
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 19bd5210c840..60b01e372d7b 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -15,10 +15,6 @@
*/
#include "Debug.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "util/Util.h"
-#include "ValueVisitor.h"
#include <algorithm>
#include <iostream>
@@ -28,224 +24,290 @@
#include <set>
#include <vector>
+#include "android-base/logging.h"
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "util/Util.h"
+
namespace aapt {
class PrintVisitor : public ValueVisitor {
-public:
- using ValueVisitor::visit;
-
- void visit(Attribute* attr) override {
- std::cout << "(attr) type=";
- 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.value().entry;
- if (symbol.symbol.id) {
- std::cout << " (" << symbol.symbol.id.value() << ")";
- }
- std::cout << " = " << symbol.value;
- }
+ public:
+ using ValueVisitor::Visit;
+
+ void Visit(Attribute* attr) override {
+ std::cout << "(attr) type=";
+ attr->PrintMask(&std::cout);
+ static constexpr uint32_t kMask =
+ android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS;
+ if (attr->type_mask & 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(Style* style) override {
- std::cout << "(style)";
- if (style->parent) {
- const Reference& parentRef = style->parent.value();
- std::cout << " parent=";
- if (parentRef.name) {
- if (parentRef.privateReference) {
- std::cout << "*";
- }
- std::cout << parentRef.name.value() << " ";
- }
-
- if (parentRef.id) {
- std::cout << parentRef.id.value();
- }
+ }
+
+ void Visit(Style* style) override {
+ std::cout << "(style)";
+ if (style->parent) {
+ const Reference& parent_ref = style->parent.value();
+ std::cout << " parent=";
+ if (parent_ref.name) {
+ if (parent_ref.private_reference) {
+ std::cout << "*";
}
+ std::cout << parent_ref.name.value() << " ";
+ }
- for (const auto& entry : style->entries) {
- std::cout << "\n ";
- 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) {
- std::cout << "(" << entry.key.id.value() << ")";
- }
-
- std::cout << "=" << *entry.value;
- }
+ if (parent_ref.id) {
+ std::cout << parent_ref.id.value();
+ }
}
- void visit(Array* array) override {
- array->print(&std::cout);
- }
+ for (const auto& entry : style->entries) {
+ std::cout << "\n ";
+ 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) {
+ std::cout << "(" << entry.key.id.value() << ")";
+ }
- void visit(Plural* plural) override {
- plural->print(&std::cout);
+ std::cout << "=" << *entry.value;
}
+ }
- 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 Visit(Array* array) override { array->Print(&std::cout); }
+
+ void Visit(Plural* plural) override { plural->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;
+ }
- void visitItem(Item* item) override {
- item->print(&std::cout);
+ if (attr.id) {
+ std::cout << "(" << attr.id.value() << ")";
+ }
}
+ }
+
+ void VisitItem(Item* item) override { item->Print(&std::cout); }
};
-void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) {
- PrintVisitor visitor;
+void Debug::PrintTable(ResourceTable* table,
+ const DebugPrintTableOptions& options) {
+ PrintVisitor visitor;
- 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;
+ 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 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*> sorted_entries;
+ for (const auto& entry : type->entries) {
+ auto iter = std::lower_bound(
+ sorted_entries.begin(), sorted_entries.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;
+ }
+ });
+ sorted_entries.insert(iter, entry.get());
+ }
+
+ for (const ResourceEntry* entry : sorted_entries) {
+ 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->symbol_status.state) {
+ case SymbolState::kPublic:
+ std::cout << " PUBLIC";
+ break;
+ case SymbolState::kPrivate:
+ std::cout << " _PRIVATE_";
+ break;
+ default:
+ break;
}
+
std::cout << std::endl;
- 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());
- }
-
- 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;
- }
- }
+ for (const auto& value : entry->values) {
+ std::cout << " (" << value->config << ") ";
+ value->value->Accept(&visitor);
+ if (options.show_sources && !value->value->GetSource().path.empty()) {
+ std::cout << " src=" << value->value->GetSource();
+ }
+ std::cout << std::endl;
}
+ }
}
+ }
}
-static size_t getNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) {
- auto iter = std::lower_bound(names.begin(), names.end(), name);
- assert(iter != names.end() && *iter == name);
- return std::distance(names.begin(), iter);
+static size_t GetNodeIndex(const std::vector<ResourceName>& names,
+ const ResourceName& name) {
+ auto iter = std::lower_bound(names.begin(), names.end(), name);
+ CHECK(iter != names.end());
+ CHECK(*iter == name);
+ return std::distance(names.begin(), iter);
}
-void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) {
- std::map<ResourceName, std::set<ResourceName>> graph;
-
- std::queue<ResourceName> stylesToVisit;
- stylesToVisit.push(targetStyle);
- for (; !stylesToVisit.empty(); stylesToVisit.pop()) {
- const ResourceName& styleName = stylesToVisit.front();
- std::set<ResourceName>& parents = graph[styleName];
- if (!parents.empty()) {
- // We've already visited this style.
- continue;
- }
-
- Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
- if (result) {
- ResourceEntry* entry = result.value().entry;
- for (const auto& value : entry->values) {
- 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());
- }
- }
- }
- }
+void Debug::PrintStyleGraph(ResourceTable* table,
+ const ResourceName& target_style) {
+ std::map<ResourceName, std::set<ResourceName>> graph;
+
+ std::queue<ResourceName> styles_to_visit;
+ styles_to_visit.push(target_style);
+ for (; !styles_to_visit.empty(); styles_to_visit.pop()) {
+ const ResourceName& style_name = styles_to_visit.front();
+ std::set<ResourceName>& parents = graph[style_name];
+ if (!parents.empty()) {
+ // We've already visited this style.
+ continue;
}
- std::vector<ResourceName> names;
- for (const auto& entry : graph) {
- names.push_back(entry.first);
+ Maybe<ResourceTable::SearchResult> result = table->FindResource(style_name);
+ if (result) {
+ ResourceEntry* entry = result.value().entry;
+ for (const auto& value : entry->values) {
+ if (Style* style = ValueCast<Style>(value->value.get())) {
+ if (style->parent && style->parent.value().name) {
+ parents.insert(style->parent.value().name.value());
+ styles_to_visit.push(style->parent.value().name.value());
+ }
+ }
+ }
}
-
- std::cout << "digraph styles {\n";
- for (const auto& name : names) {
- std::cout << " node_" << getNodeIndex(names, name)
- << " [label=\"" << name << "\"];\n";
+ }
+
+ std::vector<ResourceName> names;
+ for (const auto& entry : graph) {
+ names.push_back(entry.first);
+ }
+
+ std::cout << "digraph styles {\n";
+ for (const auto& name : names) {
+ std::cout << " node_" << GetNodeIndex(names, name) << " [label=\"" << name
+ << "\"];\n";
+ }
+
+ for (const auto& entry : graph) {
+ const ResourceName& style_name = entry.first;
+ size_t style_node_index = GetNodeIndex(names, style_name);
+
+ for (const auto& parent_name : entry.second) {
+ std::cout << " node_" << style_node_index << " -> "
+ << "node_" << GetNodeIndex(names, parent_name) << ";\n";
}
+ }
- for (const auto& entry : graph) {
- const ResourceName& styleName = entry.first;
- size_t styleNodeIndex = getNodeIndex(names, styleName);
+ std::cout << "}" << std::endl;
+}
- for (const auto& parentName : entry.second) {
- std::cout << " node_" << styleNodeIndex << " -> "
- << "node_" << getNodeIndex(names, parentName) << ";\n";
- }
+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";
}
+ }
- std::cout << "}" << std::endl;
+ if (len - 1 % 8 != 7) {
+ std::cerr << 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";
- }
- }
+namespace {
+
+class XmlPrinter : public xml::Visitor {
+ public:
+ using xml::Visitor::Visit;
- if (len - 1 % 8 != 7) {
- std::cerr << std::endl;
+ void Visit(xml::Element* el) override {
+ std::cerr << prefix_;
+ std::cerr << "E: ";
+ if (!el->namespace_uri.empty()) {
+ std::cerr << el->namespace_uri << ":";
+ }
+ std::cerr << el->name << " (line=" << el->line_number << ")\n";
+
+ for (const xml::Attribute& attr : el->attributes) {
+ std::cerr << prefix_ << " A: ";
+ if (!attr.namespace_uri.empty()) {
+ std::cerr << attr.namespace_uri << ":";
+ }
+ std::cerr << attr.name << "=" << attr.value << "\n";
}
-}
+ const size_t previous_size = prefix_.size();
+ prefix_ += " ";
+ xml::Visitor::Visit(el);
+ prefix_.resize(previous_size);
+ }
+
+ void Visit(xml::Namespace* ns) override {
+ std::cerr << prefix_;
+ std::cerr << "N: " << ns->namespace_prefix << "=" << ns->namespace_uri
+ << " (line=" << ns->line_number << ")\n";
+
+ const size_t previous_size = prefix_.size();
+ prefix_ += " ";
+ xml::Visitor::Visit(ns);
+ prefix_.resize(previous_size);
+ }
+
+ void Visit(xml::Text* text) override {
+ std::cerr << prefix_;
+ std::cerr << "T: '" << text->text << "'\n";
+ }
+
+ private:
+ std::string prefix_;
+};
+
+} // namespace
+
+void Debug::DumpXml(xml::XmlResource* doc) {
+ XmlPrinter printer;
+ doc->root->Accept(&printer);
+}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index fbe64773d4ed..56e2e95df6cb 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -17,25 +17,28 @@
#ifndef AAPT_DEBUG_H
#define AAPT_DEBUG_H
-#include "Resource.h"
-#include "ResourceTable.h"
-
// Include for printf-like debugging.
#include <iostream>
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "xml/XmlDom.h"
+
namespace aapt {
struct DebugPrintTableOptions {
- bool showSources = false;
+ bool show_sources = false;
};
struct Debug {
- 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);
+ static void PrintTable(ResourceTable* table,
+ const DebugPrintTableOptions& options = {});
+ static void PrintStyleGraph(ResourceTable* table,
+ const ResourceName& target_style);
+ static void DumpHex(const void* data, size_t len);
+ static void DumpXml(xml::XmlResource* doc);
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_DEBUG_H
+#endif // AAPT_DEBUG_H
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
index e86f2a8830e8..5bc86a9fdbaf 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -17,131 +17,125 @@
#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>
+#include "android-base/macros.h"
+
+#include "Source.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
namespace aapt {
struct DiagMessageActual {
- Source source;
- std::string message;
+ Source source;
+ std::string message;
};
struct DiagMessage {
-private:
- Source mSource;
- std::stringstream mMessage;
+ public:
+ DiagMessage() = default;
-public:
- DiagMessage() = default;
+ explicit DiagMessage(const StringPiece& src) : source_(src) {}
- DiagMessage(const StringPiece& src) : mSource(src) {
- }
+ explicit DiagMessage(const Source& src) : source_(src) {}
- DiagMessage(const Source& src) : mSource(src) {
- }
+ explicit DiagMessage(size_t line) : source_(Source().WithLine(line)) {}
- DiagMessage(size_t line) : mSource(Source().withLine(line)) {
- }
+ template <typename T>
+ DiagMessage& operator<<(const T& value) {
+ message_ << value;
+ return *this;
+ }
- template <typename T>
- DiagMessage& operator<<(const T& value) {
- mMessage << value;
- return *this;
- }
+ DiagMessageActual Build() const {
+ return DiagMessageActual{source_, message_.str()};
+ }
- DiagMessageActual build() const {
- return DiagMessageActual{ mSource, mMessage.str() };
- }
+ private:
+ Source source_;
+ std::stringstream message_;
};
struct IDiagnostics {
- virtual ~IDiagnostics() = default;
+ virtual ~IDiagnostics() = default;
- enum class Level {
- Note,
- Warn,
- Error
- };
+ enum class Level { Note, Warn, Error };
- virtual void log(Level level, DiagMessageActual& actualMsg) = 0;
+ virtual void Log(Level level, DiagMessageActual& actualMsg) = 0;
- virtual void error(const DiagMessage& message) {
- DiagMessageActual actual = message.build();
- log(Level::Error, actual);
- }
+ 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 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);
- }
+ 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;
- }
+ public:
+ StdErrDiagnostics() = default;
+
+ void Log(Level level, DiagMessageActual& actual_msg) override {
+ const char* tag;
- if (!actualMsg.source.path.empty()) {
- std::cerr << actualMsg.source << ": ";
+ switch (level) {
+ case Level::Error:
+ num_errors_++;
+ if (num_errors_ > 20) {
+ return;
}
- std::cerr << tag << ": " << actualMsg.message << "." << std::endl;
+ tag = "error";
+ break;
+
+ case Level::Warn:
+ tag = "warn";
+ break;
+
+ case Level::Note:
+ tag = "note";
+ break;
+ }
+
+ if (!actual_msg.source.path.empty()) {
+ std::cerr << actual_msg.source << ": ";
}
+ std::cerr << tag << ": " << actual_msg.message << "." << std::endl;
+ }
-private:
- size_t mNumErrors = 0;
+ private:
+ size_t num_errors_ = 0;
- DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
+ DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
};
class SourcePathDiagnostics : public IDiagnostics {
-public:
- SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) {
- }
+ public:
+ SourcePathDiagnostics(const Source& src, IDiagnostics* diag)
+ : source_(src), diag_(diag) {}
- void log(Level level, DiagMessageActual& actualMsg) override {
- actualMsg.source.path = mSource.path;
- mDiag->log(level, actualMsg);
- }
+ void Log(Level level, DiagMessageActual& actual_msg) override {
+ actual_msg.source.path = source_.path;
+ diag_->Log(level, actual_msg);
+ }
-private:
- Source mSource;
- IDiagnostics* mDiag;
+ private:
+ Source source_;
+ IDiagnostics* diag_;
- DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
+ DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_DIAGNOSTICS_H */
diff --git a/tools/aapt2/DominatorTree.cpp b/tools/aapt2/DominatorTree.cpp
new file mode 100644
index 000000000000..118a385e2253
--- /dev/null
+++ b/tools/aapt2/DominatorTree.cpp
@@ -0,0 +1,94 @@
+/*
+ * 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 "DominatorTree.h"
+
+#include <algorithm>
+
+#include "android-base/logging.h"
+
+#include "ConfigDescription.h"
+
+namespace aapt {
+
+DominatorTree::DominatorTree(
+ const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) {
+ for (const auto& config : configs) {
+ product_roots_[config->product].TryAddChild(
+ util::make_unique<Node>(config.get(), nullptr));
+ }
+}
+
+void DominatorTree::Accept(Visitor* visitor) {
+ for (auto& entry : product_roots_) {
+ visitor->VisitTree(entry.first, &entry.second);
+ }
+}
+
+bool DominatorTree::Node::TryAddChild(std::unique_ptr<Node> new_child) {
+ CHECK(new_child->value_) << "cannot add a root or empty node as a child";
+ if (value_ && !Dominates(new_child.get())) {
+ // This is not the root and the child dominates us.
+ return false;
+ }
+ return AddChild(std::move(new_child));
+}
+
+bool DominatorTree::Node::AddChild(std::unique_ptr<Node> new_child) {
+ bool has_dominated_children = false;
+ // Demote children dominated by the new config.
+ for (auto& child : children_) {
+ if (new_child->Dominates(child.get())) {
+ child->parent_ = new_child.get();
+ new_child->children_.push_back(std::move(child));
+ child = {};
+ has_dominated_children = true;
+ }
+ }
+ // Remove dominated children.
+ if (has_dominated_children) {
+ children_.erase(
+ std::remove_if(children_.begin(), children_.end(),
+ [](const std::unique_ptr<Node>& child) -> bool {
+ return child == nullptr;
+ }),
+ children_.end());
+ }
+ // Add the new config to a child if a child dominates the new config.
+ for (auto& child : children_) {
+ if (child->Dominates(new_child.get())) {
+ child->AddChild(std::move(new_child));
+ return true;
+ }
+ }
+ // The new config is not dominated by a child, so add it here.
+ new_child->parent_ = this;
+ children_.push_back(std::move(new_child));
+ return true;
+}
+
+bool DominatorTree::Node::Dominates(const Node* other) const {
+ // Check root node dominations.
+ if (other->is_root_node()) {
+ return is_root_node();
+ } else if (is_root_node()) {
+ return true;
+ }
+ // Neither node is a root node; compare the configurations.
+ return value_->config.Dominates(other->value_->config);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/DominatorTree.h b/tools/aapt2/DominatorTree.h
new file mode 100644
index 000000000000..7d50935eabfd
--- /dev/null
+++ b/tools/aapt2/DominatorTree.h
@@ -0,0 +1,121 @@
+/*
+ * 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_DOMINATOR_TREE_H
+#define AAPT_DOMINATOR_TREE_H
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "ResourceTable.h"
+
+namespace aapt {
+
+/**
+ * A dominator tree of configurations as defined by resolution rules for Android
+ * resources.
+ *
+ * A node in the tree represents a resource configuration.
+ *
+ * The tree has the following property:
+ *
+ * Each child of a given configuration defines a strict superset of qualifiers
+ * and has a value that is at least as specific as that of its ancestors. A
+ * value is "at least as specific" if it is either identical or it represents a
+ * stronger requirement.
+ * For example, v21 is more specific than v11, and w1200dp is more specific than
+ * w800dp.
+ *
+ * The dominator tree relies on the underlying configurations passed to it. If
+ * the configurations passed to the dominator tree go out of scope, the tree
+ * will exhibit undefined behavior.
+ */
+class DominatorTree {
+ public:
+ explicit DominatorTree(
+ const std::vector<std::unique_ptr<ResourceConfigValue>>& configs);
+
+ class Node {
+ public:
+ explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr)
+ : value_(value), parent_(parent) {}
+
+ inline ResourceConfigValue* value() const { return value_; }
+
+ inline Node* parent() const { return parent_; }
+
+ inline bool is_root_node() const { return !value_; }
+
+ inline const std::vector<std::unique_ptr<Node>>& children() const {
+ return children_;
+ }
+
+ bool TryAddChild(std::unique_ptr<Node> new_child);
+
+ private:
+ bool AddChild(std::unique_ptr<Node> new_child);
+ bool Dominates(const Node* other) const;
+
+ ResourceConfigValue* value_;
+ Node* parent_;
+ std::vector<std::unique_ptr<Node>> children_;
+
+ DISALLOW_COPY_AND_ASSIGN(Node);
+ };
+
+ struct Visitor {
+ virtual ~Visitor() = default;
+ virtual void VisitTree(const std::string& product, Node* root) = 0;
+ };
+
+ class BottomUpVisitor : public Visitor {
+ public:
+ virtual ~BottomUpVisitor() = default;
+
+ void VisitTree(const std::string& product, Node* root) override {
+ for (auto& child : root->children()) {
+ VisitNode(child.get());
+ }
+ }
+
+ virtual void VisitConfig(Node* node) = 0;
+
+ private:
+ void VisitNode(Node* node) {
+ for (auto& child : node->children()) {
+ VisitNode(child.get());
+ }
+ VisitConfig(node);
+ }
+ };
+
+ void Accept(Visitor* visitor);
+
+ inline const std::map<std::string, Node>& product_roots() const {
+ return product_roots_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DominatorTree);
+
+ std::map<std::string, Node> product_roots_;
+};
+
+} // namespace aapt
+
+#endif // AAPT_DOMINATOR_TREE_H
diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp
new file mode 100644
index 000000000000..e89c6beb0c57
--- /dev/null
+++ b/tools/aapt2/DominatorTree_test.cpp
@@ -0,0 +1,163 @@
+/*
+ * 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 "DominatorTree.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "test/Test.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+namespace {
+
+class PrettyPrinter : public DominatorTree::Visitor {
+ public:
+ explicit PrettyPrinter(const int indent = 2) : indent_(indent) {}
+
+ void VisitTree(const std::string& product,
+ DominatorTree::Node* root) override {
+ for (auto& child : root->children()) {
+ VisitNode(child.get(), 0);
+ }
+ }
+
+ std::string ToString(DominatorTree* tree) {
+ buffer_.str("");
+ buffer_.clear();
+ tree->Accept(this);
+ return buffer_.str();
+ }
+
+ private:
+ void VisitConfig(const DominatorTree::Node* node, const int indent) {
+ auto config_string = node->value()->config.toString();
+ buffer_ << std::string(indent, ' ')
+ << (config_string.isEmpty() ? "<default>" : config_string)
+ << std::endl;
+ }
+
+ void VisitNode(const DominatorTree::Node* node, const int indent) {
+ VisitConfig(node, indent);
+ for (const auto& child : node->children()) {
+ VisitNode(child.get(), indent + indent_);
+ }
+ }
+
+ std::stringstream buffer_;
+ const int indent_ = 2;
+};
+
+} // namespace
+
+TEST(DominatorTreeTest, DefaultDominatesEverything) {
+ const ConfigDescription default_config = {};
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+ const ConfigDescription sw600dp_land_config =
+ test::ParseConfigOrDie("sw600dp-land-v13");
+
+ std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+ configs.push_back(util::make_unique<ResourceConfigValue>(default_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(land_config, ""));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dp_land_config, ""));
+
+ DominatorTree tree(configs);
+ PrettyPrinter printer;
+
+ std::string expected =
+ "<default>\n"
+ " land\n"
+ " sw600dp-land-v13\n";
+ EXPECT_EQ(expected, printer.ToString(&tree));
+}
+
+TEST(DominatorTreeTest, ProductsAreDominatedSeparately) {
+ const ConfigDescription default_config = {};
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+ const ConfigDescription sw600dp_land_config =
+ test::ParseConfigOrDie("sw600dp-land-v13");
+
+ std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+ configs.push_back(util::make_unique<ResourceConfigValue>(default_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(land_config, ""));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(default_config, "phablet"));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet"));
+
+ DominatorTree tree(configs);
+ PrettyPrinter printer;
+
+ std::string expected =
+ "<default>\n"
+ " land\n"
+ "<default>\n"
+ " sw600dp-land-v13\n";
+ EXPECT_EQ(expected, printer.ToString(&tree));
+}
+
+TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) {
+ const ConfigDescription default_config = {};
+ const ConfigDescription en_config = test::ParseConfigOrDie("en");
+ const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
+ const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl-v4");
+ const ConfigDescription ldrtl_xhdpi_config =
+ test::ParseConfigOrDie("ldrtl-xhdpi-v4");
+ const ConfigDescription sw300dp_config =
+ test::ParseConfigOrDie("sw300dp-v13");
+ const ConfigDescription sw540dp_config =
+ test::ParseConfigOrDie("sw540dp-v14");
+ const ConfigDescription sw600dp_config =
+ test::ParseConfigOrDie("sw600dp-v14");
+ const ConfigDescription sw720dp_config =
+ test::ParseConfigOrDie("sw720dp-v13");
+ const ConfigDescription v20_config = test::ParseConfigOrDie("v20");
+
+ std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+ configs.push_back(util::make_unique<ResourceConfigValue>(default_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(en_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(en_v21_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_config, ""));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw300dp_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw540dp_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw720dp_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(v20_config, ""));
+
+ DominatorTree tree(configs);
+ PrettyPrinter printer;
+
+ std::string expected =
+ "<default>\n"
+ " en\n"
+ " en-v21\n"
+ " ldrtl-v4\n"
+ " ldrtl-xhdpi-v4\n"
+ " sw300dp-v13\n"
+ " sw540dp-v14\n"
+ " sw600dp-v14\n"
+ " sw720dp-v13\n"
+ " v20\n";
+ EXPECT_EQ(expected, printer.ToString(&tree));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
index 666e8a8efff1..c98cd374602f 100644
--- a/tools/aapt2/Flags.cpp
+++ b/tools/aapt2/Flags.cpp
@@ -15,154 +15,178 @@
*/
#include "Flags.h"
-#include "util/StringPiece.h"
-#include "util/Util.h"
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
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;
- };
+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_.push_back(
+ Flag{name.ToString(), description.ToString(), func, true, 1, false});
+ return *this;
}
-Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description,
+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;
- };
+ 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_.push_back(
+ Flag{name.ToString(), description.ToString(), func, true, 1, false});
+ return *this;
}
-Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description,
+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;
- };
+ 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_.push_back(
+ Flag{name.ToString(), description.ToString(), func, false, 1, false});
+ return *this;
}
-Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description,
+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;
- };
+ 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_.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;
- };
+Flags& Flags::OptionalFlagList(const StringPiece& name,
+ const StringPiece& description,
+ std::unordered_set<std::string>* value) {
+ auto func = [value](const StringPiece& arg) -> bool {
+ value->insert(arg.ToString());
+ return true;
+ };
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false });
- return *this;
+ flags_.push_back(
+ Flag{name.ToString(), description.ToString(), func, false, 1, false});
+ return *this;
}
-void Flags::usage(const StringPiece& command, std::ostream* out) {
- constexpr size_t kWidth = 50;
+Flags& Flags::OptionalSwitch(const StringPiece& name,
+ const StringPiece& description, bool* value) {
+ auto func = [value](const StringPiece& arg) -> bool {
+ *value = true;
+ return true;
+ };
- *out << command << " [options]";
- for (const Flag& flag : mFlags) {
- if (flag.required) {
- *out << " " << flag.name << " arg";
- }
+ flags_.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 : flags_) {
+ if (flag.required) {
+ *out << " " << flag.name << " arg";
}
+ }
- *out << " files...\n\nOptions:\n";
+ *out << " files...\n\nOptions:\n";
- for (const Flag& flag : mFlags) {
- std::string argLine = flag.name;
- if (flag.numArgs > 0) {
- argLine += " arg";
- }
+ for (const Flag& flag : flags_) {
+ std::string argline = flag.name;
+ if (flag.num_args > 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 = " ";
- }
+ // 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(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();
+ }
+ *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 Flags::Parse(const StringPiece& command,
+ const std::vector<StringPiece>& args,
+ std::ostream* out_error) {
+ for (size_t i = 0; i < args.size(); i++) {
+ StringPiece arg = args[i];
+ if (*(arg.data()) != '-') {
+ args_.push_back(arg.ToString());
+ continue;
+ }
- 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 (arg == "-h" || arg == "--help") {
+ Usage(command, out_error);
+ return false;
+ }
- if (!match) {
- *outError << "unknown option '" << arg << "'.\n\n";
- usage(command, outError);
+ bool match = false;
+ for (Flag& flag : flags_) {
+ if (arg == flag.name) {
+ if (flag.num_args > 0) {
+ i++;
+ if (i >= args.size()) {
+ *out_error << flag.name << " missing argument.\n\n";
+ Usage(command, out_error);
return false;
+ }
+ flag.action(args[i]);
+ } else {
+ flag.action({});
}
+ flag.parsed = true;
+ match = true;
+ break;
+ }
}
- for (const Flag& flag : mFlags) {
- if (flag.required && !flag.parsed) {
- *outError << "missing required flag " << flag.name << "\n\n";
- usage(command, outError);
- return false;
- }
+ if (!match) {
+ *out_error << "unknown option '" << arg << "'.\n\n";
+ Usage(command, out_error);
+ return false;
}
- return true;
-}
+ }
-const std::vector<std::string>& Flags::getArgs() {
- return mArgs;
+ for (const Flag& flag : flags_) {
+ if (flag.required && !flag.parsed) {
+ *out_error << "missing required flag " << flag.name << "\n\n";
+ Usage(command, out_error);
+ return false;
+ }
+ }
+ return true;
}
-} // namespace aapt
+const std::vector<std::string>& Flags::GetArgs() { return args_; }
+
+} // namespace aapt
diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h
index ce7a4857eb6e..9feff6b4faca 100644
--- a/tools/aapt2/Flags.h
+++ b/tools/aapt2/Flags.h
@@ -17,51 +17,57 @@
#ifndef AAPT_FLAGS_H
#define AAPT_FLAGS_H
-#include "util/Maybe.h"
-#include "util/StringPiece.h"
-
#include <functional>
#include <ostream>
#include <string>
+#include <unordered_set>
#include <vector>
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
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);
+ 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& OptionalFlagList(const StringPiece& name,
+ const StringPiece& description,
+ std::unordered_set<std::string>* value);
+ Flags& OptionalSwitch(const StringPiece& name, const StringPiece& description,
+ bool* value);
- void usage(const StringPiece& command, std::ostream* out);
+ void Usage(const StringPiece& command, std::ostream* out);
- bool parse(const StringPiece& command, const std::vector<StringPiece>& args,
- std::ostream* outError);
+ bool Parse(const StringPiece& command, const std::vector<StringPiece>& args,
+ std::ostream* outError);
- const std::vector<std::string>& getArgs();
+ 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;
+ private:
+ struct Flag {
+ std::string name;
+ std::string description;
+ std::function<bool(const StringPiece& value)> action;
+ bool required;
+ size_t num_args;
- bool parsed;
- };
+ bool parsed;
+ };
- std::vector<Flag> mFlags;
- std::vector<std::string> mArgs;
+ std::vector<Flag> flags_;
+ std::vector<std::string> args_;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_FLAGS_H
+#endif // AAPT_FLAGS_H
diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto
index d05425c5c64d..0917129ba90d 100644
--- a/tools/aapt2/Format.proto
+++ b/tools/aapt2/Format.proto
@@ -34,7 +34,7 @@ message CompiledFile {
optional string resource_name = 1;
optional uint32 line_no = 2;
}
-
+
optional string resource_name = 1;
optional ConfigDescription config = 2;
optional string source_path = 3;
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index be576613b9b2..78f56c7a5b93 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -15,264 +15,240 @@
*/
#include "Locale.h"
-#include "util/Util.h"
-#include <algorithm>
#include <ctype.h>
+
+#include <algorithm>
#include <string>
#include <vector>
+#include "util/Util.h"
+
namespace aapt {
using android::ResTable_config;
-void LocaleValue::setLanguage(const char* languageChars) {
- size_t i = 0;
- while ((*languageChars) != '\0') {
- language[i++] = ::tolower(*languageChars);
- languageChars++;
- }
+void LocaleValue::set_language(const char* language_chars) {
+ size_t i = 0;
+ while ((*language_chars) != '\0') {
+ language[i++] = ::tolower(*language_chars);
+ language_chars++;
+ }
}
-void LocaleValue::setRegion(const char* regionChars) {
- size_t i = 0;
- while ((*regionChars) != '\0') {
- region[i++] = ::toupper(*regionChars);
- regionChars++;
- }
+void LocaleValue::set_region(const char* region_chars) {
+ size_t i = 0;
+ while ((*region_chars) != '\0') {
+ region[i++] = ::toupper(*region_chars);
+ region_chars++;
+ }
}
-void LocaleValue::setScript(const char* scriptChars) {
- size_t i = 0;
- while ((*scriptChars) != '\0') {
- if (i == 0) {
- script[i++] = ::toupper(*scriptChars);
- } else {
- script[i++] = ::tolower(*scriptChars);
- }
- scriptChars++;
+void LocaleValue::set_script(const char* script_chars) {
+ size_t i = 0;
+ while ((*script_chars) != '\0') {
+ if (i == 0) {
+ script[i++] = ::toupper(*script_chars);
+ } else {
+ script[i++] = ::tolower(*script_chars);
}
+ script_chars++;
+ }
}
-void LocaleValue::setVariant(const char* variantChars) {
- size_t i = 0;
- while ((*variantChars) != '\0') {
- variant[i++] = *variantChars;
- variantChars++;
- }
-}
-
-static inline bool isAlpha(const std::string& str) {
- return std::all_of(std::begin(str), std::end(str), ::isalpha);
+void LocaleValue::set_variant(const char* variant_chars) {
+ size_t i = 0;
+ while ((*variant_chars) != '\0') {
+ variant[i++] = *variant_chars;
+ variant_chars++;
+ }
}
-static inline bool isNumber(const std::string& str) {
- return std::all_of(std::begin(str), std::end(str), ::isdigit);
+static inline bool is_alpha(const std::string& str) {
+ return std::all_of(std::begin(str), std::end(str), ::isalpha);
}
-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, '_');
-
- const int numTags = parts.size();
- bool valid = false;
- if (numTags >= 1) {
- const std::string& lang = parts[0];
- if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
- setLanguage(lang.c_str());
- valid = true;
- }
- }
-
- if (!valid || numTags == 1) {
- return valid;
- }
-
- // At this point, valid == true && numTags > 1.
- const std::string& part2 = parts[1];
- if ((part2.length() == 2 && isAlpha(part2)) ||
- (part2.length() == 3 && isNumber(part2))) {
- setRegion(part2.c_str());
- } else if (part2.length() == 4 && isAlpha(part2)) {
- setScript(part2.c_str());
- } else if (part2.length() >= 4 && part2.length() <= 8) {
- setVariant(part2.c_str());
- } else {
- valid = false;
- }
-
- if (!valid || numTags == 2) {
- return valid;
- }
-
- // At this point, valid == true && numTags > 1.
- const std::string& part3 = parts[2];
- if (((part3.length() == 2 && isAlpha(part3)) ||
- (part3.length() == 3 && isNumber(part3))) && script[0]) {
- setRegion(part3.c_str());
- } else if (part3.length() >= 4 && part3.length() <= 8) {
- setVariant(part3.c_str());
- } else {
- valid = false;
- }
-
- if (!valid || numTags == 3) {
- return valid;
- }
-
- const std::string& part4 = parts[3];
- if (part4.length() >= 4 && part4.length() <= 8) {
- setVariant(part4.c_str());
- } else {
- valid = false;
- }
-
- if (!valid || numTags > 4) {
- return false;
- }
-
- return true;
+static inline bool is_number(const std::string& str) {
+ return std::all_of(std::begin(str), std::end(str), ::isdigit);
}
-ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
- std::vector<std::string>::iterator end) {
- const std::vector<std::string>::iterator startIter = 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,
- // except that the separator is "+" and not "-".
- std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
- subtags.erase(subtags.begin());
- if (subtags.size() == 1) {
- setLanguage(subtags[0].c_str());
- } else if (subtags.size() == 2) {
- setLanguage(subtags[0].c_str());
-
- // The second tag can either be a region, a variant or a script.
- switch (subtags[1].size()) {
- case 2:
- case 3:
- setRegion(subtags[1].c_str());
- break;
- case 4:
- 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:
- case 8:
- setVariant(subtags[1].c_str());
- break;
- default:
- return -1;
- }
- } else if (subtags.size() == 3) {
- // The language is always the first subtag.
- setLanguage(subtags[0].c_str());
-
- // The second subtag can either be a script or a region code.
- // If its size is 4, it's a script code, else it's a region code.
- if (subtags[1].size() == 4) {
- setScript(subtags[1].c_str());
- } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
- setRegion(subtags[1].c_str());
- } else {
- 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) {
- setVariant(subtags[2].c_str());
- } else {
- setRegion(subtags[2].c_str());
- }
- } else if (subtags.size() == 4) {
- setLanguage(subtags[0].c_str());
- setScript(subtags[1].c_str());
- setRegion(subtags[2].c_str());
- setVariant(subtags[3].c_str());
- } else {
- return -1;
- }
-
- ++iter;
-
- } else {
- if ((part.length() == 2 || part.length() == 3)
- && isAlpha(part) && part != "car") {
- setLanguage(part.c_str());
- ++iter;
-
- if (iter != end) {
- const std::string& regionPart = *iter;
- if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
- setRegion(regionPart.c_str() + 1);
- ++iter;
- }
- }
- }
+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, '_');
+
+ const int num_tags = parts.size();
+ bool valid = false;
+ if (num_tags >= 1) {
+ const std::string& lang = parts[0];
+ if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
+ set_language(lang.c_str());
+ valid = true;
}
-
- return static_cast<ssize_t>(iter - startIter);
+ }
+
+ if (!valid || num_tags == 1) {
+ return valid;
+ }
+
+ // At this point, valid == true && numTags > 1.
+ const std::string& part2 = parts[1];
+ if ((part2.length() == 2 && is_alpha(part2)) ||
+ (part2.length() == 3 && is_number(part2))) {
+ set_region(part2.c_str());
+ } else if (part2.length() == 4 && is_alpha(part2)) {
+ set_script(part2.c_str());
+ } else if (part2.length() >= 4 && part2.length() <= 8) {
+ set_variant(part2.c_str());
+ } else {
+ valid = false;
+ }
+
+ if (!valid || num_tags == 2) {
+ return valid;
+ }
+
+ // At this point, valid == true && numTags > 1.
+ const std::string& part3 = parts[2];
+ if (((part3.length() == 2 && is_alpha(part3)) ||
+ (part3.length() == 3 && is_number(part3))) &&
+ script[0]) {
+ set_region(part3.c_str());
+ } else if (part3.length() >= 4 && part3.length() <= 8) {
+ set_variant(part3.c_str());
+ } else {
+ valid = false;
+ }
+
+ if (!valid || num_tags == 3) {
+ return valid;
+ }
+
+ const std::string& part4 = parts[3];
+ if (part4.length() >= 4 && part4.length() <= 8) {
+ set_variant(part4.c_str());
+ } else {
+ valid = false;
+ }
+
+ if (!valid || num_tags > 4) {
+ return false;
+ }
+
+ return true;
}
-
-std::string LocaleValue::toDirName() const {
- std::string dirName;
- if (language[0]) {
- dirName += language;
+ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
+ std::vector<std::string>::iterator end) {
+ const std::vector<std::string>::iterator start_iter = 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,
+ // except that the separator is "+" and not "-".
+ std::vector<std::string> subtags = util::SplitAndLowercase(part, '+');
+ subtags.erase(subtags.begin());
+ if (subtags.size() == 1) {
+ set_language(subtags[0].c_str());
+ } else if (subtags.size() == 2) {
+ set_language(subtags[0].c_str());
+
+ // The second tag can either be a region, a variant or a script.
+ switch (subtags[1].size()) {
+ case 2:
+ case 3:
+ set_region(subtags[1].c_str());
+ break;
+ case 4:
+ if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
+ // This is a variant: fall through
+ } else {
+ set_script(subtags[1].c_str());
+ break;
+ }
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ set_variant(subtags[1].c_str());
+ break;
+ default:
+ return -1;
+ }
+ } else if (subtags.size() == 3) {
+ // The language is always the first subtag.
+ set_language(subtags[0].c_str());
+
+ // The second subtag can either be a script or a region code.
+ // If its size is 4, it's a script code, else it's a region code.
+ if (subtags[1].size() == 4) {
+ set_script(subtags[1].c_str());
+ } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
+ set_region(subtags[1].c_str());
+ } else {
+ 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) {
+ set_variant(subtags[2].c_str());
+ } else {
+ set_region(subtags[2].c_str());
+ }
+ } else if (subtags.size() == 4) {
+ set_language(subtags[0].c_str());
+ set_script(subtags[1].c_str());
+ set_region(subtags[2].c_str());
+ set_variant(subtags[3].c_str());
} else {
- return dirName;
+ return -1;
}
- if (script[0]) {
- dirName += "-s";
- dirName += script;
- }
+ ++iter;
- if (region[0]) {
- dirName += "-r";
- dirName += region;
- }
+ } else {
+ if ((part.length() == 2 || part.length() == 3) && is_alpha(part) &&
+ part != "car") {
+ set_language(part.c_str());
+ ++iter;
- if (variant[0]) {
- dirName += "-v";
- dirName += variant;
+ if (iter != end) {
+ const std::string& region_part = *iter;
+ if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
+ set_region(region_part.c_str() + 1);
+ ++iter;
+ }
+ }
}
+ }
- return dirName;
+ return static_cast<ssize_t>(iter - start_iter);
}
-void LocaleValue::initFromResTable(const ResTable_config& config) {
- config.unpackLanguage(language);
- config.unpackRegion(region);
- if (config.localeScript[0] && !config.localeScriptWasComputed) {
- memcpy(script, config.localeScript, sizeof(config.localeScript));
- }
+void LocaleValue::InitFromResTable(const ResTable_config& config) {
+ config.unpackLanguage(language);
+ config.unpackRegion(region);
+ if (config.localeScript[0] && !config.localeScriptWasComputed) {
+ memcpy(script, config.localeScript, sizeof(config.localeScript));
+ }
- if (config.localeVariant[0]) {
- memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
- }
+ if (config.localeVariant[0]) {
+ memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
+ }
}
-void LocaleValue::writeTo(ResTable_config* out) const {
- out->packLanguage(language);
- out->packRegion(region);
+void LocaleValue::WriteTo(ResTable_config* out) const {
+ out->packLanguage(language);
+ out->packRegion(region);
- if (script[0]) {
- memcpy(out->localeScript, script, sizeof(out->localeScript));
- }
+ if (script[0]) {
+ memcpy(out->localeScript, script, sizeof(out->localeScript));
+ }
- if (variant[0]) {
- memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
- }
+ if (variant[0]) {
+ memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
+ }
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h
index b1c80ab27641..fc6c448ac1dc 100644
--- a/tools/aapt2/Locale.h
+++ b/tools/aapt2/Locale.h
@@ -17,100 +17,97 @@
#ifndef AAPT_LOCALE_VALUE_H
#define AAPT_LOCALE_VALUE_H
-#include "util/StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
#include <string>
#include <vector>
+#include "androidfw/ResourceTypes.h"
+
+#include "util/StringPiece.h"
+
namespace aapt {
/**
* A convenience class to build and parse locales.
*/
struct LocaleValue {
- char language[4];
- char region[4];
- char script[4];
- char variant[8];
-
- inline LocaleValue();
-
- /**
- * Initialize this LocaleValue from a config string.
- */
- bool initFromFilterString(const StringPiece& config);
-
- /**
- * Initialize this LocaleValue from parts of a vector.
- */
- ssize_t initFromParts(std::vector<std::string>::iterator iter,
- std::vector<std::string>::iterator end);
-
- /**
- * Initialize this LocaleValue from a ResTable_config.
- */
- void initFromResTable(const android::ResTable_config& config);
-
- /**
- * Set the locale in a ResTable_config from this LocaleValue.
- */
- void writeTo(android::ResTable_config* out) const;
-
- std::string toDirName() const;
-
- inline int compare(const LocaleValue& other) const;
-
- inline bool operator<(const LocaleValue& o) const;
- inline bool operator<=(const LocaleValue& o) const;
- inline bool operator==(const LocaleValue& o) const;
- inline bool operator!=(const LocaleValue& o) const;
- inline bool operator>=(const LocaleValue& o) const;
- inline bool operator>(const LocaleValue& o) const;
-
-private:
- void setLanguage(const char* language);
- void setRegion(const char* language);
- void setScript(const char* script);
- void setVariant(const char* variant);
+ char language[4];
+ char region[4];
+ char script[4];
+ char variant[8];
+
+ inline LocaleValue();
+
+ /**
+ * Initialize this LocaleValue from a config string.
+ */
+ bool InitFromFilterString(const StringPiece& config);
+
+ /**
+ * Initialize this LocaleValue from parts of a vector.
+ */
+ ssize_t InitFromParts(std::vector<std::string>::iterator iter,
+ std::vector<std::string>::iterator end);
+
+ /**
+ * Initialize this LocaleValue from a ResTable_config.
+ */
+ void InitFromResTable(const android::ResTable_config& config);
+
+ /**
+ * Set the locale in a ResTable_config from this LocaleValue.
+ */
+ void WriteTo(android::ResTable_config* out) const;
+
+ inline int compare(const LocaleValue& other) const;
+
+ inline bool operator<(const LocaleValue& o) const;
+ inline bool operator<=(const LocaleValue& o) const;
+ inline bool operator==(const LocaleValue& o) const;
+ inline bool operator!=(const LocaleValue& o) const;
+ inline bool operator>=(const LocaleValue& o) const;
+ inline bool operator>(const LocaleValue& o) const;
+
+ private:
+ void set_language(const char* language);
+ void set_region(const char* language);
+ void set_script(const char* script);
+ void set_variant(const char* variant);
};
//
// Implementation
//
-LocaleValue::LocaleValue() {
- memset(this, 0, sizeof(LocaleValue));
-}
+LocaleValue::LocaleValue() { memset(this, 0, sizeof(LocaleValue)); }
int LocaleValue::compare(const LocaleValue& other) const {
- return memcmp(this, &other, sizeof(LocaleValue));
+ return memcmp(this, &other, sizeof(LocaleValue));
}
bool LocaleValue::operator<(const LocaleValue& o) const {
- return compare(o) < 0;
+ return compare(o) < 0;
}
bool LocaleValue::operator<=(const LocaleValue& o) const {
- return compare(o) <= 0;
+ return compare(o) <= 0;
}
bool LocaleValue::operator==(const LocaleValue& o) const {
- return compare(o) == 0;
+ return compare(o) == 0;
}
bool LocaleValue::operator!=(const LocaleValue& o) const {
- return compare(o) != 0;
+ return compare(o) != 0;
}
bool LocaleValue::operator>=(const LocaleValue& o) const {
- return compare(o) >= 0;
+ return compare(o) >= 0;
}
bool LocaleValue::operator>(const LocaleValue& o) const {
- return compare(o) > 0;
+ return compare(o) > 0;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_LOCALE_VALUE_H
+#endif // AAPT_LOCALE_VALUE_H
diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp
index 758e1e31c0e7..68b4cae44e15 100644
--- a/tools/aapt2/Locale_test.cpp
+++ b/tools/aapt2/Locale_test.cpp
@@ -15,68 +15,82 @@
*/
#include "Locale.h"
-#include "util/Util.h"
-#include <gtest/gtest.h>
#include <string>
+#include "gtest/gtest.h"
+
+#include "util/Util.h"
+
namespace aapt {
-static ::testing::AssertionResult TestLanguage(const char* input, const char* lang) {
- std::vector<std::string> parts = util::splitAndLowercase(std::string(input), '-');
- LocaleValue lv;
- ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
- if (count < 0) {
- return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
- }
-
- if (count != 1) {
- return ::testing::AssertionFailure() << count
- << " parts were consumed parsing '" << input << "' but expected 1.";
- }
-
- if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
- return ::testing::AssertionFailure() << "expected " << lang << " but got "
- << std::string(lv.language, sizeof(lv.language)) << ".";
- }
-
- return ::testing::AssertionSuccess();
+static ::testing::AssertionResult TestLanguage(const char* input,
+ const char* lang) {
+ std::vector<std::string> parts = util::SplitAndLowercase(input, '-');
+ LocaleValue lv;
+ ssize_t count = lv.InitFromParts(std::begin(parts), std::end(parts));
+ if (count < 0) {
+ return ::testing::AssertionFailure() << " failed to parse '" << input
+ << "'.";
+ }
+
+ if (count != 1) {
+ return ::testing::AssertionFailure()
+ << count << " parts were consumed parsing '" << input
+ << "' but expected 1.";
+ }
+
+ if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) !=
+ 0) {
+ return ::testing::AssertionFailure()
+ << "expected " << lang << " but got "
+ << std::string(lv.language, sizeof(lv.language)) << ".";
+ }
+
+ return ::testing::AssertionSuccess();
}
-static ::testing::AssertionResult TestLanguageRegion(const char* input, const char* lang,
+static ::testing::AssertionResult TestLanguageRegion(const char* input,
+ const char* lang,
const char* region) {
- std::vector<std::string> parts = util::splitAndLowercase(std::string(input), '-');
- LocaleValue lv;
- ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
- if (count < 0) {
- return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
- }
-
- if (count != 2) {
- return ::testing::AssertionFailure() << count
- << " parts were consumed parsing '" << input << "' but expected 2.";
- }
-
- if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
- return ::testing::AssertionFailure() << "expected " << input << " but got "
- << std::string(lv.language, sizeof(lv.language)) << ".";
- }
-
- if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) != 0) {
- return ::testing::AssertionFailure() << "expected " << region << " but got "
- << std::string(lv.region, sizeof(lv.region)) << ".";
- }
-
- return ::testing::AssertionSuccess();
+ std::vector<std::string> parts = util::SplitAndLowercase(input, '-');
+ LocaleValue lv;
+ ssize_t count = lv.InitFromParts(std::begin(parts), std::end(parts));
+ if (count < 0) {
+ return ::testing::AssertionFailure() << " failed to parse '" << input
+ << "'.";
+ }
+
+ if (count != 2) {
+ return ::testing::AssertionFailure()
+ << count << " parts were consumed parsing '" << input
+ << "' but expected 2.";
+ }
+
+ if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) !=
+ 0) {
+ return ::testing::AssertionFailure()
+ << "expected " << input << " but got "
+ << std::string(lv.language, sizeof(lv.language)) << ".";
+ }
+
+ if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) !=
+ 0) {
+ return ::testing::AssertionFailure()
+ << "expected " << region << " but got "
+ << std::string(lv.region, sizeof(lv.region)) << ".";
+ }
+
+ return ::testing::AssertionSuccess();
}
TEST(ConfigDescriptionTest, ParseLanguage) {
- EXPECT_TRUE(TestLanguage("en", "en"));
- EXPECT_TRUE(TestLanguage("fr", "fr"));
- EXPECT_FALSE(TestLanguage("land", ""));
- EXPECT_TRUE(TestLanguage("fr-land", "fr"));
+ EXPECT_TRUE(TestLanguage("en", "en"));
+ EXPECT_TRUE(TestLanguage("fr", "fr"));
+ EXPECT_FALSE(TestLanguage("land", ""));
+ EXPECT_TRUE(TestLanguage("fr-land", "fr"));
- EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA"));
+ EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA"));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 00d8aaeeda55..a2b216d01b11 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -14,45 +14,60 @@
* limitations under the License.
*/
-#include "util/StringPiece.h"
-
#include <iostream>
#include <vector>
+#include "util/StringPiece.h"
+
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);
-extern int diff(const std::vector<StringPiece>& args);
+// DO NOT UPDATE, this is more of a marketing version.
+static const char* sMajorVersion = "2";
+
+// Update minor version whenever a feature or flag is added.
+static const char* sMinorVersion = "4";
-} // namespace aapt
+int PrintVersion() {
+ std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
+ << sMinorVersion << std::endl;
+ return 0;
+}
+
+extern int Compile(const std::vector<StringPiece>& args);
+extern int Link(const std::vector<StringPiece>& args);
+extern int Dump(const std::vector<StringPiece>& args);
+extern int Diff(const std::vector<StringPiece>& args);
+
+} // namespace aapt
int main(int argc, char** argv) {
- if (argc >= 2) {
- argv += 1;
- argc -= 1;
-
- std::vector<aapt::StringPiece> args;
- for (int i = 1; i < argc; i++) {
- args.push_back(argv[i]);
- }
-
- 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);
- } else if (command == "diff") {
- return aapt::diff(args);
- }
- std::cerr << "unknown command '" << command << "'\n";
- } else {
- std::cerr << "no command specified\n";
+ if (argc >= 2) {
+ argv += 1;
+ argc -= 1;
+
+ std::vector<aapt::StringPiece> args;
+ for (int i = 1; i < argc; i++) {
+ args.push_back(argv[i]);
+ }
+
+ 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);
+ } else if (command == "diff") {
+ return aapt::Diff(args);
+ } else if (command == "version") {
+ return aapt::PrintVersion();
}
+ std::cerr << "unknown command '" << command << "'\n";
+ } else {
+ std::cerr << "no command specified\n";
+ }
- std::cerr << "\nusage: aapt2 [compile|link|dump|diff] ..." << std::endl;
- return 1;
+ std::cerr << "\nusage: aapt2 [compile|link|dump|diff|version] ..."
+ << std::endl;
+ return 1;
}
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index 054b9ee116f4..dba2d096dd9d 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -17,82 +17,82 @@
#ifndef AAPT_NAME_MANGLER_H
#define AAPT_NAME_MANGLER_H
-#include "Resource.h"
-
-#include "util/Maybe.h"
-
#include <set>
#include <string>
+#include "Resource.h"
+#include "util/Maybe.h"
+
namespace aapt {
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;
+ /**
+ * Represents the package we are trying to build. References pointing
+ * to this package are not mangled, and mangled references inherit this
+ * package name.
+ */
+ std::string target_package_name;
+
+ /**
+ * We must know which references to mangle, and which to keep (android vs.
+ * com.android.support).
+ */
+ std::set<std::string> packages_to_mangle;
};
class NameMangler {
-private:
- NameManglerPolicy mPolicy;
+ public:
+ explicit NameMangler(NameManglerPolicy policy) : policy_(policy) {}
-public:
- NameMangler(NameManglerPolicy policy) : mPolicy(policy) {
+ Maybe<ResourceName> MangleName(const ResourceName& name) {
+ if (policy_.target_package_name == name.package ||
+ policy_.packages_to_mangle.count(name.package) == 0) {
+ return {};
}
- 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)
- };
- }
+ std::string mangled_entry_name = MangleEntry(name.package, name.entry);
+ return ResourceName(policy_.target_package_name, name.type,
+ mangled_entry_name);
+ }
- bool shouldMangle(const std::u16string& package) const {
- if (package.empty() || mPolicy.targetPackageName == package) {
- return false;
- }
- return mPolicy.packagesToMangle.count(package) != 0;
+ bool ShouldMangle(const std::string& package) const {
+ if (package.empty() || policy_.target_package_name == package) {
+ return false;
}
-
- /**
- * 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 std::u16string mangleEntry(const std::u16string& package, const std::u16string& name) {
- return package + u"$" + name;
+ return policy_.packages_to_mangle.count(package) != 0;
+ }
+
+ /**
+ * 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 std::string MangleEntry(const std::string& package,
+ const std::string& name) {
+ return package + "$" + name;
+ }
+
+ /**
+ * Unmangles the name in `outName`, storing the correct name back in `outName`
+ * and the package in `outPackage`. Returns true if the name was unmangled or
+ * false if the name was never mangled to begin with.
+ */
+ static bool Unmangle(std::string* out_name, std::string* out_package) {
+ size_t pivot = out_name->find('$');
+ if (pivot == std::string::npos) {
+ return false;
}
- /**
- * Unmangles the name in `outName`, storing the correct name back in `outName`
- * and the package in `outPackage`. Returns true if the name was unmangled or
- * false if the name was never mangled to begin with.
- */
- static bool unmangle(std::u16string* outName, std::u16string* outPackage) {
- size_t pivot = outName->find(u'$');
- if (pivot == std::string::npos) {
- return false;
- }
-
- outPackage->assign(outName->data(), pivot);
- outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1));
- return true;
- }
+ out_package->assign(out_name->data(), pivot);
+ out_name->assign(out_name->data() + pivot + 1,
+ out_name->size() - (pivot + 1));
+ return true;
+ }
+
+ private:
+ NameManglerPolicy policy_;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_NAME_MANGLER_H
+#endif // AAPT_NAME_MANGLER_H
diff --git a/tools/aapt2/NameMangler_test.cpp b/tools/aapt2/NameMangler_test.cpp
index 6103655c15e0..bc89b5c3fb43 100644
--- a/tools/aapt2/NameMangler_test.cpp
+++ b/tools/aapt2/NameMangler_test.cpp
@@ -16,30 +16,32 @@
#include "NameMangler.h"
-#include <gtest/gtest.h>
#include <string>
+#include "test/Test.h"
+
namespace aapt {
TEST(NameManglerTest, MangleName) {
- std::u16string package = u"android.appcompat";
- std::u16string name = u"Platform.AppCompat";
+ std::string package = "android.appcompat";
+ std::string name = "Platform.AppCompat";
- NameMangler::mangle(package, &name);
- EXPECT_EQ(name, u"android.appcompat$Platform.AppCompat");
+ std::string mangled_name = NameMangler::MangleEntry(package, name);
+ EXPECT_EQ(mangled_name, "android.appcompat$Platform.AppCompat");
- std::u16string newPackage;
- ASSERT_TRUE(NameMangler::unmangle(&name, &newPackage));
- EXPECT_EQ(name, u"Platform.AppCompat");
- EXPECT_EQ(newPackage, u"android.appcompat");
+ std::string unmangled_package;
+ std::string unmangled_name = mangled_name;
+ ASSERT_TRUE(NameMangler::Unmangle(&unmangled_name, &unmangled_package));
+ EXPECT_EQ(unmangled_name, "Platform.AppCompat");
+ EXPECT_EQ(unmangled_package, "android.appcompat");
}
TEST(NameManglerTest, IgnoreUnmangledName) {
- std::u16string package;
- std::u16string name = u"foo_bar";
+ std::string package;
+ std::string name = "foo_bar";
- EXPECT_FALSE(NameMangler::unmangle(&name, &package));
- EXPECT_EQ(name, u"foo_bar");
+ EXPECT_FALSE(NameMangler::Unmangle(&name, &package));
+ EXPECT_EQ(name, "foo_bar");
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index 9328b697719d..3eef7aa71a92 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -15,82 +15,107 @@
*/
#include "Resource.h"
-#include "util/StringPiece.h"
#include <map>
#include <string>
namespace aapt {
-StringPiece16 toString(ResourceType type) {
- switch (type) {
- case ResourceType::kAnim: return u"anim";
- case ResourceType::kAnimator: return u"animator";
- case ResourceType::kArray: return u"array";
- case ResourceType::kAttr: 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";
- case ResourceType::kDrawable: return u"drawable";
- case ResourceType::kFraction: return u"fraction";
- case ResourceType::kId: return u"id";
- case ResourceType::kInteger: return u"integer";
- case ResourceType::kInterpolator: return u"interpolator";
- case ResourceType::kLayout: return u"layout";
- case ResourceType::kMenu: return u"menu";
- case ResourceType::kMipmap: return u"mipmap";
- case ResourceType::kPlurals: return u"plurals";
- case ResourceType::kRaw: return u"raw";
- case ResourceType::kString: return u"string";
- case ResourceType::kStyle: return u"style";
- case ResourceType::kStyleable: return u"styleable";
- case ResourceType::kTransition: return u"transition";
- case ResourceType::kXml: return u"xml";
- }
- return {};
+StringPiece ToString(ResourceType type) {
+ switch (type) {
+ case ResourceType::kAnim:
+ return "anim";
+ case ResourceType::kAnimator:
+ return "animator";
+ case ResourceType::kArray:
+ return "array";
+ case ResourceType::kAttr:
+ return "attr";
+ case ResourceType::kAttrPrivate:
+ return "^attr-private";
+ case ResourceType::kBool:
+ return "bool";
+ case ResourceType::kColor:
+ return "color";
+ case ResourceType::kDimen:
+ return "dimen";
+ case ResourceType::kDrawable:
+ return "drawable";
+ case ResourceType::kFont:
+ return "font";
+ case ResourceType::kFraction:
+ return "fraction";
+ case ResourceType::kId:
+ return "id";
+ case ResourceType::kInteger:
+ return "integer";
+ case ResourceType::kInterpolator:
+ return "interpolator";
+ case ResourceType::kLayout:
+ return "layout";
+ case ResourceType::kMenu:
+ return "menu";
+ case ResourceType::kMipmap:
+ return "mipmap";
+ case ResourceType::kPlurals:
+ return "plurals";
+ case ResourceType::kRaw:
+ return "raw";
+ case ResourceType::kString:
+ return "string";
+ case ResourceType::kStyle:
+ return "style";
+ case ResourceType::kStyleable:
+ return "styleable";
+ case ResourceType::kTransition:
+ return "transition";
+ case ResourceType::kXml:
+ return "xml";
+ }
+ return {};
}
-static const std::map<StringPiece16, ResourceType> sResourceTypeMap {
- { u"anim", ResourceType::kAnim },
- { u"animator", ResourceType::kAnimator },
- { u"array", ResourceType::kArray },
- { u"attr", ResourceType::kAttr },
- { u"^attr-private", ResourceType::kAttrPrivate },
- { u"bool", ResourceType::kBool },
- { u"color", ResourceType::kColor },
- { u"dimen", ResourceType::kDimen },
- { u"drawable", ResourceType::kDrawable },
- { u"fraction", ResourceType::kFraction },
- { u"id", ResourceType::kId },
- { u"integer", ResourceType::kInteger },
- { u"interpolator", ResourceType::kInterpolator },
- { u"layout", ResourceType::kLayout },
- { u"menu", ResourceType::kMenu },
- { u"mipmap", ResourceType::kMipmap },
- { u"plurals", ResourceType::kPlurals },
- { u"raw", ResourceType::kRaw },
- { u"string", ResourceType::kString },
- { u"style", ResourceType::kStyle },
- { u"styleable", ResourceType::kStyleable },
- { u"transition", ResourceType::kTransition },
- { u"xml", ResourceType::kXml },
+static const std::map<StringPiece, ResourceType> sResourceTypeMap{
+ {"anim", ResourceType::kAnim},
+ {"animator", ResourceType::kAnimator},
+ {"array", ResourceType::kArray},
+ {"attr", ResourceType::kAttr},
+ {"^attr-private", ResourceType::kAttrPrivate},
+ {"bool", ResourceType::kBool},
+ {"color", ResourceType::kColor},
+ {"dimen", ResourceType::kDimen},
+ {"drawable", ResourceType::kDrawable},
+ {"font", ResourceType::kFont},
+ {"fraction", ResourceType::kFraction},
+ {"id", ResourceType::kId},
+ {"integer", ResourceType::kInteger},
+ {"interpolator", ResourceType::kInterpolator},
+ {"layout", ResourceType::kLayout},
+ {"menu", ResourceType::kMenu},
+ {"mipmap", ResourceType::kMipmap},
+ {"plurals", ResourceType::kPlurals},
+ {"raw", ResourceType::kRaw},
+ {"string", ResourceType::kString},
+ {"style", ResourceType::kStyle},
+ {"styleable", ResourceType::kStyleable},
+ {"transition", ResourceType::kTransition},
+ {"xml", ResourceType::kXml},
};
-const ResourceType* parseResourceType(const StringPiece16& str) {
- auto iter = sResourceTypeMap.find(str);
- if (iter == std::end(sResourceTypeMap)) {
- return nullptr;
- }
- return &iter->second;
+const ResourceType* ParseResourceType(const StringPiece& str) {
+ auto iter = sResourceTypeMap.find(str);
+ if (iter == std::end(sResourceTypeMap)) {
+ return nullptr;
+ }
+ return &iter->second;
}
bool operator<(const ResourceKey& a, const ResourceKey& b) {
- return std::tie(a.name, a.config) < std::tie(b.name, b.config);
+ 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);
+ return std::tie(a.name, a.config) < std::tie(b.name, b.config);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 9126b9592faa..13330b548d2c 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -17,17 +17,19 @@
#ifndef AAPT_RESOURCE_H
#define AAPT_RESOURCE_H
-#include "ConfigDescription.h"
-#include "Source.h"
-
-#include "util/StringPiece.h"
-
#include <iomanip>
#include <limits>
+#include <sstream>
#include <string>
#include <tuple>
#include <vector>
+#include "utils/JenkinsHash.h"
+
+#include "ConfigDescription.h"
+#include "Source.h"
+#include "util/StringPiece.h"
+
namespace aapt {
/**
@@ -35,53 +37,56 @@ namespace aapt {
* to the 'type' in package:type/entry.
*/
enum class ResourceType {
- kAnim,
- kAnimator,
- kArray,
- kAttr,
- kAttrPrivate,
- kBool,
- kColor,
- kDimen,
- kDrawable,
- kFraction,
- kId,
- kInteger,
- kInterpolator,
- kLayout,
- kMenu,
- kMipmap,
- kPlurals,
- kRaw,
- kString,
- kStyle,
- kStyleable,
- kTransition,
- kXml,
+ kAnim,
+ kAnimator,
+ kArray,
+ kAttr,
+ kAttrPrivate,
+ kBool,
+ kColor,
+ kDimen,
+ kDrawable,
+ kFont,
+ kFraction,
+ kId,
+ kInteger,
+ kInterpolator,
+ kLayout,
+ kMenu,
+ kMipmap,
+ kPlurals,
+ kRaw,
+ kString,
+ kStyle,
+ kStyleable,
+ kTransition,
+ kXml,
};
-StringPiece16 toString(ResourceType type);
+StringPiece ToString(ResourceType type);
/**
* Returns a pointer to a valid ResourceType, or nullptr if
* the string was invalid.
*/
-const ResourceType* parseResourceType(const StringPiece16& str);
+const ResourceType* ParseResourceType(const StringPiece& str);
/**
* A resource's name. This can uniquely identify
* a resource in the ResourceTable.
*/
struct ResourceName {
- std::u16string package;
- ResourceType type;
- std::u16string entry;
+ std::string package;
+ ResourceType type = ResourceType::kRaw;
+ std::string entry;
+
+ ResourceName() = default;
+ ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e);
- ResourceName() : type(ResourceType::kRaw) {}
- ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e);
+ int compare(const ResourceName& other) const;
- bool isValid() const;
- std::u16string toString() const;
+ bool is_valid() const;
+ std::string ToString() const;
};
/**
@@ -91,21 +96,21 @@ struct ResourceName {
* of the original string.
*/
struct ResourceNameRef {
- StringPiece16 package;
- ResourceType type;
- StringPiece16 entry;
-
- ResourceNameRef() = default;
- ResourceNameRef(const ResourceNameRef&) = default;
- ResourceNameRef(ResourceNameRef&&) = default;
- ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit)
- ResourceNameRef(const StringPiece16& p, ResourceType t, const StringPiece16& e);
- ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
- ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
- ResourceNameRef& operator=(const ResourceName& rhs);
-
- ResourceName toResourceName() const;
- bool isValid() const;
+ StringPiece package;
+ ResourceType type = ResourceType::kRaw;
+ StringPiece entry;
+
+ ResourceNameRef() = default;
+ ResourceNameRef(const ResourceNameRef&) = default;
+ ResourceNameRef(ResourceNameRef&&) = default;
+ ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit)
+ ResourceNameRef(const StringPiece& p, ResourceType t, const StringPiece& e);
+ ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
+ ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
+ ResourceNameRef& operator=(const ResourceName& rhs);
+
+ ResourceName ToResourceName() const;
+ bool is_valid() const;
};
/**
@@ -120,64 +125,66 @@ struct ResourceNameRef {
* EEEE: 16 bit entry identifier.
*/
struct ResourceId {
- uint32_t id;
+ uint32_t id;
- ResourceId();
- ResourceId(const ResourceId& rhs);
- ResourceId(uint32_t resId); // NOLINT(implicit)
- ResourceId(uint8_t p, uint8_t t, uint16_t e);
+ ResourceId();
+ ResourceId(const ResourceId& rhs);
+ ResourceId(uint32_t res_id); // NOLINT(implicit)
+ 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 is_valid() const;
+ uint8_t package_id() const;
+ uint8_t type_id() const;
+ uint16_t entry_id() const;
};
struct SourcedResourceName {
- ResourceName name;
- size_t line;
+ ResourceName name;
+ size_t line;
};
struct ResourceFile {
- // Name
- ResourceName name;
+ // Name
+ ResourceName name;
- // Configuration
- ConfigDescription config;
+ // Configuration
+ ConfigDescription config;
- // Source
- Source source;
+ // Source
+ Source source;
- // Exported symbols
- std::vector<SourcedResourceName> exportedSymbols;
+ // Exported symbols
+ std::vector<SourcedResourceName> exported_symbols;
};
/**
- * Useful struct used as a key to represent a unique resource in associative containers.
+ * Useful struct used as a key to represent a unique resource in associative
+ * containers.
*/
struct ResourceKey {
- ResourceName name;
- ConfigDescription config;
+ 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.
+ * 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;
+ ResourceNameRef name;
+ ConfigDescription config;
- ResourceKeyRef() = default;
- ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c) : name(n), config(c) {
- }
+ 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;
+ /**
+ * 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);
@@ -186,173 +193,194 @@ bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b);
// ResourceId implementation.
//
-inline ResourceId::ResourceId() : id(0) {
-}
+inline ResourceId::ResourceId() : id(0) {}
-inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {
-}
+inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {}
-inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
-}
+inline ResourceId::ResourceId(uint32_t res_id) : id(res_id) {}
-inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) {
-}
+inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e)
+ : id((p << 24) | (t << 16) | e) {}
-inline bool ResourceId::isValid() const {
- return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
+inline bool ResourceId::is_valid() const {
+ return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
}
-inline uint8_t ResourceId::packageId() const {
- return static_cast<uint8_t>(id >> 24);
+inline uint8_t ResourceId::package_id() const {
+ return static_cast<uint8_t>(id >> 24);
}
-inline uint8_t ResourceId::typeId() const {
- return static_cast<uint8_t>(id >> 16);
+inline uint8_t ResourceId::type_id() const {
+ return static_cast<uint8_t>(id >> 16);
}
-inline uint16_t ResourceId::entryId() const {
- return static_cast<uint16_t>(id);
+inline uint16_t ResourceId::entry_id() const {
+ return static_cast<uint16_t>(id);
}
inline bool operator<(const ResourceId& lhs, const ResourceId& rhs) {
- return lhs.id < rhs.id;
+ return lhs.id < rhs.id;
}
inline bool operator>(const ResourceId& lhs, const ResourceId& rhs) {
- return lhs.id > rhs.id;
+ return lhs.id > rhs.id;
}
inline bool operator==(const ResourceId& lhs, const ResourceId& rhs) {
- return lhs.id == rhs.id;
+ return lhs.id == rhs.id;
}
inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) {
- return lhs.id != rhs.id;
+ 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)
- << std::hex << resId.id;
- out.flags(oldFlags);
- out.fill(oldFill);
- return out;
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const ResourceId& res_id) {
+ std::ios_base::fmtflags old_flags = out.flags();
+ char old_fill = out.fill();
+ out << "0x" << std::internal << std::setfill('0') << std::setw(8) << std::hex
+ << res_id.id;
+ out.flags(old_flags);
+ out.fill(old_fill);
+ return out;
}
//
// ResourceType implementation.
//
-inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) {
- return out << toString(val);
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const ResourceType& val) {
+ return out << ToString(val);
}
//
// ResourceName implementation.
//
-inline ResourceName::ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e) :
- package(p.toString()), type(t), entry(e.toString()) {
+inline ResourceName::ResourceName(const StringPiece& p, ResourceType t,
+ const StringPiece& e)
+ : package(p.ToString()), type(t), entry(e.ToString()) {}
+
+inline int ResourceName::compare(const ResourceName& other) const {
+ int cmp = package.compare(other.package);
+ if (cmp != 0) return cmp;
+ cmp = static_cast<int>(type) - static_cast<int>(other.type);
+ if (cmp != 0) return cmp;
+ cmp = entry.compare(other.entry);
+ return cmp;
}
-inline bool ResourceName::isValid() const {
- return !package.empty() && !entry.empty();
+inline bool ResourceName::is_valid() const {
+ return !package.empty() && !entry.empty();
}
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);
+ return std::tie(lhs.package, lhs.type, lhs.entry) <
+ std::tie(rhs.package, rhs.type, rhs.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);
+ return std::tie(lhs.package, lhs.type, lhs.entry) ==
+ std::tie(rhs.package, rhs.type, rhs.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);
+ 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 << ":";
+ }
+ return out << name.type << "/" << name.entry;
}
-inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
- if (!name.package.empty()) {
- out << name.package << ":";
- }
- return out << name.type << "/" << name.entry;
+inline std::string ResourceName::ToString() const {
+ std::stringstream stream;
+ stream << *this;
+ return stream.str();
}
-
//
// ResourceNameRef implementation.
//
-inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) :
- package(rhs.package), type(rhs.type), entry(rhs.entry) {
-}
+inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs)
+ : package(rhs.package), type(rhs.type), entry(rhs.entry) {}
-inline ResourceNameRef::ResourceNameRef(const StringPiece16& p, ResourceType t,
- const StringPiece16& e) :
- package(p), type(t), entry(e) {
-}
+inline ResourceNameRef::ResourceNameRef(const StringPiece& p, ResourceType t,
+ const StringPiece& e)
+ : package(p), type(t), entry(e) {}
inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) {
- package = rhs.package;
- type = rhs.type;
- entry = rhs.entry;
- return *this;
+ package = rhs.package;
+ type = rhs.type;
+ entry = rhs.entry;
+ return *this;
}
-inline ResourceName ResourceNameRef::toResourceName() const {
- return { package.toString(), type, entry.toString() };
+inline ResourceName ResourceNameRef::ToResourceName() const {
+ return ResourceName(package, type, entry);
}
-inline bool ResourceNameRef::isValid() const {
- return !package.empty() && !entry.empty();
+inline bool ResourceNameRef::is_valid() const {
+ return !package.empty() && !entry.empty();
}
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);
+ return std::tie(lhs.package, lhs.type, lhs.entry) <
+ std::tie(rhs.package, rhs.type, rhs.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);
+ return std::tie(lhs.package, lhs.type, lhs.entry) ==
+ std::tie(rhs.package, rhs.type, rhs.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);
+ return std::tie(lhs.package, lhs.type, lhs.entry) !=
+ std::tie(rhs.package, rhs.type, rhs.entry);
}
-inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) {
- if (!name.package.empty()) {
- out << name.package << ":";
- }
- return out << name.type << "/" << name.entry;
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const ResourceNameRef& name) {
+ if (!name.package.empty()) {
+ out << name.package << ":";
+ }
+ return out << name.type << "/" << name.entry;
}
inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) {
- return ResourceNameRef(lhs) < b;
+ return ResourceNameRef(lhs) < b;
}
inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) {
- return ResourceNameRef(lhs) != rhs;
+ return ResourceNameRef(lhs) != rhs;
}
-inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) {
- return lhs.name == rhs.name && lhs.line == rhs.line;
+inline bool operator==(const SourcedResourceName& lhs,
+ const SourcedResourceName& rhs) {
+ return lhs.name == rhs.name && lhs.line == rhs.line;
}
-} // namespace aapt
+} // namespace aapt
+
+namespace std {
+
+template <>
+struct hash<aapt::ResourceName> {
+ size_t operator()(const aapt::ResourceName& name) const {
+ android::hash_t h = 0;
+ h = android::JenkinsHashMix(h, hash<string>()(name.package));
+ h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type));
+ h = android::JenkinsHashMix(h, hash<string>()(name.entry));
+ return static_cast<size_t>(h);
+ }
+};
+
+} // namespace std
-#endif // AAPT_RESOURCE_H
+#endif // AAPT_RESOURCE_H
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index a84c306e2733..b16def4025ff 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -15,6 +15,12 @@
*/
#include "ResourceParser.h"
+
+#include <functional>
+#include <sstream>
+
+#include "android-base/logging.h"
+
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
@@ -23,450 +29,492 @@
#include "util/Util.h"
#include "xml/XmlPullParser.h"
-#include <functional>
-#include <sstream>
-
namespace aapt {
-constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
+constexpr const char* sXliffNamespaceUri =
+ "urn:oasis:names:tc:xliff:document:1.2";
/**
- * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
+ * Returns true if the element is <skip> or <eat-comment> and can be safely
+ * ignored.
*/
-static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) {
- return ns.empty() && (name == u"skip" || name == u"eat-comment");
+static bool ShouldIgnoreElement(const StringPiece& ns,
+ const StringPiece& name) {
+ return ns.empty() && (name == "skip" || name == "eat-comment");
}
-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;
+static uint32_t ParseFormatType(const StringPiece& piece) {
+ if (piece == "reference")
+ return android::ResTable_map::TYPE_REFERENCE;
+ else if (piece == "string")
+ return android::ResTable_map::TYPE_STRING;
+ else if (piece == "integer")
+ return android::ResTable_map::TYPE_INTEGER;
+ else if (piece == "boolean")
+ return android::ResTable_map::TYPE_BOOLEAN;
+ else if (piece == "color")
+ return android::ResTable_map::TYPE_COLOR;
+ else if (piece == "float")
+ return android::ResTable_map::TYPE_FLOAT;
+ else if (piece == "dimension")
+ return android::ResTable_map::TYPE_DIMENSION;
+ else if (piece == "fraction")
+ return android::ResTable_map::TYPE_FRACTION;
+ else if (piece == "enum")
+ return android::ResTable_map::TYPE_ENUM;
+ else if (piece == "flags")
+ return android::ResTable_map::TYPE_FLAGS;
+ return 0;
}
-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;
- }
- mask |= type;
- }
- return mask;
+static uint32_t ParseFormatAttribute(const StringPiece& str) {
+ uint32_t mask = 0;
+ for (StringPiece part : util::Tokenize(str, '|')) {
+ StringPiece trimmed_part = util::TrimWhitespace(part);
+ uint32_t type = ParseFormatType(trimmed_part);
+ if (type == 0) {
+ return 0;
+ }
+ mask |= type;
+ }
+ return mask;
}
/**
* 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;
+ ResourceName name;
+ ConfigDescription config;
+ std::string product;
+ Source source;
+ ResourceId id;
+ Maybe<SymbolState> symbol_state;
+ std::string comment;
+ std::unique_ptr<Value> value;
+ std::list<ParsedResource> child_resources;
};
// 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 (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 (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 (!table->addResource(res->name, res->id, res->config, res->product,
- std::move(res->value), diag)) {
- return false;
- }
- }
-
- bool error = false;
- for (ParsedResource& child : res->childResources) {
- error |= !addResourcesToTable(table, diag, &child);
- }
- return !error;
+static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag,
+ ParsedResource* res) {
+ StringPiece trimmed_comment = util::TrimWhitespace(res->comment);
+ if (trimmed_comment.size() != res->comment.size()) {
+ // Only if there was a change do we re-assign.
+ res->comment = trimmed_comment.ToString();
+ }
+
+ if (res->symbol_state) {
+ Symbol symbol;
+ symbol.state = res->symbol_state.value();
+ symbol.source = res->source;
+ symbol.comment = res->comment;
+ if (!table->SetSymbolState(res->name, res->id, symbol, diag)) {
+ return false;
+ }
+ }
+
+ 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 (!table->AddResource(res->name, res->id, res->config, res->product,
+ std::move(res->value), diag)) {
+ return false;
+ }
+ }
+
+ bool error = false;
+ for (ParsedResource& child : res->child_resources) {
+ error |= !AddResourcesToTable(table, diag, &child);
+ }
+ return !error;
}
// Convenient aliases for more readable function calls.
-enum {
- kAllowRawString = true,
- kNoRawString = false
-};
+enum { kAllowRawString = true, kNoRawString = false };
-ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
+ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table,
+ const Source& source,
const ConfigDescription& config,
- const ResourceParserOptions& options) :
- mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) {
-}
+ const ResourceParserOptions& options)
+ : diag_(diag),
+ table_(table),
+ source_(source),
+ config_(config),
+ options_(options) {}
/**
* Build a string from XML that converts nested elements into Span objects.
*/
-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 (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;
- }
-
- spanStack.back().lastChar = builder.str().size() - 1;
- outStyleString->spans.push_back(spanStack.back());
- spanStack.pop_back();
-
- } else if (event == xml::XmlPullParser::Event::kText) {
- outRawString->append(parser->getText());
- builder.append(parser->getText());
-
- } 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++;
-
- // Build a span object out of the nested element.
- std::u16string spanName = parser->getElementName();
- const auto endAttrIter = parser->endAttributes();
- for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
- spanName += u";";
- spanName += attrIter->name;
- spanName += u"=";
- spanName += attrIter->value;
- }
-
- if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
- 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()) });
- }
-
- } else if (event == xml::XmlPullParser::Event::kComment) {
- // Skip
- } else {
- assert(false);
+bool ResourceParser::FlattenXmlSubtree(xml::XmlPullParser* parser,
+ std::string* out_raw_string,
+ StyleString* out_style_string) {
+ std::vector<Span> span_stack;
+
+ bool error = false;
+ out_raw_string->clear();
+ out_style_string->spans.clear();
+ util::StringBuilder builder;
+ size_t depth = 1;
+ while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
+ const xml::XmlPullParser::Event event = parser->event();
+ if (event == xml::XmlPullParser::Event::kEndElement) {
+ if (!parser->element_namespace().empty()) {
+ // We already warned and skipped the start element, so just skip here
+ // too
+ continue;
+ }
+
+ depth--;
+ if (depth == 0) {
+ break;
+ }
+
+ span_stack.back().last_char = builder.Utf16Len() - 1;
+ out_style_string->spans.push_back(span_stack.back());
+ span_stack.pop_back();
+
+ } else if (event == xml::XmlPullParser::Event::kText) {
+ out_raw_string->append(parser->text());
+ builder.Append(parser->text());
+
+ } else if (event == xml::XmlPullParser::Event::kStartElement) {
+ if (!parser->element_namespace().empty()) {
+ if (parser->element_namespace() != sXliffNamespaceUri) {
+ // Only warn if this isn't an xliff namespace.
+ diag_->Warn(DiagMessage(source_.WithLine(parser->line_number()))
+ << "skipping element '" << parser->element_name()
+ << "' with unknown namespace '"
+ << parser->element_namespace() << "'");
}
+ continue;
+ }
+ depth++;
+
+ // Build a span object out of the nested element.
+ std::string span_name = parser->element_name();
+ const auto end_attr_iter = parser->end_attributes();
+ for (auto attr_iter = parser->begin_attributes();
+ attr_iter != end_attr_iter; ++attr_iter) {
+ span_name += ";";
+ span_name += attr_iter->name;
+ span_name += "=";
+ span_name += attr_iter->value;
+ }
+
+ if (builder.Utf16Len() > std::numeric_limits<uint32_t>::max()) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "style string '" << builder.ToString()
+ << "' is too long");
+ error = true;
+ } else {
+ span_stack.push_back(
+ Span{span_name, static_cast<uint32_t>(builder.Utf16Len())});
+ }
+
+ } else if (event == xml::XmlPullParser::Event::kComment) {
+ // Skip
+ } else {
+ LOG(FATAL) << "unhandled XML event";
}
- assert(spanStack.empty() && "spans haven't been fully processed");
+ }
+ CHECK(span_stack.empty()) << "spans haven't been fully processed";
- outStyleString->str = builder.str();
- return !error;
+ out_style_string->str = builder.ToString();
+ return !error;
}
-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;
- }
+bool ResourceParser::Parse(xml::XmlPullParser* parser) {
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip comments and text.
+ continue;
+ }
- if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "root element must be <resources>");
- return false;
- }
+ if (!parser->element_namespace().empty() ||
+ parser->element_name() != "resources") {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "root element must be <resources>");
+ return false;
+ }
- error |= !parseResources(parser);
- break;
- };
+ error |= !ParseResources(parser);
+ break;
+ };
- if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "xml parser error: " << parser->getLastError());
- return false;
- }
- return !error;
+ if (parser->event() == xml::XmlPullParser::Event::kBadDocument) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "xml parser error: " << parser->error());
+ return false;
+ }
+ return !error;
}
-bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
- std::set<ResourceName> strippedResources;
-
- bool error = false;
- std::u16string comment;
- 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 == xml::XmlPullParser::Event::kText) {
- if (!util::trimWhitespace(parser->getText()).empty()) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "plain text not allowed here");
- error = true;
- }
- continue;
- }
+bool ResourceParser::ParseResources(xml::XmlPullParser* parser) {
+ std::set<ResourceName> stripped_resources;
- assert(event == xml::XmlPullParser::Event::kStartElement);
+ bool error = false;
+ std::string comment;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ const xml::XmlPullParser::Event event = parser->event();
+ if (event == xml::XmlPullParser::Event::kComment) {
+ comment = parser->comment();
+ continue;
+ }
- if (!parser->getElementNamespace().empty()) {
- // Skip unknown namespace.
- continue;
- }
+ if (event == xml::XmlPullParser::Event::kText) {
+ if (!util::TrimWhitespace(parser->text()).empty()) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "plain text not allowed here");
+ error = true;
+ }
+ continue;
+ }
- std::u16string elementName = parser->getElementName();
- if (elementName == u"skip" || elementName == u"eat-comment") {
- comment = u"";
- continue;
- }
+ CHECK(event == xml::XmlPullParser::Event::kStartElement);
- ParsedResource parsedResource;
- parsedResource.config = mConfig;
- parsedResource.source = mSource.withLine(parser->getLineNumber());
- parsedResource.comment = std::move(comment);
+ if (!parser->element_namespace().empty()) {
+ // Skip unknown namespace.
+ continue;
+ }
- // Extract the product name if it exists.
- if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) {
- parsedResource.product = util::utf16ToUtf8(maybeProduct.value());
- }
+ std::string element_name = parser->element_name();
+ if (element_name == "skip" || element_name == "eat-comment") {
+ comment = "";
+ continue;
+ }
- // Parse the resource regardless of product.
- if (!parseResource(parser, &parsedResource)) {
- error = true;
- continue;
- }
+ ParsedResource parsed_resource;
+ parsed_resource.config = config_;
+ parsed_resource.source = source_.WithLine(parser->line_number());
+ parsed_resource.comment = std::move(comment);
- if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
- error = true;
- }
+ // Extract the product name if it exists.
+ if (Maybe<StringPiece> maybe_product =
+ xml::FindNonEmptyAttribute(parser, "product")) {
+ parsed_resource.product = maybe_product.value().ToString();
}
- // 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;
- }
+ // Parse the resource regardless of product.
+ if (!ParseResource(parser, &parsed_resource)) {
+ error = true;
+ continue;
}
- return !error;
-}
+ if (!AddResourcesToTable(table_, diag_, &parsed_resource)) {
+ error = true;
+ }
+ }
+ // Check that we included at least one variant of each stripped resource.
+ for (const ResourceName& stripped_resource : stripped_resources) {
+ if (!table_->FindResource(stripped_resource)) {
+ // Failed to find the resource.
+ diag_->Error(DiagMessage(source_)
+ << "resource '" << stripped_resource
+ << "' "
+ "was filtered out but no product variant remains");
+ error = true;
+ }
+ }
-bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) {
- struct ItemTypeFormat {
- ResourceType type;
- uint32_t format;
- };
-
- 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;
- }
+ return !error;
+}
- 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;
- }
- }
+bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ struct ItemTypeFormat {
+ ResourceType type;
+ uint32_t format;
+ };
+
+ using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*,
+ ParsedResource*)>;
+
+ static const auto elToItemMap =
+ ImmutableMap<std::string, ItemTypeFormat>::CreatePreSorted({
+ {"bool", {ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN}},
+ {"color", {ResourceType::kColor, android::ResTable_map::TYPE_COLOR}},
+ {"dimen",
+ {ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT |
+ android::ResTable_map::TYPE_FRACTION |
+ android::ResTable_map::TYPE_DIMENSION}},
+ {"drawable",
+ {ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR}},
+ {"fraction",
+ {ResourceType::kFraction,
+ android::ResTable_map::TYPE_FLOAT |
+ android::ResTable_map::TYPE_FRACTION |
+ android::ResTable_map::TYPE_DIMENSION}},
+ {"integer",
+ {ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER}},
+ {"string",
+ {ResourceType::kString, android::ResTable_map::TYPE_STRING}},
+ });
+
+ static const auto elToBagMap =
+ ImmutableMap<std::string, BagParseFunc>::CreatePreSorted({
+ {"add-resource", std::mem_fn(&ResourceParser::ParseAddResource)},
+ {"array", std::mem_fn(&ResourceParser::ParseArray)},
+ {"attr", std::mem_fn(&ResourceParser::ParseAttr)},
+ {"declare-styleable",
+ std::mem_fn(&ResourceParser::ParseDeclareStyleable)},
+ {"integer-array", std::mem_fn(&ResourceParser::ParseIntegerArray)},
+ {"java-symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
+ {"plurals", std::mem_fn(&ResourceParser::ParsePlural)},
+ {"public", std::mem_fn(&ResourceParser::ParsePublic)},
+ {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
+ {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
+ {"style", std::mem_fn(&ResourceParser::ParseStyle)},
+ {"symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
+ });
+
+ std::string resource_type = parser->element_name();
+
+ // The value format accepted for this resource.
+ uint32_t resource_format = 0u;
+
+ if (resource_type == "item") {
+ // Items have their type encoded in the type attribute.
+ if (Maybe<StringPiece> maybe_type =
+ xml::FindNonEmptyAttribute(parser, "type")) {
+ resource_type = maybe_type.value().ToString();
+ } else {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "<item> must have a 'type' attribute");
+ return false;
+ }
+
+ if (Maybe<StringPiece> maybe_format =
+ xml::FindNonEmptyAttribute(parser, "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.
+ resource_format = ParseFormatType(maybe_format.value());
+ if (!resource_format) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "'" << maybe_format.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");
+ // Get the name of the resource. This will be checked later, because not all
+ // XML elements require a name.
+ Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "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;
+ if (resource_type == "id") {
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> missing 'name' attribute");
+ return false;
}
- const auto itemIter = elToItemMap.find(resourceType);
- if (itemIter != elToItemMap.end()) {
- // This is an item, record its type and format and start parsing.
+ out_resource->name.type = ResourceType::kId;
+ out_resource->name.entry = maybe_name.value().ToString();
+ out_resource->value = util::make_unique<Id>();
+ return true;
+ }
- if (!maybeName) {
- mDiag->error(DiagMessage(outResource->source)
- << "<" << parser->getElementName() << "> missing 'name' attribute");
- return false;
- }
+ const auto item_iter = elToItemMap.find(resource_type);
+ if (item_iter != elToItemMap.end()) {
+ // This is an item, record its type and format and start parsing.
- outResource->name.type = itemIter->second.type;
- outResource->name.entry = maybeName.value().toString();
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> missing 'name' attribute");
+ return false;
+ }
- // Only use the implicit format for this type if it wasn't overridden.
- if (!resourceFormat) {
- resourceFormat = itemIter->second.format;
- }
+ out_resource->name.type = item_iter->second.type;
+ out_resource->name.entry = maybe_name.value().ToString();
- if (!parseItem(parser, outResource, resourceFormat)) {
- return false;
- }
- return true;
+ // Only use the implicit format for this type if it wasn't overridden.
+ if (!resource_format) {
+ resource_format = item_iter->second.format;
}
- // 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;
+ if (!ParseItem(parser, out_resource, resource_format)) {
+ return false;
}
+ return true;
+ }
+
+ // This might be a bag or something.
+ const auto bag_iter = elToBagMap.find(resource_type);
+ if (bag_iter != elToBagMap.end()) {
+ // Ensure we have a name (unless this is a <public-group>).
+ if (resource_type != "public-group") {
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> missing 'name' attribute");
+ return false;
+ }
- // 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;
- }
+ out_resource->name.entry = maybe_name.value().ToString();
+ }
- 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;
+ // Call the associated parse method. The type will be filled in by the
+ // parse func.
+ if (!bag_iter->second(this, parser, out_resource)) {
+ 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* parsed_type = ParseResourceType(resource_type);
+ if (parsed_type) {
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> missing 'name' attribute");
+ return false;
+ }
+
+ out_resource->name.type = *parsed_type;
+ out_resource->name.entry = maybe_name.value().ToString();
+ out_resource->value =
+ ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
+ if (!out_resource->value) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid value for type '" << *parsed_type
+ << "'. Expected a reference");
+ return false;
}
+ return true;
+ }
- mDiag->warn(DiagMessage(outResource->source)
- << "unknown resource type '" << parser->getElementName() << "'");
- return false;
+ diag_->Warn(DiagMessage(out_resource->source)
+ << "unknown resource type '" << parser->element_name() << "'");
+ return false;
}
-bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource,
+bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
+ ParsedResource* out_resource,
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;
+ if (format == android::ResTable_map::TYPE_STRING) {
+ return ParseString(parser, out_resource);
+ }
+
+ out_resource->value = ParseXml(parser, format, kNoRawString);
+ if (!out_resource->value) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid "
+ << out_resource->name.type);
+ return false;
+ }
+ return true;
}
/**
@@ -476,795 +524,839 @@ bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outRe
* an Item. If allowRawValue is false, nullptr is returned in this
* case.
*/
-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;
- StyleString styleString;
- if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
- return {};
- }
-
- if (!styleString.spans.empty()) {
- // This can only be a StyledString.
- return util::make_unique<StyledString>(
- 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.
- 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 = ResourceUtils::parseItemForAttribute(rawValue, typeMask,
- onCreateReference);
- if (processedItem) {
- // Fix up the reference.
- if (Reference* ref = valueCast<Reference>(processedItem.get())) {
- transformReferenceFromNamespace(parser, u"", ref);
- }
- return processedItem;
- }
-
- // Try making a regular string.
- if (typeMask & android::ResTable_map::TYPE_STRING) {
- // Use the trimmed, escaped string.
- return util::make_unique<String>(
- mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
- }
-
- if (allowRawValue) {
- // We can't parse this so return a RawString if we are allowed.
- return util::make_unique<RawString>(
- mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
- }
+std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
+ const uint32_t type_mask,
+ const bool allow_raw_value) {
+ const size_t begin_xml_line = parser->line_number();
+
+ std::string raw_value;
+ StyleString style_string;
+ if (!FlattenXmlSubtree(parser, &raw_value, &style_string)) {
return {};
+ }
+
+ if (!style_string.spans.empty()) {
+ // This can only be a StyledString.
+ return util::make_unique<StyledString>(table_->string_pool.MakeRef(
+ style_string,
+ StringPool::Context(StringPool::Context::kStylePriority, config_)));
+ }
+
+ auto on_create_reference = [&](const ResourceName& name) {
+ // name.package can be empty here, as it will assume the package name of the
+ // table.
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->SetSource(source_.WithLine(begin_xml_line));
+ table_->AddResource(name, {}, {}, std::move(id), diag_);
+ };
+
+ // Process the raw value.
+ std::unique_ptr<Item> processed_item =
+ ResourceUtils::TryParseItemForAttribute(raw_value, type_mask,
+ on_create_reference);
+ if (processed_item) {
+ // Fix up the reference.
+ if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
+ TransformReferenceFromNamespace(parser, "", ref);
+ }
+ return processed_item;
+ }
+
+ // Try making a regular string.
+ if (type_mask & android::ResTable_map::TYPE_STRING) {
+ // Use the trimmed, escaped string.
+ return util::make_unique<String>(table_->string_pool.MakeRef(
+ style_string.str, StringPool::Context(config_)));
+ }
+
+ if (allow_raw_value) {
+ // We can't parse this so return a RawString if we are allowed.
+ return util::make_unique<RawString>(
+ table_->string_pool.MakeRef(raw_value, StringPool::Context(config_)));
+ }
+ return {};
}
-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;
+bool ResourceParser::ParseString(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ bool formatted = true;
+ if (Maybe<StringPiece> formatted_attr =
+ xml::FindAttribute(parser, "formatted")) {
+ Maybe<bool> maybe_formatted =
+ ResourceUtils::ParseBool(formatted_attr.value());
+ if (!maybe_formatted) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid value for 'formatted'. Must be a boolean");
+ return false;
+ }
+ formatted = maybe_formatted.value();
+ }
+
+ bool translateable = options_.translatable;
+ if (Maybe<StringPiece> translateable_attr =
+ xml::FindAttribute(parser, "translatable")) {
+ Maybe<bool> maybe_translateable =
+ ResourceUtils::ParseBool(translateable_attr.value());
+ if (!maybe_translateable) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid value for 'translatable'. Must be a boolean");
+ return false;
+ }
+ translateable = maybe_translateable.value();
+ }
+
+ out_resource->value =
+ ParseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
+ if (!out_resource->value) {
+ diag_->Error(DiagMessage(out_resource->source) << "not a valid string");
+ return false;
+ }
+
+ if (String* string_value = ValueCast<String>(out_resource->value.get())) {
+ string_value->SetTranslateable(translateable);
+
+ if (formatted && translateable) {
+ if (!util::VerifyJavaStringFormat(*string_value->value)) {
+ DiagMessage msg(out_resource->source);
+ msg << "multiple substitutions specified in non-positional format; "
+ "did you mean to add the formatted=\"false\" attribute?";
+ if (options_.error_on_positional_arguments) {
+ diag_->Error(msg);
+ 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;
- }
+ diag_->Warn(msg);
+ }
}
- outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
- if (!outResource->value) {
- mDiag->error(DiagMessage(outResource->source) << "not a valid string");
- return false;
- }
-
- if (String* stringValue = valueCast<String>(outResource->value.get())) {
- stringValue->setTranslateable(translateable);
-
- 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;
- }
-
- mDiag->warn(msg);
- }
- }
-
- } else if (StyledString* stringValue = valueCast<StyledString>(outResource->value.get())) {
- stringValue->setTranslateable(translateable);
- }
- return true;
+ } else if (StyledString* string_value =
+ ValueCast<StyledString>(out_resource->value.get())) {
+ string_value->SetTranslateable(translateable);
+ }
+ return true;
}
-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(maybeType.value());
- if (!parsedType) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid resource type '" << maybeType.value() << "' in <public>");
- return false;
- }
-
- outResource->name.type = *parsedType;
-
- if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) {
- android::Res_value val;
- bool result = android::ResTable::stringToInt(maybeId.value().data(),
- maybeId.value().size(), &val);
- ResourceId resourceId(val.data);
- if (!result || !resourceId.isValid()) {
- 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.
- outResource->value = util::make_unique<Id>();
- }
+bool ResourceParser::ParsePublic(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
+ if (!maybe_type) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<public> must have a 'type' attribute");
+ return false;
+ }
- outResource->symbolState = SymbolState::kPublic;
- return true;
+ const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
+ if (!parsed_type) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '"
+ << maybe_type.value()
+ << "' in <public>");
+ return false;
+ }
+
+ out_resource->name.type = *parsed_type;
+
+ if (Maybe<StringPiece> maybe_id_str =
+ xml::FindNonEmptyAttribute(parser, "id")) {
+ Maybe<ResourceId> maybe_id =
+ ResourceUtils::ParseResourceId(maybe_id_str.value());
+ if (!maybe_id) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '"
+ << maybe_id.value()
+ << "' in <public>");
+ return false;
+ }
+ out_resource->id = maybe_id.value();
+ }
+
+ if (*parsed_type == ResourceType::kId) {
+ // An ID marked as public is also the definition of an ID.
+ out_resource->value = util::make_unique<Id>();
+ }
+
+ out_resource->symbol_state = SymbolState::kPublic;
+ return true;
}
-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;
- }
-
- 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;
- }
+bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
+ if (!maybe_type) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<public-group> must have a 'type' attribute");
+ 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 ResourceType* parsed_type = ParseResourceType(maybe_type.value());
+ if (!parsed_type) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '"
+ << maybe_type.value()
+ << "' in <public-group>");
+ return 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"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;
- }
- }
- return !error;
+ Maybe<StringPiece> maybe_id_str =
+ xml::FindNonEmptyAttribute(parser, "first-id");
+ if (!maybe_id_str) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<public-group> must have a 'first-id' attribute");
+ return false;
+ }
+
+ Maybe<ResourceId> maybe_id =
+ ResourceUtils::ParseResourceId(maybe_id_str.value());
+ if (!maybe_id) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '"
+ << maybe_id_str.value()
+ << "' in <public-group>");
+ return false;
+ }
+
+ ResourceId next_id = maybe_id.value();
+
+ std::string comment;
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() == xml::XmlPullParser::Event::kComment) {
+ comment = util::TrimWhitespace(parser->comment()).ToString();
+ continue;
+ } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text.
+ continue;
+ }
+
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() && element_name == "public") {
+ Maybe<StringPiece> maybe_name =
+ xml::FindNonEmptyAttribute(parser, "name");
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(item_source)
+ << "<public> must have a 'name' attribute");
+ error = true;
+ continue;
+ }
+
+ if (xml::FindNonEmptyAttribute(parser, "id")) {
+ diag_->Error(DiagMessage(item_source)
+ << "'id' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ if (xml::FindNonEmptyAttribute(parser, "type")) {
+ diag_->Error(DiagMessage(item_source)
+ << "'type' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ ParsedResource child_resource;
+ child_resource.name.type = *parsed_type;
+ child_resource.name.entry = maybe_name.value().ToString();
+ child_resource.id = next_id;
+ child_resource.comment = std::move(comment);
+ child_resource.source = item_source;
+ child_resource.symbol_state = SymbolState::kPublic;
+ out_resource->child_resources.push_back(std::move(child_resource));
+
+ next_id.id += 1;
+
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
+ error = true;
+ }
+ }
+ return !error;
}
-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;
- }
+bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
+ if (!maybe_type) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> 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;
- }
+ const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
+ if (!parsed_type) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid resource type '" << maybe_type.value() << "' in <"
+ << parser->element_name() << ">");
+ return false;
+ }
- outResource->name.type = *parsedType;
- return true;
+ out_resource->name.type = *parsed_type;
+ return true;
}
-bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) {
- if (parseSymbolImpl(parser, outResource)) {
- outResource->symbolState = SymbolState::kPrivate;
- return true;
- }
- return false;
+bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ if (ParseSymbolImpl(parser, out_resource)) {
+ out_resource->symbol_state = 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::ParseAddResource(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ if (ParseSymbolImpl(parser, out_resource)) {
+ out_resource->symbol_state = SymbolState::kUndefined;
+ return true;
+ }
+ return false;
}
-
-bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) {
- return parseAttrImpl(parser, outResource, false);
+bool ResourceParser::ParseAttr(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ return ParseAttrImpl(parser, out_resource, false);
}
-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;
-
- Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format");
- if (maybeFormat) {
- typeMask = parseFormatAttribute(maybeFormat.value());
- if (typeMask == 0) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "invalid attribute format '" << maybeFormat.value() << "'");
- return false;
- }
- }
-
- 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;
+bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser,
+ ParsedResource* out_resource, bool weak) {
+ out_resource->name.type = ResourceType::kAttr;
+
+ // Attributes only end up in default configuration.
+ if (out_resource->config != ConfigDescription::DefaultConfig()) {
+ diag_->Warn(DiagMessage(out_resource->source)
+ << "ignoring configuration '" << out_resource->config
+ << "' for attribute " << out_resource->name);
+ out_resource->config = ConfigDescription::DefaultConfig();
+ }
+
+ uint32_t type_mask = 0;
+
+ Maybe<StringPiece> maybe_format = xml::FindAttribute(parser, "format");
+ if (maybe_format) {
+ type_mask = ParseFormatAttribute(maybe_format.value());
+ if (type_mask == 0) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "invalid attribute format '" << maybe_format.value()
+ << "'");
+ return false;
+ }
+ }
+
+ Maybe<int32_t> maybe_min, maybe_max;
+
+ if (Maybe<StringPiece> maybe_min_str = xml::FindAttribute(parser, "min")) {
+ StringPiece min_str = util::TrimWhitespace(maybe_min_str.value());
+ if (!min_str.empty()) {
+ std::u16string min_str16 = util::Utf8ToUtf16(min_str);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(),
+ &value)) {
+ maybe_min = static_cast<int32_t>(value.data);
+ }
+ }
+
+ if (!maybe_min) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "invalid 'min' value '" << min_str << "'");
+ return false;
+ }
+ }
+
+ if (Maybe<StringPiece> maybe_max_str = xml::FindAttribute(parser, "max")) {
+ StringPiece max_str = util::TrimWhitespace(maybe_max_str.value());
+ if (!max_str.empty()) {
+ std::u16string max_str16 = util::Utf8ToUtf16(max_str);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(),
+ &value)) {
+ maybe_max = static_cast<int32_t>(value.data);
+ }
+ }
+
+ if (!maybe_max) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "invalid 'max' value '" << max_str << "'");
+ return false;
+ }
+ }
+
+ if ((maybe_min || maybe_max) &&
+ (type_mask & android::ResTable_map::TYPE_INTEGER) == 0) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "'min' and 'max' can only be used when format='integer'");
+ return false;
+ }
+
+ struct SymbolComparator {
+ bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
+ return a.symbol.name.value() < b.symbol.name.value();
+ }
+ };
+
+ std::set<Attribute::Symbol, SymbolComparator> items;
+
+ std::string comment;
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() == xml::XmlPullParser::Event::kComment) {
+ comment = util::TrimWhitespace(parser->comment()).ToString();
+ continue;
+ } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text.
+ continue;
+ }
+
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() &&
+ (element_name == "flag" || element_name == "enum")) {
+ if (element_name == "enum") {
+ if (type_mask & android::ResTable_map::TYPE_FLAGS) {
+ diag_->Error(DiagMessage(item_source)
+ << "can not define an <enum>; already defined a <flag>");
+ error = true;
+ continue;
}
- }
-
- 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);
- }
+ type_mask |= android::ResTable_map::TYPE_ENUM;
+
+ } else if (element_name == "flag") {
+ if (type_mask & android::ResTable_map::TYPE_ENUM) {
+ diag_->Error(DiagMessage(item_source)
+ << "can not define a <flag>; already defined an <enum>");
+ error = true;
+ continue;
}
-
- if (!maybeMax) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "invalid 'max' value '" << maxStr << "'");
- return false;
+ type_mask |= android::ResTable_map::TYPE_FLAGS;
+ }
+
+ if (Maybe<Attribute::Symbol> s =
+ ParseEnumOrFlagItem(parser, element_name)) {
+ Attribute::Symbol& symbol = s.value();
+ ParsedResource child_resource;
+ child_resource.name = symbol.symbol.name.value();
+ child_resource.source = item_source;
+ child_resource.value = util::make_unique<Id>();
+ out_resource->child_resources.push_back(std::move(child_resource));
+
+ symbol.symbol.SetComment(std::move(comment));
+ symbol.symbol.SetSource(item_source);
+
+ auto insert_result = items.insert(std::move(symbol));
+ if (!insert_result.second) {
+ const Attribute::Symbol& existing_symbol = *insert_result.first;
+ diag_->Error(DiagMessage(item_source)
+ << "duplicate symbol '"
+ << existing_symbol.symbol.name.value().entry << "'");
+
+ diag_->Note(DiagMessage(existing_symbol.symbol.GetSource())
+ << "first defined here");
+ error = true;
}
+ } else {
+ error = true;
+ }
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
+ error = true;
}
- 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;
- }
+ comment = {};
+ }
- struct SymbolComparator {
- bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
- return a.symbol.name.value() < b.symbol.name.value();
- }
- };
-
- 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 (error) {
+ return false;
+ }
+
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
+ attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
+ attr->type_mask =
+ type_mask ? type_mask : uint32_t(android::ResTable_map::TYPE_ANY);
+ if (maybe_min) {
+ attr->min_int = maybe_min.value();
+ }
+
+ if (maybe_max) {
+ attr->max_int = maybe_max.value();
+ }
+ out_resource->value = std::move(attr);
+ return true;
+}
- 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;
- }
-
- 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 {
- error = true;
- }
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
- error = true;
- }
+Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(
+ xml::XmlPullParser* parser, const StringPiece& tag) {
+ const Source source = source_.WithLine(parser->line_number());
- comment = {};
- }
-
- if (error) {
- return false;
- }
+ Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(source) << "no attribute 'name' found for tag <"
+ << tag << ">");
+ return {};
+ }
- std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
- attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
- attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
- if (maybeMin) {
- attr->minInt = maybeMin.value();
- }
+ Maybe<StringPiece> maybe_value = xml::FindNonEmptyAttribute(parser, "value");
+ if (!maybe_value) {
+ diag_->Error(DiagMessage(source) << "no attribute 'value' found for tag <"
+ << tag << ">");
+ return {};
+ }
+
+ std::u16string value16 = util::Utf8ToUtf16(maybe_value.value());
+ android::Res_value val;
+ if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) {
+ diag_->Error(DiagMessage(source) << "invalid value '" << maybe_value.value()
+ << "' for <" << tag
+ << ">; must be an integer");
+ return {};
+ }
- if (maybeMax) {
- attr->maxInt = maybeMax.value();
- }
- outResource->value = std::move(attr);
- return true;
+ return Attribute::Symbol{
+ Reference(ResourceNameRef({}, ResourceType::kId, maybe_name.value())),
+ val.data};
}
-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 {};
- }
+bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) {
+ const Source source = source_.WithLine(parser->line_number());
- 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(maybeValue.value().data(),
- maybeValue.value().size(), &val)) {
- mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
- << "' for <" << tag << ">; must be an integer");
- return {};
- }
+ Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(source) << "<item> must have a 'name' attribute");
+ return false;
+ }
- return Attribute::Symbol{
- Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data };
-}
+ Maybe<Reference> maybe_key =
+ ResourceUtils::ParseXmlAttributeName(maybe_name.value());
+ if (!maybe_key) {
+ diag_->Error(DiagMessage(source) << "invalid attribute name '"
+ << maybe_name.value() << "'");
+ return false;
+ }
-static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) {
- str = util::trimWhitespace(str);
- 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++;
- }
+ TransformReferenceFromNamespace(parser, "", &maybe_key.value());
+ maybe_key.value().SetSource(source);
- StringPiece16 package;
- StringPiece16 name;
- while (p != end) {
- if (*p == u':') {
- package = StringPiece16(start, p - start);
- name = StringPiece16(p + 1, end - (p + 1));
- break;
- }
- p++;
- }
+ std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString);
+ if (!value) {
+ diag_->Error(DiagMessage(source) << "could not parse style item");
+ return false;
+ }
- ref.name = ResourceName(package.toString(), ResourceType::kAttr,
- name.empty() ? str.toString() : name.toString());
- return Maybe<Reference>(std::move(ref));
+ style->entries.push_back(
+ Style::Entry{std::move(maybe_key.value()), std::move(value)});
+ return true;
}
-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");
+bool ResourceParser::ParseStyle(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ out_resource->name.type = ResourceType::kStyle;
+
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+
+ Maybe<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent");
+ if (maybe_parent) {
+ // If the parent is empty, we don't have a parent, but we also don't infer
+ // either.
+ if (!maybe_parent.value().empty()) {
+ std::string err_str;
+ style->parent = ResourceUtils::ParseStyleParentReference(
+ maybe_parent.value(), &err_str);
+ if (!style->parent) {
+ diag_->Error(DiagMessage(out_resource->source) << err_str);
return false;
- }
+ }
- Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value());
- if (!maybeKey) {
- mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
- return false;
+ // Transform the namespace prefix to the actual package name, and mark the
+ // reference as
+ // private if appropriate.
+ TransformReferenceFromNamespace(parser, "", &style->parent.value());
}
- 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;
+ } else {
+ // No parent was specified, so try inferring it from the style name.
+ std::string style_name = out_resource->name.entry;
+ size_t pos = style_name.find_last_of(u'.');
+ if (pos != std::string::npos) {
+ style->parent_inferred = true;
+ style->parent = Reference(
+ ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos)));
}
+ }
- style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) });
- return true;
-}
-
-bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) {
- outResource->name.type = ResourceType::kStyle;
-
- std::unique_ptr<Style> style = util::make_unique<Style>();
-
- 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;
- }
-
- // 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 = outResource->name.entry;
- size_t pos = styleName.find_last_of(u'.');
- if (pos != std::string::npos) {
- style->parentInferred = true;
- style->parent = Reference(ResourceName({}, ResourceType::kStyle,
- styleName.substr(0, pos)));
- }
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
+ continue;
}
- 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;
- }
-
- const std::u16string& elementNamespace = parser->getElementNamespace();
- const std::u16string& elementName = parser->getElementName();
- if (elementNamespace == u"" && elementName == u"item") {
- error |= !parseStyleItem(parser, style.get());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace == "" && element_name == "item") {
+ error |= !ParseStyleItem(parser, style.get());
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << ":" << elementName << ">");
- error = true;
- }
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << ":" << element_name << ">");
+ error = true;
}
+ }
- if (error) {
- return false;
- }
+ if (error) {
+ return false;
+ }
- outResource->value = std::move(style);
- return true;
+ out_resource->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(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_ANY);
}
-bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
- return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER);
+bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ return ParseArrayImpl(parser, out_resource,
+ android::ResTable_map::TYPE_INTEGER);
}
-bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
- return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING);
+bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ return ParseArrayImpl(parser, out_resource,
+ android::ResTable_map::TYPE_STRING);
}
-bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser,
+ ParsedResource* out_resource,
const uint32_t typeMask) {
- outResource->name.type = ResourceType::kArray;
-
- std::unique_ptr<Array> array = util::make_unique<Array>();
-
- 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;
- }
- }
- array->setTranslateable(translateable);
-
- 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;
- }
-
- 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));
-
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "unknown tag <" << elementNamespace << ":" << elementName << ">");
- error = true;
- }
- }
-
- if (error) {
- return false;
- }
+ out_resource->name.type = ResourceType::kArray;
+
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+
+ bool translateable = options_.translatable;
+ if (Maybe<StringPiece> translateable_attr =
+ xml::FindAttribute(parser, "translatable")) {
+ Maybe<bool> maybe_translateable =
+ ResourceUtils::ParseBool(translateable_attr.value());
+ if (!maybe_translateable) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid value for 'translatable'. Must be a boolean");
+ return false;
+ }
+ translateable = maybe_translateable.value();
+ }
+ array->SetTranslateable(translateable);
+
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
+ continue;
+ }
+
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() && element_name == "item") {
+ std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
+ if (!item) {
+ diag_->Error(DiagMessage(item_source) << "could not parse array item");
+ error = true;
+ continue;
+ }
+ item->SetSource(item_source);
+ array->items.emplace_back(std::move(item));
+
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "unknown tag <" << element_namespace << ":"
+ << element_name << ">");
+ error = true;
+ }
+ }
+
+ if (error) {
+ return false;
+ }
- outResource->value = std::move(array);
- return true;
+ out_resource->value = std::move(array);
+ return true;
}
-bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) {
- outResource->name.type = ResourceType::kPlurals;
-
- std::unique_ptr<Plural> plural = util::make_unique<Plural>();
-
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- if (plural->values[index]) {
- mDiag->error(DiagMessage(itemSource)
- << "duplicate quantity '" << trimmedQuantity << "'");
- error = true;
- continue;
- }
-
- if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING,
- kNoRawString))) {
- error = true;
- }
- plural->values[index]->setSource(itemSource);
-
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
- << elementName << ">");
- error = true;
- }
- }
-
- if (error) {
- return false;
- }
+bool ResourceParser::ParsePlural(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ out_resource->name.type = ResourceType::kPlurals;
+
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
+ continue;
+ }
+
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() && element_name == "item") {
+ Maybe<StringPiece> maybe_quantity =
+ xml::FindNonEmptyAttribute(parser, "quantity");
+ if (!maybe_quantity) {
+ diag_->Error(DiagMessage(item_source)
+ << "<item> in <plurals> requires attribute "
+ << "'quantity'");
+ error = true;
+ continue;
+ }
+
+ StringPiece trimmed_quantity =
+ util::TrimWhitespace(maybe_quantity.value());
+ size_t index = 0;
+ if (trimmed_quantity == "zero") {
+ index = Plural::Zero;
+ } else if (trimmed_quantity == "one") {
+ index = Plural::One;
+ } else if (trimmed_quantity == "two") {
+ index = Plural::Two;
+ } else if (trimmed_quantity == "few") {
+ index = Plural::Few;
+ } else if (trimmed_quantity == "many") {
+ index = Plural::Many;
+ } else if (trimmed_quantity == "other") {
+ index = Plural::Other;
+ } else {
+ diag_->Error(DiagMessage(item_source)
+ << "<item> in <plural> has invalid value '"
+ << trimmed_quantity << "' for attribute 'quantity'");
+ error = true;
+ continue;
+ }
+
+ if (plural->values[index]) {
+ diag_->Error(DiagMessage(item_source) << "duplicate quantity '"
+ << trimmed_quantity << "'");
+ error = true;
+ continue;
+ }
+
+ if (!(plural->values[index] = ParseXml(
+ parser, android::ResTable_map::TYPE_STRING, kNoRawString))) {
+ error = true;
+ }
+ plural->values[index]->SetSource(item_source);
+
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(item_source) << "unknown tag <"
+ << element_namespace << ":"
+ << element_name << ">");
+ error = true;
+ }
+ }
+
+ if (error) {
+ return false;
+ }
- outResource->value = std::move(plural);
- return true;
+ out_resource->value = std::move(plural);
+ return true;
}
-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>();
-
- 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;
- }
-
- 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;
- }
-
- // 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;
- }
-
- Reference& childRef = maybeRef.value();
- xml::transformReferenceFromNamespace(parser, u"", &childRef);
-
- // 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;
- }
-
- // 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 (error) {
- return false;
- }
+bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ out_resource->name.type = ResourceType::kStyleable;
+
+ // Declare-styleable is kPrivate by default, because it technically only
+ // exists in R.java.
+ out_resource->symbol_state = SymbolState::kPublic;
+
+ // Declare-styleable only ends up in default config;
+ if (out_resource->config != ConfigDescription::DefaultConfig()) {
+ diag_->Warn(DiagMessage(out_resource->source)
+ << "ignoring configuration '" << out_resource->config
+ << "' for styleable " << out_resource->name.entry);
+ out_resource->config = ConfigDescription::DefaultConfig();
+ }
+
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+
+ std::string comment;
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() == xml::XmlPullParser::Event::kComment) {
+ comment = util::TrimWhitespace(parser->comment()).ToString();
+ continue;
+ } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Ignore text.
+ continue;
+ }
+
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() && element_name == "attr") {
+ Maybe<StringPiece> maybe_name =
+ xml::FindNonEmptyAttribute(parser, "name");
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(item_source)
+ << "<attr> tag must have a 'name' attribute");
+ error = true;
+ continue;
+ }
+
+ // If this is a declaration, the package name may be in the name. Separate
+ // these out.
+ // Eg. <attr name="android:text" />
+ Maybe<Reference> maybe_ref =
+ ResourceUtils::ParseXmlAttributeName(maybe_name.value());
+ if (!maybe_ref) {
+ diag_->Error(DiagMessage(item_source) << "<attr> tag has invalid name '"
+ << maybe_name.value() << "'");
+ error = true;
+ continue;
+ }
+
+ Reference& child_ref = maybe_ref.value();
+ xml::TransformReferenceFromNamespace(parser, "", &child_ref);
+
+ // Create the ParsedResource that will add the attribute to the table.
+ ParsedResource child_resource;
+ child_resource.name = child_ref.name.value();
+ child_resource.source = item_source;
+ child_resource.comment = std::move(comment);
+
+ if (!ParseAttrImpl(parser, &child_resource, true)) {
+ error = true;
+ continue;
+ }
+
+ // Create the reference to this attribute.
+ child_ref.SetComment(child_resource.comment);
+ child_ref.SetSource(item_source);
+ styleable->entries.push_back(std::move(child_ref));
+
+ out_resource->child_resources.push_back(std::move(child_resource));
+
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(item_source) << "unknown tag <"
+ << element_namespace << ":"
+ << element_name << ">");
+ error = true;
+ }
+
+ comment = {};
+ }
+
+ if (error) {
+ return false;
+ }
- outResource->value = std::move(styleable);
- return true;
+ out_resource->value = std::move(styleable);
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index ee5b33788312..11b1e5b787ca 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -17,6 +17,10 @@
#ifndef AAPT_RESOURCE_PARSER_H
#define AAPT_RESOURCE_PARSER_H
+#include <memory>
+
+#include "android-base/macros.h"
+
#include "ConfigDescription.h"
#include "Diagnostics.h"
#include "ResourceTable.h"
@@ -26,86 +30,98 @@
#include "util/StringPiece.h"
#include "xml/XmlPullParser.h"
-#include <memory>
-
namespace aapt {
struct ParsedResource;
struct ResourceParserOptions {
- /**
- * Whether the default setting for this parser is to allow translation.
- */
- bool translatable = true;
-
- /**
- * Whether positional arguments in formatted strings are treated as errors or warnings.
- */
- bool errorOnPositionalArguments = true;
+ /**
+ * Whether the default setting for this parser is to allow translation.
+ */
+ bool translatable = true;
+
+ /**
+ * Whether positional arguments in formatted strings are treated as errors or
+ * warnings.
+ */
+ bool error_on_positional_arguments = true;
};
/*
* 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(xml::XmlPullParser* parser);
-
-private:
- /*
- * Parses the XML subtree as a StyleString (flattened XML representation for strings
- * with formatting). If successful, `outStyleString`
- * contains the escaped and whitespace trimmed text, while `outRawString`
- * contains the unescaped text. Returns true on success.
- */
- bool flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
- StyleString* outStyleString);
-
- /*
- * 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(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;
- ResourceParserOptions mOptions;
+ public:
+ ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
+ const ConfigDescription& config,
+ const ResourceParserOptions& options = {});
+ bool Parse(xml::XmlPullParser* parser);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceParser);
+
+ /*
+ * Parses the XML subtree as a StyleString (flattened XML representation for
+ * strings
+ * with formatting). If successful, `out_style_string`
+ * contains the escaped and whitespace trimmed text, while `out_raw_string`
+ * contains the unescaped text. Returns true on success.
+ */
+ bool FlattenXmlSubtree(xml::XmlPullParser* parser,
+ std::string* out_raw_string,
+ StyleString* out_style_string);
+
+ /*
+ * Parses the XML subtree and returns an Item.
+ * The type of Item that can be parsed is denoted by the `type_mask`.
+ * If `allow_raw_value` 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(xml::XmlPullParser* parser,
+ const uint32_t type_mask,
+ const bool allow_raw_value);
+
+ bool ParseResources(xml::XmlPullParser* parser);
+ bool ParseResource(xml::XmlPullParser* parser, ParsedResource* out_resource);
+
+ bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource,
+ uint32_t format);
+ bool ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource);
+
+ bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParsePublicGroup(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseSymbolImpl(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseAddResource(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
+ bool weak);
+ Maybe<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
+ const StringPiece& tag);
+ bool ParseStyle(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
+ bool ParseDeclareStyleable(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseIntegerArray(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseStringArray(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseArrayImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
+ uint32_t typeMask);
+ bool ParsePlural(xml::XmlPullParser* parser, ParsedResource* out_resource);
+
+ IDiagnostics* diag_;
+ ResourceTable* table_;
+ Source source_;
+ ConfigDescription config_;
+ ResourceParserOptions options_;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RESOURCE_PARSER_H
+#endif // AAPT_RESOURCE_PARSER_H
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 3450de9078bb..2463911445e7 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -15,490 +15,573 @@
*/
#include "ResourceParser.h"
+
+#include <sstream>
+#include <string>
+
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "test/Context.h"
+#include "test/Test.h"
#include "xml/XmlPullParser.h"
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
namespace aapt {
-constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+constexpr const char* kXmlPreamble =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
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));
+ 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 xml_parser(input);
+ ASSERT_FALSE(parser.Parse(&xml_parser));
}
-struct ResourceParserTest : public ::testing::Test {
- ResourceTable mTable;
- std::unique_ptr<IAaptContext> mContext;
+class ResourceParserTest : public ::testing::Test {
+ public:
+ void SetUp() override { context_ = test::ContextBuilder().Build(); }
- void SetUp() override {
- mContext = test::ContextBuilder().build();
- }
+ ::testing::AssertionResult TestParse(const StringPiece& str) {
+ return TestParse(str, ConfigDescription{});
+ }
- ::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;
+ ResourceParserOptions parserOptions;
+ ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"},
+ config, parserOptions);
+ xml::XmlPullParser xmlParser(input);
+ if (parser.Parse(&xmlParser)) {
+ return ::testing::AssertionSuccess();
}
+ return ::testing::AssertionFailure();
+ }
- ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) {
- std::stringstream input(kXmlPreamble);
- input << "<resources>\n" << str << "\n</resources>" << std::endl;
- 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();
- }
+ protected:
+ ResourceTable table_;
+ std::unique_ptr<IAaptContext> context_;
};
TEST_F(ResourceParserTest, ParseQuotedString) {
- std::string input = "<string name=\"foo\"> \" hey there \" </string>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<string name=\"foo\"> \" hey there \" </string>";
+ ASSERT_TRUE(TestParse(input));
- String* str = test::getValue<String>(&mTable, u"@string/foo");
- ASSERT_NE(nullptr, str);
- EXPECT_EQ(std::u16string(u" hey there "), *str->value);
+ String* str = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::string(" hey there "), *str->value);
}
TEST_F(ResourceParserTest, ParseEscapedString) {
- std::string input = "<string name=\"foo\">\\?123</string>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<string name=\"foo\">\\?123</string>";
+ ASSERT_TRUE(TestParse(input));
- String* str = test::getValue<String>(&mTable, u"@string/foo");
- ASSERT_NE(nullptr, str);
- EXPECT_EQ(std::u16string(u"?123"), *str->value);
+ String* str = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::string("?123"), *str->value);
}
TEST_F(ResourceParserTest, ParseFormattedString) {
- std::string input = "<string name=\"foo\">%d %s</string>";
- ASSERT_FALSE(testParse(input));
+ 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));
+ 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));
+TEST_F(ResourceParserTest, ParseStyledString) {
+ // Use a surrogate pair unicode point so that we can verify that the span
+ // indices
+ // use UTF-16 length and not UTF-18 length.
+ std::string input =
+ "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>";
+ ASSERT_TRUE(TestParse(input));
+
+ StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
+ ASSERT_NE(nullptr, str);
- 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));
+ const std::string expected_str = "This is my aunt\u2019s string";
+ EXPECT_EQ(expected_str, *str->value->str);
+ EXPECT_EQ(1u, str->value->spans.size());
+
+ EXPECT_EQ(std::string("b"), *str->value->spans[0].name);
+ EXPECT_EQ(17u, str->value->spans[0].first_char);
+ EXPECT_EQ(23u, str->value->spans[0].last_char);
}
-TEST_F(ResourceParserTest, ParseNull) {
- std::string input = "<integer name=\"foo\">@null</integer>";
- ASSERT_TRUE(testParse(input));
+TEST_F(ResourceParserTest, ParseStringWithWhitespace) {
+ std::string input = "<string name=\"foo\"> This is what I think </string>";
+ ASSERT_TRUE(TestParse(input));
+
+ String* str = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::string("This is what I think"), *str->value);
- // The Android runtime treats a value of android::Res_value::TYPE_NULL as
- // 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.
- 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);
+ input = "<string name=\"foo2\">\" This is what I think \"</string>";
+ ASSERT_TRUE(TestParse(input));
+
+ str = test::GetValue<String>(&table_, "string/foo2");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::string(" This is what I think "), *str->value);
}
-TEST_F(ResourceParserTest, ParseEmpty) {
- std::string input = "<integer name=\"foo\">@empty</integer>";
- 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));
- 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);
+ String* str = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value));
}
-TEST_F(ResourceParserTest, ParseAttr) {
- std::string input = "<attr name=\"foo\" format=\"string\"/>\n"
- "<attr name=\"bar\"/>";
- ASSERT_TRUE(testParse(input));
+TEST_F(ResourceParserTest, ParseNull) {
+ std::string input = "<integer name=\"foo\">@null</integer>";
+ ASSERT_TRUE(TestParse(input));
+
+ // The Android runtime treats a value of android::Res_value::TYPE_NULL as
+ // 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.
+ BinaryPrimitive* integer =
+ test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
+ ASSERT_NE(nullptr, integer);
+ EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE),
+ integer->value.dataType);
+ EXPECT_EQ(0u, integer->value.data);
+}
- Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
+TEST_F(ResourceParserTest, ParseEmpty) {
+ std::string input = "<integer name=\"foo\">@empty</integer>";
+ ASSERT_TRUE(TestParse(input));
- attr = test::getValue<Attribute>(&mTable, u"@attr/bar");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
+ BinaryPrimitive* integer =
+ test::GetValue<BinaryPrimitive>(&table_, "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);
}
-// 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(
+TEST_F(ResourceParserTest, ParseAttr) {
+ std::string input =
+ "<attr name=\"foo\" format=\"string\"/>\n"
+ "<attr name=\"bar\"/>";
+ ASSERT_TRUE(TestParse(input));
+
+ Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask);
+
+ attr = test::GetValue<Attribute>(&table_, "attr/bar");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->type_mask);
+}
+
+// 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 watch_config = 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));
+ ASSERT_TRUE(TestParse(input, watch_config));
- 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_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/foo",
+ watch_config));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/baz",
+ watch_config));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<Styleable>(
+ &table_, "styleable/bar", watch_config));
- 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"));
+ EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/foo"));
+ EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/baz"));
+ EXPECT_NE(nullptr, test::GetValue<Styleable>(&table_, "styleable/bar"));
}
TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
- std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
- ASSERT_TRUE(testParse(input));
+ 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);
+ Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->type_mask);
+ EXPECT_EQ(10, attr->min_int);
+ EXPECT_EQ(23, attr->max_int);
}
TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
- std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
- ASSERT_FALSE(testParse(input));
+ 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"
- "</declare-styleable>\n"
- "<attr name=\"foo\" format=\"string\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<declare-styleable name=\"Styleable\">\n"
+ " <attr name=\"foo\" />\n"
+ "</declare-styleable>\n"
+ "<attr name=\"foo\" format=\"string\"/>";
+ 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_STRING), attr->typeMask);
+ Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask);
}
TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
- std::string input = "<declare-styleable name=\"Theme\">"
- " <attr name=\"foo\" />\n"
- "</declare-styleable>\n"
- "<declare-styleable name=\"Window\">\n"
- " <attr name=\"foo\" format=\"boolean\"/>\n"
- "</declare-styleable>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<declare-styleable name=\"Theme\">"
+ " <attr name=\"foo\" />\n"
+ "</declare-styleable>\n"
+ "<declare-styleable name=\"Window\">\n"
+ " <attr name=\"foo\" format=\"boolean\"/>\n"
+ "</declare-styleable>";
+ 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_BOOLEAN), attr->typeMask);
+ Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->type_mask);
}
TEST_F(ResourceParserTest, ParseEnumAttr) {
- std::string input = "<attr name=\"foo\">\n"
- " <enum name=\"bar\" value=\"0\"/>\n"
- " <enum name=\"bat\" value=\"1\"/>\n"
- " <enum name=\"baz\" value=\"2\"/>\n"
- "</attr>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<attr name=\"foo\">\n"
+ " <enum name=\"bar\" value=\"0\"/>\n"
+ " <enum name=\"bat\" value=\"1\"/>\n"
+ " <enum name=\"baz\" value=\"2\"/>\n"
+ "</attr>";
+ ASSERT_TRUE(TestParse(input));
- 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);
+ Attribute* enum_attr = test::GetValue<Attribute>(&table_, "attr/foo");
+ ASSERT_NE(enum_attr, nullptr);
+ EXPECT_EQ(enum_attr->type_mask, android::ResTable_map::TYPE_ENUM);
+ ASSERT_EQ(enum_attr->symbols.size(), 3u);
- 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);
+ AAPT_ASSERT_TRUE(enum_attr->symbols[0].symbol.name);
+ EXPECT_EQ(enum_attr->symbols[0].symbol.name.value().entry, "bar");
+ EXPECT_EQ(enum_attr->symbols[0].value, 0u);
- 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);
+ AAPT_ASSERT_TRUE(enum_attr->symbols[1].symbol.name);
+ EXPECT_EQ(enum_attr->symbols[1].symbol.name.value().entry, "bat");
+ EXPECT_EQ(enum_attr->symbols[1].value, 1u);
- 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);
+ AAPT_ASSERT_TRUE(enum_attr->symbols[2].symbol.name);
+ EXPECT_EQ(enum_attr->symbols[2].symbol.name.value().entry, "baz");
+ EXPECT_EQ(enum_attr->symbols[2].value, 2u);
}
TEST_F(ResourceParserTest, ParseFlagAttr) {
- std::string input = "<attr name=\"foo\">\n"
- " <flag name=\"bar\" value=\"0\"/>\n"
- " <flag name=\"bat\" value=\"1\"/>\n"
- " <flag name=\"baz\" value=\"2\"/>\n"
- "</attr>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<attr name=\"foo\">\n"
+ " <flag name=\"bar\" value=\"0\"/>\n"
+ " <flag name=\"bat\" value=\"1\"/>\n"
+ " <flag name=\"baz\" value=\"2\"/>\n"
+ "</attr>";
+ ASSERT_TRUE(TestParse(input));
- 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);
+ Attribute* flag_attr = test::GetValue<Attribute>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, flag_attr);
+ EXPECT_EQ(flag_attr->type_mask, android::ResTable_map::TYPE_FLAGS);
+ ASSERT_EQ(flag_attr->symbols.size(), 3u);
- 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);
+ AAPT_ASSERT_TRUE(flag_attr->symbols[0].symbol.name);
+ EXPECT_EQ(flag_attr->symbols[0].symbol.name.value().entry, "bar");
+ EXPECT_EQ(flag_attr->symbols[0].value, 0u);
- 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);
+ AAPT_ASSERT_TRUE(flag_attr->symbols[1].symbol.name);
+ EXPECT_EQ(flag_attr->symbols[1].symbol.name.value().entry, "bat");
+ EXPECT_EQ(flag_attr->symbols[1].value, 1u);
- 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);
+ AAPT_ASSERT_TRUE(flag_attr->symbols[2].symbol.name);
+ EXPECT_EQ(flag_attr->symbols[2].symbol.name.value().entry, "baz");
+ EXPECT_EQ(flag_attr->symbols[2].value, 2u);
- std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr,
- u"baz|bat");
- ASSERT_NE(nullptr, flagValue);
- EXPECT_EQ(flagValue->value.data, 1u | 2u);
+ std::unique_ptr<BinaryPrimitive> flag_value =
+ ResourceUtils::TryParseFlagSymbol(flag_attr, "baz|bat");
+ ASSERT_NE(nullptr, flag_value);
+ EXPECT_EQ(flag_value->value.data, 1u | 2u);
}
TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
- std::string input = "<attr name=\"foo\">\n"
- " <enum name=\"bar\" value=\"0\"/>\n"
- " <enum name=\"bat\" value=\"1\"/>\n"
- " <enum name=\"bat\" value=\"2\"/>\n"
- "</attr>";
- ASSERT_FALSE(testParse(input));
+ std::string input =
+ "<attr name=\"foo\">\n"
+ " <enum name=\"bar\" value=\"0\"/>\n"
+ " <enum name=\"bat\" value=\"1\"/>\n"
+ " <enum name=\"bat\" value=\"2\"/>\n"
+ "</attr>";
+ ASSERT_FALSE(TestParse(input));
}
TEST_F(ResourceParserTest, ParseStyle) {
- std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n"
- " <item name=\"bar\">#ffffffff</item>\n"
- " <item name=\"bat\">@string/hey</item>\n"
- " <item name=\"baz\"><b>hey</b></item>\n"
- "</style>";
- ASSERT_TRUE(testParse(input));
-
- 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());
-
- AAPT_ASSERT_TRUE(style->entries[2].key.name);
- EXPECT_EQ(test::parseNameOrDie(u"@attr/baz"), style->entries[2].key.name.value());
+ std::string input =
+ "<style name=\"foo\" parent=\"@style/fu\">\n"
+ " <item name=\"bar\">#ffffffff</item>\n"
+ " <item name=\"bat\">@string/hey</item>\n"
+ " <item name=\"baz\"><b>hey</b></item>\n"
+ "</style>";
+ ASSERT_TRUE(TestParse(input));
+
+ Style* style = test::GetValue<Style>(&table_, "style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(test::ParseNameOrDie("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("attr/bar"),
+ style->entries[0].key.name.value());
+
+ AAPT_ASSERT_TRUE(style->entries[1].key.name);
+ EXPECT_EQ(test::ParseNameOrDie("attr/bat"),
+ style->entries[1].key.name.value());
+
+ AAPT_ASSERT_TRUE(style->entries[2].key.name);
+ EXPECT_EQ(test::ParseNameOrDie("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));
+ std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
+ ASSERT_TRUE(TestParse(input));
- 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());
+ Style* style = test::GetValue<Style>(&table_, "style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(test::ParseNameOrDie("com.app:style/Theme"),
+ style->parent.value().name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
- std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
- " name=\"foo\" parent=\"app:Theme\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
+ " name=\"foo\" parent=\"app:Theme\"/>";
+ ASSERT_TRUE(TestParse(input));
- 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());
+ Style* style = test::GetValue<Style>(&table_, "style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(test::ParseNameOrDie("android:style/Theme"),
+ style->parent.value().name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
- std::string input =
- "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n"
- " <item name=\"app:bar\">0</item>\n"
- "</style>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" "
+ "name=\"foo\">\n"
+ " <item name=\"app:bar\">0</item>\n"
+ "</style>";
+ ASSERT_TRUE(TestParse(input));
- Style* style = test::getValue<Style>(&mTable, u"@style/foo");
- ASSERT_NE(nullptr, style);
- ASSERT_EQ(1u, style->entries.size());
- EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value());
+ Style* style = test::GetValue<Style>(&table_, "style/foo");
+ ASSERT_NE(nullptr, style);
+ ASSERT_EQ(1u, style->entries.size());
+ EXPECT_EQ(test::ParseNameOrDie("android:attr/bar"),
+ style->entries[0].key.name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
- std::string input = "<style name=\"foo.bar\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<style name=\"foo.bar\"/>";
+ ASSERT_TRUE(TestParse(input));
- 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);
+ Style* style = test::GetValue<Style>(&table_, "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("style/foo"));
+ EXPECT_TRUE(style->parent_inferred);
}
-TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) {
- std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
- ASSERT_TRUE(testParse(input));
+TEST_F(ResourceParserTest,
+ ParseStyleWithInferredParentOverridenByEmptyParentAttribute) {
+ std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
+ ASSERT_TRUE(TestParse(input));
- Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar");
- ASSERT_NE(nullptr, style);
- AAPT_EXPECT_FALSE(style->parent);
- EXPECT_FALSE(style->parentInferred);
+ Style* style = test::GetValue<Style>(&table_, "style/foo.bar");
+ ASSERT_NE(nullptr, style);
+ AAPT_EXPECT_FALSE(style->parent);
+ EXPECT_FALSE(style->parent_inferred);
}
TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) {
- std::string input = R"EOF(<style name="foo" parent="*android:style/bar" />)EOF";
- ASSERT_TRUE(testParse(input));
+ 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);
+ Style* style = test::GetValue<Style>(&table_, "style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ EXPECT_TRUE(style->parent.value().private_reference);
}
TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
- std::string input = "<string name=\"foo\">@+id/bar</string>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<string name=\"foo\">@+id/bar</string>";
+ ASSERT_TRUE(TestParse(input));
- Id* id = test::getValue<Id>(&mTable, u"@id/bar");
- ASSERT_NE(id, nullptr);
+ Id* id = test::GetValue<Id>(&table_, "id/bar");
+ ASSERT_NE(id, nullptr);
}
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));
-
- 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 = 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"));
-
- 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());
+ 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));
+
+ Maybe<ResourceTable::SearchResult> result =
+ table_.FindResource(test::ParseNameOrDie("styleable/foo"));
+ AAPT_ASSERT_TRUE(result);
+ EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state);
+
+ Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar");
+ ASSERT_NE(attr, nullptr);
+ EXPECT_TRUE(attr->IsWeak());
+
+ attr = test::GetValue<Attribute>(&table_, "attr/bat");
+ ASSERT_NE(attr, nullptr);
+ EXPECT_TRUE(attr->IsWeak());
+
+ attr = test::GetValue<Attribute>(&table_, "attr/baz");
+ ASSERT_NE(attr, nullptr);
+ EXPECT_TRUE(attr->IsWeak());
+ EXPECT_EQ(1u, attr->symbols.size());
+
+ EXPECT_NE(nullptr, test::GetValue<Id>(&table_, "id/foo"));
+
+ Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo");
+ ASSERT_NE(styleable, nullptr);
+ ASSERT_EQ(3u, styleable->entries.size());
+
+ EXPECT_EQ(test::ParseNameOrDie("attr/bar"),
+ styleable->entries[0].name.value());
+ EXPECT_EQ(test::ParseNameOrDie("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_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);
+ 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>(&table_, "styleable/foo");
+ ASSERT_NE(nullptr, styleable);
+ ASSERT_EQ(2u, styleable->entries.size());
+
+ EXPECT_TRUE(styleable->entries[0].private_reference);
+ AAPT_ASSERT_TRUE(styleable->entries[0].name);
+ EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package);
+
+ EXPECT_TRUE(styleable->entries[1].private_reference);
+ AAPT_ASSERT_TRUE(styleable->entries[1].name);
+ EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package);
}
TEST_F(ResourceParserTest, ParseArray) {
- std::string input = "<array name=\"foo\">\n"
- " <item>@string/ref</item>\n"
- " <item>hey</item>\n"
- " <item>23</item>\n"
- "</array>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<array name=\"foo\">\n"
+ " <item>@string/ref</item>\n"
+ " <item>hey</item>\n"
+ " <item>23</item>\n"
+ "</array>";
+ ASSERT_TRUE(TestParse(input));
- Array* array = test::getValue<Array>(&mTable, u"@array/foo");
- ASSERT_NE(array, nullptr);
- ASSERT_EQ(3u, array->items.size());
+ Array* array = test::GetValue<Array>(&table_, "array/foo");
+ ASSERT_NE(array, nullptr);
+ ASSERT_EQ(3u, array->items.size());
- 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()));
+ 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"));
+ 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>(&table_, "array/foo"));
}
TEST_F(ResourceParserTest, ParsePlural) {
- std::string input = "<plurals name=\"foo\">\n"
- " <item quantity=\"other\">apples</item>\n"
- " <item quantity=\"one\">apple</item>\n"
- "</plurals>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<plurals name=\"foo\">\n"
+ " <item quantity=\"other\">apples</item>\n"
+ " <item quantity=\"one\">apple</item>\n"
+ "</plurals>";
+ ASSERT_TRUE(TestParse(input));
}
TEST_F(ResourceParserTest, ParseCommentsWithResource) {
- std::string input = "<!--This is a comment-->\n"
- "<string name=\"foo\">Hi</string>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<!--This is a comment-->\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"This is a comment");
+ String* value = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->GetComment(), "This is a comment");
}
TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
- std::string input = "<!--One-->\n"
- "<!--Two-->\n"
- "<string name=\"foo\">Hi</string>";
+ std::string input =
+ "<!--One-->\n"
+ "<!--Two-->\n"
+ "<string name=\"foo\">Hi</string>";
- ASSERT_TRUE(testParse(input));
+ ASSERT_TRUE(TestParse(input));
- String* value = test::getValue<String>(&mTable, u"@string/foo");
- ASSERT_NE(nullptr, value);
- EXPECT_EQ(value->getComment(), u"Two");
+ String* value = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->GetComment(), "Two");
}
TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
- std::string input = "<!--One-->\n"
- "<string name=\"foo\">\n"
- " Hi\n"
- "<!--Two-->\n"
- "</string>";
+ std::string input =
+ "<!--One-->\n"
+ "<string name=\"foo\">\n"
+ " Hi\n"
+ "<!--Two-->\n"
+ "</string>";
- ASSERT_TRUE(testParse(input));
+ ASSERT_TRUE(TestParse(input));
- String* value = test::getValue<String>(&mTable, u"@string/foo");
- ASSERT_NE(nullptr, value);
- EXPECT_EQ(value->getComment(), u"One");
+ String* value = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->GetComment(), "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(
+ // 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" />
@@ -508,19 +591,21 @@ TEST_F(ResourceParserTest, ParseNestedComments) {
<!-- The very first -->
<enum name="one" value="1" />
</attr>)EOF";
- ASSERT_TRUE(testParse(input));
+ ASSERT_TRUE(TestParse(input));
- Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
- ASSERT_NE(nullptr, styleable);
- ASSERT_EQ(1u, styleable->entries.size());
+ Styleable* styleable = test::GetValue<Styleable>(&table_, "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());
+ EXPECT_EQ(StringPiece("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());
+ Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ ASSERT_EQ(1u, attr->symbols.size());
- EXPECT_EQ(StringPiece16(u"The very first"), attr->symbols.front().symbol.getComment());
+ EXPECT_EQ(StringPiece("The very first"),
+ attr->symbols.front().symbol.GetComment());
}
/*
@@ -528,15 +613,15 @@ TEST_F(ResourceParserTest, ParseNestedComments) {
* (as an ID has no value).
*/
TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
- std::string input = "<public type=\"id\" name=\"foo\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<public type=\"id\" name=\"foo\"/>";
+ ASSERT_TRUE(TestParse(input));
- Id* id = test::getValue<Id>(&mTable, u"@id/foo");
- ASSERT_NE(nullptr, id);
+ Id* id = test::GetValue<Id>(&table_, "id/foo");
+ ASSERT_NE(nullptr, id);
}
TEST_F(ResourceParserTest, KeepAllProducts) {
- std::string input = R"EOF(
+ 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>
@@ -544,88 +629,92 @@ TEST_F(ResourceParserTest, KeepAllProducts) {
<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"));
+ ASSERT_TRUE(TestParse(input));
+
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
+ &table_, "string/foo",
+ ConfigDescription::DefaultConfig(), "phone"));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
+ &table_, "string/foo",
+ ConfigDescription::DefaultConfig(), "no-sdcard"));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfigAndProduct<String>(
+ &table_, "string/bar", ConfigDescription::DefaultConfig(), ""));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfigAndProduct<String>(
+ &table_, "string/baz", ConfigDescription::DefaultConfig(), ""));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
+ &table_, "string/bit",
+ ConfigDescription::DefaultConfig(), "phablet"));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
+ &table_, "string/bot",
+ ConfigDescription::DefaultConfig(), "default"));
}
TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
- std::string input = R"EOF(
+ 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));
+ ASSERT_TRUE(TestParse(input));
- Maybe<ResourceTable::SearchResult> result = mTable.findResource(
- test::parseNameOrDie(u"@attr/foo"));
- AAPT_ASSERT_TRUE(result);
+ Maybe<ResourceTable::SearchResult> result =
+ table_.FindResource(test::ParseNameOrDie("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);
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ ResourceId actual_id(result.value().package->id.value(),
+ result.value().type->id.value(),
+ result.value().entry->id.value());
+ EXPECT_EQ(ResourceId(0x01010040), actual_id);
- result = mTable.findResource(test::parseNameOrDie(u"@attr/bar"));
- AAPT_ASSERT_TRUE(result);
+ result = table_.FindResource(test::ParseNameOrDie("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);
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ actual_id = ResourceId(result.value().package->id.value(),
+ result.value().type->id.value(),
+ result.value().entry->id.value());
+ EXPECT_EQ(ResourceId(0x01010041), actual_id);
}
TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
- std::string input = R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF";
- ASSERT_TRUE(testParse(input));
+ 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));
+ 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));
+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);
+ Maybe<ResourceTable::SearchResult> result =
+ table_.FindResource(test::ParseNameOrDie("string/bar"));
+ AAPT_ASSERT_TRUE(result);
+ const ResourceEntry* entry = result.value().entry;
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(SymbolState::kUndefined, entry->symbol_status.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));
+ 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);
+ BinaryPrimitive* val =
+ test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
+ ASSERT_NE(nullptr, val);
- EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType);
+ EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index f9707e41bb3d..4e6a50ae149b 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -14,531 +14,543 @@
* limitations under the License.
*/
+#include "ResourceTable.h"
#include "ConfigDescription.h"
#include "NameMangler.h"
-#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
#include "util/Util.h"
-#include <algorithm>
+#include <android-base/logging.h>
#include <androidfw/ResourceTypes.h>
+#include <algorithm>
#include <memory>
#include <string>
#include <tuple>
namespace aapt {
-static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
- return lhs->type < rhs;
+static bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs,
+ ResourceType rhs) {
+ return lhs->type < 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;
+static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs,
+ const StringPiece& rhs) {
+ return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
}
-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;
+ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) {
+ const auto last = packages.end();
+ auto iter =
+ std::lower_bound(packages.begin(), last, name,
+ less_than_struct_with_name<ResourceTablePackage>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
+ return nullptr;
}
-ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) {
- for (auto& package : packages) {
- if (package->id && package->id.value() == id) {
- return package.get();
- }
+ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) {
+ for (auto& package : packages) {
+ if (package->id && package->id.value() == id) {
+ return package.get();
}
- return nullptr;
+ }
+ return nullptr;
}
-ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, Maybe<uint8_t> id) {
- ResourceTablePackage* package = findOrCreatePackage(name);
- if (id && !package->id) {
- package->id = id;
- return package;
- }
-
- if (id && package->id && package->id.value() != id.value()) {
- return nullptr;
- }
+ResourceTablePackage* ResourceTable::CreatePackage(const StringPiece& name,
+ Maybe<uint8_t> id) {
+ ResourceTablePackage* package = FindOrCreatePackage(name);
+ if (id && !package->id) {
+ package->id = id;
return package;
-}
+ }
-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();
- }
+ if (id && package->id && package->id.value() != id.value()) {
+ return nullptr;
+ }
+ return package;
+}
- std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>();
- newPackage->name = name.toString();
- return packages.emplace(iter, std::move(newPackage))->get();
+ResourceTablePackage* ResourceTable::FindOrCreatePackage(
+ const StringPiece& name) {
+ const auto last = packages.end();
+ auto iter =
+ std::lower_bound(packages.begin(), last, name,
+ less_than_struct_with_name<ResourceTablePackage>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
+
+ std::unique_ptr<ResourceTablePackage> new_package =
+ util::make_unique<ResourceTablePackage>();
+ new_package->name = name.ToString();
+ return packages.emplace(iter, std::move(new_package))->get();
}
-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::FindType(ResourceType type) {
+ const auto last = types.end();
+ auto iter = std::lower_bound(types.begin(), last, type, less_than_type);
+ 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();
+ResourceTableType* ResourceTablePackage::FindOrCreateType(ResourceType type) {
+ const auto last = types.end();
+ auto iter = std::lower_bound(types.begin(), last, type, less_than_type);
+ if (iter != last && (*iter)->type == type) {
+ return iter->get();
+ }
+ return types.emplace(iter, new ResourceTableType(type))->get();
}
-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::FindEntry(const StringPiece& name) {
+ const auto last = entries.end();
+ auto iter = std::lower_bound(entries.begin(), last, name,
+ less_than_struct_with_name<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();
+ResourceEntry* ResourceTableType::FindOrCreateEntry(const StringPiece& name) {
+ auto last = entries.end();
+ auto iter = std::lower_bound(entries.begin(), last, name,
+ less_than_struct_with_name<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());
+ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config) {
+ return FindValue(config, StringPiece());
}
struct ConfigKey {
- const ConfigDescription* config;
- const StringPiece& product;
+ 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;
+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,
+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;
+ 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;
+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;
- }
+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);
- }
+ for (; iter != values.end(); ++iter) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config) {
+ results.push_back(value);
}
- return results;
+ }
+ return results;
}
-std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf(
- const std::function<bool(ResourceConfigValue*)>& f) {
- std::vector<ResourceConfigValue*> results;
- for (auto& configValue : values) {
- if (f(configValue.get())) {
- results.push_back(configValue.get());
- }
+std::vector<ResourceConfigValue*> ResourceEntry::FindValuesIf(
+ const std::function<bool(ResourceConfigValue*)>& f) {
+ std::vector<ResourceConfigValue*> results;
+ for (auto& configValue : values) {
+ if (f(configValue.get())) {
+ results.push_back(configValue.get());
}
- return results;
+ }
+ 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.
+ * The default handler for collisions.
+ *
+ * Typically, a weak value will be overridden by a strong value. An existing
+ * weak
+ * value will not be overridden by an incoming weak value.
+ *
+ * There are some exceptions:
+ *
+ * Attributes: There are two types of Attribute values: USE and DECL.
+ *
+ * USE is anywhere an Attribute is declared without a format, and in a place
+ * that would
+ * be legal to declare if the Attribute already existed. This is typically in a
+ * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also
+ * weak.
+ *
+ * DECL is an absolute declaration of an Attribute and specifies an explicit
+ * format.
+ *
+ * A DECL will override a USE without error. Two DECLs must match in their
+ * format for there to be
+ * no error.
*/
-int ResourceTable::resolveValueCollision(Value* existing, Value* incoming) {
- Attribute* existingAttr = valueCast<Attribute>(existing);
- Attribute* incomingAttr = valueCast<Attribute>(incoming);
-
- 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()) {
- // Override the weak resource with the new strong resource.
- return 1;
- }
- // The existing and incoming values are strong, this is an error
- // if the values are not both attributes.
- return 0;
- }
-
- if (!existingAttr) {
- if (existing->isWeak()) {
- // The existing value is not an attribute and it is weak,
- // so take the incoming attribute value.
- return 1;
- }
- // The existing value is not an attribute and it is strong,
- // so the incoming attribute value is an error.
- 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.
- //
- 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;
- }
-
- 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) {
- // The incoming attribute may be a USE instead of a DECL.
- // Keep the existing attribute.
- return -1;
- }
- return 0;
+ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(
+ Value* existing, Value* incoming) {
+ Attribute* existing_attr = ValueCast<Attribute>(existing);
+ Attribute* incoming_attr = ValueCast<Attribute>(incoming);
+ if (!incoming_attr) {
+ if (incoming->IsWeak()) {
+ // We're trying to add a weak resource but a resource
+ // already exists. Keep the existing.
+ return CollisionResult::kKeepOriginal;
+ } else if (existing->IsWeak()) {
+ // Override the weak resource with the new strong resource.
+ return CollisionResult::kTakeNew;
+ }
+ // The existing and incoming values are strong, this is an error
+ // if the values are not both attributes.
+ return CollisionResult::kConflict;
+ }
+
+ if (!existing_attr) {
+ if (existing->IsWeak()) {
+ // The existing value is not an attribute and it is weak,
+ // so take the incoming attribute value.
+ return CollisionResult::kTakeNew;
+ }
+ // The existing value is not an attribute and it is strong,
+ // so the incoming attribute value is an error.
+ return CollisionResult::kConflict;
+ }
+
+ CHECK(incoming_attr != nullptr && existing_attr != nullptr);
+
+ //
+ // 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.
+ //
+ if (existing_attr->type_mask == incoming_attr->type_mask) {
+ // The two attributes are both DECLs, but they are plain attributes
+ // with the same formats.
+ // Keep the strongest one.
+ return existing_attr->IsWeak() ? CollisionResult::kTakeNew
+ : CollisionResult::kKeepOriginal;
+ }
+
+ if (existing_attr->IsWeak() &&
+ existing_attr->type_mask == android::ResTable_map::TYPE_ANY) {
+ // Any incoming attribute is better than this.
+ return CollisionResult::kTakeNew;
+ }
+
+ if (incoming_attr->IsWeak() &&
+ incoming_attr->type_mask == android::ResTable_map::TYPE_ANY) {
+ // The incoming attribute may be a USE instead of a DECL.
+ // Keep the existing attribute.
+ return CollisionResult::kKeepOriginal;
+ }
+ return CollisionResult::kConflict;
}
-static constexpr const char16_t* kValidNameChars = u"._-";
-static constexpr const char16_t* kValidNameMangledChars = u"._-$";
+static constexpr const char* kValidNameChars = "._-";
+static constexpr const char* kValidNameMangledChars = "._-$";
-bool ResourceTable::addResource(const ResourceNameRef& name,
+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);
+ return AddResourceImpl(name, {}, config, product, std::move(value),
+ kValidNameChars, ResolveValueCollision, diag);
}
-bool ResourceTable::addResource(const ResourceNameRef& name,
- const ResourceId& resId,
+bool ResourceTable::AddResource(const ResourceNameRef& name,
+ const ResourceId& res_id,
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);
+ return AddResourceImpl(name, res_id, config, product, std::move(value),
+ kValidNameChars, ResolveValueCollision, diag);
}
-bool ResourceTable::addFileReference(const ResourceNameRef& name,
+bool ResourceTable::AddFileReference(const ResourceNameRef& name,
const ConfigDescription& config,
const Source& source,
- const StringPiece16& path,
+ const StringPiece& path,
IDiagnostics* diag) {
- return addFileReferenceImpl(name, config, source, path, nullptr, kValidNameChars, 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::AddFileReferenceAllowMangled(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const Source& source, const StringPiece& 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::AddFileReferenceImpl(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const Source& source, const StringPiece& path, io::IFile* file,
+ const char* valid_chars, IDiagnostics* diag) {
+ std::unique_ptr<FileReference> fileRef =
+ util::make_unique<FileReference>(string_pool.MakeRef(path));
+ fileRef->SetSource(source);
+ fileRef->file = file;
+ return AddResourceImpl(name, ResourceId{}, config, StringPiece{},
+ std::move(fileRef), valid_chars, ResolveValueCollision,
+ diag);
}
-bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
+bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
const ConfigDescription& config,
const StringPiece& product,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return addResourceImpl(name, ResourceId{}, config, product, std::move(value),
- kValidNameMangledChars, resolveValueCollision, diag);
+ return AddResourceImpl(name, ResourceId{}, config, product, std::move(value),
+ kValidNameMangledChars, ResolveValueCollision, diag);
}
-bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
+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);
+ 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()) {
- diag->error(DiagMessage(value->getSource())
- << "resource '"
- << name
- << "' has invalid entry name '"
- << name.entry
- << "'. Invalid character '"
- << StringPiece16(badCharIter, 1)
- << "'");
- return false;
- }
-
- 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;
- }
-
- 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;
- }
-
- 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()));
+bool ResourceTable::AddResourceImpl(
+ const ResourceNameRef& name, const ResourceId& res_id,
+ const ConfigDescription& config, const StringPiece& product,
+ std::unique_ptr<Value> value, const char* valid_chars,
+ const CollisionResolverFunc& conflictResolver, IDiagnostics* diag) {
+ CHECK(value != nullptr);
+ CHECK(diag != nullptr);
+
+ auto bad_char_iter =
+ util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars);
+ if (bad_char_iter != name.entry.end()) {
+ diag->Error(DiagMessage(value->GetSource())
+ << "resource '" << name << "' has invalid entry name '"
+ << name.entry << "'. Invalid character '"
+ << StringPiece(bad_char_iter, 1) << "'");
+ return false;
+ }
+
+ ResourceTablePackage* package = FindOrCreatePackage(name.package);
+ if (res_id.is_valid() && package->id &&
+ package->id.value() != res_id.package_id()) {
+ diag->Error(DiagMessage(value->GetSource())
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " but package '" << package->name << "' already has ID "
+ << std::hex << (int)package->id.value() << std::dec);
+ return false;
+ }
+
+ ResourceTableType* type = package->FindOrCreateType(name.type);
+ if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) {
+ diag->Error(DiagMessage(value->GetSource())
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " but type '" << type->type << "' already has ID "
+ << std::hex << (int)type->id.value() << std::dec);
+ return false;
+ }
+
+ ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+ if (res_id.is_valid() && entry->id &&
+ entry->id.value() != res_id.entry_id()) {
+ diag->Error(DiagMessage(value->GetSource())
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " but resource already has ID "
+ << ResourceId(package->id.value(), type->id.value(),
+ entry->id.value()));
+ return false;
+ }
+
+ ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product);
+ if (!config_value->value) {
+ // Resource does not exist, add it now.
+ config_value->value = std::move(value);
+
+ } else {
+ switch (conflictResolver(config_value->value.get(), value.get())) {
+ case CollisionResult::kTakeNew:
+ // Take the incoming value.
+ config_value->value = std::move(value);
+ break;
+
+ case CollisionResult::kConflict:
+ diag->Error(DiagMessage(value->GetSource())
+ << "duplicate value for resource '" << name << "' "
+ << "with config '" << config << "'");
+ diag->Error(DiagMessage(config_value->value->GetSource())
+ << "resource previously defined here");
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 = conflictResolver(configValue->value.get(), value.get());
- if (collisionResult > 0) {
- // Take the incoming value.
- configValue->value = std::move(value);
- } else if (collisionResult == 0) {
- 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;
- }
+ case CollisionResult::kKeepOriginal:
+ break;
}
+ }
- if (resId.isValid()) {
- package->id = resId.packageId();
- type->id = resId.typeId();
- entry->id = resId.entryId();
- }
- return true;
+ if (res_id.is_valid()) {
+ package->id = res_id.package_id();
+ type->id = res_id.type_id();
+ entry->id = res_id.entry_id();
+ }
+ return true;
}
-bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId& resId,
+bool ResourceTable::SetSymbolState(const ResourceNameRef& name,
+ const ResourceId& res_id,
const Symbol& symbol, IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
+ return SetSymbolStateImpl(name, res_id, symbol, kValidNameChars, diag);
}
-bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
- const ResourceId& resId,
- const Symbol& symbol, IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
+bool ResourceTable::SetSymbolStateAllowMangled(const ResourceNameRef& name,
+ const ResourceId& res_id,
+ const Symbol& symbol,
+ IDiagnostics* diag) {
+ return SetSymbolStateImpl(name, res_id, symbol, kValidNameMangledChars, diag);
}
-bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId,
- const Symbol& symbol, const char16_t* validChars,
+bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name,
+ const ResourceId& res_id,
+ const Symbol& symbol,
+ const char* valid_chars,
IDiagnostics* diag) {
- assert(diag && "diagnostics can't be nullptr");
-
- auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
- if (badCharIter != name.entry.end()) {
- diag->error(DiagMessage(symbol.source)
- << "resource '"
- << name
- << "' has invalid entry name '"
- << name.entry
- << "'. Invalid character '"
- << StringPiece16(badCharIter, 1)
- << "'");
- return false;
- }
-
- 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;
- }
-
- 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;
- }
-
- 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()) {
- package->id = resId.packageId();
- type->id = resId.typeId();
- entry->id = resId.entryId();
- }
-
- // Only mark the type state as public, it doesn't care about being private.
- if (symbol.state == SymbolState::kPublic) {
- type->symbolStatus.state = SymbolState::kPublic;
- }
-
- 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);
+ CHECK(diag != nullptr);
+
+ auto bad_char_iter =
+ util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars);
+ if (bad_char_iter != name.entry.end()) {
+ diag->Error(DiagMessage(symbol.source)
+ << "resource '" << name << "' has invalid entry name '"
+ << name.entry << "'. Invalid character '"
+ << StringPiece(bad_char_iter, 1) << "'");
+ return false;
+ }
+
+ ResourceTablePackage* package = FindOrCreatePackage(name.package);
+ if (res_id.is_valid() && package->id &&
+ package->id.value() != res_id.package_id()) {
+ diag->Error(DiagMessage(symbol.source)
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " but package '" << package->name << "' already has ID "
+ << std::hex << (int)package->id.value() << std::dec);
+ return false;
+ }
+
+ ResourceTableType* type = package->FindOrCreateType(name.type);
+ if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) {
+ diag->Error(DiagMessage(symbol.source)
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " but type '" << type->type << "' already has ID "
+ << std::hex << (int)type->id.value() << std::dec);
+ return false;
+ }
+
+ ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+ if (res_id.is_valid() && entry->id &&
+ entry->id.value() != res_id.entry_id()) {
+ diag->Error(DiagMessage(symbol.source)
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " but resource already has ID "
+ << ResourceId(package->id.value(), type->id.value(),
+ entry->id.value()));
+ return false;
+ }
+
+ if (res_id.is_valid()) {
+ package->id = res_id.package_id();
+ type->id = res_id.type_id();
+ entry->id = res_id.entry_id();
+ }
+
+ // Only mark the type state as public, it doesn't care about being private.
+ if (symbol.state == SymbolState::kPublic) {
+ type->symbol_status.state = SymbolState::kPublic;
+ }
+
+ if (symbol.state == SymbolState::kUndefined &&
+ entry->symbol_status.state != SymbolState::kUndefined) {
+ // We can't undefine a symbol (remove its visibility). Ignore.
return true;
-}
+ }
-Maybe<ResourceTable::SearchResult>
-ResourceTable::findResource(const ResourceNameRef& name) {
- ResourceTablePackage* package = findPackage(name.package);
- if (!package) {
- return {};
- }
+ if (symbol.state == SymbolState::kPrivate &&
+ entry->symbol_status.state == SymbolState::kPublic) {
+ // We can't downgrade public to private. Ignore.
+ return true;
+ }
- ResourceTableType* type = package->findType(name.type);
- if (!type) {
- return {};
- }
+ entry->symbol_status = std::move(symbol);
+ return true;
+}
- ResourceEntry* entry = type->findEntry(name.entry);
- if (!entry) {
- return {};
- }
- return SearchResult{ package, type, entry };
+Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(
+ const ResourceNameRef& name) {
+ ResourceTablePackage* package = FindPackage(name.package);
+ if (!package) {
+ return {};
+ }
+
+ ResourceTableType* type = package->FindType(name.type);
+ if (!type) {
+ return {};
+ }
+
+ ResourceEntry* entry = type->FindEntry(name.entry);
+ if (!entry) {
+ return {};
+ }
+ return SearchResult{package, type, entry};
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index cf9e7b8bfc00..a0c3217df610 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -37,42 +37,43 @@
namespace aapt {
enum class SymbolState {
- kUndefined,
- kPublic,
- kPrivate
+ kUndefined,
+ kPrivate,
+ kPublic,
};
/**
* The Public status of a resource.
*/
struct Symbol {
- SymbolState state = SymbolState::kUndefined;
- Source source;
- std::u16string comment;
+ SymbolState state = SymbolState::kUndefined;
+ Source source;
+ std::string 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);
+ 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);
};
/**
@@ -80,42 +81,44 @@ private:
* varying values for each defined configuration.
*/
class ResourceEntry {
-public:
- /**
- * The name of the resource. Immutable, as
- * this determines the order of this resource
- * when doing lookups.
- */
- const std::u16string name;
-
- /**
- * The entry ID for this resource.
- */
- Maybe<uint16_t> id;
-
- /**
- * Whether this resource is public (and must maintain the same entry ID across builds).
- */
- Symbol symbolStatus;
-
- /**
- * The resource's values for each configuration.
- */
- std::vector<std::unique_ptr<ResourceConfigValue>> values;
-
- explicit ResourceEntry(const StringPiece16& name) : name(name.toString()) { }
-
- 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);
- std::vector<ResourceConfigValue*> findValuesIf(
- const std::function<bool(ResourceConfigValue*)>& f);
-
-
-private:
- DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
+ public:
+ /**
+ * The name of the resource. Immutable, as
+ * this determines the order of this resource
+ * when doing lookups.
+ */
+ const std::string name;
+
+ /**
+ * The entry ID for this resource.
+ */
+ Maybe<uint16_t> id;
+
+ /**
+ * Whether this resource is public (and must maintain the same entry ID across
+ * builds).
+ */
+ Symbol symbol_status;
+
+ /**
+ * The resource's values for each configuration.
+ */
+ std::vector<std::unique_ptr<ResourceConfigValue>> values;
+
+ explicit ResourceEntry(const StringPiece& name) : name(name.ToString()) {}
+
+ 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);
+ std::vector<ResourceConfigValue*> FindValuesIf(
+ const std::function<bool(ResourceConfigValue*)>& f);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
};
/**
@@ -123,58 +126,53 @@ private:
* for this type.
*/
class ResourceTableType {
-public:
- /**
- * The logical type of resource (string, drawable, layout, etc.).
- */
- const ResourceType type;
-
- /**
- * The type ID for this resource.
- */
- Maybe<uint8_t> id;
-
- /**
- * Whether this type is public (and must maintain the same
- * type ID across builds).
- */
- Symbol symbolStatus;
-
- /**
- * List of resources for this type.
- */
- std::vector<std::unique_ptr<ResourceEntry>> entries;
-
- explicit ResourceTableType(const ResourceType type) : type(type) { }
-
- ResourceEntry* findEntry(const StringPiece16& name);
- ResourceEntry* findOrCreateEntry(const StringPiece16& name);
-
-private:
- DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
+ public:
+ /**
+ * The logical type of resource (string, drawable, layout, etc.).
+ */
+ const ResourceType type;
+
+ /**
+ * The type ID for this resource.
+ */
+ Maybe<uint8_t> id;
+
+ /**
+ * Whether this type is public (and must maintain the same
+ * type ID across builds).
+ */
+ Symbol symbol_status;
+
+ /**
+ * List of resources for this type.
+ */
+ std::vector<std::unique_ptr<ResourceEntry>> entries;
+
+ explicit ResourceTableType(const ResourceType type) : type(type) {}
+
+ ResourceEntry* FindEntry(const StringPiece& name);
+ ResourceEntry* FindOrCreateEntry(const StringPiece& name);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
};
-enum class PackageType {
- System,
- Vendor,
- App,
- Dynamic
-};
+enum class PackageType { System, Vendor, App, Dynamic };
class ResourceTablePackage {
-public:
- PackageType type = PackageType::App;
- Maybe<uint8_t> id;
- std::u16string name;
+ public:
+ PackageType type = PackageType::App;
+ Maybe<uint8_t> id;
+ std::string name;
- std::vector<std::unique_ptr<ResourceTableType>> types;
+ std::vector<std::unique_ptr<ResourceTableType>> types;
- ResourceTablePackage() = default;
- ResourceTableType* findType(ResourceType type);
- ResourceTableType* findOrCreateType(const ResourceType type);
+ ResourceTablePackage() = default;
+ ResourceTableType* FindType(ResourceType type);
+ ResourceTableType* FindOrCreateType(const ResourceType type);
-private:
- DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
};
/**
@@ -182,135 +180,129 @@ private:
* flattened into a binary resource table (resources.arsc).
*/
class ResourceTable {
-public:
- ResourceTable() = default;
-
- /**
- * 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 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,
+ public:
+ ResourceTable() = default;
+
+ enum class CollisionResult { kKeepOriginal, kConflict, kTakeNew };
+
+ using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
+
+ /**
+ * When a collision of resources occurs, this method decides which value to
+ * keep.
+ */
+ static CollisionResult 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& res_id,
+ 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 StringPiece& path, IDiagnostics* diag);
+
+ bool AddFileReferenceAllowMangled(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece& path, io::IFile* file,
IDiagnostics* diag);
- struct SearchResult {
- ResourceTablePackage* package;
- ResourceTableType* type;
- ResourceEntry* entry;
- };
-
- Maybe<SearchResult> findResource(const ResourceNameRef& name);
-
- /**
- * 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;
-
- /**
- * The list of packages in this table, sorted alphabetically by package name.
- */
- std::vector<std::unique_ptr<ResourceTablePackage>> packages;
-
- /**
- * 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.
- */
- ResourceTablePackage* findPackage(const StringPiece16& name);
-
- ResourceTablePackage* findPackageById(uint8_t id);
-
- ResourceTablePackage* createPackage(const StringPiece16& name, Maybe<uint8_t> id = {});
-
-private:
- 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,
- 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);
-
- bool setSymbolStateImpl(const ResourceNameRef& name,
- const ResourceId& resId,
- const Symbol& symbol,
- const char16_t* validChars,
+ /**
+ * 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 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& res_id,
+ const Symbol& symbol, IDiagnostics* diag);
+
+ bool SetSymbolStateAllowMangled(const ResourceNameRef& name,
+ const ResourceId& res_id,
+ const Symbol& symbol, IDiagnostics* diag);
+
+ struct SearchResult {
+ ResourceTablePackage* package;
+ ResourceTableType* type;
+ ResourceEntry* entry;
+ };
+
+ Maybe<SearchResult> FindResource(const ResourceNameRef& name);
+
+ /**
+ * The string pool used by this resource table. Values that reference strings
+ * must use
+ * this pool to create their strings.
+ *
+ * NOTE: `string_pool` 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 string_pool;
+
+ /**
+ * The list of packages in this table, sorted alphabetically by package name.
+ */
+ std::vector<std::unique_ptr<ResourceTablePackage>> packages;
+
+ /**
+ * 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.
+ */
+ ResourceTablePackage* FindPackage(const StringPiece& name);
+
+ ResourceTablePackage* FindPackageById(uint8_t id);
+
+ ResourceTablePackage* CreatePackage(const StringPiece& name,
+ Maybe<uint8_t> id = {});
+
+ private:
+ ResourceTablePackage* FindOrCreatePackage(const StringPiece& name);
+
+ bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
+ const ConfigDescription& config,
+ const StringPiece& product, std::unique_ptr<Value> value,
+ const char* valid_chars,
+ const CollisionResolverFunc& conflict_resolver,
+ IDiagnostics* diag);
+
+ bool AddFileReferenceImpl(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source, const StringPiece& path,
+ io::IFile* file, const char* valid_chars,
IDiagnostics* diag);
- DISALLOW_COPY_AND_ASSIGN(ResourceTable);
+ bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
+ const Symbol& symbol, const char* valid_chars,
+ IDiagnostics* diag);
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceTable);
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RESOURCE_TABLE_H
+#endif // AAPT_RESOURCE_TABLE_H
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index d6c52ab83d93..cb3699a00f75 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -14,140 +14,126 @@
* limitations under the License.
*/
-#include "Diagnostics.h"
#include "ResourceTable.h"
+#include "Diagnostics.h"
#include "ResourceValues.h"
+#include "test/Test.h"
#include "util/Util.h"
-#include "test/Builders.h"
-
#include <algorithm>
-#include <gtest/gtest.h>
#include <ostream>
#include <string>
namespace aapt {
TEST(ResourceTableTest, FailToAddResourceWithBadName) {
- ResourceTable table;
-
- EXPECT_FALSE(table.addResource(
- 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"),
- ConfigDescription{}, "",
- test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
- test::getDiagnostics()));
+ ResourceTable table;
+
+ EXPECT_FALSE(table.AddResource(
+ test::ParseNameOrDie("android:id/hey,there"), ConfigDescription{}, "",
+ test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(),
+ test::GetDiagnostics()));
+
+ EXPECT_FALSE(table.AddResource(
+ test::ParseNameOrDie("android:id/hey:there"), ConfigDescription{}, "",
+ test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(),
+ test::GetDiagnostics()));
}
TEST(ResourceTableTest, AddOneResource) {
- ResourceTable table;
+ ResourceTable table;
- EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"),
- ConfigDescription{},
- "",
- test::ValueBuilder<Id>()
- .setSource("test/path/file.xml", 23u).build(),
- test::getDiagnostics()));
+ EXPECT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:attr/id"), ConfigDescription{}, "",
+ test::ValueBuilder<Id>().SetSource("test/path/file.xml", 23u).Build(),
+ test::GetDiagnostics()));
- ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
+ ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/id"));
}
TEST(ResourceTableTest, AddMultipleResources) {
- ResourceTable table;
-
- ConfigDescription config;
- ConfigDescription languageConfig;
- memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
-
- EXPECT_TRUE(table.addResource(
- test::parseNameOrDie(u"@android:attr/layout_width"),
- config,
- "",
- test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
- test::getDiagnostics()));
-
- EXPECT_TRUE(table.addResource(
- test::parseNameOrDie(u"@android:attr/id"),
- config,
- "",
- test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
- test::getDiagnostics()));
-
- EXPECT_TRUE(table.addResource(
- test::parseNameOrDie(u"@android:string/ok"),
- config,
- "",
- test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
- test::getDiagnostics()));
-
- EXPECT_TRUE(table.addResource(
- 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));
+ ResourceTable table;
+
+ ConfigDescription config;
+ ConfigDescription language_config;
+ memcpy(language_config.language, "pl", sizeof(language_config.language));
+
+ EXPECT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:attr/layout_width"), config, "",
+ test::ValueBuilder<Id>().SetSource("test/path/file.xml", 10u).Build(),
+ test::GetDiagnostics()));
+
+ EXPECT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:attr/id"), config, "",
+ test::ValueBuilder<Id>().SetSource("test/path/file.xml", 12u).Build(),
+ test::GetDiagnostics()));
+
+ EXPECT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:string/ok"), config, "",
+ test::ValueBuilder<Id>().SetSource("test/path/file.xml", 14u).Build(),
+ test::GetDiagnostics()));
+
+ EXPECT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:string/ok"), language_config, "",
+ test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
+ .SetSource("test/path/file.xml", 20u)
+ .Build(),
+ test::GetDiagnostics()));
+
+ ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/layout_width"));
+ ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/id"));
+ ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:string/ok"));
+ ASSERT_NE(nullptr, test::GetValueForConfig<BinaryPrimitive>(
+ &table, "android:string/ok", language_config));
}
TEST(ResourceTableTest, OverrideWeakResourceValue) {
- ResourceTable table;
+ ResourceTable table;
- ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
- "", util::make_unique<Attribute>(true), test::getDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("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());
+ Attribute* attr = test::GetValue<Attribute>(&table, "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()));
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("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());
+ attr = test::GetValue<Attribute>(&table, "android:attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_FALSE(attr->IsWeak());
}
TEST(ResourceTableTest, ProductVaryingValues) {
- ResourceTable table;
-
- 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);
+ ResourceTable table;
+
+ EXPECT_TRUE(table.AddResource(test::ParseNameOrDie("android:string/foo"),
+ test::ParseConfigOrDie("land"), "tablet",
+ util::make_unique<Id>(),
+ test::GetDiagnostics()));
+ EXPECT_TRUE(table.AddResource(test::ParseNameOrDie("android:string/foo"),
+ test::ParseConfigOrDie("land"), "phone",
+ util::make_unique<Id>(),
+ test::GetDiagnostics()));
+
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
+ &table, "android:string/foo",
+ test::ParseConfigOrDie("land"), "tablet"));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
+ &table, "android:string/foo",
+ test::ParseConfigOrDie("land"), "phone"));
+
+ Maybe<ResourceTable::SearchResult> sr =
+ table.FindResource(test::ParseNameOrDie("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
+} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index a0a7efc46476..fce9b338d726 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -14,167 +14,211 @@
* limitations under the License.
*/
-#include "NameMangler.h"
#include "ResourceUtils.h"
+
+#include <sstream>
+
+#include "androidfw/ResourceTypes.h"
+
+#include "NameMangler.h"
+#include "SdkConstants.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);
+Maybe<ResourceName> ToResourceName(
+ const android::ResTable::resource_name& name_in) {
+ ResourceName name_out;
+ if (!name_in.package) {
+ return {};
+ }
+
+ name_out.package =
+ util::Utf16ToUtf8(StringPiece16(name_in.package, name_in.packageLen));
+
+ const ResourceType* type;
+ if (name_in.type) {
+ type = ParseResourceType(
+ util::Utf16ToUtf8(StringPiece16(name_in.type, name_in.typeLen)));
+ } else if (name_in.type8) {
+ type = ParseResourceType(StringPiece(name_in.type8, name_in.typeLen));
+ } else {
+ return {};
+ }
+
+ if (!type) {
+ return {};
+ }
- return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
+ name_out.type = *type;
+
+ if (name_in.name) {
+ name_out.entry =
+ util::Utf16ToUtf8(StringPiece16(name_in.name, name_in.nameLen));
+ } else if (name_in.name8) {
+ name_out.entry = StringPiece(name_in.name8, name_in.nameLen).ToString();
+ } else {
+ return {};
+ }
+ return name_out;
+}
+
+bool ExtractResourceName(const StringPiece& str, StringPiece* out_package,
+ StringPiece* out_type, StringPiece* out_entry) {
+ bool has_package_separator = false;
+ bool has_type_separator = false;
+ const char* start = str.data();
+ const char* end = start + str.size();
+ const char* current = start;
+ while (current != end) {
+ if (out_type->size() == 0 && *current == '/') {
+ has_type_separator = true;
+ out_type->assign(start, current - start);
+ start = current + 1;
+ } else if (out_package->size() == 0 && *current == ':') {
+ has_package_separator = true;
+ out_package->assign(start, current - start);
+ start = current + 1;
+ }
+ current++;
+ }
+ out_entry->assign(start, end - start);
+
+ return !(has_package_separator && out_package->empty()) &&
+ !(has_type_separator && out_type->empty());
}
-bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
- if (str.empty()) {
- return false;
+bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref,
+ bool* out_private) {
+ if (str.empty()) {
+ return false;
+ }
+
+ size_t offset = 0;
+ bool priv = false;
+ if (str.data()[0] == '*') {
+ priv = true;
+ offset = 1;
+ }
+
+ StringPiece package;
+ StringPiece type;
+ StringPiece entry;
+ if (!ExtractResourceName(str.substr(offset, str.size() - offset), &package,
+ &type, &entry)) {
+ return false;
+ }
+
+ const ResourceType* parsed_type = ParseResourceType(type);
+ if (!parsed_type) {
+ return false;
+ }
+
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (out_ref) {
+ out_ref->package = package;
+ out_ref->type = *parsed_type;
+ out_ref->entry = entry;
+ }
+
+ if (out_private) {
+ *out_private = priv;
+ }
+ return true;
+}
+
+bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref,
+ bool* out_create, bool* out_private) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ if (trimmed_str.empty()) {
+ return false;
+ }
+
+ bool create = false;
+ bool priv = false;
+ if (trimmed_str.data()[0] == '@') {
+ size_t offset = 1;
+ if (trimmed_str.data()[1] == '+') {
+ create = true;
+ offset += 1;
}
- size_t offset = 0;
- bool priv = false;
- if (str.data()[0] == u'*') {
- priv = true;
- offset = 1;
+ ResourceNameRef name;
+ if (!ParseResourceName(
+ trimmed_str.substr(offset, trimmed_str.size() - offset), &name,
+ &priv)) {
+ return false;
}
- StringPiece16 package;
- StringPiece16 type;
- StringPiece16 entry;
- if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
- return false;
+ if (create && priv) {
+ return false;
}
- const ResourceType* parsedType = parseResourceType(type);
- if (!parsedType) {
- return false;
+ if (create && name.type != ResourceType::kId) {
+ return false;
}
- if (entry.empty()) {
- return false;
+ if (out_ref) {
+ *out_ref = name;
}
- if (outRef) {
- outRef->package = package;
- outRef->type = *parsedType;
- outRef->entry = entry;
+ if (out_create) {
+ *out_create = create;
}
- if (outPrivate) {
- *outPrivate = priv;
+ if (out_private) {
+ *out_private = priv;
}
return true;
+ }
+ return false;
}
-bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
- bool* outPrivate) {
- StringPiece16 trimmedStr(util::trimWhitespace(str));
- if (trimmedStr.empty()) {
- return false;
- }
+bool IsReference(const StringPiece& str) {
+ return ParseReference(str, nullptr, nullptr, nullptr);
+}
- 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;
- }
+bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ if (trimmed_str.empty()) {
return false;
-}
+ }
-bool isReference(const StringPiece16& str) {
- return tryParseReference(str, nullptr, nullptr, nullptr);
-}
+ if (*trimmed_str.data() == '?') {
+ StringPiece package;
+ StringPiece type;
+ StringPiece entry;
+ if (!ExtractResourceName(trimmed_str.substr(1, trimmed_str.size() - 1),
+ &package, &type, &entry)) {
+ return false;
+ }
-bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
- StringPiece16 trimmedStr(util::trimWhitespace(str));
- if (trimmedStr.empty()) {
- return false;
+ if (!type.empty() && type != "attr") {
+ 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;
+ if (entry.empty()) {
+ return false;
}
- return false;
+
+ if (out_ref) {
+ out_ref->package = package;
+ out_ref->type = ResourceType::kAttr;
+ out_ref->entry = entry;
+ }
+ return true;
+ }
+ return false;
}
-bool isAttributeReference(const StringPiece16& str) {
- return tryParseAttributeReference(str, nullptr);
+bool IsAttributeReference(const StringPiece& str) {
+ return ParseAttributeReference(str, nullptr);
}
/*
@@ -185,396 +229,473 @@ bool isAttributeReference(const StringPiece16& str) {
* <[*]package>:[style/]<entry>
* [[*]package:style/]<entry>
*/
-Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
- if (str.empty()) {
- return {};
- }
+Maybe<Reference> ParseStyleParentReference(const StringPiece& str,
+ std::string* out_error) {
+ if (str.empty()) {
+ return {};
+ }
+
+ StringPiece name = str;
+
+ bool has_leading_identifiers = false;
+ bool private_ref = false;
+
+ // Skip over these identifiers. A style's parent is a normal reference.
+ if (name.data()[0] == '@' || name.data()[0] == '?') {
+ has_leading_identifiers = true;
+ name = name.substr(1, name.size() - 1);
+ }
+
+ if (name.data()[0] == '*') {
+ private_ref = true;
+ name = name.substr(1, name.size() - 1);
+ }
+
+ ResourceNameRef ref;
+ ref.type = ResourceType::kStyle;
+
+ StringPiece type_str;
+ ExtractResourceName(name, &ref.package, &type_str, &ref.entry);
+ if (!type_str.empty()) {
+ // If we have a type, make sure it is a Style.
+ const ResourceType* parsed_type = ParseResourceType(type_str);
+ if (!parsed_type || *parsed_type != ResourceType::kStyle) {
+ std::stringstream err;
+ err << "invalid resource type '" << type_str << "' for parent of style";
+ *out_error = err.str();
+ return {};
+ }
+ }
+
+ if (!has_leading_identifiers && ref.package.empty() && !type_str.empty()) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *out_error = err.str();
+ return {};
+ }
- StringPiece16 name = str;
+ Reference result(ref);
+ result.private_reference = private_ref;
+ return result;
+}
- bool hasLeadingIdentifiers = false;
- bool privateRef = false;
+Maybe<Reference> ParseXmlAttributeName(const StringPiece& str) {
+ StringPiece trimmed_str = util::TrimWhitespace(str);
+ const char* start = trimmed_str.data();
+ const char* const end = start + trimmed_str.size();
+ const char* p = start;
+
+ Reference ref;
+ if (p != end && *p == '*') {
+ ref.private_reference = true;
+ start++;
+ p++;
+ }
+
+ StringPiece package;
+ StringPiece name;
+ while (p != end) {
+ if (*p == ':') {
+ package = StringPiece(start, p - start);
+ name = StringPiece(p + 1, end - (p + 1));
+ break;
+ }
+ p++;
+ }
+
+ ref.name =
+ ResourceName(package.ToString(), ResourceType::kAttr,
+ name.empty() ? trimmed_str.ToString() : name.ToString());
+ return Maybe<Reference>(std::move(ref));
+}
- // 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);
- }
+std::unique_ptr<Reference> TryParseReference(const StringPiece& str,
+ bool* out_create) {
+ ResourceNameRef ref;
+ bool private_ref = false;
+ if (ParseReference(str, &ref, out_create, &private_ref)) {
+ std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
+ value->private_reference = private_ref;
+ return value;
+ }
+
+ if (ParseAttributeReference(str, &ref)) {
+ if (out_create) {
+ *out_create = false;
+ }
+ return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+ }
+ return {};
+}
- if (name.data()[0] == u'*') {
- privateRef = true;
- name = name.substr(1, name.size() - 1);
- }
+std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ android::Res_value value = {};
+ if (trimmed_str == "@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 (trimmed_str == "@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);
+}
- 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 {};
- }
- }
+std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
+ const StringPiece& str) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ for (const Attribute::Symbol& symbol : enum_attr->symbols) {
+ // Enum symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& enum_symbol_resource_name = symbol.symbol.name.value();
+ if (trimmed_str == enum_symbol_resource_name.entry) {
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_DEC;
+ value.data = symbol.value;
+ return util::make_unique<BinaryPrimitive>(value);
+ }
+ }
+ return {};
+}
- if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
- std::stringstream err;
- err << "invalid parent reference '" << str << "'";
- *outError = err.str();
- return {};
- }
+std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr,
+ const StringPiece& str) {
+ android::Res_value flags = {};
+ flags.dataType = android::Res_value::TYPE_INT_HEX;
+ flags.data = 0u;
- Reference result(ref);
- result.privateReference = privateRef;
- return result;
-}
+ if (util::TrimWhitespace(str).empty()) {
+ // Empty string is a valid flag (0).
+ return util::make_unique<BinaryPrimitive>(flags);
+ }
-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;
+ for (StringPiece part : util::Tokenize(str, '|')) {
+ StringPiece trimmed_part = util::TrimWhitespace(part);
+
+ bool flag_set = false;
+ for (const Attribute::Symbol& symbol : flag_attr->symbols) {
+ // Flag symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& flag_symbol_resource_name =
+ symbol.symbol.name.value();
+ if (trimmed_part == flag_symbol_resource_name.entry) {
+ flags.data |= symbol.value;
+ flag_set = true;
+ break;
+ }
}
- if (tryParseAttributeReference(str, &ref)) {
- if (outCreate) {
- *outCreate = false;
- }
- return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+ if (!flag_set) {
+ return {};
}
- return {};
+ }
+ return util::make_unique<BinaryPrimitive>(flags);
}
-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);
+static uint32_t ParseHex(char c, bool* out_error) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 0xa;
+ } else if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 0xa;
+ } else {
+ *out_error = true;
+ return 0xffffffffu;
+ }
}
-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);
- }
- }
+std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) {
+ StringPiece color_str(util::TrimWhitespace(str));
+ const char* start = color_str.data();
+ const size_t len = color_str.size();
+ if (len == 0 || start[0] != '#') {
+ 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> tryParseFlagSymbol(const Attribute* flagAttr,
- const StringPiece16& str) {
- android::Res_value flags = { };
- flags.dataType = android::Res_value::TYPE_INT_HEX;
- 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);
+Maybe<bool> ParseBool(const StringPiece& str) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") {
+ return Maybe<bool>(true);
+ } else if (trimmed_str == "false" || trimmed_str == "FALSE" ||
+ trimmed_str == "False") {
+ return Maybe<bool>(false);
+ }
+ return {};
}
-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;
- }
+Maybe<uint32_t> ParseInt(const StringPiece& str) {
+ std::u16string str16 = util::Utf8ToUtf16(str);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
+ return value.data;
+ }
+ return {};
}
-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 {};
- }
+Maybe<ResourceId> ParseResourceId(const StringPiece& str) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
- 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 {};
+ std::u16string str16 = util::Utf8ToUtf16(trimmed_str);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
+ if (value.dataType == android::Res_value::TYPE_INT_HEX) {
+ ResourceId id(value.data);
+ if (id.is_valid()) {
+ return id;
+ }
}
- return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
+ }
+ return {};
}
-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;
+Maybe<int> ParseSdkVersion(const StringPiece& str) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+
+ std::u16string str16 = util::Utf8ToUtf16(trimmed_str);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
+ return static_cast<int>(value.data);
+ }
+
+ // Try parsing the code name.
+ std::pair<StringPiece, int> entry = GetDevelopmentSdkCodeNameAndVersion();
+ if (entry.first == trimmed_str) {
+ return entry.second;
+ }
+ return {};
}
-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> TryParseBool(const StringPiece& str) {
+ if (Maybe<bool> maybe_result = ParseBool(str)) {
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
-std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
- android::Res_value value;
- if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
- return {};
+ if (maybe_result.value()) {
+ value.data = 0xffffffffu;
+ } else {
+ value.data = 0;
}
return util::make_unique<BinaryPrimitive>(value);
+ }
+ return {};
}
-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);
+std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) {
+ std::u16string str16 = util::Utf8ToUtf16(str);
+ android::Res_value value;
+ if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) {
+ std::u16string str16 = util::Utf8ToUtf16(str);
+ android::Res_value value;
+ if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
}
-uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
- switch (type) {
+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;
+ return android::ResTable_map::TYPE_REFERENCE;
case android::Res_value::TYPE_STRING:
- return android::ResTable_map::TYPE_STRING;
+ return android::ResTable_map::TYPE_STRING;
case android::Res_value::TYPE_FLOAT:
- return android::ResTable_map::TYPE_FLOAT;
+ return android::ResTable_map::TYPE_FLOAT;
case android::Res_value::TYPE_DIMENSION:
- return android::ResTable_map::TYPE_DIMENSION;
+ return android::ResTable_map::TYPE_DIMENSION;
case android::Res_value::TYPE_FRACTION:
- return android::ResTable_map::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;
+ 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;
+ 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;
+ return android::ResTable_map::TYPE_COLOR;
default:
- return 0;
- };
+ 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 {};
+std::unique_ptr<Item> TryParseItemForAttribute(
+ const StringPiece& value, uint32_t type_mask,
+ const std::function<void(const ResourceName&)>& on_create_reference) {
+ std::unique_ptr<BinaryPrimitive> null_or_empty = TryParseNullOrEmpty(value);
+ if (null_or_empty) {
+ return std::move(null_or_empty);
+ }
+
+ bool create = false;
+ std::unique_ptr<Reference> reference = TryParseReference(value, &create);
+ if (reference) {
+ if (create && on_create_reference) {
+ on_create_reference(reference->name.value());
+ }
+ return std::move(reference);
+ }
+
+ if (type_mask & 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 (type_mask & 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 (type_mask & 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 float_mask = android::ResTable_map::TYPE_FLOAT |
+ android::ResTable_map::TYPE_DIMENSION |
+ android::ResTable_map::TYPE_FRACTION;
+ if (type_mask & float_mask) {
+ // Try parsing this as a float.
+ std::unique_ptr<BinaryPrimitive> floating_point = TryParseFloat(value);
+ if (floating_point) {
+ if (type_mask &
+ AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) {
+ return std::move(floating_point);
+ }
+ }
+ }
+ 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::unique_ptr<Item> TryParseItemForAttribute(
+ const StringPiece& str, const Attribute* attr,
+ const std::function<void(const ResourceName&)>& on_create_reference) {
+ const uint32_t type_mask = attr->type_mask;
+ std::unique_ptr<Item> value =
+ TryParseItemForAttribute(str, type_mask, on_create_reference);
+ if (value) {
+ return value;
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_ENUM) {
+ // Try parsing this as an enum.
+ std::unique_ptr<BinaryPrimitive> enum_value = TryParseEnumSymbol(attr, str);
+ if (enum_value) {
+ return std::move(enum_value);
+ }
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_FLAGS) {
+ // Try parsing this as a flag.
+ std::unique_ptr<BinaryPrimitive> flag_value = TryParseFlagSymbol(attr, str);
+ if (flag_value) {
+ return std::move(flag_value);
+ }
+ }
+ 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();
+std::string BuildResourceFileName(const ResourceFile& res_file,
+ const NameMangler* mangler) {
+ std::stringstream out;
+ out << "res/" << res_file.name.type;
+ if (res_file.config != ConfigDescription{}) {
+ out << "-" << res_file.config;
+ }
+ out << "/";
+
+ if (mangler && mangler->ShouldMangle(res_file.name.package)) {
+ out << NameMangler::MangleEntry(res_file.name.package, res_file.name.entry);
+ } else {
+ out << res_file.name.entry;
+ }
+ out << file::GetExtension(res_file.source.path);
+ return out.str();
}
-} // namespace ResourceUtils
-} // namespace aapt
+} // namespace ResourceUtils
+} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index a0fbcc6e700b..9766f6a7b2fa 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -17,14 +17,14 @@
#ifndef AAPT_RESOURCEUTILS_H
#define AAPT_RESOURCEUTILS_H
+#include <functional>
+#include <memory>
+
#include "NameMangler.h"
#include "Resource.h"
#include "ResourceValues.h"
#include "util/StringPiece.h"
-#include <functional>
-#include <memory>
-
namespace aapt {
namespace ResourceUtils {
@@ -37,136 +37,183 @@ namespace ResourceUtils {
* 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);
+bool ExtractResourceName(const StringPiece& str, StringPiece* out_package,
+ StringPiece* out_type, StringPiece* out_entry);
/**
- * 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.
+ * Returns true if the string was parsed as a resource name
+ * ([*][package:]type/name), with
+ * `out_resource` set to the parsed resource name and `out_private` set to true
+ * if a '*' prefix was present.
*/
-bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource,
- bool* outPrivate = nullptr);
+bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_resource,
+ bool* out_private = nullptr);
/*
- * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
- * `outReference` set to the parsed reference.
+ * Returns true if the string was parsed as a reference
+ * (@[+][package:]type/name), with
+ * `out_reference` 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.
+ * If '+' was present in the reference, `out_create` is set to true.
+ * If '*' was present in the reference, `out_private` is set to true.
*/
-bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
- bool* outCreate = nullptr, bool* outPrivate = nullptr);
+bool ParseReference(const StringPiece& str, ResourceNameRef* out_reference,
+ bool* out_create = nullptr, bool* out_private = nullptr);
/*
- * Returns true if the string is in the form of a resource reference (@[+][package:]type/name).
+ * Returns true if the string is in the form of a resource reference
+ * (@[+][package:]type/name).
*/
-bool isReference(const StringPiece16& str);
+bool IsReference(const StringPiece& str);
/*
- * Returns true if the string was parsed as an attribute reference (?[package:][type/]name),
- * with `outReference` set to the parsed reference.
+ * Returns true if the string was parsed as an attribute reference
+ * (?[package:][type/]name),
+ * with `out_reference` set to the parsed reference.
+ */
+bool ParseAttributeReference(const StringPiece& str,
+ ResourceNameRef* out_reference);
+
+/**
+ * Returns true if the string is in the form of an attribute
+ * reference(?[package:][type/]name).
*/
-bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
+bool IsAttributeReference(const StringPiece& str);
/**
- * Returns true if the string is in the form of an attribute reference(?[package:][type/]name).
+ * Convert an android::ResTable::resource_name to an aapt::ResourceName struct.
*/
-bool isAttributeReference(const StringPiece16& str);
+Maybe<ResourceName> ToResourceName(
+ const android::ResTable::resource_name& name);
/**
- * Returns true if the value is a boolean, putting the result in `outValue`.
+ * Returns a boolean value if the string is equal to TRUE, true, True, FALSE,
+ * false, or False.
*/
-bool tryParseBool(const StringPiece16& str, bool* outValue);
+Maybe<bool> ParseBool(const StringPiece& str);
+
+/**
+ * Returns a uint32_t if the string is an integer.
+ */
+Maybe<uint32_t> ParseInt(const StringPiece& str);
+
+/**
+ * Returns an ID if it the string represented a valid ID.
+ */
+Maybe<ResourceId> ParseResourceId(const StringPiece& str);
+
+/**
+ * Parses an SDK version, which can be an integer, or a letter from A-Z.
+ */
+Maybe<int> ParseSdkVersion(const StringPiece& str);
/*
- * Returns a Reference, or None Maybe instance if the string `str` was parsed as a
+ * 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:
+ * 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);
+Maybe<Reference> ParseStyleParentReference(const StringPiece& str,
+ std::string* out_error);
/*
- * 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
+ * Returns a Reference if the string `str` was parsed as a valid XML attribute
+ * name.
+ * The valid format for an XML attribute name is:
+ *
+ * package:entry
+ */
+Maybe<Reference> ParseXmlAttributeName(const StringPiece& str);
+
+/*
+ * 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);
+std::unique_ptr<Reference> TryParseReference(const StringPiece& str,
+ bool* out_create = nullptr);
/*
- * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
- * as one.
+ * Returns a BinaryPrimitve object representing @null or @empty if the string
+ * was parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
+std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str);
/*
* Returns a BinaryPrimitve object representing a color if the string was parsed
* as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
+std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing a boolean if the string was parsed
- * as one.
+ * Returns a BinaryPrimitve object representing a boolean if the string was
+ * parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
+std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing an integer if the string was parsed
- * as one.
+ * Returns a BinaryPrimitve object representing an integer if the string was
+ * parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
+std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& 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);
+std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
- * as one.
+ * 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);
+std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
+ const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
- * as one.
+ * 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);
+std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
+ const StringPiece& str);
/*
- * Try to convert a string to an Item for the given attribute. The attribute will
+ * 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
+ * The callback function on_create_reference 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> TryParseItemForAttribute(
+ const StringPiece& value, const Attribute* attr,
+ const std::function<void(const ResourceName&)>& on_create_reference = {});
-std::unique_ptr<Item> parseItemForAttribute(
- const StringPiece16& value, uint32_t typeMask,
- std::function<void(const ResourceName&)> onCreateReference = {});
+std::unique_ptr<Item> TryParseItemForAttribute(
+ const StringPiece& value, uint32_t type_mask,
+ const std::function<void(const ResourceName&)>& on_create_reference = {});
-uint32_t androidTypeToAttributeTypeMask(uint16_t type);
+uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
/**
- * Returns a string path suitable for use within an APK. The path will look like:
+ * 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
+ * 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);
+std::string BuildResourceFileName(const ResourceFile& res_file,
+ const NameMangler* mangler = nullptr);
-} // namespace ResourceUtils
-} // namespace aapt
+} // namespace ResourceUtils
+} // namespace aapt
#endif /* AAPT_RESOURCEUTILS_H */
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 7425f97ef8de..f9c500b42c13 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -14,194 +14,191 @@
* limitations under the License.
*/
-#include "Resource.h"
#include "ResourceUtils.h"
-#include "test/Builders.h"
-#include "test/Common.h"
-#include <gtest/gtest.h>
+#include "Resource.h"
+#include "test/Test.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);
+ EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("true"));
+ EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("TRUE"));
+ EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("True"));
+ EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("false"));
+ EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("FALSE"));
+ EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("False"));
}
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));
+ ResourceNameRef actual;
+ bool actual_priv = false;
+ EXPECT_TRUE(ResourceUtils::ParseResourceName("android:color/foo", &actual,
+ &actual_priv));
+ EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
+ EXPECT_FALSE(actual_priv);
+
+ EXPECT_TRUE(
+ ResourceUtils::ParseResourceName("color/foo", &actual, &actual_priv));
+ EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual);
+ EXPECT_FALSE(actual_priv);
+
+ EXPECT_TRUE(ResourceUtils::ParseResourceName("*android:color/foo", &actual,
+ &actual_priv));
+ EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
+ EXPECT_TRUE(actual_priv);
+
+ EXPECT_FALSE(
+ ResourceUtils::ParseResourceName(StringPiece(), &actual, &actual_priv));
}
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);
+ ResourceNameRef expected({}, ResourceType::kColor, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("@color/foo", &actual, &create,
+ &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(private_ref);
}
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);
+ ResourceNameRef expected("android", ResourceType::kColor, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("@android:color/foo", &actual,
+ &create, &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(private_ref);
}
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);
+ ResourceNameRef expected("android", ResourceType::kColor, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("\t @android:color/foo\n \n\t",
+ &actual, &create, &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(private_ref);
}
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);
+ ResourceNameRef expected("android", ResourceType::kId, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("@+android:id/foo", &actual,
+ &create, &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_TRUE(create);
+ EXPECT_FALSE(private_ref);
}
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);
+ ResourceNameRef expected("android", ResourceType::kId, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("@*android:id/foo", &actual,
+ &create, &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_TRUE(private_ref);
}
TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
- bool create = false;
- bool privateRef = false;
- ResourceNameRef actual;
- EXPECT_FALSE(ResourceUtils::tryParseReference(u"@+android:color/foo", &actual, &create,
- &privateRef));
+ bool create = false;
+ bool private_ref = false;
+ ResourceNameRef actual;
+ EXPECT_FALSE(ResourceUtils::ParseReference("@+android:color/foo", &actual,
+ &create, &private_ref));
}
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"));
+ EXPECT_TRUE(ResourceUtils::IsAttributeReference("?android"));
+ EXPECT_TRUE(ResourceUtils::IsAttributeReference("?android:foo"));
+ EXPECT_TRUE(ResourceUtils::IsAttributeReference("?attr/foo"));
+ EXPECT_TRUE(ResourceUtils::IsAttributeReference("?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"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?style/foo"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?android:style/foo"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?android:"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?android:attr/"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?:attr/"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?:attr/foo"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?:/"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?:/foo"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?attr/"));
+ EXPECT_FALSE(ResourceUtils::IsAttributeReference("?/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);
+ const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle,
+ "foo");
+ const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo");
+
+ std::string err_str;
+ Maybe<Reference> ref =
+ ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::ParseStyleParentReference("@style/foo", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+ ref =
+ ResourceUtils::ParseStyleParentReference("?android:style/foo", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::ParseStyleParentReference("?style/foo", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+ ref = ResourceUtils::ParseStyleParentReference("android:style/foo", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::ParseStyleParentReference("android:foo", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::ParseStyleParentReference("@android:foo", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+ ref = ResourceUtils::ParseStyleParentReference("foo", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+ ref =
+ ResourceUtils::ParseStyleParentReference("*android:style/foo", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ EXPECT_TRUE(ref.value().private_reference);
}
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);
+ std::unique_ptr<Attribute> attr =
+ test::AttributeBuilder(false)
+ .SetTypeMask(android::ResTable_map::TYPE_FLAGS)
+ .AddItem("one", 0x01)
+ .AddItem("two", 0x02)
+ .Build();
+
+ std::unique_ptr<BinaryPrimitive> result =
+ ResourceUtils::TryParseFlagSymbol(attr.get(), "");
+ ASSERT_NE(nullptr, result);
+ EXPECT_EQ(0u, result->value.data);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index c10b134cb36e..7956ad826acd 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -14,665 +14,760 @@
* limitations under the License.
*/
+#include "ResourceValues.h"
+
+#include <algorithm>
+#include <limits>
+#include <set>
+
+#include "androidfw/ResourceTypes.h"
+
#include "Resource.h"
#include "ResourceUtils.h"
-#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "io/File.h"
#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-#include <limits>
-
namespace aapt {
template <typename Derived>
-void BaseValue<Derived>::accept(RawValueVisitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
+void BaseValue<Derived>::Accept(RawValueVisitor* visitor) {
+ visitor->Visit(static_cast<Derived*>(this));
}
template <typename Derived>
-void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
+void BaseItem<Derived>::Accept(RawValueVisitor* visitor) {
+ visitor->Visit(static_cast<Derived*>(this));
}
-RawString::RawString(const StringPool::Ref& ref) : value(ref) {
-}
+RawString::RawString(const StringPool::Ref& ref) : value(ref) {}
-bool RawString::equals(const Value* value) const {
- const RawString* other = valueCast<RawString>(value);
- if (!other) {
- return false;
- }
- return *this->value == *other->value;
+bool RawString::Equals(const Value* value) const {
+ const RawString* other = ValueCast<RawString>(value);
+ if (!other) {
+ return false;
+ }
+ return *this->value == *other->value;
}
-RawString* RawString::clone(StringPool* newPool) const {
- RawString* rs = new RawString(newPool->makeRef(*value));
- rs->mComment = mComment;
- rs->mSource = mSource;
- return rs;
+RawString* RawString::Clone(StringPool* new_pool) const {
+ RawString* rs = new RawString(new_pool->MakeRef(*value));
+ rs->comment_ = comment_;
+ rs->source_ = source_;
+ return rs;
}
-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;
+bool RawString::Flatten(android::Res_value* out_value) const {
+ out_value->dataType = android::Res_value::TYPE_STRING;
+ out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
+ 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) {
-}
+Reference::Reference() : reference_type(Type::kResource) {}
-Reference::Reference(const ResourceNameRef& n, Type t) :
- name(n.toResourceName()), referenceType(t) {
-}
+Reference::Reference(const ResourceNameRef& n, Type t)
+ : name(n.ToResourceName()), reference_type(t) {}
-Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
-}
+Reference::Reference(const ResourceId& i, Type type)
+ : id(i), reference_type(type) {}
-bool Reference::equals(const Value* value) const {
- const Reference* other = valueCast<Reference>(value);
- if (!other) {
- return false;
- }
- return referenceType == other->referenceType && privateReference == other->privateReference &&
- id == other->id && name == other->name;
+Reference::Reference(const ResourceNameRef& n, const ResourceId& i)
+ : name(n.ToResourceName()), id(i), reference_type(Type::kResource) {}
+
+bool Reference::Equals(const Value* value) const {
+ const Reference* other = ValueCast<Reference>(value);
+ if (!other) {
+ return false;
+ }
+ return reference_type == other->reference_type &&
+ private_reference == other->private_reference && id == other->id &&
+ name == other->name;
}
-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;
+bool Reference::Flatten(android::Res_value* out_value) const {
+ out_value->dataType = (reference_type == Reference::Type::kResource)
+ ? android::Res_value::TYPE_REFERENCE
+ : android::Res_value::TYPE_ATTRIBUTE;
+ out_value->data = util::HostToDevice32(id ? id.value().id : 0);
+ return true;
}
-Reference* Reference::clone(StringPool* /*newPool*/) const {
- return new Reference(*this);
+Reference* Reference::Clone(StringPool* /*new_pool*/) const {
+ return new Reference(*this);
}
-void Reference::print(std::ostream* out) const {
- *out << "(reference) ";
- if (referenceType == Reference::Type::kResource) {
- *out << "@";
- if (privateReference) {
- *out << "*";
- }
- } else {
- *out << "?";
+void Reference::Print(std::ostream* out) const {
+ *out << "(reference) ";
+ if (reference_type == Reference::Type::kResource) {
+ *out << "@";
+ if (private_reference) {
+ *out << "*";
}
+ } else {
+ *out << "?";
+ }
- if (name) {
- *out << name.value();
- }
+ if (name) {
+ *out << name.value();
+ }
- if (id && !Res_INTERNALID(id.value().id)) {
- *out << " " << id.value();
- }
+ if (id && !Res_INTERNALID(id.value().id)) {
+ *out << " " << id.value();
+ }
}
-bool Id::equals(const Value* value) const {
- return valueCast<Id>(value) != nullptr;
+bool Id::Equals(const Value* value) const {
+ return ValueCast<Id>(value) != nullptr;
}
-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 = util::HostToDevice32(0);
+ return true;
}
-Id* Id::clone(StringPool* /*newPool*/) const {
- return new Id(*this);
-}
+Id* Id::Clone(StringPool* /*new_pool*/) const { return new Id(*this); }
-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) {
-}
+String::String(const StringPool::Ref& ref) : value(ref) {}
-bool String::equals(const Value* value) const {
- const String* other = valueCast<String>(value);
- if (!other) {
- return false;
- }
- return *this->value == *other->value;
+bool String::Equals(const Value* value) const {
+ const String* other = ValueCast<String>(value);
+ if (!other) {
+ return false;
+ }
+ return *this->value == *other->value;
}
-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 = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
- return true;
-}
+bool String::Flatten(android::Res_value* out_value) const {
+ // Verify that our StringPool index is within encode-able limits.
+ if (value.index() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
-String* String::clone(StringPool* newPool) const {
- String* str = new String(newPool->makeRef(*value));
- str->mComment = mComment;
- str->mSource = mSource;
- return str;
+ out_value->dataType = android::Res_value::TYPE_STRING;
+ out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
+ return true;
}
-void String::print(std::ostream* out) const {
- *out << "(string) \"" << *value << "\"";
+String* String::Clone(StringPool* new_pool) const {
+ String* str = new String(new_pool->MakeRef(*value));
+ str->comment_ = comment_;
+ str->source_ = source_;
+ return str;
}
-StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
+void String::Print(std::ostream* out) const {
+ *out << "(string) \"" << *value << "\"";
}
-bool StyledString::equals(const Value* value) const {
- const StyledString* other = valueCast<StyledString>(value);
- if (!other) {
- return false;
- }
+StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {}
- if (*this->value->str == *other->value->str) {
- const std::vector<StringPool::Span>& spansA = this->value->spans;
- const std::vector<StringPool::Span>& spansB = other->value->spans;
- return std::equal(spansA.begin(), spansA.end(), spansB.begin(),
- [](const StringPool::Span& a, const StringPool::Span& b) -> bool {
- return *a.name == *b.name && a.firstChar == b.firstChar && a.lastChar == b.lastChar;
- });
- }
+bool StyledString::Equals(const Value* value) const {
+ const StyledString* other = ValueCast<StyledString>(value);
+ if (!other) {
return false;
+ }
+
+ if (*this->value->str == *other->value->str) {
+ const std::vector<StringPool::Span>& spans_a = this->value->spans;
+ const std::vector<StringPool::Span>& spans_b = other->value->spans;
+ return std::equal(
+ spans_a.begin(), spans_a.end(), spans_b.begin(),
+ [](const StringPool::Span& a, const StringPool::Span& b) -> bool {
+ return *a.name == *b.name && a.first_char == b.first_char &&
+ a.last_char == b.last_char;
+ });
+ }
+ return false;
}
-bool StyledString::flatten(android::Res_value* outValue) const {
- if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
- return false;
- }
+bool StyledString::Flatten(android::Res_value* out_value) const {
+ if (value.index() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
- outValue->dataType = android::Res_value::TYPE_STRING;
- outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
- return true;
+ out_value->dataType = android::Res_value::TYPE_STRING;
+ out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
+ return true;
}
-StyledString* StyledString::clone(StringPool* newPool) const {
- StyledString* str = new StyledString(newPool->makeRef(value));
- str->mComment = mComment;
- str->mSource = mSource;
- return str;
+StyledString* StyledString::Clone(StringPool* new_pool) const {
+ StyledString* str = new StyledString(new_pool->MakeRef(value));
+ str->comment_ = comment_;
+ str->source_ = source_;
+ return str;
}
-void StyledString::print(std::ostream* out) const {
- *out << "(styled string) \"" << *value->str << "\"";
- for (const StringPool::Span& span : value->spans) {
- *out << " "<< *span.name << ":" << span.firstChar << "," << span.lastChar;
- }
+void StyledString::Print(std::ostream* out) const {
+ *out << "(styled string) \"" << *value->str << "\"";
+ for (const StringPool::Span& span : value->spans) {
+ *out << " " << *span.name << ":" << span.first_char << ","
+ << span.last_char;
+ }
}
-FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
-}
+FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {}
-bool FileReference::equals(const Value* value) const {
- const FileReference* other = valueCast<FileReference>(value);
- if (!other) {
- return false;
- }
- return *path == *other->path;
+bool FileReference::Equals(const Value* value) const {
+ const FileReference* other = ValueCast<FileReference>(value);
+ if (!other) {
+ return false;
+ }
+ return *path == *other->path;
}
-bool FileReference::flatten(android::Res_value* outValue) const {
- if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
- return false;
- }
+bool FileReference::Flatten(android::Res_value* out_value) const {
+ if (path.index() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
- outValue->dataType = android::Res_value::TYPE_STRING;
- outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex()));
- return true;
+ out_value->dataType = android::Res_value::TYPE_STRING;
+ out_value->data = util::HostToDevice32(static_cast<uint32_t>(path.index()));
+ return true;
}
-FileReference* FileReference::clone(StringPool* newPool) const {
- FileReference* fr = new FileReference(newPool->makeRef(*path));
- fr->file = file;
- fr->mComment = mComment;
- fr->mSource = mSource;
- return fr;
+FileReference* FileReference::Clone(StringPool* new_pool) const {
+ FileReference* fr = new FileReference(new_pool->MakeRef(*path));
+ fr->file = file;
+ fr->comment_ = comment_;
+ fr->source_ = source_;
+ 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) {
-}
+BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {}
BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
- value.dataType = dataType;
- value.data = data;
-}
-
-bool BinaryPrimitive::equals(const Value* value) const {
- const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value);
- if (!other) {
- return false;
- }
- return this->value.dataType == other->value.dataType && this->value.data == other->value.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(*this);
-}
-
-void BinaryPrimitive::print(std::ostream* out) const {
- switch (value.dataType) {
- case android::Res_value::TYPE_NULL:
- *out << "(null)";
- break;
- case android::Res_value::TYPE_INT_DEC:
- *out << "(integer) " << static_cast<int32_t>(value.data);
- break;
- case android::Res_value::TYPE_INT_HEX:
- *out << "(integer) 0x" << std::hex << value.data << std::dec;
- break;
- case android::Res_value::TYPE_INT_BOOLEAN:
- *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;
- break;
- default:
- *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
- << std::hex << value.data << std::dec;
- break;
- }
+ value.dataType = dataType;
+ value.data = data;
}
-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;
-}
-
-bool Attribute::equals(const Value* value) const {
- const Attribute* other = valueCast<Attribute>(value);
- if (!other) {
- return false;
- }
-
- return this->typeMask == other->typeMask && this->minInt == other->minInt &&
- this->maxInt == other->maxInt &&
- std::equal(this->symbols.begin(), this->symbols.end(),
- other->symbols.begin(),
- [](const Symbol& a, const Symbol& b) -> bool {
- return a.symbol.equals(&b.symbol) && a.value == b.value;
- });
-}
-
-Attribute* Attribute::clone(StringPool* /*newPool*/) const {
- return new Attribute(*this);
-}
+bool BinaryPrimitive::Equals(const Value* value) const {
+ const BinaryPrimitive* other = ValueCast<BinaryPrimitive>(value);
+ if (!other) {
+ return false;
+ }
+ return this->value.dataType == other->value.dataType &&
+ this->value.data == other->value.data;
+}
+
+bool BinaryPrimitive::Flatten(android::Res_value* out_value) const {
+ out_value->dataType = value.dataType;
+ out_value->data = util::HostToDevice32(value.data);
+ return true;
+}
+
+BinaryPrimitive* BinaryPrimitive::Clone(StringPool* /*new_pool*/) const {
+ return new BinaryPrimitive(*this);
+}
+
+void BinaryPrimitive::Print(std::ostream* out) const {
+ switch (value.dataType) {
+ case android::Res_value::TYPE_NULL:
+ *out << "(null)";
+ break;
+ case android::Res_value::TYPE_INT_DEC:
+ *out << "(integer) " << static_cast<int32_t>(value.data);
+ break;
+ case android::Res_value::TYPE_INT_HEX:
+ *out << "(integer) 0x" << std::hex << value.data << std::dec;
+ break;
+ case android::Res_value::TYPE_INT_BOOLEAN:
+ *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;
+ break;
+ default:
+ *out << "(unknown 0x" << std::hex << (int)value.dataType << ") 0x"
+ << std::hex << value.data << std::dec;
+ break;
+ }
+}
+
+Attribute::Attribute(bool w, uint32_t t)
+ : type_mask(t),
+ min_int(std::numeric_limits<int32_t>::min()),
+ max_int(std::numeric_limits<int32_t>::max()) {
+ weak_ = w;
+}
+
+template <typename T>
+T* addPointer(T& val) {
+ return &val;
+}
+
+bool Attribute::Equals(const Value* value) const {
+ const Attribute* other = ValueCast<Attribute>(value);
+ if (!other) {
+ return false;
+ }
-void Attribute::printMask(std::ostream* out) const {
- if (typeMask == android::ResTable_map::TYPE_ANY) {
- *out << "any";
- return;
- }
+ if (symbols.size() != other->symbols.size()) {
+ return false;
+ }
- bool set = false;
- if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "reference";
+ if (type_mask != other->type_mask || min_int != other->min_int ||
+ max_int != other->max_int) {
+ return false;
+ }
+
+ std::vector<const Symbol*> sorted_a;
+ std::transform(symbols.begin(), symbols.end(), std::back_inserter(sorted_a),
+ addPointer<const Symbol>);
+ std::sort(sorted_a.begin(), sorted_a.end(),
+ [](const Symbol* a, const Symbol* b) -> bool {
+ return a->symbol.name < b->symbol.name;
+ });
+
+ std::vector<const Symbol*> sorted_b;
+ std::transform(other->symbols.begin(), other->symbols.end(),
+ std::back_inserter(sorted_b), addPointer<const Symbol>);
+ std::sort(sorted_b.begin(), sorted_b.end(),
+ [](const Symbol* a, const Symbol* b) -> bool {
+ return a->symbol.name < b->symbol.name;
+ });
+
+ return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin(),
+ [](const Symbol* a, const Symbol* b) -> bool {
+ return a->symbol.Equals(&b->symbol) &&
+ a->value == b->value;
+ });
+}
+
+Attribute* Attribute::Clone(StringPool* /*new_pool*/) const {
+ return new Attribute(*this);
+}
+
+void Attribute::PrintMask(std::ostream* out) const {
+ if (type_mask == android::ResTable_map::TYPE_ANY) {
+ *out << "any";
+ return;
+ }
+
+ bool set = false;
+ if ((type_mask & android::ResTable_map::TYPE_REFERENCE) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "reference";
+ }
- if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "string";
+ if ((type_mask & android::ResTable_map::TYPE_STRING) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "string";
+ }
- if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "integer";
+ if ((type_mask & android::ResTable_map::TYPE_INTEGER) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "integer";
+ }
- if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "boolean";
+ if ((type_mask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "boolean";
+ }
- if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "color";
+ if ((type_mask & android::ResTable_map::TYPE_COLOR) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "color";
+ }
- if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "float";
+ if ((type_mask & android::ResTable_map::TYPE_FLOAT) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "float";
+ }
- if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "dimension";
+ if ((type_mask & android::ResTable_map::TYPE_DIMENSION) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "dimension";
+ }
- if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "fraction";
+ if ((type_mask & android::ResTable_map::TYPE_FRACTION) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "fraction";
+ }
- if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "enum";
+ if ((type_mask & android::ResTable_map::TYPE_ENUM) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "enum";
+ }
- if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "flags";
+ if ((type_mask & android::ResTable_map::TYPE_FLAGS) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "flags";
+ }
}
-void Attribute::print(std::ostream* out) const {
- *out << "(attr) ";
- printMask(out);
+void Attribute::Print(std::ostream* out) const {
+ *out << "(attr) ";
+ PrintMask(out);
- if (!symbols.empty()) {
- *out << " ["
- << util::joiner(symbols.begin(), symbols.end(), ", ")
- << "]";
- }
+ if (!symbols.empty()) {
+ *out << " [" << util::Joiner(symbols, ", ") << "]";
+ }
- if (minInt != std::numeric_limits<int32_t>::min()) {
- *out << " min=" << minInt;
- }
+ if (min_int != std::numeric_limits<int32_t>::min()) {
+ *out << " min=" << min_int;
+ }
- if (maxInt != std::numeric_limits<int32_t>::max()) {
- *out << " max=" << maxInt;
- }
+ if (max_int != std::numeric_limits<int32_t>::max()) {
+ *out << " max=" << max_int;
+ }
- if (isWeak()) {
- *out << " [weak]";
- }
+ if (IsWeak()) {
+ *out << " [weak]";
+ }
}
-static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+static void BuildAttributeMismatchMessage(DiagMessage* msg,
+ const Attribute* attr,
const Item* value) {
- *msg << "expected";
- if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
- *msg << " boolean";
- }
+ *msg << "expected";
+ if (attr->type_mask & android::ResTable_map::TYPE_BOOLEAN) {
+ *msg << " boolean";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
- *msg << " color";
- }
+ if (attr->type_mask & android::ResTable_map::TYPE_COLOR) {
+ *msg << " color";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
- *msg << " dimension";
- }
+ if (attr->type_mask & android::ResTable_map::TYPE_DIMENSION) {
+ *msg << " dimension";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
- *msg << " enum";
- }
+ if (attr->type_mask & android::ResTable_map::TYPE_ENUM) {
+ *msg << " enum";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
- *msg << " flags";
- }
+ if (attr->type_mask & android::ResTable_map::TYPE_FLAGS) {
+ *msg << " flags";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
- *msg << " float";
- }
+ if (attr->type_mask & android::ResTable_map::TYPE_FLOAT) {
+ *msg << " float";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
- *msg << " fraction";
- }
+ if (attr->type_mask & android::ResTable_map::TYPE_FRACTION) {
+ *msg << " fraction";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
- *msg << " integer";
- }
+ if (attr->type_mask & android::ResTable_map::TYPE_INTEGER) {
+ *msg << " integer";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
- *msg << " reference";
- }
+ if (attr->type_mask & android::ResTable_map::TYPE_REFERENCE) {
+ *msg << " reference";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
- *msg << " string";
- }
+ if (attr->type_mask & 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;
+ *msg << " but got " << *value;
}
-bool Style::equals(const Value* value) const {
- const Style* other = valueCast<Style>(value);
- if (!other) {
- return false;
- }
- if (bool(parent) != bool(other->parent) ||
- (parent && other->parent && !parent.value().equals(&other->parent.value()))) {
- return false;
- }
- return std::equal(entries.begin(), entries.end(), other->entries.begin(),
- [](const Entry& a, const Entry& b) -> bool {
- return a.key.equals(&b.key) && a.value->equals(b.value.get());
- });
-}
-
-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,
- std::unique_ptr<Item>(entry.value->clone(newPool))
- });
+bool Attribute::Matches(const Item* item, DiagMessage* out_msg) const {
+ android::Res_value val = {};
+ item->Flatten(&val);
+
+ // Always allow references.
+ const uint32_t mask = type_mask | android::ResTable_map::TYPE_REFERENCE;
+ if (!(mask & ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType))) {
+ if (out_msg) {
+ BuildAttributeMismatchMessage(out_msg, this, item);
}
- return style;
+ return false;
+
+ } else if (ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType) &
+ android::ResTable_map::TYPE_INTEGER) {
+ if (static_cast<int32_t>(util::DeviceToHost32(val.data)) < min_int) {
+ if (out_msg) {
+ *out_msg << *item << " is less than minimum integer " << min_int;
+ }
+ return false;
+ } else if (static_cast<int32_t>(util::DeviceToHost32(val.data)) > max_int) {
+ if (out_msg) {
+ *out_msg << *item << " is greater than maximum integer " << max_int;
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Style::Equals(const Value* value) const {
+ const Style* other = ValueCast<Style>(value);
+ if (!other) {
+ return false;
+ }
+ if (bool(parent) != bool(other->parent) ||
+ (parent && other->parent &&
+ !parent.value().Equals(&other->parent.value()))) {
+ return false;
+ }
+
+ if (entries.size() != other->entries.size()) {
+ return false;
+ }
+
+ std::vector<const Entry*> sorted_a;
+ std::transform(entries.begin(), entries.end(), std::back_inserter(sorted_a),
+ addPointer<const Entry>);
+ std::sort(sorted_a.begin(), sorted_a.end(),
+ [](const Entry* a, const Entry* b) -> bool {
+ return a->key.name < b->key.name;
+ });
+
+ std::vector<const Entry*> sorted_b;
+ std::transform(other->entries.begin(), other->entries.end(),
+ std::back_inserter(sorted_b), addPointer<const Entry>);
+ std::sort(sorted_b.begin(), sorted_b.end(),
+ [](const Entry* a, const Entry* b) -> bool {
+ return a->key.name < b->key.name;
+ });
+
+ return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin(),
+ [](const Entry* a, const Entry* b) -> bool {
+ return a->key.Equals(&b->key) &&
+ a->value->Equals(b->value.get());
+ });
+}
+
+Style* Style::Clone(StringPool* new_pool) const {
+ Style* style = new Style();
+ style->parent = parent;
+ style->parent_inferred = parent_inferred;
+ style->comment_ = comment_;
+ style->source_ = source_;
+ for (auto& entry : entries) {
+ style->entries.push_back(
+ Entry{entry.key, std::unique_ptr<Item>(entry.value->Clone(new_pool))});
+ }
+ return style;
+}
+
+void Style::Print(std::ostream* out) const {
+ *out << "(style) ";
+ if (parent && parent.value().name) {
+ if (parent.value().private_reference) {
+ *out << "*";
+ }
+ *out << parent.value().name.value();
+ }
+ *out << " [" << util::Joiner(entries, ", ") << "]";
+}
+
+static ::std::ostream& operator<<(::std::ostream& out,
+ const Style::Entry& value) {
+ if (value.key.name) {
+ out << value.key.name.value();
+ } else if (value.key.id) {
+ out << value.key.id.value();
+ } else {
+ out << "???";
+ }
+ out << " = ";
+ value.value->Print(&out);
+ return out;
+}
+
+bool Array::Equals(const Value* value) const {
+ const Array* other = ValueCast<Array>(value);
+ if (!other) {
+ return false;
+ }
+
+ if (items.size() != other->items.size()) {
+ return false;
+ }
+
+ return std::equal(items.begin(), items.end(), other->items.begin(),
+ [](const std::unique_ptr<Item>& a,
+ const std::unique_ptr<Item>& b) -> bool {
+ return a->Equals(b.get());
+ });
}
-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 << " ["
- << util::joiner(entries.begin(), entries.end(), ", ")
- << "]";
+Array* Array::Clone(StringPool* new_pool) const {
+ Array* array = new Array();
+ array->comment_ = comment_;
+ array->source_ = source_;
+ for (auto& item : items) {
+ array->items.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool)));
+ }
+ return array;
}
-static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) {
- if (value.key.name) {
- out << value.key.name.value();
- } else {
- out << "???";
- }
- out << " = ";
- value.value->print(&out);
- return out;
+void Array::Print(std::ostream* out) const {
+ *out << "(array) [" << util::Joiner(items, ", ") << "]";
}
-bool Array::equals(const Value* value) const {
- const Array* other = valueCast<Array>(value);
- if (!other) {
- return false;
- }
+bool Plural::Equals(const Value* value) const {
+ const Plural* other = ValueCast<Plural>(value);
+ if (!other) {
+ return false;
+ }
+
+ if (values.size() != other->values.size()) {
+ return false;
+ }
- return std::equal(items.begin(), items.end(), other->items.begin(),
- [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
- return a->equals(b.get());
- });
+ return std::equal(values.begin(), values.end(), other->values.begin(),
+ [](const std::unique_ptr<Item>& a,
+ const std::unique_ptr<Item>& b) -> bool {
+ if (bool(a) != bool(b)) {
+ return false;
+ }
+ return bool(a) == bool(b) || a->Equals(b.get());
+ });
}
-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)));
+Plural* Plural::Clone(StringPool* new_pool) const {
+ Plural* p = new Plural();
+ p->comment_ = comment_;
+ p->source_ = source_;
+ const size_t count = values.size();
+ for (size_t i = 0; i < count; i++) {
+ if (values[i]) {
+ p->values[i] = std::unique_ptr<Item>(values[i]->Clone(new_pool));
}
- return array;
+ }
+ return p;
}
-void Array::print(std::ostream* out) const {
- *out << "(array) ["
- << util::joiner(items.begin(), items.end(), ", ")
- << "]";
-}
+void Plural::Print(std::ostream* out) const {
+ *out << "(plural)";
+ if (values[Zero]) {
+ *out << " zero=" << *values[Zero];
+ }
-bool Plural::equals(const Value* value) const {
- const Plural* other = valueCast<Plural>(value);
- if (!other) {
- return false;
- }
+ if (values[One]) {
+ *out << " one=" << *values[One];
+ }
- return std::equal(values.begin(), values.end(), other->values.begin(),
- [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
- if (bool(a) != bool(b)) {
- return false;
- }
- return bool(a) == bool(b) || a->equals(b.get());
- });
-}
-
-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]) {
- p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool));
- }
- }
- return p;
+ if (values[Two]) {
+ *out << " two=" << *values[Two];
+ }
+
+ if (values[Few]) {
+ *out << " few=" << *values[Few];
+ }
+
+ if (values[Many]) {
+ *out << " many=" << *values[Many];
+ }
}
-void Plural::print(std::ostream* out) const {
- *out << "(plural)";
- if (values[Zero]) {
- *out << " zero=" << *values[Zero];
- }
+static ::std::ostream& operator<<(::std::ostream& out,
+ const std::unique_ptr<Item>& item) {
+ return out << *item;
+}
- if (values[One]) {
- *out << " one=" << *values[One];
- }
+bool Styleable::Equals(const Value* value) const {
+ const Styleable* other = ValueCast<Styleable>(value);
+ if (!other) {
+ return false;
+ }
- if (values[Two]) {
- *out << " two=" << *values[Two];
- }
+ if (entries.size() != other->entries.size()) {
+ return false;
+ }
- if (values[Few]) {
- *out << " few=" << *values[Few];
- }
+ return std::equal(entries.begin(), entries.end(), other->entries.begin(),
+ [](const Reference& a, const Reference& b) -> bool {
+ return a.Equals(&b);
+ });
+}
- if (values[Many]) {
- *out << " many=" << *values[Many];
- }
+Styleable* Styleable::Clone(StringPool* /*new_pool*/) const {
+ return new Styleable(*this);
}
-static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
- return out << *item;
+void Styleable::Print(std::ostream* out) const {
+ *out << "(styleable) "
+ << " [" << util::Joiner(entries, ", ") << "]";
}
-bool Styleable::equals(const Value* value) const {
- const Styleable* other = valueCast<Styleable>(value);
- if (!other) {
- return false;
- }
- return std::equal(entries.begin(), entries.end(), other->entries.begin(),
- [](const Reference& a, const Reference& b) -> bool {
- return a.equals(&b);
- });
+bool operator<(const Reference& a, const Reference& b) {
+ int cmp = a.name.value_or_default({}).compare(b.name.value_or_default({}));
+ if (cmp != 0) return cmp < 0;
+ return a.id < b.id;
}
-Styleable* Styleable::clone(StringPool* /*newPool*/) const {
- return new Styleable(*this);
+bool operator==(const Reference& a, const Reference& b) {
+ return a.name == b.name && a.id == b.id;
}
-void Styleable::print(std::ostream* out) const {
- *out << "(styleable) " << " ["
- << util::joiner(entries.begin(), entries.end(), ", ")
- << "]";
+bool operator!=(const Reference& a, const Reference& b) {
+ return a.name != b.name || a.id != b.id;
+}
+
+struct NameOnlyComparator {
+ bool operator()(const Reference& a, const Reference& b) const {
+ return a.name < b.name;
+ }
+};
+
+void Styleable::MergeWith(Styleable* other) {
+ // Compare only names, because some References may already have their IDs
+ // assigned
+ // (framework IDs that don't change).
+ std::set<Reference, NameOnlyComparator> references;
+ references.insert(entries.begin(), entries.end());
+ references.insert(other->entries.begin(), other->entries.end());
+ entries.clear();
+ entries.reserve(references.size());
+ entries.insert(entries.end(), references.begin(), references.end());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 9a6f1a1dff96..ea73615e372a 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -17,17 +17,18 @@
#ifndef AAPT_RESOURCE_VALUES_H
#define AAPT_RESOURCE_VALUES_H
+#include <array>
+#include <ostream>
+#include <vector>
+
+#include "androidfw/ResourceTypes.h"
+
#include "Diagnostics.h"
#include "Resource.h"
#include "StringPool.h"
#include "io/File.h"
#include "util/Maybe.h"
-#include <array>
-#include <androidfw/ResourceTypes.h>
-#include <ostream>
-#include <vector>
-
namespace aapt {
struct RawValueVisitor;
@@ -40,84 +41,65 @@ struct RawValueVisitor;
* but it is the simplest strategy.
*/
struct Value {
- virtual ~Value() = default;
-
- /**
- * Whether this value is weak and can be overridden without
- * warning or error. Default is false.
- */
- bool isWeak() const {
- return mWeak;
- }
-
- void setWeak(bool val) {
- mWeak = val;
- }
-
- // Whether the value is marked as translateable.
- // This does not persist when flattened.
- // It is only used during compilation phase.
- void setTranslateable(bool val) {
- mTranslateable = val;
- }
-
- // Default true.
- bool isTranslateable() const {
- return mTranslateable;
- }
-
- /**
- * Returns the source where this value was defined.
- */
- const Source& getSource() const {
- return mSource;
- }
-
- void setSource(const Source& source) {
- mSource = source;
- }
-
- void setSource(Source&& source) {
- mSource = std::move(source);
- }
-
- /**
- * Returns the comment that was associated with this resource.
- */
- StringPiece16 getComment() const {
- return mComment;
- }
-
- void setComment(const StringPiece16& str) {
- mComment = str.toString();
- }
-
- void setComment(std::u16string&& str) {
- mComment = std::move(str);
- }
-
- virtual bool equals(const Value* value) const = 0;
-
- /**
- * Calls the appropriate overload of ValueVisitor.
- */
- virtual void accept(RawValueVisitor* visitor) = 0;
-
- /**
- * Clone the value.
- */
- virtual Value* clone(StringPool* newPool) const = 0;
-
- /**
- * Human readable printout of this value.
- */
- virtual void print(std::ostream* out) const = 0;
-
-protected:
- Source mSource;
- std::u16string mComment;
- bool mWeak = false;
- bool mTranslateable = true;
+ virtual ~Value() = default;
+
+ /**
+ * Whether this value is weak and can be overridden without
+ * warning or error. Default is false.
+ */
+ bool IsWeak() const { return weak_; }
+
+ void SetWeak(bool val) { weak_ = val; }
+
+ // Whether the value is marked as translateable.
+ // This does not persist when flattened.
+ // It is only used during compilation phase.
+ void SetTranslateable(bool val) { translateable_ = val; }
+
+ // Default true.
+ bool IsTranslateable() const { return translateable_; }
+
+ /**
+ * Returns the source where this value was defined.
+ */
+ const Source& GetSource() const { return source_; }
+
+ void SetSource(const Source& source) { source_ = source; }
+
+ void SetSource(Source&& source) { source_ = std::move(source); }
+
+ /**
+ * Returns the comment that was associated with this resource.
+ */
+ const std::string& GetComment() const { return comment_; }
+
+ void SetComment(const StringPiece& str) { comment_ = str.ToString(); }
+
+ void SetComment(std::string&& str) { comment_ = std::move(str); }
+
+ virtual bool Equals(const Value* value) const = 0;
+
+ /**
+ * Calls the appropriate overload of ValueVisitor.
+ */
+ virtual void Accept(RawValueVisitor* visitor) = 0;
+
+ /**
+ * Clone the value. new_pool is the new StringPool that
+ * any resources with strings should use when copying their string.
+ */
+ virtual Value* Clone(StringPool* new_pool) const = 0;
+
+ /**
+ * Human readable printout of this value.
+ */
+ virtual void Print(std::ostream* out) const = 0;
+
+ protected:
+ Source source_;
+ std::string comment_;
+ bool weak_ = false;
+ bool translateable_ = true;
};
/**
@@ -125,23 +107,24 @@ protected:
*/
template <typename Derived>
struct BaseValue : public Value {
- void accept(RawValueVisitor* visitor) override;
+ void Accept(RawValueVisitor* visitor) override;
};
/**
* A resource item with a single value. This maps to android::ResTable_entry.
*/
struct Item : public Value {
- /**
- * 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 occurred.
- */
- virtual bool flatten(android::Res_value* outValue) const = 0;
+ /**
+ * Clone the Item.
+ */
+ virtual Item* Clone(StringPool* new_pool) const override = 0;
+
+ /**
+ * Fills in an android::Res_value structure with this Item's binary
+ * representation.
+ * Returns false if an error occurred.
+ */
+ virtual bool Flatten(android::Res_value* out_value) const = 0;
};
/**
@@ -149,45 +132,51 @@ struct Item : public Value {
*/
template <typename Derived>
struct BaseItem : public Item {
- void accept(RawValueVisitor* visitor) override;
+ void Accept(RawValueVisitor* visitor) override;
};
/**
- * A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE.
+ * A reference to another resource. This maps to
+ * android::Res_value::TYPE_REFERENCE.
*
- * A reference can be symbolic (with the name set to a valid resource name) or be
+ * A reference can be symbolic (with the name set to a valid resource name) or
+ * be
* numeric (the id is set to a valid resource ID).
*/
struct Reference : public BaseItem<Reference> {
- enum class Type {
- kResource,
- kAttribute,
- };
-
- Maybe<ResourceName> name;
- Maybe<ResourceId> id;
- Reference::Type referenceType;
- bool privateReference = false;
-
- Reference();
- explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
- explicit Reference(const ResourceId& i, Type type = Type::kResource);
-
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- Reference* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ enum class Type {
+ kResource,
+ kAttribute,
+ };
+
+ Maybe<ResourceName> name;
+ Maybe<ResourceId> id;
+ Reference::Type reference_type;
+ bool private_reference = false;
+
+ Reference();
+ explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
+ explicit Reference(const ResourceId& i, Type type = Type::kResource);
+ Reference(const ResourceNameRef& n, const ResourceId& i);
+
+ bool Equals(const Value* value) const override;
+ bool Flatten(android::Res_value* out_value) const override;
+ Reference* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
+bool operator<(const Reference&, const Reference&);
+bool operator==(const Reference&, const Reference&);
+
/**
* An ID resource. Has no real value, just a place holder.
*/
struct Id : public BaseItem<Id> {
- Id() { mWeak = true; }
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* out) const override;
- Id* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ Id() { weak_ = true; }
+ bool Equals(const Value* value) const override;
+ bool Flatten(android::Res_value* out) const override;
+ Id* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
/**
@@ -196,163 +185,157 @@ struct Id : public BaseItem<Id> {
* end up in the final resource table.
*/
struct RawString : public BaseItem<RawString> {
- StringPool::Ref value;
+ StringPool::Ref value;
- explicit RawString(const StringPool::Ref& ref);
+ explicit RawString(const StringPool::Ref& ref);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- RawString* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool Equals(const Value* value) const override;
+ bool Flatten(android::Res_value* out_value) const override;
+ RawString* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
struct String : public BaseItem<String> {
- StringPool::Ref value;
+ StringPool::Ref value;
- explicit String(const StringPool::Ref& ref);
+ explicit String(const StringPool::Ref& ref);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- String* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool Equals(const Value* value) const override;
+ bool Flatten(android::Res_value* out_value) const override;
+ String* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
struct StyledString : public BaseItem<StyledString> {
- StringPool::StyleRef value;
+ StringPool::StyleRef value;
- explicit StyledString(const StringPool::StyleRef& ref);
+ explicit StyledString(const StringPool::StyleRef& ref);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- StyledString* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool Equals(const Value* value) const override;
+ bool Flatten(android::Res_value* out_value) const override;
+ StyledString* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
struct FileReference : public BaseItem<FileReference> {
- StringPool::Ref path;
+ StringPool::Ref path;
- /**
- * A handle to the file object from which this file can be read.
- */
- io::IFile* file = nullptr;
+ /**
+ * A handle to the file object from which this file can be read.
+ */
+ io::IFile* file = nullptr;
- FileReference() = default;
- explicit FileReference(const StringPool::Ref& path);
+ FileReference() = default;
+ explicit FileReference(const StringPool::Ref& path);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- FileReference* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool Equals(const Value* value) const override;
+ bool Flatten(android::Res_value* out_value) const override;
+ FileReference* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
/**
* Represents any other android::Res_value.
*/
struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
- android::Res_value value;
+ android::Res_value value;
- BinaryPrimitive() = default;
- explicit BinaryPrimitive(const android::Res_value& val);
- BinaryPrimitive(uint8_t dataType, uint32_t data);
+ BinaryPrimitive() = default;
+ explicit BinaryPrimitive(const android::Res_value& val);
+ BinaryPrimitive(uint8_t dataType, uint32_t data);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- BinaryPrimitive* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool Equals(const Value* value) const override;
+ bool Flatten(android::Res_value* out_value) const override;
+ BinaryPrimitive* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
struct Attribute : public BaseValue<Attribute> {
- struct Symbol {
- Reference symbol;
- uint32_t value;
- };
-
- uint32_t typeMask;
- int32_t minInt;
- int32_t maxInt;
- std::vector<Symbol> symbols;
-
- explicit Attribute(bool w, uint32_t t = 0u);
-
- bool equals(const Value* value) 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 Symbol {
+ Reference symbol;
+ uint32_t value;
+ };
+
+ uint32_t type_mask;
+ int32_t min_int;
+ int32_t max_int;
+ std::vector<Symbol> symbols;
+
+ explicit Attribute(bool w, uint32_t t = 0u);
+
+ bool Equals(const Value* value) const override;
+ Attribute* Clone(StringPool* new_pool) const override;
+ void PrintMask(std::ostream* out) const;
+ void Print(std::ostream* out) const override;
+ bool Matches(const Item* item, DiagMessage* out_msg) const;
};
struct Style : public BaseValue<Style> {
- struct Entry {
- Reference key;
- std::unique_ptr<Item> value;
- };
+ struct Entry {
+ Reference key;
+ std::unique_ptr<Item> value;
+ };
- Maybe<Reference> parent;
+ Maybe<Reference> parent;
- /**
- * If set to true, the parent was auto inferred from the
- * style's name.
- */
- bool parentInferred = false;
+ /**
+ * If set to true, the parent was auto inferred from the
+ * style's name.
+ */
+ bool parent_inferred = false;
- std::vector<Entry> entries;
+ std::vector<Entry> entries;
- bool equals(const Value* value) const override;
- Style* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool Equals(const Value* value) const override;
+ Style* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
struct Array : public BaseValue<Array> {
- std::vector<std::unique_ptr<Item>> items;
+ std::vector<std::unique_ptr<Item>> items;
- bool equals(const Value* value) const override;
- Array* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool Equals(const Value* value) const override;
+ Array* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
struct Plural : public BaseValue<Plural> {
- enum {
- Zero = 0,
- One,
- Two,
- Few,
- Many,
- Other,
- Count
- };
-
- std::array<std::unique_ptr<Item>, Count> values;
-
- bool equals(const Value* value) const override;
- Plural* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ enum { Zero = 0, One, Two, Few, Many, Other, Count };
+
+ std::array<std::unique_ptr<Item>, Count> values;
+
+ bool Equals(const Value* value) const override;
+ Plural* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
struct Styleable : public BaseValue<Styleable> {
- std::vector<Reference> entries;
+ std::vector<Reference> entries;
- bool equals(const Value* value) const override;
- Styleable* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool Equals(const Value* value) const override;
+ Styleable* Clone(StringPool* newPool) const override;
+ void Print(std::ostream* out) const override;
+ void MergeWith(Styleable* styleable);
};
/**
* Stream operator for printing Value objects.
*/
inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) {
- value.print(&out);
- return out;
+ value.Print(&out);
+ return out;
}
-inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
- if (s.symbol.name) {
- out << s.symbol.name.value().entry;
- } else {
- out << "???";
- }
- return out << "=" << s.value;
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const Attribute::Symbol& s) {
+ if (s.symbol.name) {
+ out << s.symbol.name.value().entry;
+ } else {
+ out << "???";
+ }
+ return out << "=" << s.value;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RESOURCE_VALUES_H
+#endif // AAPT_RESOURCE_VALUES_H
diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp
index 48dc521d843c..6acb4d3eb850 100644
--- a/tools/aapt2/Resource_test.cpp
+++ b/tools/aapt2/Resource_test.cpp
@@ -14,103 +14,107 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
-
#include "Resource.h"
+#include "test/Test.h"
+
namespace aapt {
TEST(ResourceTypeTest, ParseResourceTypes) {
- const ResourceType* type = parseResourceType(u"anim");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kAnim);
+ const ResourceType* type = ParseResourceType("anim");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAnim);
+
+ type = ParseResourceType("animator");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAnimator);
- type = parseResourceType(u"animator");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kAnimator);
+ type = ParseResourceType("array");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kArray);
- type = parseResourceType(u"array");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kArray);
+ type = ParseResourceType("attr");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAttr);
- type = parseResourceType(u"attr");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kAttr);
+ type = ParseResourceType("^attr-private");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAttrPrivate);
- type = parseResourceType(u"^attr-private");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kAttrPrivate);
+ type = ParseResourceType("bool");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kBool);
- type = parseResourceType(u"bool");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kBool);
+ type = ParseResourceType("color");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kColor);
- type = parseResourceType(u"color");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kColor);
+ type = ParseResourceType("dimen");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kDimen);
- type = parseResourceType(u"dimen");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kDimen);
+ type = ParseResourceType("drawable");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kDrawable);
- type = parseResourceType(u"drawable");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kDrawable);
+ type = ParseResourceType("font");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kFont);
- type = parseResourceType(u"fraction");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kFraction);
+ type = ParseResourceType("fraction");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kFraction);
- type = parseResourceType(u"id");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kId);
+ type = ParseResourceType("id");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kId);
- type = parseResourceType(u"integer");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kInteger);
+ type = ParseResourceType("integer");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kInteger);
- type = parseResourceType(u"interpolator");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kInterpolator);
+ type = ParseResourceType("interpolator");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kInterpolator);
- type = parseResourceType(u"layout");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kLayout);
+ type = ParseResourceType("layout");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kLayout);
- type = parseResourceType(u"menu");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kMenu);
+ type = ParseResourceType("menu");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kMenu);
- type = parseResourceType(u"mipmap");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kMipmap);
+ type = ParseResourceType("mipmap");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kMipmap);
- type = parseResourceType(u"plurals");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kPlurals);
+ type = ParseResourceType("plurals");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kPlurals);
- type = parseResourceType(u"raw");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kRaw);
+ type = ParseResourceType("raw");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kRaw);
- type = parseResourceType(u"string");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kString);
+ type = ParseResourceType("string");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kString);
- type = parseResourceType(u"style");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kStyle);
+ type = ParseResourceType("style");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kStyle);
- type = parseResourceType(u"transition");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kTransition);
+ type = ParseResourceType("transition");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kTransition);
- type = parseResourceType(u"xml");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kXml);
+ type = ParseResourceType("xml");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kXml);
- type = parseResourceType(u"blahaha");
- EXPECT_EQ(type, nullptr);
+ type = ParseResourceType("blahaha");
+ EXPECT_EQ(type, nullptr);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 00bac05fb3b4..c7f920ac6c58 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -23,716 +23,725 @@
namespace aapt {
+static const char* sDevelopmentSdkCodeName = "O";
+static int sDevelopmentSdkLevel = 26;
+
static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = {
- { 0x021c, 1 },
- { 0x021d, 2 },
- { 0x0269, SDK_CUPCAKE },
- { 0x028d, SDK_DONUT },
- { 0x02ad, SDK_ECLAIR },
- { 0x02b3, SDK_ECLAIR_0_1 },
- { 0x02b5, SDK_ECLAIR_MR1 },
- { 0x02bd, SDK_FROYO },
- { 0x02cb, SDK_GINGERBREAD },
- { 0x0361, SDK_HONEYCOMB },
- { 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 },
- { 0x03f1, SDK_KITKAT },
- { 0x03f6, SDK_KITKAT_WATCH },
- { 0x04ce, SDK_LOLLIPOP },
+ {0x021c, 1},
+ {0x021d, 2},
+ {0x0269, SDK_CUPCAKE},
+ {0x028d, SDK_DONUT},
+ {0x02ad, SDK_ECLAIR},
+ {0x02b3, SDK_ECLAIR_0_1},
+ {0x02b5, SDK_ECLAIR_MR1},
+ {0x02bd, SDK_FROYO},
+ {0x02cb, SDK_GINGERBREAD},
+ {0x0361, SDK_HONEYCOMB},
+ {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},
+ {0x03f1, SDK_KITKAT},
+ {0x03f6, SDK_KITKAT_WATCH},
+ {0x04ce, SDK_LOLLIPOP},
};
-static bool lessEntryId(const std::pair<uint16_t, size_t>& p, uint16_t entryId) {
- return p.first < entryId;
+static bool less_entry_id(const std::pair<uint16_t, size_t>& p,
+ uint16_t entryId) {
+ return p.first < entryId;
}
-size_t findAttributeSdkLevel(const ResourceId& id) {
- if (id.packageId() != 0x01 && id.typeId() != 0x01) {
- return 0;
- }
- auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entryId(), lessEntryId);
- if (iter == sAttrIdMap.end()) {
- return SDK_LOLLIPOP_MR1;
- }
- return iter->second;
+size_t FindAttributeSdkLevel(const ResourceId& id) {
+ if (id.package_id() != 0x01 && id.type_id() != 0x01) {
+ return 0;
+ }
+ auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(),
+ id.entry_id(), less_entry_id);
+ if (iter == sAttrIdMap.end()) {
+ return SDK_LOLLIPOP_MR1;
+ }
+ return iter->second;
}
-static const std::unordered_map<std::u16string, size_t> sAttrMap = {
- { u"marqueeRepeatLimit", 2 },
- { u"windowNoDisplay", 3 },
- { u"backgroundDimEnabled", 3 },
- { u"inputType", 3 },
- { u"isDefault", 3 },
- { u"windowDisablePreview", 3 },
- { u"privateImeOptions", 3 },
- { u"editorExtras", 3 },
- { u"settingsActivity", 3 },
- { u"fastScrollEnabled", 3 },
- { u"reqTouchScreen", 3 },
- { u"reqKeyboardType", 3 },
- { u"reqHardKeyboard", 3 },
- { u"reqNavigation", 3 },
- { u"windowSoftInputMode", 3 },
- { u"imeFullscreenBackground", 3 },
- { u"noHistory", 3 },
- { u"headerDividersEnabled", 3 },
- { u"footerDividersEnabled", 3 },
- { u"candidatesTextStyleSpans", 3 },
- { u"smoothScrollbar", 3 },
- { u"reqFiveWayNav", 3 },
- { u"keyBackground", 3 },
- { u"keyTextSize", 3 },
- { u"labelTextSize", 3 },
- { u"keyTextColor", 3 },
- { u"keyPreviewLayout", 3 },
- { u"keyPreviewOffset", 3 },
- { u"keyPreviewHeight", 3 },
- { u"verticalCorrection", 3 },
- { u"popupLayout", 3 },
- { u"state_long_pressable", 3 },
- { u"keyWidth", 3 },
- { u"keyHeight", 3 },
- { u"horizontalGap", 3 },
- { u"verticalGap", 3 },
- { u"rowEdgeFlags", 3 },
- { u"codes", 3 },
- { u"popupKeyboard", 3 },
- { u"popupCharacters", 3 },
- { u"keyEdgeFlags", 3 },
- { u"isModifier", 3 },
- { u"isSticky", 3 },
- { u"isRepeatable", 3 },
- { u"iconPreview", 3 },
- { u"keyOutputText", 3 },
- { u"keyLabel", 3 },
- { u"keyIcon", 3 },
- { u"keyboardMode", 3 },
- { u"isScrollContainer", 3 },
- { u"fillEnabled", 3 },
- { u"updatePeriodMillis", 3 },
- { u"initialLayout", 3 },
- { u"voiceSearchMode", 3 },
- { u"voiceLanguageModel", 3 },
- { u"voicePromptText", 3 },
- { u"voiceLanguage", 3 },
- { u"voiceMaxResults", 3 },
- { u"bottomOffset", 3 },
- { u"topOffset", 3 },
- { u"allowSingleTap", 3 },
- { u"handle", 3 },
- { u"content", 3 },
- { u"animateOnClick", 3 },
- { u"configure", 3 },
- { u"hapticFeedbackEnabled", 3 },
- { u"innerRadius", 3 },
- { u"thickness", 3 },
- { u"sharedUserLabel", 3 },
- { u"dropDownWidth", 3 },
- { u"dropDownAnchor", 3 },
- { u"imeOptions", 3 },
- { u"imeActionLabel", 3 },
- { u"imeActionId", 3 },
- { u"imeExtractEnterAnimation", 3 },
- { u"imeExtractExitAnimation", 3 },
- { u"tension", 4 },
- { u"extraTension", 4 },
- { u"anyDensity", 4 },
- { u"searchSuggestThreshold", 4 },
- { u"includeInGlobalSearch", 4 },
- { u"onClick", 4 },
- { u"targetSdkVersion", 4 },
- { u"maxSdkVersion", 4 },
- { u"testOnly", 4 },
- { u"contentDescription", 4 },
- { u"gestureStrokeWidth", 4 },
- { u"gestureColor", 4 },
- { u"uncertainGestureColor", 4 },
- { u"fadeOffset", 4 },
- { u"fadeDuration", 4 },
- { u"gestureStrokeType", 4 },
- { u"gestureStrokeLengthThreshold", 4 },
- { u"gestureStrokeSquarenessThreshold", 4 },
- { u"gestureStrokeAngleThreshold", 4 },
- { u"eventsInterceptionEnabled", 4 },
- { u"fadeEnabled", 4 },
- { u"backupAgent", 4 },
- { u"allowBackup", 4 },
- { u"glEsVersion", 4 },
- { u"queryAfterZeroResults", 4 },
- { u"dropDownHeight", 4 },
- { u"smallScreens", 4 },
- { u"normalScreens", 4 },
- { u"largeScreens", 4 },
- { u"progressBarStyleInverse", 4 },
- { u"progressBarStyleSmallInverse", 4 },
- { u"progressBarStyleLargeInverse", 4 },
- { u"searchSettingsDescription", 4 },
- { u"textColorPrimaryInverseDisableOnly", 4 },
- { u"autoUrlDetect", 4 },
- { u"resizeable", 4 },
- { u"required", 5 },
- { u"accountType", 5 },
- { u"contentAuthority", 5 },
- { u"userVisible", 5 },
- { u"windowShowWallpaper", 5 },
- { u"wallpaperOpenEnterAnimation", 5 },
- { u"wallpaperOpenExitAnimation", 5 },
- { u"wallpaperCloseEnterAnimation", 5 },
- { u"wallpaperCloseExitAnimation", 5 },
- { u"wallpaperIntraOpenEnterAnimation", 5 },
- { u"wallpaperIntraOpenExitAnimation", 5 },
- { u"wallpaperIntraCloseEnterAnimation", 5 },
- { u"wallpaperIntraCloseExitAnimation", 5 },
- { u"supportsUploading", 5 },
- { u"killAfterRestore", 5 },
- { u"restoreNeedsApplication", 5 },
- { u"smallIcon", 5 },
- { u"accountPreferences", 5 },
- { u"textAppearanceSearchResultSubtitle", 5 },
- { u"textAppearanceSearchResultTitle", 5 },
- { u"summaryColumn", 5 },
- { u"detailColumn", 5 },
- { u"detailSocialSummary", 5 },
- { u"thumbnail", 5 },
- { u"detachWallpaper", 5 },
- { u"finishOnCloseSystemDialogs", 5 },
- { u"scrollbarFadeDuration", 5 },
- { u"scrollbarDefaultDelayBeforeFade", 5 },
- { u"fadeScrollbars", 5 },
- { u"colorBackgroundCacheHint", 5 },
- { u"dropDownHorizontalOffset", 5 },
- { u"dropDownVerticalOffset", 5 },
- { u"quickContactBadgeStyleWindowSmall", 6 },
- { u"quickContactBadgeStyleWindowMedium", 6 },
- { u"quickContactBadgeStyleWindowLarge", 6 },
- { u"quickContactBadgeStyleSmallWindowSmall", 6 },
- { u"quickContactBadgeStyleSmallWindowMedium", 6 },
- { u"quickContactBadgeStyleSmallWindowLarge", 6 },
- { u"author", 7 },
- { u"autoStart", 7 },
- { u"expandableListViewWhiteStyle", 8 },
- { u"installLocation", 8 },
- { u"vmSafeMode", 8 },
- { u"webTextViewStyle", 8 },
- { u"restoreAnyVersion", 8 },
- { u"tabStripLeft", 8 },
- { u"tabStripRight", 8 },
- { u"tabStripEnabled", 8 },
- { u"logo", 9 },
- { u"xlargeScreens", 9 },
- { u"immersive", 9 },
- { u"overScrollMode", 9 },
- { u"overScrollHeader", 9 },
- { u"overScrollFooter", 9 },
- { u"filterTouchesWhenObscured", 9 },
- { u"textSelectHandleLeft", 9 },
- { u"textSelectHandleRight", 9 },
- { u"textSelectHandle", 9 },
- { u"textSelectHandleWindowStyle", 9 },
- { u"popupAnimationStyle", 9 },
- { u"screenSize", 9 },
- { u"screenDensity", 9 },
- { u"allContactsName", 11 },
- { u"windowActionBar", 11 },
- { u"actionBarStyle", 11 },
- { u"navigationMode", 11 },
- { u"displayOptions", 11 },
- { u"subtitle", 11 },
- { u"customNavigationLayout", 11 },
- { u"hardwareAccelerated", 11 },
- { u"measureWithLargestChild", 11 },
- { u"animateFirstView", 11 },
- { u"dropDownSpinnerStyle", 11 },
- { u"actionDropDownStyle", 11 },
- { u"actionButtonStyle", 11 },
- { u"showAsAction", 11 },
- { u"previewImage", 11 },
- { u"actionModeBackground", 11 },
- { u"actionModeCloseDrawable", 11 },
- { u"windowActionModeOverlay", 11 },
- { u"valueFrom", 11 },
- { u"valueTo", 11 },
- { u"valueType", 11 },
- { u"propertyName", 11 },
- { u"ordering", 11 },
- { u"fragment", 11 },
- { u"windowActionBarOverlay", 11 },
- { u"fragmentOpenEnterAnimation", 11 },
- { u"fragmentOpenExitAnimation", 11 },
- { u"fragmentCloseEnterAnimation", 11 },
- { u"fragmentCloseExitAnimation", 11 },
- { u"fragmentFadeEnterAnimation", 11 },
- { u"fragmentFadeExitAnimation", 11 },
- { u"actionBarSize", 11 },
- { u"imeSubtypeLocale", 11 },
- { u"imeSubtypeMode", 11 },
- { u"imeSubtypeExtraValue", 11 },
- { u"splitMotionEvents", 11 },
- { u"listChoiceBackgroundIndicator", 11 },
- { u"spinnerMode", 11 },
- { u"animateLayoutChanges", 11 },
- { u"actionBarTabStyle", 11 },
- { u"actionBarTabBarStyle", 11 },
- { u"actionBarTabTextStyle", 11 },
- { u"actionOverflowButtonStyle", 11 },
- { u"actionModeCloseButtonStyle", 11 },
- { u"titleTextStyle", 11 },
- { u"subtitleTextStyle", 11 },
- { u"iconifiedByDefault", 11 },
- { u"actionLayout", 11 },
- { u"actionViewClass", 11 },
- { u"activatedBackgroundIndicator", 11 },
- { u"state_activated", 11 },
- { u"listPopupWindowStyle", 11 },
- { u"popupMenuStyle", 11 },
- { u"textAppearanceLargePopupMenu", 11 },
- { u"textAppearanceSmallPopupMenu", 11 },
- { u"breadCrumbTitle", 11 },
- { u"breadCrumbShortTitle", 11 },
- { u"listDividerAlertDialog", 11 },
- { u"textColorAlertDialogListItem", 11 },
- { u"loopViews", 11 },
- { u"dialogTheme", 11 },
- { u"alertDialogTheme", 11 },
- { u"dividerVertical", 11 },
- { u"homeAsUpIndicator", 11 },
- { u"enterFadeDuration", 11 },
- { u"exitFadeDuration", 11 },
- { u"selectableItemBackground", 11 },
- { u"autoAdvanceViewId", 11 },
- { u"useIntrinsicSizeAsMinimum", 11 },
- { u"actionModeCutDrawable", 11 },
- { u"actionModeCopyDrawable", 11 },
- { u"actionModePasteDrawable", 11 },
- { u"textEditPasteWindowLayout", 11 },
- { u"textEditNoPasteWindowLayout", 11 },
- { u"textIsSelectable", 11 },
- { u"windowEnableSplitTouch", 11 },
- { u"indeterminateProgressStyle", 11 },
- { u"progressBarPadding", 11 },
- { u"animationResolution", 11 },
- { u"state_accelerated", 11 },
- { u"baseline", 11 },
- { u"homeLayout", 11 },
- { u"opacity", 11 },
- { u"alpha", 11 },
- { u"transformPivotX", 11 },
- { u"transformPivotY", 11 },
- { u"translationX", 11 },
- { u"translationY", 11 },
- { u"scaleX", 11 },
- { u"scaleY", 11 },
- { u"rotation", 11 },
- { u"rotationX", 11 },
- { u"rotationY", 11 },
- { u"showDividers", 11 },
- { u"dividerPadding", 11 },
- { u"borderlessButtonStyle", 11 },
- { u"dividerHorizontal", 11 },
- { u"itemPadding", 11 },
- { u"buttonBarStyle", 11 },
- { u"buttonBarButtonStyle", 11 },
- { u"segmentedButtonStyle", 11 },
- { u"staticWallpaperPreview", 11 },
- { u"allowParallelSyncs", 11 },
- { u"isAlwaysSyncable", 11 },
- { u"verticalScrollbarPosition", 11 },
- { u"fastScrollAlwaysVisible", 11 },
- { u"fastScrollThumbDrawable", 11 },
- { u"fastScrollPreviewBackgroundLeft", 11 },
- { u"fastScrollPreviewBackgroundRight", 11 },
- { u"fastScrollTrackDrawable", 11 },
- { u"fastScrollOverlayPosition", 11 },
- { u"customTokens", 11 },
- { u"nextFocusForward", 11 },
- { u"firstDayOfWeek", 11 },
- { u"showWeekNumber", 11 },
- { u"minDate", 11 },
- { u"maxDate", 11 },
- { u"shownWeekCount", 11 },
- { u"selectedWeekBackgroundColor", 11 },
- { u"focusedMonthDateColor", 11 },
- { u"unfocusedMonthDateColor", 11 },
- { u"weekNumberColor", 11 },
- { u"weekSeparatorLineColor", 11 },
- { u"selectedDateVerticalBar", 11 },
- { u"weekDayTextAppearance", 11 },
- { u"dateTextAppearance", 11 },
- { u"solidColor", 11 },
- { u"spinnersShown", 11 },
- { u"calendarViewShown", 11 },
- { u"state_multiline", 11 },
- { u"detailsElementBackground", 11 },
- { u"textColorHighlightInverse", 11 },
- { u"textColorLinkInverse", 11 },
- { u"editTextColor", 11 },
- { u"editTextBackground", 11 },
- { u"horizontalScrollViewStyle", 11 },
- { u"layerType", 11 },
- { u"alertDialogIcon", 11 },
- { u"windowMinWidthMajor", 11 },
- { u"windowMinWidthMinor", 11 },
- { u"queryHint", 11 },
- { u"fastScrollTextColor", 11 },
- { u"largeHeap", 11 },
- { u"windowCloseOnTouchOutside", 11 },
- { u"datePickerStyle", 11 },
- { u"calendarViewStyle", 11 },
- { u"textEditSidePasteWindowLayout", 11 },
- { u"textEditSideNoPasteWindowLayout", 11 },
- { u"actionMenuTextAppearance", 11 },
- { u"actionMenuTextColor", 11 },
- { u"textCursorDrawable", 12 },
- { u"resizeMode", 12 },
- { u"requiresSmallestWidthDp", 12 },
- { u"compatibleWidthLimitDp", 12 },
- { u"largestWidthLimitDp", 12 },
- { u"state_hovered", 13 },
- { u"state_drag_can_accept", 13 },
- { u"state_drag_hovered", 13 },
- { u"stopWithTask", 13 },
- { u"switchTextOn", 13 },
- { u"switchTextOff", 13 },
- { u"switchPreferenceStyle", 13 },
- { u"switchTextAppearance", 13 },
- { u"track", 13 },
- { u"switchMinWidth", 13 },
- { u"switchPadding", 13 },
- { u"thumbTextPadding", 13 },
- { u"textSuggestionsWindowStyle", 13 },
- { u"textEditSuggestionItemLayout", 13 },
- { u"rowCount", 13 },
- { u"rowOrderPreserved", 13 },
- { u"columnCount", 13 },
- { u"columnOrderPreserved", 13 },
- { u"useDefaultMargins", 13 },
- { u"alignmentMode", 13 },
- { u"layout_row", 13 },
- { u"layout_rowSpan", 13 },
- { u"layout_columnSpan", 13 },
- { u"actionModeSelectAllDrawable", 13 },
- { u"isAuxiliary", 13 },
- { u"accessibilityEventTypes", 13 },
- { u"packageNames", 13 },
- { u"accessibilityFeedbackType", 13 },
- { u"notificationTimeout", 13 },
- { u"accessibilityFlags", 13 },
- { u"canRetrieveWindowContent", 13 },
- { u"listPreferredItemHeightLarge", 13 },
- { u"listPreferredItemHeightSmall", 13 },
- { u"actionBarSplitStyle", 13 },
- { u"actionProviderClass", 13 },
- { u"backgroundStacked", 13 },
- { u"backgroundSplit", 13 },
- { u"textAllCaps", 13 },
- { u"colorPressedHighlight", 13 },
- { u"colorLongPressedHighlight", 13 },
- { u"colorFocusedHighlight", 13 },
- { u"colorActivatedHighlight", 13 },
- { u"colorMultiSelectHighlight", 13 },
- { u"drawableStart", 13 },
- { u"drawableEnd", 13 },
- { u"actionModeStyle", 13 },
- { u"minResizeWidth", 13 },
- { u"minResizeHeight", 13 },
- { u"actionBarWidgetTheme", 13 },
- { u"uiOptions", 13 },
- { u"subtypeLocale", 13 },
- { u"subtypeExtraValue", 13 },
- { u"actionBarDivider", 13 },
- { u"actionBarItemBackground", 13 },
- { u"actionModeSplitBackground", 13 },
- { u"textAppearanceListItem", 13 },
- { u"textAppearanceListItemSmall", 13 },
- { u"targetDescriptions", 13 },
- { u"directionDescriptions", 13 },
- { u"overridesImplicitlyEnabledSubtype", 13 },
- { u"listPreferredItemPaddingLeft", 13 },
- { u"listPreferredItemPaddingRight", 13 },
- { u"requiresFadingEdge", 13 },
- { u"publicKey", 13 },
- { u"parentActivityName", 16 },
- { u"isolatedProcess", 16 },
- { u"importantForAccessibility", 16 },
- { u"keyboardLayout", 16 },
- { u"fontFamily", 16 },
- { u"mediaRouteButtonStyle", 16 },
- { u"mediaRouteTypes", 16 },
- { u"supportsRtl", 17 },
- { u"textDirection", 17 },
- { u"textAlignment", 17 },
- { u"layoutDirection", 17 },
- { u"paddingStart", 17 },
- { u"paddingEnd", 17 },
- { u"layout_marginStart", 17 },
- { u"layout_marginEnd", 17 },
- { u"layout_toStartOf", 17 },
- { u"layout_toEndOf", 17 },
- { u"layout_alignStart", 17 },
- { u"layout_alignEnd", 17 },
- { u"layout_alignParentStart", 17 },
- { u"layout_alignParentEnd", 17 },
- { u"listPreferredItemPaddingStart", 17 },
- { u"listPreferredItemPaddingEnd", 17 },
- { u"singleUser", 17 },
- { u"presentationTheme", 17 },
- { u"subtypeId", 17 },
- { u"initialKeyguardLayout", 17 },
- { u"widgetCategory", 17 },
- { u"permissionGroupFlags", 17 },
- { u"labelFor", 17 },
- { u"permissionFlags", 17 },
- { u"checkedTextViewStyle", 17 },
- { u"showOnLockScreen", 17 },
- { u"format12Hour", 17 },
- { u"format24Hour", 17 },
- { u"timeZone", 17 },
- { u"mipMap", 18 },
- { u"mirrorForRtl", 18 },
- { u"windowOverscan", 18 },
- { u"requiredForAllUsers", 18 },
- { u"indicatorStart", 18 },
- { u"indicatorEnd", 18 },
- { u"childIndicatorStart", 18 },
- { u"childIndicatorEnd", 18 },
- { u"restrictedAccountType", 18 },
- { u"requiredAccountType", 18 },
- { u"canRequestTouchExplorationMode", 18 },
- { u"canRequestEnhancedWebAccessibility", 18 },
- { u"canRequestFilterKeyEvents", 18 },
- { u"layoutMode", 18 },
- { u"keySet", 19 },
- { u"targetId", 19 },
- { u"fromScene", 19 },
- { u"toScene", 19 },
- { u"transition", 19 },
- { u"transitionOrdering", 19 },
- { u"fadingMode", 19 },
- { u"startDelay", 19 },
- { u"ssp", 19 },
- { u"sspPrefix", 19 },
- { u"sspPattern", 19 },
- { u"addPrintersActivity", 19 },
- { u"vendor", 19 },
- { u"category", 19 },
- { u"isAsciiCapable", 19 },
- { u"autoMirrored", 19 },
- { u"supportsSwitchingToNextInputMethod", 19 },
- { u"requireDeviceUnlock", 19 },
- { u"apduServiceBanner", 19 },
- { u"accessibilityLiveRegion", 19 },
- { u"windowTranslucentStatus", 19 },
- { u"windowTranslucentNavigation", 19 },
- { u"advancedPrintOptionsActivity", 19 },
- { u"banner", 20 },
- { u"windowSwipeToDismiss", 20 },
- { u"isGame", 20 },
- { u"allowEmbedded", 20 },
- { u"setupActivity", 20 },
- { u"fastScrollStyle", 21 },
- { u"windowContentTransitions", 21 },
- { u"windowContentTransitionManager", 21 },
- { u"translationZ", 21 },
- { u"tintMode", 21 },
- { u"controlX1", 21 },
- { u"controlY1", 21 },
- { u"controlX2", 21 },
- { u"controlY2", 21 },
- { u"transitionName", 21 },
- { u"transitionGroup", 21 },
- { u"viewportWidth", 21 },
- { u"viewportHeight", 21 },
- { u"fillColor", 21 },
- { u"pathData", 21 },
- { u"strokeColor", 21 },
- { u"strokeWidth", 21 },
- { u"trimPathStart", 21 },
- { u"trimPathEnd", 21 },
- { u"trimPathOffset", 21 },
- { u"strokeLineCap", 21 },
- { u"strokeLineJoin", 21 },
- { u"strokeMiterLimit", 21 },
- { u"colorControlNormal", 21 },
- { u"colorControlActivated", 21 },
- { u"colorButtonNormal", 21 },
- { u"colorControlHighlight", 21 },
- { u"persistableMode", 21 },
- { u"titleTextAppearance", 21 },
- { u"subtitleTextAppearance", 21 },
- { u"slideEdge", 21 },
- { u"actionBarTheme", 21 },
- { u"textAppearanceListItemSecondary", 21 },
- { u"colorPrimary", 21 },
- { u"colorPrimaryDark", 21 },
- { u"colorAccent", 21 },
- { u"nestedScrollingEnabled", 21 },
- { u"windowEnterTransition", 21 },
- { u"windowExitTransition", 21 },
- { u"windowSharedElementEnterTransition", 21 },
- { u"windowSharedElementExitTransition", 21 },
- { u"windowAllowReturnTransitionOverlap", 21 },
- { u"windowAllowEnterTransitionOverlap", 21 },
- { u"sessionService", 21 },
- { u"stackViewStyle", 21 },
- { u"switchStyle", 21 },
- { u"elevation", 21 },
- { u"excludeId", 21 },
- { u"excludeClass", 21 },
- { u"hideOnContentScroll", 21 },
- { u"actionOverflowMenuStyle", 21 },
- { u"documentLaunchMode", 21 },
- { u"maxRecents", 21 },
- { u"autoRemoveFromRecents", 21 },
- { u"stateListAnimator", 21 },
- { u"toId", 21 },
- { u"fromId", 21 },
- { u"reversible", 21 },
- { u"splitTrack", 21 },
- { u"targetName", 21 },
- { u"excludeName", 21 },
- { u"matchOrder", 21 },
- { u"windowDrawsSystemBarBackgrounds", 21 },
- { u"statusBarColor", 21 },
- { u"navigationBarColor", 21 },
- { u"contentInsetStart", 21 },
- { u"contentInsetEnd", 21 },
- { u"contentInsetLeft", 21 },
- { u"contentInsetRight", 21 },
- { u"paddingMode", 21 },
- { u"layout_rowWeight", 21 },
- { u"layout_columnWeight", 21 },
- { u"translateX", 21 },
- { u"translateY", 21 },
- { u"selectableItemBackgroundBorderless", 21 },
- { u"elegantTextHeight", 21 },
- { u"searchKeyphraseId", 21 },
- { u"searchKeyphrase", 21 },
- { u"searchKeyphraseSupportedLocales", 21 },
- { u"windowTransitionBackgroundFadeDuration", 21 },
- { u"overlapAnchor", 21 },
- { u"progressTint", 21 },
- { u"progressTintMode", 21 },
- { u"progressBackgroundTint", 21 },
- { u"progressBackgroundTintMode", 21 },
- { u"secondaryProgressTint", 21 },
- { u"secondaryProgressTintMode", 21 },
- { u"indeterminateTint", 21 },
- { u"indeterminateTintMode", 21 },
- { u"backgroundTint", 21 },
- { u"backgroundTintMode", 21 },
- { u"foregroundTint", 21 },
- { u"foregroundTintMode", 21 },
- { u"buttonTint", 21 },
- { u"buttonTintMode", 21 },
- { u"thumbTint", 21 },
- { u"thumbTintMode", 21 },
- { u"fullBackupOnly", 21 },
- { u"propertyXName", 21 },
- { u"propertyYName", 21 },
- { u"relinquishTaskIdentity", 21 },
- { u"tileModeX", 21 },
- { u"tileModeY", 21 },
- { u"actionModeShareDrawable", 21 },
- { u"actionModeFindDrawable", 21 },
- { u"actionModeWebSearchDrawable", 21 },
- { u"transitionVisibilityMode", 21 },
- { u"minimumHorizontalAngle", 21 },
- { u"minimumVerticalAngle", 21 },
- { u"maximumAngle", 21 },
- { u"searchViewStyle", 21 },
- { u"closeIcon", 21 },
- { u"goIcon", 21 },
- { u"searchIcon", 21 },
- { u"voiceIcon", 21 },
- { u"commitIcon", 21 },
- { u"suggestionRowLayout", 21 },
- { u"queryBackground", 21 },
- { u"submitBackground", 21 },
- { u"buttonBarPositiveButtonStyle", 21 },
- { u"buttonBarNeutralButtonStyle", 21 },
- { u"buttonBarNegativeButtonStyle", 21 },
- { u"popupElevation", 21 },
- { u"actionBarPopupTheme", 21 },
- { u"multiArch", 21 },
- { u"touchscreenBlocksFocus", 21 },
- { u"windowElevation", 21 },
- { u"launchTaskBehindTargetAnimation", 21 },
- { u"launchTaskBehindSourceAnimation", 21 },
- { u"restrictionType", 21 },
- { u"dayOfWeekBackground", 21 },
- { u"dayOfWeekTextAppearance", 21 },
- { u"headerMonthTextAppearance", 21 },
- { u"headerDayOfMonthTextAppearance", 21 },
- { u"headerYearTextAppearance", 21 },
- { u"yearListItemTextAppearance", 21 },
- { u"yearListSelectorColor", 21 },
- { u"calendarTextColor", 21 },
- { u"recognitionService", 21 },
- { u"timePickerStyle", 21 },
- { u"timePickerDialogTheme", 21 },
- { u"headerTimeTextAppearance", 21 },
- { u"headerAmPmTextAppearance", 21 },
- { u"numbersTextColor", 21 },
- { u"numbersBackgroundColor", 21 },
- { u"numbersSelectorColor", 21 },
- { u"amPmTextColor", 21 },
- { u"amPmBackgroundColor", 21 },
- { u"searchKeyphraseRecognitionFlags", 21 },
- { u"checkMarkTint", 21 },
- { u"checkMarkTintMode", 21 },
- { u"popupTheme", 21 },
- { u"toolbarStyle", 21 },
- { u"windowClipToOutline", 21 },
- { u"datePickerDialogTheme", 21 },
- { u"showText", 21 },
- { u"windowReturnTransition", 21 },
- { u"windowReenterTransition", 21 },
- { u"windowSharedElementReturnTransition", 21 },
- { u"windowSharedElementReenterTransition", 21 },
- { u"resumeWhilePausing", 21 },
- { u"datePickerMode", 21 },
- { u"timePickerMode", 21 },
- { u"inset", 21 },
- { u"letterSpacing", 21 },
- { u"fontFeatureSettings", 21 },
- { u"outlineProvider", 21 },
- { u"contentAgeHint", 21 },
- { u"country", 21 },
- { u"windowSharedElementsUseOverlay", 21 },
- { u"reparent", 21 },
- { u"reparentWithOverlay", 21 },
- { u"ambientShadowAlpha", 21 },
- { u"spotShadowAlpha", 21 },
- { u"navigationIcon", 21 },
- { u"navigationContentDescription", 21 },
- { u"fragmentExitTransition", 21 },
- { u"fragmentEnterTransition", 21 },
- { u"fragmentSharedElementEnterTransition", 21 },
- { u"fragmentReturnTransition", 21 },
- { u"fragmentSharedElementReturnTransition", 21 },
- { u"fragmentReenterTransition", 21 },
- { u"fragmentAllowEnterTransitionOverlap", 21 },
- { u"fragmentAllowReturnTransitionOverlap", 21 },
- { u"patternPathData", 21 },
- { u"strokeAlpha", 21 },
- { u"fillAlpha", 21 },
- { u"windowActivityTransitions", 21 },
- { u"colorEdgeEffect", 21 }
-};
+static const std::unordered_map<std::string, size_t> sAttrMap = {
+ {"marqueeRepeatLimit", 2},
+ {"windowNoDisplay", 3},
+ {"backgroundDimEnabled", 3},
+ {"inputType", 3},
+ {"isDefault", 3},
+ {"windowDisablePreview", 3},
+ {"privateImeOptions", 3},
+ {"editorExtras", 3},
+ {"settingsActivity", 3},
+ {"fastScrollEnabled", 3},
+ {"reqTouchScreen", 3},
+ {"reqKeyboardType", 3},
+ {"reqHardKeyboard", 3},
+ {"reqNavigation", 3},
+ {"windowSoftInputMode", 3},
+ {"imeFullscreenBackground", 3},
+ {"noHistory", 3},
+ {"headerDividersEnabled", 3},
+ {"footerDividersEnabled", 3},
+ {"candidatesTextStyleSpans", 3},
+ {"smoothScrollbar", 3},
+ {"reqFiveWayNav", 3},
+ {"keyBackground", 3},
+ {"keyTextSize", 3},
+ {"labelTextSize", 3},
+ {"keyTextColor", 3},
+ {"keyPreviewLayout", 3},
+ {"keyPreviewOffset", 3},
+ {"keyPreviewHeight", 3},
+ {"verticalCorrection", 3},
+ {"popupLayout", 3},
+ {"state_long_pressable", 3},
+ {"keyWidth", 3},
+ {"keyHeight", 3},
+ {"horizontalGap", 3},
+ {"verticalGap", 3},
+ {"rowEdgeFlags", 3},
+ {"codes", 3},
+ {"popupKeyboard", 3},
+ {"popupCharacters", 3},
+ {"keyEdgeFlags", 3},
+ {"isModifier", 3},
+ {"isSticky", 3},
+ {"isRepeatable", 3},
+ {"iconPreview", 3},
+ {"keyOutputText", 3},
+ {"keyLabel", 3},
+ {"keyIcon", 3},
+ {"keyboardMode", 3},
+ {"isScrollContainer", 3},
+ {"fillEnabled", 3},
+ {"updatePeriodMillis", 3},
+ {"initialLayout", 3},
+ {"voiceSearchMode", 3},
+ {"voiceLanguageModel", 3},
+ {"voicePromptText", 3},
+ {"voiceLanguage", 3},
+ {"voiceMaxResults", 3},
+ {"bottomOffset", 3},
+ {"topOffset", 3},
+ {"allowSingleTap", 3},
+ {"handle", 3},
+ {"content", 3},
+ {"animateOnClick", 3},
+ {"configure", 3},
+ {"hapticFeedbackEnabled", 3},
+ {"innerRadius", 3},
+ {"thickness", 3},
+ {"sharedUserLabel", 3},
+ {"dropDownWidth", 3},
+ {"dropDownAnchor", 3},
+ {"imeOptions", 3},
+ {"imeActionLabel", 3},
+ {"imeActionId", 3},
+ {"imeExtractEnterAnimation", 3},
+ {"imeExtractExitAnimation", 3},
+ {"tension", 4},
+ {"extraTension", 4},
+ {"anyDensity", 4},
+ {"searchSuggestThreshold", 4},
+ {"includeInGlobalSearch", 4},
+ {"onClick", 4},
+ {"targetSdkVersion", 4},
+ {"maxSdkVersion", 4},
+ {"testOnly", 4},
+ {"contentDescription", 4},
+ {"gestureStrokeWidth", 4},
+ {"gestureColor", 4},
+ {"uncertainGestureColor", 4},
+ {"fadeOffset", 4},
+ {"fadeDuration", 4},
+ {"gestureStrokeType", 4},
+ {"gestureStrokeLengthThreshold", 4},
+ {"gestureStrokeSquarenessThreshold", 4},
+ {"gestureStrokeAngleThreshold", 4},
+ {"eventsInterceptionEnabled", 4},
+ {"fadeEnabled", 4},
+ {"backupAgent", 4},
+ {"allowBackup", 4},
+ {"glEsVersion", 4},
+ {"queryAfterZeroResults", 4},
+ {"dropDownHeight", 4},
+ {"smallScreens", 4},
+ {"normalScreens", 4},
+ {"largeScreens", 4},
+ {"progressBarStyleInverse", 4},
+ {"progressBarStyleSmallInverse", 4},
+ {"progressBarStyleLargeInverse", 4},
+ {"searchSettingsDescription", 4},
+ {"textColorPrimaryInverseDisableOnly", 4},
+ {"autoUrlDetect", 4},
+ {"resizeable", 4},
+ {"required", 5},
+ {"accountType", 5},
+ {"contentAuthority", 5},
+ {"userVisible", 5},
+ {"windowShowWallpaper", 5},
+ {"wallpaperOpenEnterAnimation", 5},
+ {"wallpaperOpenExitAnimation", 5},
+ {"wallpaperCloseEnterAnimation", 5},
+ {"wallpaperCloseExitAnimation", 5},
+ {"wallpaperIntraOpenEnterAnimation", 5},
+ {"wallpaperIntraOpenExitAnimation", 5},
+ {"wallpaperIntraCloseEnterAnimation", 5},
+ {"wallpaperIntraCloseExitAnimation", 5},
+ {"supportsUploading", 5},
+ {"killAfterRestore", 5},
+ {"restoreNeedsApplication", 5},
+ {"smallIcon", 5},
+ {"accountPreferences", 5},
+ {"textAppearanceSearchResultSubtitle", 5},
+ {"textAppearanceSearchResultTitle", 5},
+ {"summaryColumn", 5},
+ {"detailColumn", 5},
+ {"detailSocialSummary", 5},
+ {"thumbnail", 5},
+ {"detachWallpaper", 5},
+ {"finishOnCloseSystemDialogs", 5},
+ {"scrollbarFadeDuration", 5},
+ {"scrollbarDefaultDelayBeforeFade", 5},
+ {"fadeScrollbars", 5},
+ {"colorBackgroundCacheHint", 5},
+ {"dropDownHorizontalOffset", 5},
+ {"dropDownVerticalOffset", 5},
+ {"quickContactBadgeStyleWindowSmall", 6},
+ {"quickContactBadgeStyleWindowMedium", 6},
+ {"quickContactBadgeStyleWindowLarge", 6},
+ {"quickContactBadgeStyleSmallWindowSmall", 6},
+ {"quickContactBadgeStyleSmallWindowMedium", 6},
+ {"quickContactBadgeStyleSmallWindowLarge", 6},
+ {"author", 7},
+ {"autoStart", 7},
+ {"expandableListViewWhiteStyle", 8},
+ {"installLocation", 8},
+ {"vmSafeMode", 8},
+ {"webTextViewStyle", 8},
+ {"restoreAnyVersion", 8},
+ {"tabStripLeft", 8},
+ {"tabStripRight", 8},
+ {"tabStripEnabled", 8},
+ {"logo", 9},
+ {"xlargeScreens", 9},
+ {"immersive", 9},
+ {"overScrollMode", 9},
+ {"overScrollHeader", 9},
+ {"overScrollFooter", 9},
+ {"filterTouchesWhenObscured", 9},
+ {"textSelectHandleLeft", 9},
+ {"textSelectHandleRight", 9},
+ {"textSelectHandle", 9},
+ {"textSelectHandleWindowStyle", 9},
+ {"popupAnimationStyle", 9},
+ {"screenSize", 9},
+ {"screenDensity", 9},
+ {"allContactsName", 11},
+ {"windowActionBar", 11},
+ {"actionBarStyle", 11},
+ {"navigationMode", 11},
+ {"displayOptions", 11},
+ {"subtitle", 11},
+ {"customNavigationLayout", 11},
+ {"hardwareAccelerated", 11},
+ {"measureWithLargestChild", 11},
+ {"animateFirstView", 11},
+ {"dropDownSpinnerStyle", 11},
+ {"actionDropDownStyle", 11},
+ {"actionButtonStyle", 11},
+ {"showAsAction", 11},
+ {"previewImage", 11},
+ {"actionModeBackground", 11},
+ {"actionModeCloseDrawable", 11},
+ {"windowActionModeOverlay", 11},
+ {"valueFrom", 11},
+ {"valueTo", 11},
+ {"valueType", 11},
+ {"propertyName", 11},
+ {"ordering", 11},
+ {"fragment", 11},
+ {"windowActionBarOverlay", 11},
+ {"fragmentOpenEnterAnimation", 11},
+ {"fragmentOpenExitAnimation", 11},
+ {"fragmentCloseEnterAnimation", 11},
+ {"fragmentCloseExitAnimation", 11},
+ {"fragmentFadeEnterAnimation", 11},
+ {"fragmentFadeExitAnimation", 11},
+ {"actionBarSize", 11},
+ {"imeSubtypeLocale", 11},
+ {"imeSubtypeMode", 11},
+ {"imeSubtypeExtraValue", 11},
+ {"splitMotionEvents", 11},
+ {"listChoiceBackgroundIndicator", 11},
+ {"spinnerMode", 11},
+ {"animateLayoutChanges", 11},
+ {"actionBarTabStyle", 11},
+ {"actionBarTabBarStyle", 11},
+ {"actionBarTabTextStyle", 11},
+ {"actionOverflowButtonStyle", 11},
+ {"actionModeCloseButtonStyle", 11},
+ {"titleTextStyle", 11},
+ {"subtitleTextStyle", 11},
+ {"iconifiedByDefault", 11},
+ {"actionLayout", 11},
+ {"actionViewClass", 11},
+ {"activatedBackgroundIndicator", 11},
+ {"state_activated", 11},
+ {"listPopupWindowStyle", 11},
+ {"popupMenuStyle", 11},
+ {"textAppearanceLargePopupMen", 11},
+ {"textAppearanceSmallPopupMen", 11},
+ {"breadCrumbTitle", 11},
+ {"breadCrumbShortTitle", 11},
+ {"listDividerAlertDialog", 11},
+ {"textColorAlertDialogListItem", 11},
+ {"loopViews", 11},
+ {"dialogTheme", 11},
+ {"alertDialogTheme", 11},
+ {"dividerVertical", 11},
+ {"homeAsUpIndicator", 11},
+ {"enterFadeDuration", 11},
+ {"exitFadeDuration", 11},
+ {"selectableItemBackground", 11},
+ {"autoAdvanceViewId", 11},
+ {"useIntrinsicSizeAsMinimum", 11},
+ {"actionModeCutDrawable", 11},
+ {"actionModeCopyDrawable", 11},
+ {"actionModePasteDrawable", 11},
+ {"textEditPasteWindowLayout", 11},
+ {"textEditNoPasteWindowLayout", 11},
+ {"textIsSelectable", 11},
+ {"windowEnableSplitTouch", 11},
+ {"indeterminateProgressStyle", 11},
+ {"progressBarPadding", 11},
+ {"animationResolution", 11},
+ {"state_accelerated", 11},
+ {"baseline", 11},
+ {"homeLayout", 11},
+ {"opacity", 11},
+ {"alpha", 11},
+ {"transformPivotX", 11},
+ {"transformPivotY", 11},
+ {"translationX", 11},
+ {"translationY", 11},
+ {"scaleX", 11},
+ {"scaleY", 11},
+ {"rotation", 11},
+ {"rotationX", 11},
+ {"rotationY", 11},
+ {"showDividers", 11},
+ {"dividerPadding", 11},
+ {"borderlessButtonStyle", 11},
+ {"dividerHorizontal", 11},
+ {"itemPadding", 11},
+ {"buttonBarStyle", 11},
+ {"buttonBarButtonStyle", 11},
+ {"segmentedButtonStyle", 11},
+ {"staticWallpaperPreview", 11},
+ {"allowParallelSyncs", 11},
+ {"isAlwaysSyncable", 11},
+ {"verticalScrollbarPosition", 11},
+ {"fastScrollAlwaysVisible", 11},
+ {"fastScrollThumbDrawable", 11},
+ {"fastScrollPreviewBackgroundLeft", 11},
+ {"fastScrollPreviewBackgroundRight", 11},
+ {"fastScrollTrackDrawable", 11},
+ {"fastScrollOverlayPosition", 11},
+ {"customTokens", 11},
+ {"nextFocusForward", 11},
+ {"firstDayOfWeek", 11},
+ {"showWeekNumber", 11},
+ {"minDate", 11},
+ {"maxDate", 11},
+ {"shownWeekCount", 11},
+ {"selectedWeekBackgroundColor", 11},
+ {"focusedMonthDateColor", 11},
+ {"unfocusedMonthDateColor", 11},
+ {"weekNumberColor", 11},
+ {"weekSeparatorLineColor", 11},
+ {"selectedDateVerticalBar", 11},
+ {"weekDayTextAppearance", 11},
+ {"dateTextAppearance", 11},
+ {"solidColor", 11},
+ {"spinnersShown", 11},
+ {"calendarViewShown", 11},
+ {"state_multiline", 11},
+ {"detailsElementBackground", 11},
+ {"textColorHighlightInverse", 11},
+ {"textColorLinkInverse", 11},
+ {"editTextColor", 11},
+ {"editTextBackground", 11},
+ {"horizontalScrollViewStyle", 11},
+ {"layerType", 11},
+ {"alertDialogIcon", 11},
+ {"windowMinWidthMajor", 11},
+ {"windowMinWidthMinor", 11},
+ {"queryHint", 11},
+ {"fastScrollTextColor", 11},
+ {"largeHeap", 11},
+ {"windowCloseOnTouchOutside", 11},
+ {"datePickerStyle", 11},
+ {"calendarViewStyle", 11},
+ {"textEditSidePasteWindowLayout", 11},
+ {"textEditSideNoPasteWindowLayout", 11},
+ {"actionMenuTextAppearance", 11},
+ {"actionMenuTextColor", 11},
+ {"textCursorDrawable", 12},
+ {"resizeMode", 12},
+ {"requiresSmallestWidthDp", 12},
+ {"compatibleWidthLimitDp", 12},
+ {"largestWidthLimitDp", 12},
+ {"state_hovered", 13},
+ {"state_drag_can_accept", 13},
+ {"state_drag_hovered", 13},
+ {"stopWithTask", 13},
+ {"switchTextOn", 13},
+ {"switchTextOff", 13},
+ {"switchPreferenceStyle", 13},
+ {"switchTextAppearance", 13},
+ {"track", 13},
+ {"switchMinWidth", 13},
+ {"switchPadding", 13},
+ {"thumbTextPadding", 13},
+ {"textSuggestionsWindowStyle", 13},
+ {"textEditSuggestionItemLayout", 13},
+ {"rowCount", 13},
+ {"rowOrderPreserved", 13},
+ {"columnCount", 13},
+ {"columnOrderPreserved", 13},
+ {"useDefaultMargins", 13},
+ {"alignmentMode", 13},
+ {"layout_row", 13},
+ {"layout_rowSpan", 13},
+ {"layout_columnSpan", 13},
+ {"actionModeSelectAllDrawable", 13},
+ {"isAuxiliary", 13},
+ {"accessibilityEventTypes", 13},
+ {"packageNames", 13},
+ {"accessibilityFeedbackType", 13},
+ {"notificationTimeout", 13},
+ {"accessibilityFlags", 13},
+ {"canRetrieveWindowContent", 13},
+ {"listPreferredItemHeightLarge", 13},
+ {"listPreferredItemHeightSmall", 13},
+ {"actionBarSplitStyle", 13},
+ {"actionProviderClass", 13},
+ {"backgroundStacked", 13},
+ {"backgroundSplit", 13},
+ {"textAllCaps", 13},
+ {"colorPressedHighlight", 13},
+ {"colorLongPressedHighlight", 13},
+ {"colorFocusedHighlight", 13},
+ {"colorActivatedHighlight", 13},
+ {"colorMultiSelectHighlight", 13},
+ {"drawableStart", 13},
+ {"drawableEnd", 13},
+ {"actionModeStyle", 13},
+ {"minResizeWidth", 13},
+ {"minResizeHeight", 13},
+ {"actionBarWidgetTheme", 13},
+ {"uiOptions", 13},
+ {"subtypeLocale", 13},
+ {"subtypeExtraValue", 13},
+ {"actionBarDivider", 13},
+ {"actionBarItemBackground", 13},
+ {"actionModeSplitBackground", 13},
+ {"textAppearanceListItem", 13},
+ {"textAppearanceListItemSmall", 13},
+ {"targetDescriptions", 13},
+ {"directionDescriptions", 13},
+ {"overridesImplicitlyEnabledSubtype", 13},
+ {"listPreferredItemPaddingLeft", 13},
+ {"listPreferredItemPaddingRight", 13},
+ {"requiresFadingEdge", 13},
+ {"publicKey", 13},
+ {"parentActivityName", 16},
+ {"isolatedProcess", 16},
+ {"importantForAccessibility", 16},
+ {"keyboardLayout", 16},
+ {"fontFamily", 16},
+ {"mediaRouteButtonStyle", 16},
+ {"mediaRouteTypes", 16},
+ {"supportsRtl", 17},
+ {"textDirection", 17},
+ {"textAlignment", 17},
+ {"layoutDirection", 17},
+ {"paddingStart", 17},
+ {"paddingEnd", 17},
+ {"layout_marginStart", 17},
+ {"layout_marginEnd", 17},
+ {"layout_toStartOf", 17},
+ {"layout_toEndOf", 17},
+ {"layout_alignStart", 17},
+ {"layout_alignEnd", 17},
+ {"layout_alignParentStart", 17},
+ {"layout_alignParentEnd", 17},
+ {"listPreferredItemPaddingStart", 17},
+ {"listPreferredItemPaddingEnd", 17},
+ {"singleUser", 17},
+ {"presentationTheme", 17},
+ {"subtypeId", 17},
+ {"initialKeyguardLayout", 17},
+ {"widgetCategory", 17},
+ {"permissionGroupFlags", 17},
+ {"labelFor", 17},
+ {"permissionFlags", 17},
+ {"checkedTextViewStyle", 17},
+ {"showOnLockScreen", 17},
+ {"format12Hour", 17},
+ {"format24Hour", 17},
+ {"timeZone", 17},
+ {"mipMap", 18},
+ {"mirrorForRtl", 18},
+ {"windowOverscan", 18},
+ {"requiredForAllUsers", 18},
+ {"indicatorStart", 18},
+ {"indicatorEnd", 18},
+ {"childIndicatorStart", 18},
+ {"childIndicatorEnd", 18},
+ {"restrictedAccountType", 18},
+ {"requiredAccountType", 18},
+ {"canRequestTouchExplorationMode", 18},
+ {"canRequestEnhancedWebAccessibility", 18},
+ {"canRequestFilterKeyEvents", 18},
+ {"layoutMode", 18},
+ {"keySet", 19},
+ {"targetId", 19},
+ {"fromScene", 19},
+ {"toScene", 19},
+ {"transition", 19},
+ {"transitionOrdering", 19},
+ {"fadingMode", 19},
+ {"startDelay", 19},
+ {"ssp", 19},
+ {"sspPrefix", 19},
+ {"sspPattern", 19},
+ {"addPrintersActivity", 19},
+ {"vendor", 19},
+ {"category", 19},
+ {"isAsciiCapable", 19},
+ {"autoMirrored", 19},
+ {"supportsSwitchingToNextInputMethod", 19},
+ {"requireDeviceUnlock", 19},
+ {"apduServiceBanner", 19},
+ {"accessibilityLiveRegion", 19},
+ {"windowTranslucentStatus", 19},
+ {"windowTranslucentNavigation", 19},
+ {"advancedPrintOptionsActivity", 19},
+ {"banner", 20},
+ {"windowSwipeToDismiss", 20},
+ {"isGame", 20},
+ {"allowEmbedded", 20},
+ {"setupActivity", 20},
+ {"fastScrollStyle", 21},
+ {"windowContentTransitions", 21},
+ {"windowContentTransitionManager", 21},
+ {"translationZ", 21},
+ {"tintMode", 21},
+ {"controlX1", 21},
+ {"controlY1", 21},
+ {"controlX2", 21},
+ {"controlY2", 21},
+ {"transitionName", 21},
+ {"transitionGroup", 21},
+ {"viewportWidth", 21},
+ {"viewportHeight", 21},
+ {"fillColor", 21},
+ {"pathData", 21},
+ {"strokeColor", 21},
+ {"strokeWidth", 21},
+ {"trimPathStart", 21},
+ {"trimPathEnd", 21},
+ {"trimPathOffset", 21},
+ {"strokeLineCap", 21},
+ {"strokeLineJoin", 21},
+ {"strokeMiterLimit", 21},
+ {"colorControlNormal", 21},
+ {"colorControlActivated", 21},
+ {"colorButtonNormal", 21},
+ {"colorControlHighlight", 21},
+ {"persistableMode", 21},
+ {"titleTextAppearance", 21},
+ {"subtitleTextAppearance", 21},
+ {"slideEdge", 21},
+ {"actionBarTheme", 21},
+ {"textAppearanceListItemSecondary", 21},
+ {"colorPrimary", 21},
+ {"colorPrimaryDark", 21},
+ {"colorAccent", 21},
+ {"nestedScrollingEnabled", 21},
+ {"windowEnterTransition", 21},
+ {"windowExitTransition", 21},
+ {"windowSharedElementEnterTransition", 21},
+ {"windowSharedElementExitTransition", 21},
+ {"windowAllowReturnTransitionOverlap", 21},
+ {"windowAllowEnterTransitionOverlap", 21},
+ {"sessionService", 21},
+ {"stackViewStyle", 21},
+ {"switchStyle", 21},
+ {"elevation", 21},
+ {"excludeId", 21},
+ {"excludeClass", 21},
+ {"hideOnContentScroll", 21},
+ {"actionOverflowMenuStyle", 21},
+ {"documentLaunchMode", 21},
+ {"maxRecents", 21},
+ {"autoRemoveFromRecents", 21},
+ {"stateListAnimator", 21},
+ {"toId", 21},
+ {"fromId", 21},
+ {"reversible", 21},
+ {"splitTrack", 21},
+ {"targetName", 21},
+ {"excludeName", 21},
+ {"matchOrder", 21},
+ {"windowDrawsSystemBarBackgrounds", 21},
+ {"statusBarColor", 21},
+ {"navigationBarColor", 21},
+ {"contentInsetStart", 21},
+ {"contentInsetEnd", 21},
+ {"contentInsetLeft", 21},
+ {"contentInsetRight", 21},
+ {"paddingMode", 21},
+ {"layout_rowWeight", 21},
+ {"layout_columnWeight", 21},
+ {"translateX", 21},
+ {"translateY", 21},
+ {"selectableItemBackgroundBorderless", 21},
+ {"elegantTextHeight", 21},
+ {"searchKeyphraseId", 21},
+ {"searchKeyphrase", 21},
+ {"searchKeyphraseSupportedLocales", 21},
+ {"windowTransitionBackgroundFadeDuration", 21},
+ {"overlapAnchor", 21},
+ {"progressTint", 21},
+ {"progressTintMode", 21},
+ {"progressBackgroundTint", 21},
+ {"progressBackgroundTintMode", 21},
+ {"secondaryProgressTint", 21},
+ {"secondaryProgressTintMode", 21},
+ {"indeterminateTint", 21},
+ {"indeterminateTintMode", 21},
+ {"backgroundTint", 21},
+ {"backgroundTintMode", 21},
+ {"foregroundTint", 21},
+ {"foregroundTintMode", 21},
+ {"buttonTint", 21},
+ {"buttonTintMode", 21},
+ {"thumbTint", 21},
+ {"thumbTintMode", 21},
+ {"fullBackupOnly", 21},
+ {"propertyXName", 21},
+ {"propertyYName", 21},
+ {"relinquishTaskIdentity", 21},
+ {"tileModeX", 21},
+ {"tileModeY", 21},
+ {"actionModeShareDrawable", 21},
+ {"actionModeFindDrawable", 21},
+ {"actionModeWebSearchDrawable", 21},
+ {"transitionVisibilityMode", 21},
+ {"minimumHorizontalAngle", 21},
+ {"minimumVerticalAngle", 21},
+ {"maximumAngle", 21},
+ {"searchViewStyle", 21},
+ {"closeIcon", 21},
+ {"goIcon", 21},
+ {"searchIcon", 21},
+ {"voiceIcon", 21},
+ {"commitIcon", 21},
+ {"suggestionRowLayout", 21},
+ {"queryBackground", 21},
+ {"submitBackground", 21},
+ {"buttonBarPositiveButtonStyle", 21},
+ {"buttonBarNeutralButtonStyle", 21},
+ {"buttonBarNegativeButtonStyle", 21},
+ {"popupElevation", 21},
+ {"actionBarPopupTheme", 21},
+ {"multiArch", 21},
+ {"touchscreenBlocksFocus", 21},
+ {"windowElevation", 21},
+ {"launchTaskBehindTargetAnimation", 21},
+ {"launchTaskBehindSourceAnimation", 21},
+ {"restrictionType", 21},
+ {"dayOfWeekBackground", 21},
+ {"dayOfWeekTextAppearance", 21},
+ {"headerMonthTextAppearance", 21},
+ {"headerDayOfMonthTextAppearance", 21},
+ {"headerYearTextAppearance", 21},
+ {"yearListItemTextAppearance", 21},
+ {"yearListSelectorColor", 21},
+ {"calendarTextColor", 21},
+ {"recognitionService", 21},
+ {"timePickerStyle", 21},
+ {"timePickerDialogTheme", 21},
+ {"headerTimeTextAppearance", 21},
+ {"headerAmPmTextAppearance", 21},
+ {"numbersTextColor", 21},
+ {"numbersBackgroundColor", 21},
+ {"numbersSelectorColor", 21},
+ {"amPmTextColor", 21},
+ {"amPmBackgroundColor", 21},
+ {"searchKeyphraseRecognitionFlags", 21},
+ {"checkMarkTint", 21},
+ {"checkMarkTintMode", 21},
+ {"popupTheme", 21},
+ {"toolbarStyle", 21},
+ {"windowClipToOutline", 21},
+ {"datePickerDialogTheme", 21},
+ {"showText", 21},
+ {"windowReturnTransition", 21},
+ {"windowReenterTransition", 21},
+ {"windowSharedElementReturnTransition", 21},
+ {"windowSharedElementReenterTransition", 21},
+ {"resumeWhilePausing", 21},
+ {"datePickerMode", 21},
+ {"timePickerMode", 21},
+ {"inset", 21},
+ {"letterSpacing", 21},
+ {"fontFeatureSettings", 21},
+ {"outlineProvider", 21},
+ {"contentAgeHint", 21},
+ {"country", 21},
+ {"windowSharedElementsUseOverlay", 21},
+ {"reparent", 21},
+ {"reparentWithOverlay", 21},
+ {"ambientShadowAlpha", 21},
+ {"spotShadowAlpha", 21},
+ {"navigationIcon", 21},
+ {"navigationContentDescription", 21},
+ {"fragmentExitTransition", 21},
+ {"fragmentEnterTransition", 21},
+ {"fragmentSharedElementEnterTransition", 21},
+ {"fragmentReturnTransition", 21},
+ {"fragmentSharedElementReturnTransition", 21},
+ {"fragmentReenterTransition", 21},
+ {"fragmentAllowEnterTransitionOverlap", 21},
+ {"fragmentAllowReturnTransitionOverlap", 21},
+ {"patternPathData", 21},
+ {"strokeAlpha", 21},
+ {"fillAlpha", 21},
+ {"windowActivityTransitions", 21},
+ {"colorEdgeEffect", 21}};
-size_t findAttributeSdkLevel(const ResourceName& name) {
- if (name.package != u"android" && name.type != ResourceType::kAttr) {
- return 0;
- }
+size_t FindAttributeSdkLevel(const ResourceName& name) {
+ if (name.package != "android" && name.type != ResourceType::kAttr) {
+ return 0;
+ }
- auto iter = sAttrMap.find(name.entry);
- if (iter != sAttrMap.end()) {
- return iter->second;
- }
- return SDK_LOLLIPOP_MR1;
+ auto iter = sAttrMap.find(name.entry);
+ if (iter != sAttrMap.end()) {
+ return iter->second;
+ }
+ return SDK_LOLLIPOP_MR1;
+}
+
+std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion() {
+ return std::make_pair(StringPiece(sDevelopmentSdkCodeName),
+ sDevelopmentSdkLevel);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 8a7e3436ed6e..9b38ecbeae99 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -17,37 +17,40 @@
#ifndef AAPT_SDK_CONSTANTS_H
#define AAPT_SDK_CONSTANTS_H
+#include <utility>
+
#include "Resource.h"
namespace aapt {
enum {
- SDK_CUPCAKE = 3,
- SDK_DONUT = 4,
- SDK_ECLAIR = 5,
- SDK_ECLAIR_0_1 = 6,
- SDK_ECLAIR_MR1 = 7,
- SDK_FROYO = 8,
- SDK_GINGERBREAD = 9,
- SDK_GINGERBREAD_MR1 = 10,
- SDK_HONEYCOMB = 11,
- SDK_HONEYCOMB_MR1 = 12,
- SDK_HONEYCOMB_MR2 = 13,
- SDK_ICE_CREAM_SANDWICH = 14,
- SDK_ICE_CREAM_SANDWICH_MR1 = 15,
- SDK_JELLY_BEAN = 16,
- SDK_JELLY_BEAN_MR1 = 17,
- SDK_JELLY_BEAN_MR2 = 18,
- SDK_KITKAT = 19,
- SDK_KITKAT_WATCH = 20,
- SDK_LOLLIPOP = 21,
- SDK_LOLLIPOP_MR1 = 22,
- SDK_MARSHMALLOW = 23,
+ SDK_CUPCAKE = 3,
+ SDK_DONUT = 4,
+ SDK_ECLAIR = 5,
+ SDK_ECLAIR_0_1 = 6,
+ SDK_ECLAIR_MR1 = 7,
+ SDK_FROYO = 8,
+ SDK_GINGERBREAD = 9,
+ SDK_GINGERBREAD_MR1 = 10,
+ SDK_HONEYCOMB = 11,
+ SDK_HONEYCOMB_MR1 = 12,
+ SDK_HONEYCOMB_MR2 = 13,
+ SDK_ICE_CREAM_SANDWICH = 14,
+ SDK_ICE_CREAM_SANDWICH_MR1 = 15,
+ SDK_JELLY_BEAN = 16,
+ SDK_JELLY_BEAN_MR1 = 17,
+ SDK_JELLY_BEAN_MR2 = 18,
+ SDK_KITKAT = 19,
+ SDK_KITKAT_WATCH = 20,
+ SDK_LOLLIPOP = 21,
+ SDK_LOLLIPOP_MR1 = 22,
+ SDK_MARSHMALLOW = 23,
};
-size_t findAttributeSdkLevel(const ResourceId& id);
-size_t findAttributeSdkLevel(const ResourceName& name);
+size_t FindAttributeSdkLevel(const ResourceId& id);
+size_t FindAttributeSdkLevel(const ResourceName& name);
+std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion();
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_SDK_CONSTANTS_H
+#endif // AAPT_SDK_CONSTANTS_H
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
index e81f412dda15..716d922fb5a0 100644
--- a/tools/aapt2/SdkConstants_test.cpp
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -16,23 +16,23 @@
#include "SdkConstants.h"
-#include <gtest/gtest.h>
+#include "gtest/gtest.h"
namespace aapt {
TEST(SdkConstantsTest, FirstAttributeIsSdk1) {
- EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000)));
+ 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, 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(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)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104d9)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x0101ffff)));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 319528e0ea1b..459a8e603b6b 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -17,12 +17,12 @@
#ifndef AAPT_SOURCE_H
#define AAPT_SOURCE_H
-#include "util/Maybe.h"
-#include "util/StringPiece.h"
-
#include <ostream>
#include <string>
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
namespace aapt {
/**
@@ -30,20 +30,19 @@ namespace aapt {
* showing errors.
*/
struct Source {
- std::string path;
- Maybe<size_t> line;
+ std::string path;
+ Maybe<size_t> line;
- Source() = default;
+ Source() = default;
- inline Source(const StringPiece& path) : path(path.toString()) {
- }
+ inline Source(const StringPiece& path)
+ : path(path.ToString()) { // NOLINT(implicit)
+ }
- inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) {
- }
+ inline Source(const StringPiece& path, size_t line)
+ : path(path.ToString()), line(line) {}
- inline Source withLine(size_t line) const {
- return Source(path, line);
- }
+ inline Source WithLine(size_t line) const { return Source(path, line); }
};
//
@@ -51,30 +50,30 @@ struct Source {
//
inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
- out << source.path;
- if (source.line) {
- out << ":" << source.line.value();
- }
- return out;
+ out << source.path;
+ if (source.line) {
+ out << ":" << source.line.value();
+ }
+ return out;
}
inline bool operator==(const Source& lhs, const Source& rhs) {
- return lhs.path == rhs.path && lhs.line == rhs.line;
+ return lhs.path == rhs.path && lhs.line == 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;
+ 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 bool(rhs.line);
+ return false;
+ }
+ return bool(rhs.line);
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_SOURCE_H
+#endif // AAPT_SOURCE_H
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index aadb00b6be2a..30328299bb84 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -15,393 +15,405 @@
*/
#include "StringPool.h"
-#include "util/BigBuffer.h"
-#include "util/StringPiece.h"
-#include "util/Util.h"
#include <algorithm>
-#include <androidfw/ResourceTypes.h>
#include <memory>
#include <string>
+#include "android-base/logging.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "util/BigBuffer.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
namespace aapt {
-StringPool::Ref::Ref() : mEntry(nullptr) {
-}
+StringPool::Ref::Ref() : entry_(nullptr) {}
-StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
+StringPool::Ref::Ref(const StringPool::Ref& rhs) : entry_(rhs.entry_) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
}
-StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
+StringPool::Ref::Ref(StringPool::Entry* entry) : entry_(entry) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
}
StringPool::Ref::~Ref() {
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
}
StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
- if (rhs.mEntry != nullptr) {
- rhs.mEntry->ref++;
- }
+ if (rhs.entry_ != nullptr) {
+ rhs.entry_->ref_++;
+ }
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
- mEntry = rhs.mEntry;
- return *this;
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
+ entry_ = rhs.entry_;
+ return *this;
}
-const std::u16string* StringPool::Ref::operator->() const {
- return &mEntry->value;
+const std::string* StringPool::Ref::operator->() const {
+ return &entry_->value;
}
-const std::u16string& StringPool::Ref::operator*() const {
- return mEntry->value;
-}
+const std::string& StringPool::Ref::operator*() const { return entry_->value; }
-size_t StringPool::Ref::getIndex() const {
- return mEntry->index;
-}
+size_t StringPool::Ref::index() const { return entry_->index; }
-const StringPool::Context& StringPool::Ref::getContext() const {
- return mEntry->context;
+const StringPool::Context& StringPool::Ref::GetContext() const {
+ return entry_->context;
}
-StringPool::StyleRef::StyleRef() : mEntry(nullptr) {
-}
+StringPool::StyleRef::StyleRef() : entry_(nullptr) {}
-StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : mEntry(rhs.mEntry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
+StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs)
+ : entry_(rhs.entry_) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
}
-StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : mEntry(entry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
+StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : entry_(entry) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
}
StringPool::StyleRef::~StyleRef() {
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
}
-StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) {
- if (rhs.mEntry != nullptr) {
- rhs.mEntry->ref++;
- }
+StringPool::StyleRef& StringPool::StyleRef::operator=(
+ const StringPool::StyleRef& rhs) {
+ if (rhs.entry_ != nullptr) {
+ rhs.entry_->ref_++;
+ }
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
- mEntry = rhs.mEntry;
- return *this;
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
+ entry_ = rhs.entry_;
+ return *this;
}
const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
- return mEntry;
+ return entry_;
}
const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
- return *mEntry;
+ return *entry_;
}
-size_t StringPool::StyleRef::getIndex() const {
- return mEntry->str.getIndex();
-}
+size_t StringPool::StyleRef::index() const { return entry_->str.index(); }
-const StringPool::Context& StringPool::StyleRef::getContext() const {
- return mEntry->str.getContext();
+const StringPool::Context& StringPool::StyleRef::GetContext() const {
+ return entry_->str.GetContext();
}
-StringPool::Ref StringPool::makeRef(const StringPiece16& str) {
- return makeRefImpl(str, Context{}, true);
+StringPool::Ref StringPool::MakeRef(const StringPiece& str) {
+ return MakeRefImpl(str, Context{}, true);
}
-StringPool::Ref StringPool::makeRef(const StringPiece16& str, const Context& context) {
- return makeRefImpl(str, context, true);
+StringPool::Ref StringPool::MakeRef(const StringPiece& str,
+ const Context& context) {
+ return MakeRefImpl(str, context, true);
}
-StringPool::Ref StringPool::makeRefImpl(const StringPiece16& str, const Context& context,
- bool unique) {
- if (unique) {
- auto iter = mIndexedStrings.find(str);
- if (iter != std::end(mIndexedStrings)) {
- return Ref(iter->second);
- }
+StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str,
+ const Context& context, bool unique) {
+ if (unique) {
+ auto iter = indexed_strings_.find(str);
+ if (iter != std::end(indexed_strings_)) {
+ return Ref(iter->second);
}
-
- Entry* entry = new Entry();
- entry->value = str.toString();
- entry->context = context;
- entry->index = mStrings.size();
- entry->ref = 0;
- mStrings.emplace_back(entry);
- mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry));
- return Ref(entry);
-}
-
-StringPool::StyleRef StringPool::makeRef(const StyleString& str) {
- return makeRef(str, Context{});
-}
-
-StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context& context) {
- Entry* entry = new Entry();
- entry->value = str.str;
- entry->context = context;
- entry->index = mStrings.size();
- entry->ref = 0;
- mStrings.emplace_back(entry);
- mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry));
-
- StyleEntry* styleEntry = new StyleEntry();
- styleEntry->str = Ref(entry);
- for (const aapt::Span& span : str.spans) {
- styleEntry->spans.emplace_back(Span{makeRef(span.name),
- span.firstChar, span.lastChar});
- }
- styleEntry->ref = 0;
- mStyles.emplace_back(styleEntry);
- return StyleRef(styleEntry);
-}
-
-StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) {
- Entry* entry = new Entry();
- entry->value = *ref.mEntry->str;
- entry->context = ref.mEntry->str.mEntry->context;
- entry->index = mStrings.size();
- entry->ref = 0;
- mStrings.emplace_back(entry);
- mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry));
-
- StyleEntry* styleEntry = new StyleEntry();
- styleEntry->str = Ref(entry);
- for (const Span& span : ref.mEntry->spans) {
- styleEntry->spans.emplace_back(Span{ makeRef(*span.name), span.firstChar, span.lastChar });
- }
- styleEntry->ref = 0;
- mStyles.emplace_back(styleEntry);
- return StyleRef(styleEntry);
-}
-
-void StringPool::merge(StringPool&& pool) {
- mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end());
- pool.mIndexedStrings.clear();
- std::move(pool.mStrings.begin(), pool.mStrings.end(), std::back_inserter(mStrings));
- pool.mStrings.clear();
- std::move(pool.mStyles.begin(), pool.mStyles.end(), std::back_inserter(mStyles));
- pool.mStyles.clear();
-
- // Assign the indices.
- const size_t len = mStrings.size();
- for (size_t index = 0; index < len; index++) {
- mStrings[index]->index = index;
- }
-}
-
-void StringPool::hintWillAdd(size_t stringCount, size_t styleCount) {
- mStrings.reserve(mStrings.size() + stringCount);
- mStyles.reserve(mStyles.size() + styleCount);
-}
-
-void StringPool::prune() {
- const auto iterEnd = std::end(mIndexedStrings);
- auto indexIter = std::begin(mIndexedStrings);
- while (indexIter != iterEnd) {
- if (indexIter->second->ref <= 0) {
- indexIter = mIndexedStrings.erase(indexIter);
- } else {
- ++indexIter;
- }
- }
-
- auto endIter2 = std::remove_if(std::begin(mStrings), std::end(mStrings),
- [](const std::unique_ptr<Entry>& entry) -> bool {
- return entry->ref <= 0;
- }
- );
-
- auto endIter3 = std::remove_if(std::begin(mStyles), std::end(mStyles),
- [](const std::unique_ptr<StyleEntry>& entry) -> bool {
- return entry->ref <= 0;
- }
- );
-
- // Remove the entries at the end or else we'll be accessing
- // 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) {
- std::sort(std::begin(mStrings), std::end(mStrings),
- [&cmp](const std::unique_ptr<Entry>& a, const std::unique_ptr<Entry>& b) -> bool {
- return cmp(*a, *b);
- }
- );
-
- // Assign the indices.
- const size_t len = mStrings.size();
- for (size_t index = 0; index < len; index++) {
- mStrings[index]->index = index;
+ }
+
+ Entry* entry = new Entry();
+ entry->value = str.ToString();
+ entry->context = context;
+ entry->index = strings_.size();
+ entry->ref_ = 0;
+ strings_.emplace_back(entry);
+ indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
+ return Ref(entry);
+}
+
+StringPool::StyleRef StringPool::MakeRef(const StyleString& str) {
+ return MakeRef(str, Context{});
+}
+
+StringPool::StyleRef StringPool::MakeRef(const StyleString& str,
+ const Context& context) {
+ Entry* entry = new Entry();
+ entry->value = str.str;
+ entry->context = context;
+ entry->index = strings_.size();
+ entry->ref_ = 0;
+ strings_.emplace_back(entry);
+ indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
+
+ StyleEntry* style_entry = new StyleEntry();
+ style_entry->str = Ref(entry);
+ for (const aapt::Span& span : str.spans) {
+ style_entry->spans.emplace_back(
+ Span{MakeRef(span.name), span.first_char, span.last_char});
+ }
+ style_entry->ref_ = 0;
+ styles_.emplace_back(style_entry);
+ return StyleRef(style_entry);
+}
+
+StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) {
+ Entry* entry = new Entry();
+ entry->value = *ref.entry_->str;
+ entry->context = ref.entry_->str.entry_->context;
+ entry->index = strings_.size();
+ entry->ref_ = 0;
+ strings_.emplace_back(entry);
+ indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
+
+ StyleEntry* style_entry = new StyleEntry();
+ style_entry->str = Ref(entry);
+ for (const Span& span : ref.entry_->spans) {
+ style_entry->spans.emplace_back(
+ Span{MakeRef(*span.name), span.first_char, span.last_char});
+ }
+ style_entry->ref_ = 0;
+ styles_.emplace_back(style_entry);
+ return StyleRef(style_entry);
+}
+
+void StringPool::Merge(StringPool&& pool) {
+ indexed_strings_.insert(pool.indexed_strings_.begin(),
+ pool.indexed_strings_.end());
+ pool.indexed_strings_.clear();
+ std::move(pool.strings_.begin(), pool.strings_.end(),
+ std::back_inserter(strings_));
+ pool.strings_.clear();
+ std::move(pool.styles_.begin(), pool.styles_.end(),
+ std::back_inserter(styles_));
+ pool.styles_.clear();
+
+ // Assign the indices.
+ const size_t len = strings_.size();
+ for (size_t index = 0; index < len; index++) {
+ strings_[index]->index = index;
+ }
+}
+
+void StringPool::HintWillAdd(size_t stringCount, size_t styleCount) {
+ strings_.reserve(strings_.size() + stringCount);
+ styles_.reserve(styles_.size() + styleCount);
+}
+
+void StringPool::Prune() {
+ const auto iter_end = indexed_strings_.end();
+ auto index_iter = indexed_strings_.begin();
+ while (index_iter != iter_end) {
+ if (index_iter->second->ref_ <= 0) {
+ index_iter = indexed_strings_.erase(index_iter);
+ } else {
+ ++index_iter;
}
-
- // Reorder the styles.
- std::sort(std::begin(mStyles), std::end(mStyles),
+ }
+
+ auto end_iter2 =
+ std::remove_if(strings_.begin(), strings_.end(),
+ [](const std::unique_ptr<Entry>& entry) -> bool {
+ return entry->ref_ <= 0;
+ });
+
+ auto end_iter3 =
+ std::remove_if(styles_.begin(), styles_.end(),
+ [](const std::unique_ptr<StyleEntry>& entry) -> bool {
+ return entry->ref_ <= 0;
+ });
+
+ // Remove the entries at the end or else we'll be accessing
+ // a deleted string from the StyleEntry.
+ strings_.erase(end_iter2, strings_.end());
+ styles_.erase(end_iter3, styles_.end());
+
+ // Reassign the indices.
+ const size_t len = strings_.size();
+ for (size_t index = 0; index < len; index++) {
+ strings_[index]->index = index;
+ }
+}
+
+void StringPool::Sort(
+ const std::function<bool(const Entry&, const Entry&)>& cmp) {
+ std::sort(
+ strings_.begin(), strings_.end(),
+ [&cmp](const std::unique_ptr<Entry>& a,
+ const std::unique_ptr<Entry>& b) -> bool { return cmp(*a, *b); });
+
+ // Assign the indices.
+ const size_t len = strings_.size();
+ for (size_t index = 0; index < len; index++) {
+ strings_[index]->index = index;
+ }
+
+ // Reorder the styles.
+ std::sort(styles_.begin(), styles_.end(),
[](const std::unique_ptr<StyleEntry>& lhs,
const std::unique_ptr<StyleEntry>& rhs) -> bool {
- return lhs->str.getIndex() < rhs->str.getIndex();
- }
- );
+ return lhs->str.index() < rhs->str.index();
+ });
}
template <typename T>
-static T* encodeLength(T* data, size_t length) {
- static_assert(std::is_integral<T>::value, "wat.");
+static T* EncodeLength(T* data, size_t length) {
+ static_assert(std::is_integral<T>::value, "wat.");
- constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
- constexpr size_t kMaxSize = kMask - 1;
- if (length > kMaxSize) {
- *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8)));
- }
- *data++ = length;
- return data;
+ constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
+ constexpr size_t kMaxSize = kMask - 1;
+ if (length > kMaxSize) {
+ *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8)));
+ }
+ *data++ = length;
+ return data;
}
template <typename T>
-static size_t encodedLengthUnits(size_t length) {
- static_assert(std::is_integral<T>::value, "wat.");
+static size_t EncodedLengthUnits(size_t length) {
+ static_assert(std::is_integral<T>::value, "wat.");
- constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
- constexpr size_t kMaxSize = kMask - 1;
- return length > kMaxSize ? 2 : 1;
+ constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
+ constexpr size_t kMaxSize = kMask - 1;
+ return length > kMaxSize ? 2 : 1;
}
+bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
+ const size_t start_index = out->size();
+ android::ResStringPool_header* header =
+ out->NextBlock<android::ResStringPool_header>();
+ header->header.type = android::RES_STRING_POOL_TYPE;
+ header->header.headerSize = sizeof(*header);
+ header->stringCount = pool.size();
+ if (utf8) {
+ header->flags |= android::ResStringPool_header::UTF8_FLAG;
+ }
-bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
- const size_t startIndex = out->size();
- android::ResStringPool_header* header = out->nextBlock<android::ResStringPool_header>();
- header->header.type = android::RES_STRING_POOL_TYPE;
- header->header.headerSize = sizeof(*header);
- header->stringCount = pool.size();
- if (utf8) {
- header->flags |= android::ResStringPool_header::UTF8_FLAG;
- }
+ uint32_t* indices =
+ pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr;
- uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr;
+ uint32_t* style_indices = nullptr;
+ if (!pool.styles_.empty()) {
+ header->styleCount = pool.styles_.back()->str.index() + 1;
+ style_indices = out->NextBlock<uint32_t>(header->styleCount);
+ }
- uint32_t* styleIndices = nullptr;
- if (!pool.mStyles.empty()) {
- header->styleCount = pool.mStyles.back()->str.getIndex() + 1;
- styleIndices = out->nextBlock<uint32_t>(header->styleCount);
- }
+ const size_t before_strings_index = out->size();
+ header->stringsStart = before_strings_index - start_index;
+
+ for (const auto& entry : pool) {
+ *indices = out->size() - before_strings_index;
+ indices++;
- const size_t beforeStringsIndex = out->size();
- header->stringsStart = beforeStringsIndex - startIndex;
+ if (utf8) {
+ const std::string& encoded = entry->value;
+ const ssize_t utf16_length = utf8_to_utf16_length(
+ reinterpret_cast<const uint8_t*>(entry->value.data()),
+ entry->value.size());
+ CHECK(utf16_length >= 0);
- for (const auto& entry : pool) {
- *indices = out->size() - beforeStringsIndex;
- indices++;
+ const size_t total_size = EncodedLengthUnits<char>(utf16_length) +
+ EncodedLengthUnits<char>(encoded.length()) +
+ encoded.size() + 1;
- if (utf8) {
- std::string encoded = util::utf16ToUtf8(entry->value);
+ char* data = out->NextBlock<char>(total_size);
- const size_t totalSize = encodedLengthUnits<char>(entry->value.size())
- + encodedLengthUnits<char>(encoded.length())
- + encoded.size() + 1;
+ // First encode the UTF16 string length.
+ data = EncodeLength(data, utf16_length);
- char* data = out->nextBlock<char>(totalSize);
+ // Now encode the size of the real UTF8 string.
+ data = EncodeLength(data, encoded.length());
+ strncpy(data, encoded.data(), encoded.size());
- // First encode the actual UTF16 string length.
- data = encodeLength(data, entry->value.size());
+ } else {
+ const std::u16string encoded = util::Utf8ToUtf16(entry->value);
+ const ssize_t utf16_length = encoded.size();
- // Now encode the size of the converted UTF8 string.
- data = encodeLength(data, encoded.length());
- strncpy(data, encoded.data(), encoded.size());
- } else {
- const size_t totalSize = encodedLengthUnits<char16_t>(entry->value.size())
- + entry->value.size() + 1;
+ // Total number of 16-bit words to write.
+ const size_t total_size =
+ EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1;
- char16_t* data = out->nextBlock<char16_t>(totalSize);
+ char16_t* data = out->NextBlock<char16_t>(total_size);
- // Encode the actual UTF16 string length.
- data = encodeLength(data, entry->value.size());
- const size_t byteLength = entry->value.size() * sizeof(char16_t);
+ // Encode the actual UTF16 string length.
+ data = EncodeLength(data, utf16_length);
+ const size_t byte_length = encoded.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);
+ // NOTE: For some reason, strncpy16(data, entry->value.data(),
+ // entry->value.size()) truncates the string.
+ memcpy(data, encoded.data(), byte_length);
- // The null-terminating character is already here due to the block of data being set
- // to 0s on allocation.
- }
+ // The null-terminating character is already here due to the block of data
+ // being set to 0s on allocation.
}
-
- out->align4();
-
- if (!pool.mStyles.empty()) {
- const size_t beforeStylesIndex = out->size();
- header->stylesStart = beforeStylesIndex - startIndex;
-
- size_t currentIndex = 0;
- for (const auto& entry : pool.mStyles) {
- while (entry->str.getIndex() > currentIndex) {
- styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
-
- uint32_t* spanOffset = out->nextBlock<uint32_t>();
- *spanOffset = android::ResStringPool_span::END;
- }
- styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
-
- android::ResStringPool_span* span =
- out->nextBlock<android::ResStringPool_span>(entry->spans.size());
- for (const auto& s : entry->spans) {
- span->name.index = s.name.getIndex();
- span->firstChar = s.firstChar;
- span->lastChar = s.lastChar;
- span++;
- }
-
- uint32_t* spanEnd = out->nextBlock<uint32_t>();
- *spanEnd = android::ResStringPool_span::END;
- }
-
- // The error checking code in the platform looks for an entire
- // ResStringPool_span structure worth of 0xFFFFFFFF at the end
- // of the style block, so fill in the remaining 2 32bit words
- // with 0xFFFFFFFF.
- const size_t paddingLength = sizeof(android::ResStringPool_span)
- - sizeof(android::ResStringPool_span::name);
- uint8_t* padding = out->nextBlock<uint8_t>(paddingLength);
- memset(padding, 0xff, paddingLength);
- out->align4();
+ }
+
+ out->Align4();
+
+ if (!pool.styles_.empty()) {
+ const size_t before_styles_index = out->size();
+ header->stylesStart = before_styles_index - start_index;
+
+ size_t current_index = 0;
+ for (const auto& entry : pool.styles_) {
+ while (entry->str.index() > current_index) {
+ style_indices[current_index++] = out->size() - before_styles_index;
+
+ uint32_t* span_offset = out->NextBlock<uint32_t>();
+ *span_offset = android::ResStringPool_span::END;
+ }
+ style_indices[current_index++] = out->size() - before_styles_index;
+
+ android::ResStringPool_span* span =
+ out->NextBlock<android::ResStringPool_span>(entry->spans.size());
+ for (const auto& s : entry->spans) {
+ span->name.index = s.name.index();
+ span->firstChar = s.first_char;
+ span->lastChar = s.last_char;
+ span++;
+ }
+
+ uint32_t* spanEnd = out->NextBlock<uint32_t>();
+ *spanEnd = android::ResStringPool_span::END;
}
- header->header.size = out->size() - startIndex;
- return true;
+
+ // The error checking code in the platform looks for an entire
+ // ResStringPool_span structure worth of 0xFFFFFFFF at the end
+ // of the style block, so fill in the remaining 2 32bit words
+ // with 0xFFFFFFFF.
+ const size_t padding_length = sizeof(android::ResStringPool_span) -
+ sizeof(android::ResStringPool_span::name);
+ uint8_t* padding = out->NextBlock<uint8_t>(padding_length);
+ memset(padding, 0xff, padding_length);
+ out->Align4();
+ }
+ header->header.size = out->size() - start_index;
+ return true;
}
-bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) {
- return flatten(out, pool, true);
+bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool) {
+ return Flatten(out, pool, true);
}
-bool StringPool::flattenUtf16(BigBuffer* out, const StringPool& pool) {
- return flatten(out, pool, false);
+bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool) {
+ return Flatten(out, pool, false);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 9e1ca912796a..a4f556ca52e4 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -17,207 +17,218 @@
#ifndef AAPT_STRING_POOL_H
#define AAPT_STRING_POOL_H
-#include "util/BigBuffer.h"
-#include "ConfigDescription.h"
-#include "util/StringPiece.h"
-
#include <functional>
-#include <map>
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
+#include "ConfigDescription.h"
+#include "util/BigBuffer.h"
+#include "util/StringPiece.h"
+
namespace aapt {
struct Span {
- std::u16string name;
- uint32_t firstChar;
- uint32_t lastChar;
+ std::string name;
+ uint32_t first_char;
+ uint32_t last_char;
};
struct StyleString {
- std::u16string str;
- std::vector<Span> spans;
+ std::string str;
+ std::vector<Span> spans;
};
class StringPool {
-public:
- struct Context {
- uint32_t priority;
- ConfigDescription config;
+ public:
+ class Context {
+ public:
+ enum : uint32_t {
+ kStylePriority = 0u,
+ kHighPriority = 1u,
+ kNormalPriority = 0x7fffffffu,
+ kLowPriority = 0xffffffffu,
};
+ uint32_t priority = kNormalPriority;
+ ConfigDescription config;
- class Entry;
+ Context() = default;
+ Context(uint32_t p, const ConfigDescription& c) : priority(p), config(c) {}
+ explicit Context(uint32_t p) : priority(p) {}
+ explicit Context(const ConfigDescription& c)
+ : priority(kNormalPriority), config(c) {}
+ };
- class Ref {
- public:
- Ref();
- Ref(const Ref&);
- ~Ref();
+ class Entry;
- Ref& operator=(const Ref& rhs);
- const std::u16string* operator->() const;
- const std::u16string& operator*() const;
+ class Ref {
+ public:
+ Ref();
+ Ref(const Ref&);
+ ~Ref();
- size_t getIndex() const;
- const Context& getContext() const;
+ Ref& operator=(const Ref& rhs);
+ const std::string* operator->() const;
+ const std::string& operator*() const;
- private:
- friend class StringPool;
+ size_t index() const;
+ const Context& GetContext() const;
- explicit Ref(Entry* entry);
+ private:
+ friend class StringPool;
- Entry* mEntry;
- };
-
- class StyleEntry;
+ explicit Ref(Entry* entry);
- class StyleRef {
- public:
- StyleRef();
- StyleRef(const StyleRef&);
- ~StyleRef();
+ Entry* entry_;
+ };
- StyleRef& operator=(const StyleRef& rhs);
- const StyleEntry* operator->() const;
- const StyleEntry& operator*() const;
+ class StyleEntry;
- size_t getIndex() const;
- const Context& getContext() const;
+ class StyleRef {
+ public:
+ StyleRef();
+ StyleRef(const StyleRef&);
+ ~StyleRef();
- private:
- friend class StringPool;
+ StyleRef& operator=(const StyleRef& rhs);
+ const StyleEntry* operator->() const;
+ const StyleEntry& operator*() const;
- explicit StyleRef(StyleEntry* entry);
+ size_t index() const;
+ const Context& GetContext() const;
- StyleEntry* mEntry;
- };
+ private:
+ friend class StringPool;
- class Entry {
- public:
- std::u16string value;
- Context context;
- size_t index;
+ explicit StyleRef(StyleEntry* entry);
- private:
- friend class StringPool;
- friend class Ref;
+ StyleEntry* entry_;
+ };
- int ref;
- };
+ class Entry {
+ public:
+ std::string value;
+ Context context;
+ size_t index;
- struct Span {
- Ref name;
- uint32_t firstChar;
- uint32_t lastChar;
- };
+ private:
+ friend class StringPool;
+ friend class Ref;
- class StyleEntry {
- public:
- Ref str;
- std::vector<Span> spans;
+ int ref_;
+ };
- private:
- friend class StringPool;
- friend class StyleRef;
+ struct Span {
+ Ref name;
+ uint32_t first_char;
+ uint32_t last_char;
+ };
- int ref;
- };
+ class StyleEntry {
+ public:
+ Ref str;
+ std::vector<Span> spans;
- using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator;
-
- static bool flattenUtf8(BigBuffer* out, const StringPool& pool);
- static bool flattenUtf16(BigBuffer* out, const StringPool& pool);
-
- StringPool() = default;
- StringPool(const StringPool&) = delete;
-
- /**
- * Adds a string to the pool, unless it already exists. Returns
- * a reference to the string in the pool.
- */
- Ref makeRef(const StringPiece16& str);
-
- /**
- * Adds a string to the pool, unless it already exists, with a context
- * object that can be used when sorting the string pool. Returns
- * a reference to the string in the pool.
- */
- Ref makeRef(const StringPiece16& str, const Context& context);
-
- /**
- * Adds a style to the string pool and returns a reference to it.
- */
- StyleRef makeRef(const StyleString& str);
-
- /**
- * Adds a style to the string pool with a context object that
- * can be used when sorting the string pool. Returns a reference
- * to the style in the string pool.
- */
- StyleRef makeRef(const StyleString& str, const Context& context);
-
- /**
- * Adds a style from another string pool. Returns a reference to the
- * style in the string pool.
- */
- StyleRef makeRef(const StyleRef& ref);
-
- /**
- * Moves pool into this one without coalescing strings. When this
- * function returns, pool will be empty.
- */
- void merge(StringPool&& pool);
-
- /**
- * Retuns the number of strings in the table.
- */
- inline size_t size() const;
-
- /**
- * Reserves space for strings and styles as an optimization.
- */
- void hintWillAdd(size_t stringCount, size_t styleCount);
-
- /**
- * Sorts the strings according to some comparison function.
- */
- void sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
-
- /**
- * Removes any strings that have no references.
- */
- void prune();
-
-private:
- friend const_iterator begin(const StringPool& pool);
- friend const_iterator end(const StringPool& pool);
-
- static bool flatten(BigBuffer* out, const StringPool& pool, bool utf8);
-
- Ref makeRefImpl(const StringPiece16& str, const Context& context, bool unique);
-
- std::vector<std::unique_ptr<Entry>> mStrings;
- std::vector<std::unique_ptr<StyleEntry>> mStyles;
- std::multimap<StringPiece16, Entry*> mIndexedStrings;
+ private:
+ friend class StringPool;
+ friend class StyleRef;
+
+ int ref_;
+ };
+
+ using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator;
+
+ static bool FlattenUtf8(BigBuffer* out, const StringPool& pool);
+ static bool FlattenUtf16(BigBuffer* out, const StringPool& pool);
+
+ StringPool() = default;
+ StringPool(const StringPool&) = delete;
+
+ /**
+ * Adds a string to the pool, unless it already exists. Returns
+ * a reference to the string in the pool.
+ */
+ Ref MakeRef(const StringPiece& str);
+
+ /**
+ * Adds a string to the pool, unless it already exists, with a context
+ * object that can be used when sorting the string pool. Returns
+ * a reference to the string in the pool.
+ */
+ Ref MakeRef(const StringPiece& str, const Context& context);
+
+ /**
+ * Adds a style to the string pool and returns a reference to it.
+ */
+ StyleRef MakeRef(const StyleString& str);
+
+ /**
+ * Adds a style to the string pool with a context object that
+ * can be used when sorting the string pool. Returns a reference
+ * to the style in the string pool.
+ */
+ StyleRef MakeRef(const StyleString& str, const Context& context);
+
+ /**
+ * Adds a style from another string pool. Returns a reference to the
+ * style in the string pool.
+ */
+ StyleRef MakeRef(const StyleRef& ref);
+
+ /**
+ * Moves pool into this one without coalescing strings. When this
+ * function returns, pool will be empty.
+ */
+ void Merge(StringPool&& pool);
+
+ /**
+ * Returns the number of strings in the table.
+ */
+ inline size_t size() const;
+
+ /**
+ * Reserves space for strings and styles as an optimization.
+ */
+ void HintWillAdd(size_t string_count, size_t style_count);
+
+ /**
+ * Sorts the strings according to some comparison function.
+ */
+ void Sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
+
+ /**
+ * Removes any strings that have no references.
+ */
+ void Prune();
+
+ private:
+ friend const_iterator begin(const StringPool& pool);
+ friend const_iterator end(const StringPool& pool);
+
+ static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8);
+
+ Ref MakeRefImpl(const StringPiece& str, const Context& context, bool unique);
+
+ std::vector<std::unique_ptr<Entry>> strings_;
+ std::vector<std::unique_ptr<StyleEntry>> styles_;
+ std::unordered_multimap<StringPiece, Entry*> indexed_strings_;
};
//
// Inline implementation
//
-inline size_t StringPool::size() const {
- return mStrings.size();
-}
+inline size_t StringPool::size() const { return strings_.size(); }
inline StringPool::const_iterator begin(const StringPool& pool) {
- return pool.mStrings.begin();
+ return pool.strings_.begin();
}
inline StringPool::const_iterator end(const StringPool& pool) {
- return pool.mStrings.end();
+ return pool.strings_.end();
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_STRING_POOL_H
+#endif // AAPT_STRING_POOL_H
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 2b2d348fd17c..e1394fc0221f 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -15,237 +15,254 @@
*/
#include "StringPool.h"
-#include "util/Util.h"
-#include <gtest/gtest.h>
#include <string>
+#include "test/Test.h"
+#include "util/Util.h"
+
namespace aapt {
TEST(StringPoolTest, InsertOneString) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef(u"wut");
- EXPECT_EQ(*ref, u"wut");
+ StringPool::Ref ref = pool.MakeRef("wut");
+ EXPECT_EQ(*ref, "wut");
}
TEST(StringPoolTest, InsertTwoUniqueStrings) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef(u"wut");
- StringPool::Ref ref2 = pool.makeRef(u"hey");
+ StringPool::Ref ref = pool.MakeRef("wut");
+ StringPool::Ref ref2 = pool.MakeRef("hey");
- EXPECT_EQ(*ref, u"wut");
- EXPECT_EQ(*ref2, u"hey");
+ EXPECT_EQ(*ref, "wut");
+ EXPECT_EQ(*ref2, "hey");
}
TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef(u"wut");
- StringPool::Ref ref2 = pool.makeRef(u"wut");
+ StringPool::Ref ref = pool.MakeRef("wut");
+ StringPool::Ref ref2 = pool.MakeRef("wut");
- EXPECT_EQ(*ref, u"wut");
- EXPECT_EQ(*ref2, u"wut");
- EXPECT_EQ(1u, pool.size());
+ EXPECT_EQ(*ref, "wut");
+ EXPECT_EQ(*ref2, "wut");
+ EXPECT_EQ(1u, pool.size());
}
TEST(StringPoolTest, MaintainInsertionOrderIndex) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef(u"z");
- StringPool::Ref ref2 = pool.makeRef(u"a");
- StringPool::Ref ref3 = pool.makeRef(u"m");
+ StringPool::Ref ref = pool.MakeRef("z");
+ StringPool::Ref ref2 = pool.MakeRef("a");
+ StringPool::Ref ref3 = pool.MakeRef("m");
- EXPECT_EQ(0u, ref.getIndex());
- EXPECT_EQ(1u, ref2.getIndex());
- EXPECT_EQ(2u, ref3.getIndex());
+ EXPECT_EQ(0u, ref.index());
+ EXPECT_EQ(1u, ref2.index());
+ EXPECT_EQ(2u, ref3.index());
}
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(2u, pool.size());
- }
- StringPool::Ref refB = pool.makeRef(u"bar");
-
- EXPECT_EQ(3u, pool.size());
- pool.prune();
+ StringPool pool;
+
+ StringPool::Ref refA = pool.MakeRef("foo");
+ {
+ StringPool::Ref ref = pool.MakeRef("wut");
+ EXPECT_EQ(*ref, "wut");
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);
+ }
+ StringPool::Ref refB = pool.MakeRef("bar");
+
+ EXPECT_EQ(3u, pool.size());
+ pool.Prune();
+ EXPECT_EQ(2u, pool.size());
+ StringPool::const_iterator iter = begin(pool);
+ EXPECT_EQ((*iter)->value, "foo");
+ EXPECT_LT((*iter)->index, 2u);
+ ++iter;
+ EXPECT_EQ((*iter)->value, "bar");
+ EXPECT_LT((*iter)->index, 2u);
}
TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
- StringPool pool;
-
- StringPool::Ref ref = pool.makeRef(u"z");
- StringPool::StyleRef ref2 = pool.makeRef(StyleString{ {u"a"} });
- StringPool::Ref ref3 = pool.makeRef(u"m");
+ StringPool pool;
- EXPECT_EQ(*ref, u"z");
- EXPECT_EQ(0u, ref.getIndex());
+ StringPool::Ref ref = pool.MakeRef("z");
+ StringPool::StyleRef ref2 = pool.MakeRef(StyleString{{"a"}});
+ StringPool::Ref ref3 = pool.MakeRef("m");
- EXPECT_EQ(*(ref2->str), u"a");
- EXPECT_EQ(1u, ref2.getIndex());
+ EXPECT_EQ(*ref, "z");
+ EXPECT_EQ(0u, ref.index());
- EXPECT_EQ(*ref3, u"m");
- EXPECT_EQ(2u, ref3.getIndex());
+ EXPECT_EQ(*(ref2->str), "a");
+ EXPECT_EQ(1u, ref2.index());
- pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- return a.value < b.value;
- });
+ EXPECT_EQ(*ref3, "m");
+ EXPECT_EQ(2u, ref3.index());
+ pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ return a.value < b.value;
+ });
- EXPECT_EQ(*ref, u"z");
- EXPECT_EQ(2u, ref.getIndex());
+ EXPECT_EQ(*ref, "z");
+ EXPECT_EQ(2u, ref.index());
- EXPECT_EQ(*(ref2->str), u"a");
- EXPECT_EQ(0u, ref2.getIndex());
+ EXPECT_EQ(*(ref2->str), "a");
+ EXPECT_EQ(0u, ref2.index());
- EXPECT_EQ(*ref3, u"m");
- EXPECT_EQ(1u, ref3.getIndex());
+ EXPECT_EQ(*ref3, "m");
+ EXPECT_EQ(1u, ref3.index());
}
TEST(StringPoolTest, SortAndStillDedupe) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef(u"z");
- StringPool::Ref ref2 = pool.makeRef(u"a");
- StringPool::Ref ref3 = pool.makeRef(u"m");
+ StringPool::Ref ref = pool.MakeRef("z");
+ StringPool::Ref ref2 = pool.MakeRef("a");
+ StringPool::Ref ref3 = pool.MakeRef("m");
- pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- return a.value < b.value;
- });
+ pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ return a.value < b.value;
+ });
- StringPool::Ref ref4 = pool.makeRef(u"z");
- StringPool::Ref ref5 = pool.makeRef(u"a");
- StringPool::Ref ref6 = pool.makeRef(u"m");
+ StringPool::Ref ref4 = pool.MakeRef("z");
+ StringPool::Ref ref5 = pool.MakeRef("a");
+ StringPool::Ref ref6 = pool.MakeRef("m");
- EXPECT_EQ(ref4.getIndex(), ref.getIndex());
- EXPECT_EQ(ref5.getIndex(), ref2.getIndex());
- EXPECT_EQ(ref6.getIndex(), ref3.getIndex());
+ EXPECT_EQ(ref4.index(), ref.index());
+ EXPECT_EQ(ref5.index(), ref2.index());
+ EXPECT_EQ(ref6.index(), ref3.index());
}
TEST(StringPoolTest, AddStyles) {
- StringPool pool;
+ StringPool pool;
- StyleString str {
- { u"android" },
- {
- Span{ { u"b" }, 2, 6 }
- }
- };
+ StyleString str{{"android"}, {Span{{"b"}, 2, 6}}};
- StringPool::StyleRef ref = pool.makeRef(str);
+ StringPool::StyleRef ref = pool.MakeRef(str);
- EXPECT_EQ(0u, ref.getIndex());
- EXPECT_EQ(std::u16string(u"android"), *(ref->str));
- ASSERT_EQ(1u, ref->spans.size());
+ EXPECT_EQ(0u, ref.index());
+ EXPECT_EQ(std::string("android"), *(ref->str));
+ ASSERT_EQ(1u, ref->spans.size());
- const StringPool::Span& span = ref->spans.front();
- EXPECT_EQ(*(span.name), u"b");
- EXPECT_EQ(2u, span.firstChar);
- EXPECT_EQ(6u, span.lastChar);
+ const StringPool::Span& span = ref->spans.front();
+ EXPECT_EQ(*(span.name), "b");
+ EXPECT_EQ(2u, span.first_char);
+ EXPECT_EQ(6u, span.last_char);
}
TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef(u"android");
+ StringPool::Ref ref = pool.MakeRef("android");
- StyleString str { { u"android" } };
- StringPool::StyleRef styleRef = pool.makeRef(str);
+ StyleString str{{"android"}};
+ StringPool::StyleRef styleRef = pool.MakeRef(str);
- EXPECT_NE(ref.getIndex(), styleRef.getIndex());
+ EXPECT_NE(ref.index(), styleRef.index());
}
TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
- using namespace android; // For NO_ERROR on Windows.
+ using namespace android; // For NO_ERROR on Windows.
- StringPool pool;
- BigBuffer buffer(1024);
- StringPool::flattenUtf8(&buffer, pool);
+ StringPool pool;
+ BigBuffer buffer(1024);
+ StringPool::FlattenUtf8(&buffer, pool);
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+ std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
}
TEST(StringPoolTest, FlattenOddCharactersUtf16) {
- using namespace android; // For NO_ERROR on Windows.
+ using namespace android; // For NO_ERROR on Windows.
+
+ StringPool pool;
+ pool.MakeRef("\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 char* sLongString =
+ "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑"
+ "え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限"
+ "します。メール、SMSや、同期を使 "
+ "用するその他のアプリは、起動しても更新されないことがあります。バッテリーセ"
+ "ーバーは端末の充電中は自動的にOFFになります。";
+
+TEST(StringPoolTest, Flatten) {
+ using namespace android; // For NO_ERROR on Windows.
+
+ StringPool pool;
- StringPool pool;
- pool.makeRef(u"\u093f");
- BigBuffer buffer(1024);
- StringPool::flattenUtf16(&buffer, pool);
+ StringPool::Ref ref1 = pool.MakeRef("hello");
+ StringPool::Ref ref2 = pool.MakeRef("goodbye");
+ StringPool::Ref ref3 = pool.MakeRef(sLongString);
+ StringPool::Ref ref4 = pool.MakeRef("");
+ StringPool::StyleRef ref5 = pool.MakeRef(
+ StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}});
+
+ EXPECT_EQ(0u, ref1.index());
+ EXPECT_EQ(1u, ref2.index());
+ EXPECT_EQ(2u, ref3.index());
+ EXPECT_EQ(3u, ref4.index());
+ EXPECT_EQ(4u, ref5.index());
+
+ BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)};
+ StringPool::FlattenUtf8(&buffers[0], pool);
+ StringPool::FlattenUtf16(&buffers[1], pool);
+
+ // Test both UTF-8 and UTF-16 buffers.
+ for (const BigBuffer& buffer : buffers) {
+ std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
- 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");
- StringPool::Ref ref2 = pool.makeRef(u"goodbye");
- StringPool::Ref ref3 = pool.makeRef(sLongString);
- StringPool::StyleRef ref4 = pool.makeRef(StyleString{
- { u"style" },
- { Span{ { u"b" }, 0, 1 }, Span{ { u"i" }, 2, 3 } }
- });
-
- EXPECT_EQ(0u, ref1.getIndex());
- EXPECT_EQ(1u, ref2.getIndex());
- EXPECT_EQ(2u, ref3.getIndex());
- EXPECT_EQ(3u, ref4.getIndex());
-
- BigBuffer buffer(1024);
- StringPool::flattenUtf8(&buffer, pool);
-
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- {
- 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");
- EXPECT_EQ(util::getString(test, 2), sLongString);
- EXPECT_EQ(util::getString(test, 3), u"style");
-
- const ResStringPool_span* span = test.styleAt(3);
- ASSERT_NE(nullptr, span);
- EXPECT_EQ(util::getString(test, span->name.index), u"b");
- EXPECT_EQ(0u, span->firstChar);
- EXPECT_EQ(1u, span->lastChar);
- span++;
-
- ASSERT_NE(ResStringPool_span::END, span->name.index);
- EXPECT_EQ(util::getString(test, span->name.index), u"i");
- EXPECT_EQ(2u, span->firstChar);
- EXPECT_EQ(3u, span->lastChar);
- span++;
-
- EXPECT_EQ(ResStringPool_span::END, span->name.index);
- }
+ EXPECT_EQ(std::string("hello"), util::GetString(test, 0));
+ EXPECT_EQ(StringPiece16(u"hello"), util::GetString16(test, 0));
+
+ EXPECT_EQ(std::string("goodbye"), util::GetString(test, 1));
+ EXPECT_EQ(StringPiece16(u"goodbye"), util::GetString16(test, 1));
+
+ EXPECT_EQ(StringPiece(sLongString), util::GetString(test, 2));
+ EXPECT_EQ(util::Utf8ToUtf16(sLongString),
+ util::GetString16(test, 2).ToString());
+
+ size_t len;
+ EXPECT_TRUE(test.stringAt(3, &len) != nullptr ||
+ test.string8At(3, &len) != nullptr);
+
+ EXPECT_EQ(std::string("style"), util::GetString(test, 4));
+ EXPECT_EQ(StringPiece16(u"style"), util::GetString16(test, 4));
+
+ const ResStringPool_span* span = test.styleAt(4);
+ ASSERT_NE(nullptr, span);
+ EXPECT_EQ(std::string("b"), util::GetString(test, span->name.index));
+ EXPECT_EQ(StringPiece16(u"b"), util::GetString16(test, span->name.index));
+ EXPECT_EQ(0u, span->firstChar);
+ EXPECT_EQ(1u, span->lastChar);
+ span++;
+
+ ASSERT_NE(ResStringPool_span::END, span->name.index);
+ EXPECT_EQ(std::string("i"), util::GetString(test, span->name.index));
+ EXPECT_EQ(StringPiece16(u"i"), util::GetString16(test, span->name.index));
+ EXPECT_EQ(2u, span->firstChar);
+ EXPECT_EQ(3u, span->lastChar);
+ span++;
+
+ EXPECT_EQ(ResStringPool_span::END, span->name.index);
+ }
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index b8bc5db7d6e4..1cb6aa13f336 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -17,90 +17,92 @@
#ifndef AAPT_VALUE_VISITOR_H
#define AAPT_VALUE_VISITOR_H
-#include "ResourceValues.h"
#include "ResourceTable.h"
+#include "ResourceValues.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.
+ * 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) {}
+ 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); \
- }
+// NOLINT, do not add parentheses around T.
+#define DECL_VISIT_COMPOUND_VALUE(T) \
+ virtual void Visit(T* value) override { /* NOLINT */ \
+ VisitSubValues(value); \
+ }
/**
- * Visits values, and if they are compound values, visits the components as well.
+ * 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);
- }
+ // 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());
- }
+ void VisitSubValues(Style* style) {
+ if (style->parent) {
+ Visit(&style->parent.value());
+ }
- for (Style::Entry& entry : style->entries) {
- visit(&entry.key);
- entry.value->accept(this);
- }
+ 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(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(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);
- }
+ 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);
+ 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);
};
/**
@@ -108,11 +110,9 @@ struct ValueVisitor : public RawValueVisitor {
*/
template <typename T>
struct DynCastVisitor : public RawValueVisitor {
- T* value = nullptr;
+ T* value = nullptr;
- void visit(T* v) override {
- value = v;
- }
+ void Visit(T* v) override { value = v; }
};
/**
@@ -120,16 +120,14 @@ struct DynCastVisitor : public RawValueVisitor {
*/
template <>
struct DynCastVisitor<Item> : public RawValueVisitor {
- Item* value = nullptr;
+ Item* value = nullptr;
- void visitItem(Item* item) override {
- value = item;
- }
+ void VisitItem(Item* item) override { value = item; }
};
template <typename T>
-const T* valueCast(const Value* value) {
- return valueCast<T>(const_cast<Value*>(value));
+const T* ValueCast(const Value* value) {
+ return ValueCast<T>(const_cast<Value*>(value));
}
/**
@@ -137,31 +135,33 @@ const T* valueCast(const Value* value) {
* Otherwise, returns nullptr.
*/
template <typename T>
-T* valueCast(Value* value) {
- if (!value) {
- return nullptr;
- }
- DynCastVisitor<T> visitor;
- value->accept(&visitor);
- return visitor.value;
+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 VisitAllValuesInPackage(ResourceTablePackage* pkg,
+ RawValueVisitor* visitor) {
+ for (auto& type : pkg->types) {
+ for (auto& entry : type->entries) {
+ for (auto& config_value : entry->values) {
+ config_value->value->Accept(visitor);
+ }
}
+ }
}
-inline void visitAllValuesInTable(ResourceTable* table, RawValueVisitor* visitor) {
- for (auto& pkg : table->packages) {
- visitAllValuesInPackage(pkg.get(), visitor);
- }
+inline void VisitAllValuesInTable(ResourceTable* table,
+ RawValueVisitor* visitor) {
+ for (auto& pkg : table->packages) {
+ VisitAllValuesInPackage(pkg.get(), visitor);
+ }
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_VALUE_VISITOR_H
+#endif // AAPT_VALUE_VISITOR_H
diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp
index 1624079727bb..eb75b102e427 100644
--- a/tools/aapt2/ValueVisitor_test.cpp
+++ b/tools/aapt2/ValueVisitor_test.cpp
@@ -14,74 +14,74 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
+#include "ValueVisitor.h"
+
#include <string>
#include "ResourceValues.h"
+#include "test/Test.h"
#include "util/Util.h"
-#include "ValueVisitor.h"
-#include "test/Builders.h"
namespace aapt {
struct SingleReferenceVisitor : public ValueVisitor {
- using ValueVisitor::visit;
+ using ValueVisitor::Visit;
- Reference* visited = nullptr;
+ Reference* visited = nullptr;
- void visit(Reference* ref) override {
- visited = ref;
- }
+ void Visit(Reference* ref) override { visited = ref; }
};
struct StyleVisitor : public ValueVisitor {
- using ValueVisitor::visit;
+ using ValueVisitor::Visit;
- std::list<Reference*> visitedRefs;
- Style* visitedStyle = nullptr;
+ std::list<Reference*> visited_refs;
+ Style* visited_style = nullptr;
- void visit(Reference* ref) override {
- visitedRefs.push_back(ref);
- }
+ void Visit(Reference* ref) override { visited_refs.push_back(ref); }
- void visit(Style* style) override {
- visitedStyle = style;
- ValueVisitor::visit(style);
- }
+ void Visit(Style* style) override {
+ visited_style = style;
+ ValueVisitor::Visit(style);
+ }
};
TEST(ValueVisitorTest, VisitsReference) {
- Reference ref(ResourceName{u"android", ResourceType::kAttr, u"foo"});
- SingleReferenceVisitor visitor;
- ref.accept(&visitor);
+ Reference ref(ResourceName{"android", ResourceType::kAttr, "foo"});
+ SingleReferenceVisitor visitor;
+ ref.Accept(&visitor);
- EXPECT_EQ(visitor.visited, &ref);
+ 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();
+ std::unique_ptr<Style> style =
+ test::StyleBuilder()
+ .SetParent("android:style/foo")
+ .AddItem("android:attr/one", test::BuildReference("android:id/foo"))
+ .Build();
- StyleVisitor visitor;
- style->accept(&visitor);
+ StyleVisitor visitor;
+ style->Accept(&visitor);
- ASSERT_EQ(style.get(), visitor.visitedStyle);
+ ASSERT_EQ(style.get(), visitor.visited_style);
- // Entry attribute references, plus the parent reference, plus one value reference.
- ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
+ // Entry attribute references, plus the parent reference, plus one value
+ // reference.
+ ASSERT_EQ(style->entries.size() + 2, visitor.visited_refs.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);
+ std::unique_ptr<Reference> ref = test::BuildReference("android:color/white");
+ EXPECT_NE(ValueCast<Reference>(ref.get()), nullptr);
+
+ std::unique_ptr<Style> style =
+ test::StyleBuilder()
+ .AddItem("android:attr/foo",
+ test::BuildReference("android:color/black"))
+ .Build();
+ EXPECT_NE(ValueCast<Style>(style.get()), nullptr);
+ EXPECT_EQ(ValueCast<Reference>(style.get()), nullptr);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 2452a1d29410..f0b18e65cc1a 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -14,12 +14,18 @@
* limitations under the License.
*/
+#include <dirent.h>
+
+#include <fstream>
+#include <string>
+
#include "ConfigDescription.h"
#include "Diagnostics.h"
#include "Flags.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
#include "compile/IdAssigner.h"
+#include "compile/InlineXmlFormatParser.h"
#include "compile/Png.h"
#include "compile/PseudolocaleGenerator.h"
#include "compile/XmlIdCollector.h"
@@ -32,547 +38,771 @@
#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 "android-base/errors.h"
+#include "android-base/file.h"
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-#include <dirent.h>
-#include <fstream>
-#include <string>
+using google::protobuf::io::CopyingOutputStreamAdaptor;
+using google::protobuf::io::ZeroCopyOutputStream;
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;
+ Source source;
+ std::string resource_dir;
+ std::string 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 config_str;
+ 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
- };
+static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,
+ std::string* out_error) {
+ std::vector<std::string> parts = util::Split(path, file::sDirSep);
+ if (parts.size() < 2) {
+ if (out_error) *out_error = "bad resource path";
+ return {};
+ }
+
+ std::string& dir = parts[parts.size() - 2];
+ StringPiece dir_str = dir;
+
+ StringPiece config_str;
+ ConfigDescription config;
+ size_t dash_pos = dir.find('-');
+ if (dash_pos != std::string::npos) {
+ config_str = dir_str.substr(dash_pos + 1, dir.size() - (dash_pos + 1));
+ if (!ConfigDescription::Parse(config_str, &config)) {
+ if (out_error) {
+ std::stringstream err_str;
+ err_str << "invalid configuration '" << config_str << "'";
+ *out_error = err_str.str();
+ }
+ return {};
+ }
+ dir_str = dir_str.substr(0, dash_pos);
+ }
+
+ std::string& filename = parts[parts.size() - 1];
+ StringPiece name = filename;
+ StringPiece extension;
+ size_t dot_pos = filename.find('.');
+ if (dot_pos != std::string::npos) {
+ extension = name.substr(dot_pos + 1, filename.size() - (dot_pos + 1));
+ name = name.substr(0, dot_pos);
+ }
+
+ return ResourcePathData{Source(path), dir_str.ToString(),
+ name.ToString(), extension.ToString(),
+ config_str.ToString(), config};
}
struct CompileOptions {
- std::string outputPath;
- Maybe<std::string> resDir;
- bool pseudolocalize = false;
- bool legacyMode = false;
- bool verbose = false;
+ std::string output_path;
+ Maybe<std::string> res_dir;
+ bool pseudolocalize = false;
+ bool legacy_mode = 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 std::string BuildIntermediateFilename(const ResourcePathData& data) {
+ std::stringstream name;
+ name << data.resource_dir;
+ if (!data.config_str.empty()) {
+ name << "-" << data.config_str;
+ }
+ 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, ".");
+static bool IsHidden(const StringPiece& filename) {
+ return util::StartsWith(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;
- }
+static bool LoadInputFilesFromDir(
+ IAaptContext* context, const CompileOptions& options,
+ std::vector<ResourcePathData>* out_path_data) {
+ const std::string& root_dir = options.res_dir.value();
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()),
+ closedir);
+ if (!d) {
+ context->GetDiagnostics()->Error(DiagMessage() << strerror(errno));
+ return false;
+ }
- outPathData->push_back(std::move(pathData.value()));
- }
+ while (struct dirent* entry = readdir(d.get())) {
+ if (IsHidden(entry->d_name)) {
+ continue;
}
- 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;
+ std::string prefix_path = root_dir;
+ file::AppendPath(&prefix_path, entry->d_name);
- // 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 (file::GetFileType(prefix_path) != file::FileType::kDirectory) {
+ continue;
}
- 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;
- }
+ std::unique_ptr<DIR, decltype(closedir)*> subdir(
+ opendir(prefix_path.data()), closedir);
+ if (!subdir) {
+ context->GetDiagnostics()->Error(DiagMessage() << strerror(errno));
+ return false;
}
- // Ensure we have the compilation package at least.
- table.createPackage(context->getCompilationPackage());
+ while (struct dirent* leaf_entry = readdir(subdir.get())) {
+ if (IsHidden(leaf_entry->d_name)) {
+ continue;
+ }
- // 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();
- }
- }
+ std::string full_path = prefix_path;
+ file::AppendPath(&full_path, leaf_entry->d_name);
- // Create the file/zip entry.
- if (!writer->startEntry(outputPath, 0)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
+ std::string err_str;
+ Maybe<ResourcePathData> path_data =
+ ExtractResourcePathData(full_path, &err_str);
+ if (!path_data) {
+ context->GetDiagnostics()->Error(DiagMessage() << err_str);
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;
- }
+ out_path_data->push_back(std::move(path_data.value()));
}
+ }
+ return true;
+}
- if (!writer->finishEntry()) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry");
- return false;
- }
- return true;
+static bool CompileTable(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& path_data,
+ IArchiveWriter* writer,
+ const std::string& output_path) {
+ ResourceTable table;
+ {
+ std::ifstream fin(path_data.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->GetDiagnostics()->Error(DiagMessage(path_data.source)
+ << strerror(errno));
+ return false;
+ }
+
+ // Parse the values file from XML.
+ xml::XmlPullParser xml_parser(fin);
+
+ ResourceParserOptions parser_options;
+ parser_options.error_on_positional_arguments = !options.legacy_mode;
+
+ // If the filename includes donottranslate, then the default translatable is
+ // false.
+ parser_options.translatable =
+ path_data.name.find("donottranslate") == std::string::npos;
+
+ ResourceParser res_parser(context->GetDiagnostics(), &table,
+ path_data.source, path_data.config,
+ parser_options);
+ if (!res_parser.Parse(&xml_parser)) {
+ 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 pseudolocale_generator;
+ if (!pseudolocale_generator.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(output_path, 0)) {
+ context->GetDiagnostics()->Error(DiagMessage(output_path)
+ << "failed to open");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->FinishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copying_adaptor(writer);
+
+ std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table);
+ if (!pb_table->SerializeToZeroCopyStream(&copying_adaptor)) {
+ context->GetDiagnostics()->Error(DiagMessage(output_path)
+ << "failed to write");
+ return false;
+ }
+ }
+
+ if (!writer->FinishEntry()) {
+ context->GetDiagnostics()->Error(DiagMessage(output_path)
+ << "failed to finish entry");
+ return false;
+ }
+ return true;
}
-static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file,
- const BigBuffer& buffer, IArchiveWriter* writer,
+static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path,
+ 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;
+ // Start the entry so we can write the header.
+ if (!writer->StartEntry(output_path, 0)) {
+ diag->Error(DiagMessage(output_path) << "failed to open file");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->FinishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copying_adaptor(writer);
+ CompiledFileOutputStream output_stream(&copying_adaptor);
+
+ // Number of CompiledFiles.
+ output_stream.WriteLittleEndian32(1);
+
+ std::unique_ptr<pb::CompiledFile> compiled_file =
+ SerializeCompiledFileToPb(file);
+ output_stream.WriteCompiledFile(compiled_file.get());
+ output_stream.WriteData(&buffer);
+
+ if (output_stream.HadError()) {
+ diag->Error(DiagMessage(output_path) << "failed to write data");
+ return false;
+ }
+ }
+
+ if (!writer->FinishEntry()) {
+ diag->Error(DiagMessage(output_path) << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
-static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file,
- const android::FileMap& map, IArchiveWriter* writer,
+static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path,
+ 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;
- }
+ // Start the entry so we can write the header.
+ if (!writer->StartEntry(output_path, 0)) {
+ diag->Error(DiagMessage(output_path) << "failed to open file");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->FinishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream interface.
+ CopyingOutputStreamAdaptor copying_adaptor(writer);
+ CompiledFileOutputStream output_stream(&copying_adaptor);
+
+ // Number of CompiledFiles.
+ output_stream.WriteLittleEndian32(1);
+
+ std::unique_ptr<pb::CompiledFile> compiled_file =
+ SerializeCompiledFileToPb(file);
+ output_stream.WriteCompiledFile(compiled_file.get());
+ output_stream.WriteData(map.getDataPtr(), map.getDataLength());
+
+ if (output_stream.HadError()) {
+ diag->Error(DiagMessage(output_path) << "failed to write data");
+ return false;
+ }
+ }
+
+ if (!writer->FinishEntry()) {
+ diag->Error(DiagMessage(output_path) << "failed to finish writing data");
+ return false;
+ }
+ return true;
+}
- // 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;
- }
- }
+static bool FlattenXmlToOutStream(IAaptContext* context,
+ const StringPiece& output_path,
+ xml::XmlResource* xmlres,
+ CompiledFileOutputStream* out) {
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions xml_flattener_options;
+ xml_flattener_options.keep_raw_values = true;
+ XmlFlattener flattener(&buffer, xml_flattener_options);
+ if (!flattener.Consume(context, xmlres)) {
+ return false;
+ }
+
+ std::unique_ptr<pb::CompiledFile> pb_compiled_file =
+ SerializeCompiledFileToPb(xmlres->file);
+ out->WriteCompiledFile(pb_compiled_file.get());
+ out->WriteData(&buffer);
+
+ if (out->HadError()) {
+ context->GetDiagnostics()->Error(DiagMessage(output_path)
+ << "failed to write data");
+ return false;
+ }
+ return true;
+}
- if (!writer->finishEntry()) {
- diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+static bool CompileXml(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& path_data,
+ IArchiveWriter* writer, const std::string& output_path) {
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "compiling XML");
+ }
+
+ std::unique_ptr<xml::XmlResource> xmlres;
+ {
+ std::ifstream fin(path_data.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->GetDiagnostics()->Error(DiagMessage(path_data.source)
+ << strerror(errno));
+ return false;
+ }
+
+ xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source);
+
+ fin.close();
+ }
+
+ if (!xmlres) {
+ return false;
+ }
+
+ xmlres->file.name = ResourceName(
+ {}, *ParseResourceType(path_data.resource_dir), path_data.name);
+ xmlres->file.config = path_data.config;
+ xmlres->file.source = path_data.source;
+
+ // Collect IDs that are defined here.
+ XmlIdCollector collector;
+ if (!collector.Consume(context, xmlres.get())) {
+ return false;
+ }
+
+ // Look for and process any <aapt:attr> tags and create sub-documents.
+ InlineXmlFormatParser inline_xml_format_parser;
+ if (!inline_xml_format_parser.Consume(context, xmlres.get())) {
+ return false;
+ }
+
+ // Start the entry so we can write the header.
+ if (!writer->StartEntry(output_path, 0)) {
+ context->GetDiagnostics()->Error(DiagMessage(output_path)
+ << "failed to open file");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->FinishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copying_adaptor(writer);
+ CompiledFileOutputStream output_stream(&copying_adaptor);
+
+ std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
+ inline_xml_format_parser.GetExtractedInlineXmlDocuments();
+
+ // Number of CompiledFiles.
+ output_stream.WriteLittleEndian32(1 + inline_documents.size());
+
+ if (!FlattenXmlToOutStream(context, output_path, xmlres.get(),
+ &output_stream)) {
+ return false;
+ }
+
+ for (auto& inline_xml_doc : inline_documents) {
+ if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(),
+ &output_stream)) {
return false;
+ }
}
- return true;
+ }
+
+ if (!writer->FinishEntry()) {
+ context->GetDiagnostics()->Error(DiagMessage(output_path)
+ << "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) {
+class BigBufferOutputStream : public io::OutputStream {
+ public:
+ explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
- 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;
- }
+ bool Next(void** data, int* len) override {
+ size_t count;
+ *data = buffer_->NextBlock(&count);
+ *len = static_cast<int>(count);
+ return true;
+ }
- xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
+ void BackUp(int count) override { buffer_->BackUp(count); }
- fin.close();
- }
+ google::protobuf::int64 ByteCount() const override {
+ return buffer_->size();
+ }
- if (!xmlRes) {
- return false;
- }
+ bool HadError() const override { return false; }
- // Collect IDs that are defined here.
- XmlIdCollector collector;
- if (!collector.consume(context, xmlRes.get())) {
- return false;
- }
+ private:
+ BigBuffer* buffer_;
- xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
- xmlRes->file.config = pathData.config;
- xmlRes->file.source = pathData.source;
+ DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+};
- BigBuffer buffer(1024);
- XmlFlattenerOptions xmlFlattenerOptions;
- xmlFlattenerOptions.keepRawValues = true;
- XmlFlattener flattener(&buffer, xmlFlattenerOptions);
- if (!flattener.consume(context, xmlRes.get())) {
+static bool CompilePng(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& path_data,
+ IArchiveWriter* writer, const std::string& output_path) {
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "compiling PNG");
+ }
+
+ BigBuffer buffer(4096);
+ ResourceFile res_file;
+ res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir),
+ path_data.name);
+ res_file.config = path_data.config;
+ res_file.source = path_data.source;
+
+ {
+ std::string content;
+ if (!android::base::ReadFileToString(path_data.source.path, &content)) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(path_data.source)
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+
+ BigBuffer crunched_png_buffer(4096);
+ BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
+
+ // Ensure that we only keep the chunks we care about if we end up
+ // using the original PNG instead of the crunched one.
+ PngChunkFilter png_chunk_filter(content);
+ std::unique_ptr<Image> image = ReadPng(context, &png_chunk_filter);
+ if (!image) {
+ return false;
+ }
+
+ std::unique_ptr<NinePatch> nine_patch;
+ if (path_data.extension == "9.png") {
+ std::string err;
+ nine_patch = NinePatch::Create(image->rows.get(), image->width,
+ image->height, &err);
+ if (!nine_patch) {
+ context->GetDiagnostics()->Error(DiagMessage() << err);
return false;
- }
-
- if (!writeHeaderAndBufferToWriter(outputPath, xmlRes->file, buffer, writer,
- context->getDiagnostics())) {
+ }
+
+ // Remove the 1px border around the NinePatch.
+ // Basically the row array is shifted up by 1, and the length is treated
+ // as height - 2.
+ // For each row, shift the array to the left by 1, and treat the length as
+ // width - 2.
+ image->width -= 2;
+ image->height -= 2;
+ memmove(image->rows.get(), image->rows.get() + 1,
+ image->height * sizeof(uint8_t**));
+ for (int32_t h = 0; h < image->height; h++) {
+ memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
+ }
+
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "9-patch: " << *nine_patch);
+ }
+ }
+
+ // Write the crunched PNG.
+ if (!WritePng(context, image.get(), nine_patch.get(),
+ &crunched_png_buffer_out, {})) {
+ return false;
+ }
+
+ if (nine_patch != nullptr ||
+ crunched_png_buffer_out.ByteCount() <= png_chunk_filter.ByteCount()) {
+ // No matter what, we must use the re-encoded PNG, even if it is larger.
+ // 9-patch images must be re-encoded since their borders are stripped.
+ buffer.AppendBuffer(std::move(crunched_png_buffer));
+ } else {
+ // The re-encoded PNG is larger than the original, and there is
+ // no mandatory transformation. Use the original.
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(
+ DiagMessage(path_data.source)
+ << "original PNG is smaller than crunched PNG"
+ << ", using original");
+ }
+
+ PngChunkFilter png_chunk_filter_again(content);
+ BigBuffer filtered_png_buffer(4096);
+ BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
+ io::Copy(&filtered_png_buffer_out, &png_chunk_filter_again);
+ buffer.AppendBuffer(std::move(filtered_png_buffer));
+ }
+
+ if (context->IsVerbose()) {
+ // For debugging only, use the legacy PNG cruncher and compare the
+ // resulting file sizes.
+ // This will help catch exotic cases where the new code may generate
+ // larger PNGs.
+ std::stringstream legacy_stream(content);
+ BigBuffer legacy_buffer(4096);
+ Png png(context->GetDiagnostics());
+ if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
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;
- }
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "legacy=" << legacy_buffer.size()
+ << " new=" << buffer.size());
}
+ }
- if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
- context->getDiagnostics())) {
- return false;
- }
- return true;
+ if (!WriteHeaderAndBufferToWriter(output_path, res_file, 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;
+static bool CompileFile(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& path_data,
+ IArchiveWriter* writer,
+ const std::string& output_path) {
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "compiling file");
+ }
+
+ BigBuffer buffer(256);
+ ResourceFile res_file;
+ res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir),
+ path_data.name);
+ res_file.config = path_data.config;
+ res_file.source = path_data.source;
+
+ std::string error_str;
+ Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
+ if (!f) {
+ context->GetDiagnostics()->Error(DiagMessage(path_data.source)
+ << error_str);
+ return false;
+ }
+
+ if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer,
+ context->GetDiagnostics())) {
+ return false;
+ }
+ return true;
}
class CompileContext : public IAaptContext {
-public:
- void setVerbose(bool val) {
- mVerbose = val;
- }
+ public:
+ void SetVerbose(bool val) { verbose_ = val; }
- bool verbose() override {
- return mVerbose;
- }
+ bool IsVerbose() override { return verbose_; }
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
- NameMangler* getNameMangler() override {
- abort();
- return nullptr;
- }
+ NameMangler* GetNameMangler() override {
+ abort();
+ return nullptr;
+ }
- const std::u16string& getCompilationPackage() override {
- static std::u16string empty;
- return empty;
- }
+ const std::string& GetCompilationPackage() override {
+ static std::string empty;
+ return empty;
+ }
- uint8_t getPackageId() override {
- return 0x0;
- }
+ uint8_t GetPackageId() override { return 0x0; }
- SymbolTable* getExternalSymbols() override {
- abort();
- return nullptr;
- }
+ SymbolTable* GetExternalSymbols() override {
+ abort();
+ return nullptr;
+ }
-private:
- StdErrDiagnostics mDiagnostics;
- bool mVerbose = false;
+ int GetMinSdkVersion() override { return 0; }
+ private:
+ StdErrDiagnostics diagnostics_;
+ bool verbose_ = false;
};
/**
- * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
+ * 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)) {
+int Compile(const std::vector<StringPiece>& args) {
+ CompileContext context;
+ CompileOptions options;
+
+ bool verbose = false;
+ Flags flags =
+ Flags()
+ .RequiredFlag("-o", "Output path", &options.output_path)
+ .OptionalFlag("--dir", "Directory to scan for resources",
+ &options.res_dir)
+ .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.legacy_mode)
+ .OptionalSwitch("-v", "Enables verbose logging", &verbose);
+ if (!flags.Parse("aapt2 compile", args, &std::cerr)) {
+ return 1;
+ }
+
+ context.SetVerbose(verbose);
+
+ std::unique_ptr<IArchiveWriter> archive_writer;
+
+ std::vector<ResourcePathData> input_data;
+ if (options.res_dir) {
+ 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, &input_data)) {
+ return 1;
+ }
+
+ archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
+ options.output_path);
+
+ } else {
+ input_data.reserve(flags.GetArgs().size());
+
+ // Collect data from the path for each input file.
+ for (const std::string& arg : flags.GetArgs()) {
+ std::string error_str;
+ if (Maybe<ResourcePathData> path_data =
+ ExtractResourcePathData(arg, &error_str)) {
+ input_data.push_back(std::move(path_data.value()));
+ } else {
+ context.GetDiagnostics()->Error(DiagMessage() << error_str << " ("
+ << arg << ")");
return 1;
+ }
}
- context.setVerbose(verbose);
+ archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(),
+ options.output_path);
+ }
- std::unique_ptr<IArchiveWriter> archiveWriter;
+ if (!archive_writer) {
+ return 1;
+ }
- 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;
- }
+ bool error = false;
+ for (ResourcePathData& path_data : input_data) {
+ if (options.verbose) {
+ context.GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "processing");
+ }
- if (!loadInputFilesFromDir(&context, options, &inputData)) {
- return 1;
- }
+ if (path_data.resource_dir == "values") {
+ // Overwrite the extension.
+ path_data.extension = "arsc";
- archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
+ const std::string output_filename = BuildIntermediateFilename(path_data);
+ if (!CompileTable(&context, options, path_data, archive_writer.get(),
+ output_filename)) {
+ error = true;
+ }
} 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;
+ const std::string output_filename = BuildIntermediateFilename(path_data);
+ if (const ResourceType* type =
+ ParseResourceType(path_data.resource_dir)) {
+ if (*type != ResourceType::kRaw) {
+ if (path_data.extension == "xml") {
+ if (!CompileXml(&context, options, path_data, archive_writer.get(),
+ output_filename)) {
+ error = true;
}
- }
-
- 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 if (path_data.extension == "png" ||
+ path_data.extension == "9.png") {
+ if (!CompilePng(&context, options, path_data, archive_writer.get(),
+ output_filename)) {
+ 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;
+ } else {
+ if (!CompileFile(&context, options, path_data, archive_writer.get(),
+ output_filename)) {
+ error = true;
}
+ }
+ } else {
+ if (!CompileFile(&context, options, path_data, archive_writer.get(),
+ output_filename)) {
+ error = true;
+ }
}
- }
-
- if (error) {
- return 1;
- }
- return 0;
+ } else {
+ context.GetDiagnostics()->Error(
+ DiagMessage() << "invalid file path '" << path_data.source << "'");
+ error = true;
+ }
+ }
+ }
+
+ if (error) {
+ return 1;
+ }
+ return 0;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index aa4a5803b8df..17c22c5aac6b 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -14,97 +14,205 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
-
#include "compile/IdAssigner.h"
+
+#include <map>
+
+#include "android-base/logging.h"
+
+#include "ResourceTable.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;
+/**
+ * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and
+ * ResourceEntry,
+ * as long as there is no existing ID or the ID is the same.
+ */
+static bool AssignId(IDiagnostics* diag, const ResourceId& id,
+ const ResourceName& name, ResourceTablePackage* pkg,
+ ResourceTableType* type, ResourceEntry* entry) {
+ if (pkg->id.value() == id.package_id()) {
+ if (!type->id || type->id.value() == id.type_id()) {
+ type->id = id.type_id();
- for (auto& package : table->packages) {
- assert(package->id && "packages must have manually assigned IDs");
+ if (!entry->id || entry->id.value() == id.entry_id()) {
+ entry->id = id.entry_id();
+ return true;
+ }
+ }
+ }
- usedTypeIds.reset();
+ const ResourceId existing_id(pkg->id.value(), type->id ? type->id.value() : 0,
+ entry->id ? entry->id.value() : 0);
+ diag->Error(DiagMessage() << "can't assign ID " << id << " to resource "
+ << name << " with conflicting ID " << existing_id);
+ return false;
+}
- // Type ID 0 is invalid, reserve it.
- usedTypeIds.set(0);
+bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
+ std::map<ResourceId, ResourceName> assigned_ids;
- // Collect used type IDs.
- for (auto& type : package->types) {
- if (type->id) {
- usedEntryIds.clear();
+ for (auto& package : table->packages) {
+ CHECK(bool(package->id)) << "packages must have manually assigned IDs";
- 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;
- }
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ const ResourceName name(package->name, type->type, entry->name);
- // Mark the type ID as taken.
- usedTypeIds.set(type->id.value());
+ if (assigned_id_map_) {
+ // Assign the pre-assigned stable ID meant for this resource.
+ const auto iter = assigned_id_map_->find(name);
+ if (iter != assigned_id_map_->end()) {
+ const ResourceId assigned_id = iter->second;
+ const bool result =
+ AssignId(context->GetDiagnostics(), assigned_id, name,
+ package.get(), type.get(), entry.get());
+ if (!result) {
+ return false;
}
+ }
+ }
- // 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;
- }
- }
- }
+ if (package->id && type->id && entry->id) {
+ // If the ID is set for this resource, then reserve it.
+ ResourceId resource_id(package->id.value(), type->id.value(),
+ entry->id.value());
+ auto result = assigned_ids.insert({resource_id, name});
+ const ResourceName& existing_name = result.first->second;
+ if (!result.second) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "resource " << name << " has same ID "
+ << resource_id << " as " << existing_name);
+ 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++;
- }
- }
+ if (assigned_id_map_) {
+ // Reserve all the IDs mentioned in the stable ID map. That way we won't
+ // assign
+ // IDs that were listed in the map if they don't exist in the table.
+ for (const auto& stable_id_entry : *assigned_id_map_) {
+ const ResourceName& pre_assigned_name = stable_id_entry.first;
+ const ResourceId& pre_assigned_id = stable_id_entry.second;
+ auto result = assigned_ids.insert({pre_assigned_id, pre_assigned_name});
+ const ResourceName& existing_name = result.first->second;
+ if (!result.second && existing_name != pre_assigned_name) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "stable ID " << pre_assigned_id << " for resource "
+ << pre_assigned_name
+ << " is already taken by resource " << existing_name);
+ return false;
+ }
+ }
+ }
+
+ // Assign any resources without IDs the next available ID. Gaps will be filled
+ // if possible,
+ // unless those IDs have been reserved.
+
+ const auto assigned_ids_iter_end = assigned_ids.end();
+ for (auto& package : table->packages) {
+ CHECK(bool(package->id)) << "packages must have manually assigned IDs";
+
+ // Build a half filled ResourceId object, which will be used to find the
+ // closest matching
+ // reserved ID in the assignedId map. From that point the next available
+ // type ID can be
+ // found.
+ ResourceId resource_id(package->id.value(), 0, 0);
+ uint8_t next_expected_type_id = 1;
+
+ // Find the closest matching ResourceId that is <= the one with only the
+ // package set.
+ auto next_type_iter = assigned_ids.lower_bound(resource_id);
+ for (auto& type : package->types) {
+ if (!type->id) {
+ // We need to assign a type ID. Iterate over the reserved IDs until we
+ // find
+ // some type ID that is a distance of 2 greater than the last one we've
+ // seen.
+ // That means there is an available type ID between these reserved IDs.
+ while (next_type_iter != assigned_ids_iter_end) {
+ if (next_type_iter->first.package_id() != package->id.value()) {
+ break;
+ }
+
+ const uint8_t type_id = next_type_iter->first.type_id();
+ if (type_id > next_expected_type_id) {
+ // There is a gap in the type IDs, so use the missing one.
+ type->id = next_expected_type_id++;
+ break;
+ }
+
+ // Set our expectation to be the next type ID after the reserved one
+ // we
+ // just saw.
+ next_expected_type_id = type_id + 1;
+
+ // Move to the next reserved ID.
+ ++next_type_iter;
}
- // 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++;
+ if (!type->id) {
+ // We must have hit the end of the reserved IDs and not found a gap.
+ // That means the next ID is available.
+ type->id = next_expected_type_id++;
+ }
+ }
+
+ resource_id = ResourceId(package->id.value(), type->id.value(), 0);
+ uint16_t next_expected_entry_id = 0;
+
+ // Find the closest matching ResourceId that is <= the one with only the
+ // package
+ // and type set.
+ auto next_entry_iter = assigned_ids.lower_bound(resource_id);
+ for (auto& entry : type->entries) {
+ if (!entry->id) {
+ // We need to assign an entry ID. Iterate over the reserved IDs until
+ // we find
+ // some entry ID that is a distance of 2 greater than the last one
+ // we've seen.
+ // That means there is an available entry ID between these reserved
+ // IDs.
+ while (next_entry_iter != assigned_ids_iter_end) {
+ if (next_entry_iter->first.package_id() != package->id.value() ||
+ next_entry_iter->first.type_id() != type->id.value()) {
+ break;
+ }
+
+ const uint16_t entry_id = next_entry_iter->first.entry_id();
+ if (entry_id > next_expected_entry_id) {
+ // There is a gap in the entry IDs, so use the missing one.
+ entry->id = next_expected_entry_id++;
+ break;
}
+
+ // Set our expectation to be the next type ID after the reserved one
+ // we
+ // just saw.
+ next_expected_entry_id = entry_id + 1;
+
+ // Move to the next reserved entry ID.
+ ++next_entry_iter;
+ }
+
+ if (!entry->id) {
+ // We must have hit the end of the reserved IDs and not found a gap.
+ // That means the next ID is available.
+ entry->id = next_expected_entry_id++;
+ }
}
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h
index 514df3ad3861..371ec01818cd 100644
--- a/tools/aapt2/compile/IdAssigner.h
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -17,18 +17,33 @@
#ifndef AAPT_COMPILE_IDASSIGNER_H
#define AAPT_COMPILE_IDASSIGNER_H
+#include <unordered_map>
+
+#include "Resource.h"
#include "process/IResourceTableConsumer.h"
+#include "android-base/macros.h"
+
namespace aapt {
/**
- * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps
+ * 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;
+class IdAssigner : public IResourceTableConsumer {
+ public:
+ IdAssigner() = default;
+ explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map)
+ : assigned_id_map_(map) {}
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ const std::unordered_map<ResourceName, ResourceId>* assigned_id_map_ =
+ nullptr;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_IDASSIGNER_H */
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
index e25a17ab125e..d465091d224e 100644
--- a/tools/aapt2/compile/IdAssigner_test.cpp
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -16,108 +16,170 @@
#include "compile/IdAssigner.h"
-#include "test/Context.h"
-#include "test/Builders.h"
-
-#include <gtest/gtest.h>
+#include "test/Test.h"
namespace aapt {
-::testing::AssertionResult verifyIds(ResourceTable* table);
+::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()));
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo")
+ .AddSimple("android:attr/bar")
+ .AddSimple("android:id/foo")
+ .SetPackageId("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()));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddSimple("android:id/foo", ResourceId(0x01010000))
+ .AddSimple("android:dimen/two")
+ .AddSimple("android:integer/three")
+ .AddSimple("android:string/five")
+ .AddSimple("android:attr/fun", ResourceId(0x01040000))
+ .AddSimple("android:attr/foo", ResourceId(0x01040006))
+ .AddSimple("android:attr/bar")
+ .AddSimple("android:attr/baz")
+ .AddSimple("app:id/biz")
+ .SetPackageId("android", 0x01)
+ .SetPackageId("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()));
+
+ Maybe<ResourceTable::SearchResult> maybe_result;
+
+ // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX.
+
+ maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint8_t>(2), maybe_result.value().type->id);
+
+ maybe_result =
+ table->FindResource(test::ParseNameOrDie("android:integer/three"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint8_t>(3), maybe_result.value().type->id);
+
+ // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX
+ // IDs.
+
+ maybe_result =
+ table->FindResource(test::ParseNameOrDie("android:string/five"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint8_t>(5), maybe_result.value().type->id);
+
+ // Expect to fill in the gaps between 0x01040000 and 0x01040006.
+
+ maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint16_t>(1), maybe_result.value().entry->id);
+
+ maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint16_t>(2), maybe_result.value().entry->id);
}
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;
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo", ResourceId(0x01040006))
+ .AddSimple("android:attr/bar", ResourceId(0x01040006))
+ .SetPackageId("android", 0x01)
+ .SetPackageId("app", 0x7f)
+ .Build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ IdAssigner assigner;
+
+ ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
+}
- ASSERT_FALSE(assigner.consume(context.get(), table.get()));
+TEST(IdAssignerTest, AssignIdsWithIdMap) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo")
+ .AddSimple("android:attr/bar")
+ .SetPackageId("android", 0x01)
+ .Build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unordered_map<ResourceName, ResourceId> id_map = {
+ {test::ParseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}};
+ IdAssigner assigner(&id_map);
+ ASSERT_TRUE(assigner.Consume(context.get(), table.get()));
+ ASSERT_TRUE(VerifyIds(table.get()));
+ Maybe<ResourceTable::SearchResult> result =
+ table->FindResource(test::ParseNameOrDie("android:attr/foo"));
+ AAPT_ASSERT_TRUE(result);
+
+ const ResourceTable::SearchResult& search_result = result.value();
+ EXPECT_EQ(make_value<uint8_t>(0x01), search_result.package->id);
+ EXPECT_EQ(make_value<uint8_t>(0x01), search_result.type->id);
+ EXPECT_EQ(make_value<uint16_t>(0x0002), search_result.entry->id);
}
-::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";
- }
+::testing::AssertionResult VerifyIds(ResourceTable* table) {
+ std::set<uint8_t> package_ids;
+ 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;
- }
+ if (!package_ids.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> type_ids;
+ for (auto& type : package->types) {
+ if (!type->id) {
+ return ::testing::AssertionFailure() << "type " << type->type
+ << " of package " << package->name
+ << " has no ID";
+ }
+
+ if (!type_ids.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& 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> entry_ids;
+ 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";
}
-
- 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;
- }
- }
+ if (!entry_ids.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";
+ }
+ return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Image.h b/tools/aapt2/compile/Image.h
new file mode 100644
index 000000000000..db0b945e1f18
--- /dev/null
+++ b/tools/aapt2/compile/Image.h
@@ -0,0 +1,209 @@
+/*
+ * 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_IMAGE_H
+#define AAPT_COMPILE_IMAGE_H
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/macros.h"
+
+namespace aapt {
+
+/**
+ * An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
+ */
+class Image {
+ public:
+ explicit Image() = default;
+
+ /**
+ * A `height` sized array of pointers, where each element points to a
+ * `width` sized row of RGBA_8888 pixels.
+ */
+ std::unique_ptr<uint8_t* []> rows;
+
+ /**
+ * The width of the image in RGBA_8888 pixels. This is int32_t because of
+ * 9-patch data
+ * format limitations.
+ */
+ int32_t width = 0;
+
+ /**
+ * The height of the image in RGBA_8888 pixels. This is int32_t because of
+ * 9-patch data
+ * format limitations.
+ */
+ int32_t height = 0;
+
+ /**
+ * Buffer to the raw image data stored sequentially.
+ * Use `rows` to access the data on a row-by-row basis.
+ */
+ std::unique_ptr<uint8_t[]> data;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Image);
+};
+
+/**
+ * A range of pixel values, starting at 'start' and ending before 'end'
+ * exclusive. Or rather [a, b).
+ */
+struct Range {
+ int32_t start = 0;
+ int32_t end = 0;
+
+ explicit Range() = default;
+ inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {}
+};
+
+inline bool operator==(const Range& left, const Range& right) {
+ return left.start == right.start && left.end == right.end;
+}
+
+/**
+ * Inset lengths from all edges of a rectangle. `left` and `top` are measured
+ * from the left and top
+ * edges, while `right` and `bottom` are measured from the right and bottom
+ * edges, respectively.
+ */
+struct Bounds {
+ int32_t left = 0;
+ int32_t top = 0;
+ int32_t right = 0;
+ int32_t bottom = 0;
+
+ explicit Bounds() = default;
+ inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b)
+ : left(l), top(t), right(r), bottom(b) {}
+
+ bool nonZero() const;
+};
+
+inline bool Bounds::nonZero() const {
+ return left != 0 || top != 0 || right != 0 || bottom != 0;
+}
+
+inline bool operator==(const Bounds& left, const Bounds& right) {
+ return left.left == right.left && left.top == right.top &&
+ left.right == right.right && left.bottom == right.bottom;
+}
+
+/**
+ * Contains 9-patch data from a source image. All measurements exclude the 1px
+ * border of the
+ * source 9-patch image.
+ */
+class NinePatch {
+ public:
+ static std::unique_ptr<NinePatch> Create(uint8_t** rows, const int32_t width,
+ const int32_t height,
+ std::string* err_out);
+
+ /**
+ * Packs the RGBA_8888 data pointed to by pixel into a uint32_t
+ * with format 0xAARRGGBB (the way 9-patch expects it).
+ */
+ static uint32_t PackRGBA(const uint8_t* pixel);
+
+ /**
+ * 9-patch content padding/insets. All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ Bounds padding;
+
+ /**
+ * Optical layout bounds/insets. This overrides the padding for
+ * layout purposes. All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ * See
+ * https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
+ */
+ Bounds layout_bounds;
+
+ /**
+ * Outline of the image, calculated based on opacity.
+ */
+ Bounds outline;
+
+ /**
+ * The computed radius of the outline. If non-zero, the outline is a
+ * rounded-rect.
+ */
+ float outline_radius = 0.0f;
+
+ /**
+ * The largest alpha value within the outline.
+ */
+ uint32_t outline_alpha = 0x000000ffu;
+
+ /**
+ * Horizontal regions of the image that are stretchable.
+ * All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ std::vector<Range> horizontal_stretch_regions;
+
+ /**
+ * Vertical regions of the image that are stretchable.
+ * All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ std::vector<Range> vertical_stretch_regions;
+
+ /**
+ * The colors within each region, fixed or stretchable.
+ * For w*h regions, the color of region (x,y) is addressable
+ * via index y*w + x.
+ */
+ std::vector<uint32_t> region_colors;
+
+ /**
+ * Returns serialized data containing the original basic 9-patch meta data.
+ * Optical layout bounds and round rect outline data must be serialized
+ * separately using SerializeOpticalLayoutBounds() and
+ * SerializeRoundedRectOutline().
+ */
+ std::unique_ptr<uint8_t[]> SerializeBase(size_t* out_len) const;
+
+ /**
+ * Serializes the layout bounds.
+ */
+ std::unique_ptr<uint8_t[]> SerializeLayoutBounds(size_t* out_len) const;
+
+ /**
+ * Serializes the rounded-rect outline.
+ */
+ std::unique_ptr<uint8_t[]> SerializeRoundedRectOutline(size_t* out_len) const;
+
+ private:
+ explicit NinePatch() = default;
+
+ DISALLOW_COPY_AND_ASSIGN(NinePatch);
+};
+
+::std::ostream& operator<<(::std::ostream& out, const Range& range);
+::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds);
+::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch);
+
+} // namespace aapt
+
+#endif /* AAPT_COMPILE_IMAGE_H */
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp
new file mode 100644
index 000000000000..786494b6ad1c
--- /dev/null
+++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp
@@ -0,0 +1,207 @@
+/*
+ * 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/InlineXmlFormatParser.h"
+
+#include <sstream>
+#include <string>
+
+#include "android-base/macros.h"
+
+#include "Debug.h"
+#include "ResourceUtils.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlUtil.h"
+
+namespace aapt {
+
+namespace {
+
+/**
+ * XML Visitor that will find all <aapt:attr> elements for extraction.
+ */
+class Visitor : public xml::PackageAwareVisitor {
+ public:
+ using xml::PackageAwareVisitor::Visit;
+
+ struct InlineDeclaration {
+ xml::Element* el;
+ std::string attr_namespace_uri;
+ std::string attr_name;
+ };
+
+ explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource)
+ : context_(context), xml_resource_(xml_resource) {}
+
+ void Visit(xml::Element* el) override {
+ if (el->namespace_uri != xml::kSchemaAapt || el->name != "attr") {
+ xml::PackageAwareVisitor::Visit(el);
+ return;
+ }
+
+ const Source& src = xml_resource_->file.source.WithLine(el->line_number);
+
+ xml::Attribute* attr = el->FindAttribute({}, "name");
+ if (!attr) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "missing 'name' attribute");
+ error_ = true;
+ return;
+ }
+
+ Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value);
+ if (!ref) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(src) << "invalid XML attribute '" << attr->value << "'");
+ error_ = true;
+ return;
+ }
+
+ const ResourceName& name = ref.value().name.value();
+
+ // Use an empty string for the compilation package because we don't want to
+ // default to
+ // the local package if the user specified name="style" or something. This
+ // should just
+ // be the default namespace.
+ Maybe<xml::ExtractedPackage> maybe_pkg =
+ TransformPackageAlias(name.package, {});
+ if (!maybe_pkg) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "invalid namespace prefix '"
+ << name.package << "'");
+ error_ = true;
+ return;
+ }
+
+ const xml::ExtractedPackage& pkg = maybe_pkg.value();
+ const bool private_namespace =
+ pkg.private_namespace || ref.value().private_reference;
+
+ InlineDeclaration decl;
+ decl.el = el;
+ decl.attr_name = name.entry;
+ if (!pkg.package.empty()) {
+ decl.attr_namespace_uri =
+ xml::BuildPackageNamespace(pkg.package, private_namespace);
+ }
+
+ inline_declarations_.push_back(std::move(decl));
+ }
+
+ const std::vector<InlineDeclaration>& GetInlineDeclarations() const {
+ return inline_declarations_;
+ }
+
+ bool HasError() const { return error_; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Visitor);
+
+ IAaptContext* context_;
+ xml::XmlResource* xml_resource_;
+ std::vector<InlineDeclaration> inline_declarations_;
+ bool error_ = false;
+};
+
+} // namespace
+
+bool InlineXmlFormatParser::Consume(IAaptContext* context,
+ xml::XmlResource* doc) {
+ Visitor visitor(context, doc);
+ doc->root->Accept(&visitor);
+ if (visitor.HasError()) {
+ return false;
+ }
+
+ size_t name_suffix_counter = 0;
+ for (const Visitor::InlineDeclaration& decl :
+ visitor.GetInlineDeclarations()) {
+ auto new_doc = util::make_unique<xml::XmlResource>();
+ new_doc->file.config = doc->file.config;
+ new_doc->file.source = doc->file.source.WithLine(decl.el->line_number);
+ new_doc->file.name = doc->file.name;
+
+ // Modify the new entry name. We need to suffix the entry with a number to
+ // avoid
+ // local collisions, then mangle it with the empty package, such that it
+ // won't show up
+ // in R.java.
+
+ new_doc->file.name.entry =
+ NameMangler::MangleEntry({}, new_doc->file.name.entry + "__" +
+ std::to_string(name_suffix_counter));
+
+ // Extracted elements must be the only child of <aapt:attr>.
+ // Make sure there is one root node in the children (ignore empty text).
+ for (auto& child : decl.el->children) {
+ const Source child_source = doc->file.source.WithLine(child->line_number);
+ if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) {
+ if (!util::TrimWhitespace(t->text).empty()) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(child_source)
+ << "can't extract text into its own resource");
+ return false;
+ }
+ } else if (new_doc->root) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(child_source)
+ << "inline XML resources must have a single root");
+ return false;
+ } else {
+ new_doc->root = std::move(child);
+ new_doc->root->parent = nullptr;
+ }
+ }
+
+ // Walk up and find the parent element.
+ xml::Node* node = decl.el;
+ xml::Element* parent_el = nullptr;
+ while (node->parent &&
+ (parent_el = xml::NodeCast<xml::Element>(node->parent)) == nullptr) {
+ node = node->parent;
+ }
+
+ if (!parent_el) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(new_doc->file.source)
+ << "no suitable parent for inheriting attribute");
+ return false;
+ }
+
+ // Add the inline attribute to the parent.
+ parent_el->attributes.push_back(
+ xml::Attribute{decl.attr_namespace_uri, decl.attr_name,
+ "@" + new_doc->file.name.ToString()});
+
+ // Delete the subtree.
+ for (auto iter = parent_el->children.begin();
+ iter != parent_el->children.end(); ++iter) {
+ if (iter->get() == node) {
+ parent_el->children.erase(iter);
+ break;
+ }
+ }
+
+ queue_.push_back(std::move(new_doc));
+
+ name_suffix_counter++;
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h
new file mode 100644
index 000000000000..1a658fd6a180
--- /dev/null
+++ b/tools/aapt2/compile/InlineXmlFormatParser.h
@@ -0,0 +1,69 @@
+/*
+ * 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_INLINEXMLFORMATPARSER_H
+#define AAPT_COMPILE_INLINEXMLFORMATPARSER_H
+
+#include <memory>
+#include <vector>
+
+#include "android-base/macros.h"
+
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+/**
+ * Extracts Inline XML definitions into their own xml::XmlResource objects.
+ *
+ * Inline XML looks like:
+ *
+ * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ * xmlns:aapt="http://schemas.android.com/aapt" >
+ * <aapt:attr name="android:drawable" >
+ * <vector
+ * android:height="64dp"
+ * android:width="64dp"
+ * android:viewportHeight="600"
+ * android:viewportWidth="600"/>
+ * </aapt:attr>
+ * </animated-vector>
+ *
+ * The <vector> will be extracted into its own XML file and <animated-vector>
+ * will
+ * gain an attribute 'android:drawable' set to a reference to the extracted
+ * <vector> resource.
+ */
+class InlineXmlFormatParser : public IXmlResourceConsumer {
+ public:
+ explicit InlineXmlFormatParser() = default;
+
+ bool Consume(IAaptContext* context, xml::XmlResource* doc) override;
+
+ std::vector<std::unique_ptr<xml::XmlResource>>&
+ GetExtractedInlineXmlDocuments() {
+ return queue_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser);
+
+ std::vector<std::unique_ptr<xml::XmlResource>> queue_;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_COMPILE_INLINEXMLFORMATPARSER_H */
diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
new file mode 100644
index 000000000000..348796c98c22
--- /dev/null
+++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
@@ -0,0 +1,143 @@
+/*
+ * 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/InlineXmlFormatParser.h"
+
+#include "test/Test.h"
+
+namespace aapt {
+
+TEST(InlineXmlFormatParserTest, PassThrough) {
+ 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">
+ <View android:text="hey">
+ <View android:id="hi" />
+ </View>
+ </View>)EOF");
+
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
+ EXPECT_EQ(0u, parser.GetExtractedInlineXmlDocuments().size());
+}
+
+TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
+ <View1 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:text">
+ <View2 android:text="hey">
+ <View3 android:id="hi" />
+ </View2>
+ </aapt:attr>
+ </View1>)EOF");
+
+ doc->file.name = test::ParseNameOrDie("layout/main");
+
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
+
+ // One XML resource should have been extracted.
+ EXPECT_EQ(1u, parser.GetExtractedInlineXmlDocuments().size());
+
+ xml::Element* el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+
+ EXPECT_EQ("View1", el->name);
+
+ // The <aapt:attr> tag should be extracted.
+ EXPECT_EQ(nullptr, el->FindChild(xml::kSchemaAapt, "attr"));
+
+ // The 'android:text' attribute should be set with a reference.
+ xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(nullptr, attr);
+
+ ResourceNameRef name_ref;
+ ASSERT_TRUE(ResourceUtils::ParseReference(attr->value, &name_ref));
+
+ xml::XmlResource* extracted_doc =
+ parser.GetExtractedInlineXmlDocuments()[0].get();
+ ASSERT_NE(nullptr, extracted_doc);
+
+ // Make sure the generated reference is correct.
+ EXPECT_EQ(name_ref.package, extracted_doc->file.name.package);
+ EXPECT_EQ(name_ref.type, extracted_doc->file.name.type);
+ EXPECT_EQ(name_ref.entry, extracted_doc->file.name.entry);
+
+ // Verify the structure of the extracted XML.
+ el = xml::FindRootElement(extracted_doc);
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ("View2", el->name);
+ EXPECT_NE(nullptr, el->FindChild({}, "View3"));
+}
+
+TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
+ <View1 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:text">
+ <View2 android:text="hey">
+ <View3 android:id="hi" />
+ </View2>
+ </aapt:attr>
+
+ <aapt:attr name="android:drawable">
+ <vector />
+ </aapt:attr>
+ </View1>)EOF");
+
+ doc->file.name = test::ParseNameOrDie("layout/main");
+
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
+ ASSERT_EQ(2u, parser.GetExtractedInlineXmlDocuments().size());
+
+ xml::Element* el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+
+ EXPECT_EQ("View1", el->name);
+
+ xml::Attribute* attr_text = el->FindAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(nullptr, attr_text);
+
+ xml::Attribute* attr_drawable =
+ el->FindAttribute(xml::kSchemaAndroid, "drawable");
+ ASSERT_NE(nullptr, attr_drawable);
+
+ // The two extracted resources should have different names.
+ EXPECT_NE(attr_text->value, attr_drawable->value);
+
+ // The child <aapt:attr> elements should be gone.
+ EXPECT_EQ(nullptr, el->FindChild(xml::kSchemaAapt, "attr"));
+
+ xml::XmlResource* extracted_doc_text =
+ parser.GetExtractedInlineXmlDocuments()[0].get();
+ ASSERT_NE(nullptr, extracted_doc_text);
+ el = xml::FindRootElement(extracted_doc_text);
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ("View2", el->name);
+
+ xml::XmlResource* extracted_doc_drawable =
+ parser.GetExtractedInlineXmlDocuments()[1].get();
+ ASSERT_NE(nullptr, extracted_doc_drawable);
+ el = xml::FindRootElement(extracted_doc_drawable);
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ("vector", el->name);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
new file mode 100644
index 000000000000..eab5c97c437c
--- /dev/null
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -0,0 +1,698 @@
+/*
+ * 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/Image.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "androidfw/ResourceTypes.h"
+
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
+constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
+constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
+constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
+
+constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
+constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
+
+/**
+ * Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
+ */
+static uint32_t get_alpha(uint32_t color);
+
+/**
+ * Determines whether a color on an ImageLine is valid.
+ * A 9patch image may use a transparent color as neutral,
+ * or a fully opaque white color as neutral, based on the
+ * pixel color at (0,0) of the image. One or the other is fine,
+ * but we need to ensure consistency throughout the image.
+ */
+class ColorValidator {
+ public:
+ virtual ~ColorValidator() = default;
+
+ /**
+ * Returns true if the color specified is a neutral color
+ * (no padding, stretching, or optical bounds).
+ */
+ virtual bool IsNeutralColor(uint32_t color) const = 0;
+
+ /**
+ * Returns true if the color is either a neutral color
+ * or one denoting padding, stretching, or optical bounds.
+ */
+ bool IsValidColor(uint32_t color) const {
+ switch (color) {
+ case kPrimaryColor:
+ case kSecondaryColor:
+ return true;
+ }
+ return IsNeutralColor(color);
+ }
+};
+
+// Walks an ImageLine and records Ranges of primary and secondary colors.
+// The primary color is black and is used to denote a padding or stretching
+// range,
+// depending on which border we're iterating over.
+// The secondary color is red and is used to denote optical bounds.
+//
+// An ImageLine is a templated-interface that would look something like this if
+// it
+// were polymorphic:
+//
+// class ImageLine {
+// public:
+// virtual int32_t GetLength() const = 0;
+// virtual uint32_t GetColor(int32_t idx) const = 0;
+// };
+//
+template <typename ImageLine>
+static bool FillRanges(const ImageLine* image_line,
+ const ColorValidator* color_validator,
+ std::vector<Range>* primary_ranges,
+ std::vector<Range>* secondary_ranges,
+ std::string* out_err) {
+ const int32_t length = image_line->GetLength();
+
+ uint32_t last_color = 0xffffffffu;
+ for (int32_t idx = 1; idx < length - 1; idx++) {
+ const uint32_t color = image_line->GetColor(idx);
+ if (!color_validator->IsValidColor(color)) {
+ *out_err = "found an invalid color";
+ return false;
+ }
+
+ if (color != last_color) {
+ // We are ending a range. Which range?
+ // note: encode the x offset without the final 1 pixel border.
+ if (last_color == kPrimaryColor) {
+ primary_ranges->back().end = idx - 1;
+ } else if (last_color == kSecondaryColor) {
+ secondary_ranges->back().end = idx - 1;
+ }
+
+ // We are starting a range. Which range?
+ // note: encode the x offset without the final 1 pixel border.
+ if (color == kPrimaryColor) {
+ primary_ranges->push_back(Range(idx - 1, length - 2));
+ } else if (color == kSecondaryColor) {
+ secondary_ranges->push_back(Range(idx - 1, length - 2));
+ }
+ last_color = color;
+ }
+ }
+ return true;
+}
+
+/**
+ * Iterates over a row in an image. Implements the templated ImageLine
+ * interface.
+ */
+class HorizontalImageLine {
+ public:
+ explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
+ int32_t length)
+ : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
+
+ inline int32_t GetLength() const { return length_; }
+
+ inline uint32_t GetColor(int32_t idx) const {
+ return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
+ }
+
+ private:
+ uint8_t** rows_;
+ int32_t xoffset_, yoffset_, length_;
+
+ DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
+};
+
+/**
+ * Iterates over a column in an image. Implements the templated ImageLine
+ * interface.
+ */
+class VerticalImageLine {
+ public:
+ explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
+ int32_t length)
+ : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
+
+ inline int32_t GetLength() const { return length_; }
+
+ inline uint32_t GetColor(int32_t idx) const {
+ return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
+ }
+
+ private:
+ uint8_t** rows_;
+ int32_t xoffset_, yoffset_, length_;
+
+ DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
+};
+
+class DiagonalImageLine {
+ public:
+ explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
+ int32_t xstep, int32_t ystep, int32_t length)
+ : rows_(rows),
+ xoffset_(xoffset),
+ yoffset_(yoffset),
+ xstep_(xstep),
+ ystep_(ystep),
+ length_(length) {}
+
+ inline int32_t GetLength() const { return length_; }
+
+ inline uint32_t GetColor(int32_t idx) const {
+ return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] +
+ ((idx + xoffset_) * xstep_) * 4);
+ }
+
+ private:
+ uint8_t** rows_;
+ int32_t xoffset_, yoffset_, xstep_, ystep_, length_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
+};
+
+class TransparentNeutralColorValidator : public ColorValidator {
+ public:
+ bool IsNeutralColor(uint32_t color) const override {
+ return get_alpha(color) == 0;
+ }
+};
+
+class WhiteNeutralColorValidator : public ColorValidator {
+ public:
+ bool IsNeutralColor(uint32_t color) const override {
+ return color == kColorOpaqueWhite;
+ }
+};
+
+inline static uint32_t get_alpha(uint32_t color) {
+ return (color & 0xff000000u) >> 24;
+}
+
+static bool PopulateBounds(const std::vector<Range>& padding,
+ const std::vector<Range>& layout_bounds,
+ const std::vector<Range>& stretch_regions,
+ const int32_t length, int32_t* padding_start,
+ int32_t* padding_end, int32_t* layout_start,
+ int32_t* layout_end, const StringPiece& edge_name,
+ std::string* out_err) {
+ if (padding.size() > 1) {
+ std::stringstream err_stream;
+ err_stream << "too many padding sections on " << edge_name << " border";
+ *out_err = err_stream.str();
+ return false;
+ }
+
+ *padding_start = 0;
+ *padding_end = 0;
+ if (!padding.empty()) {
+ const Range& range = padding.front();
+ *padding_start = range.start;
+ *padding_end = length - range.end;
+ } else if (!stretch_regions.empty()) {
+ // No padding was defined. Compute the padding from the first and last
+ // stretch regions.
+ *padding_start = stretch_regions.front().start;
+ *padding_end = length - stretch_regions.back().end;
+ }
+
+ if (layout_bounds.size() > 2) {
+ std::stringstream err_stream;
+ err_stream << "too many layout bounds sections on " << edge_name
+ << " border";
+ *out_err = err_stream.str();
+ return false;
+ }
+
+ *layout_start = 0;
+ *layout_end = 0;
+ if (layout_bounds.size() >= 1) {
+ const Range& range = layout_bounds.front();
+ // If there is only one layout bound segment, it might not start at 0, but
+ // then it should
+ // end at length.
+ if (range.start != 0 && range.end != length) {
+ std::stringstream err_stream;
+ err_stream << "layout bounds on " << edge_name
+ << " border must start at edge";
+ *out_err = err_stream.str();
+ return false;
+ }
+ *layout_start = range.end;
+
+ if (layout_bounds.size() >= 2) {
+ const Range& range = layout_bounds.back();
+ if (range.end != length) {
+ std::stringstream err_stream;
+ err_stream << "layout bounds on " << edge_name
+ << " border must start at edge";
+ *out_err = err_stream.str();
+ return false;
+ }
+ *layout_end = length - range.start;
+ }
+ }
+ return true;
+}
+
+static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions,
+ int32_t length) {
+ if (stretch_regions.size() == 0) {
+ return 0;
+ }
+
+ const bool start_is_fixed = stretch_regions.front().start != 0;
+ const bool end_is_fixed = stretch_regions.back().end != length;
+ int32_t modifier = 0;
+ if (start_is_fixed && end_is_fixed) {
+ modifier = 1;
+ } else if (!start_is_fixed && !end_is_fixed) {
+ modifier = -1;
+ }
+ return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier;
+}
+
+static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
+ // Sample the first pixel to compare against.
+ const uint32_t expected_color =
+ NinePatch::PackRGBA(rows[region.top] + region.left * 4);
+ for (int32_t y = region.top; y < region.bottom; y++) {
+ const uint8_t* row = rows[y];
+ for (int32_t x = region.left; x < region.right; x++) {
+ const uint32_t color = NinePatch::PackRGBA(row + x * 4);
+ if (get_alpha(color) == 0) {
+ // The color is transparent.
+ // If the expectedColor is not transparent, NO_COLOR.
+ if (get_alpha(expected_color) != 0) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ } else if (color != expected_color) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ }
+ }
+
+ if (get_alpha(expected_color) == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return expected_color;
+}
+
+// Fills out_colors with each 9-patch section's color. If the whole section is
+// transparent,
+// it gets the special TRANSPARENT color. If the whole section is the same
+// color, it is assigned
+// that color. Otherwise it gets the special NO_COLOR color.
+//
+// Note that the rows contain the 9-patch 1px border, and the indices in the
+// stretch regions are
+// already offset to exclude the border. This means that each time the rows are
+// accessed,
+// the indices must be offset by 1.
+//
+// width and height also include the 9-patch 1px border.
+static void CalculateRegionColors(
+ uint8_t** rows, const std::vector<Range>& horizontal_stretch_regions,
+ const std::vector<Range>& vertical_stretch_regions, const int32_t width,
+ const int32_t height, std::vector<uint32_t>* out_colors) {
+ int32_t next_top = 0;
+ Bounds bounds;
+ auto row_iter = vertical_stretch_regions.begin();
+ while (next_top != height) {
+ if (row_iter != vertical_stretch_regions.end()) {
+ if (next_top != row_iter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = next_top + 1;
+ bounds.bottom = row_iter->start + 1;
+ next_top = row_iter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = row_iter->start + 1;
+ bounds.bottom = row_iter->end + 1;
+ next_top = row_iter->end;
+ ++row_iter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = next_top + 1;
+ bounds.bottom = height + 1;
+ next_top = height;
+ }
+
+ int32_t next_left = 0;
+ auto col_iter = horizontal_stretch_regions.begin();
+ while (next_left != width) {
+ if (col_iter != horizontal_stretch_regions.end()) {
+ if (next_left != col_iter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = next_left + 1;
+ bounds.right = col_iter->start + 1;
+ next_left = col_iter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = col_iter->start + 1;
+ bounds.right = col_iter->end + 1;
+ next_left = col_iter->end;
+ ++col_iter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = next_left + 1;
+ bounds.right = width + 1;
+ next_left = width;
+ }
+ out_colors->push_back(GetRegionColor(rows, bounds));
+ }
+ }
+}
+
+// Calculates the insets of a row/column of pixels based on where the largest
+// alpha value begins
+// (on both sides).
+template <typename ImageLine>
+static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start,
+ int32_t* out_end) {
+ *out_start = 0;
+ *out_end = 0;
+
+ const int32_t length = image_line->GetLength();
+ if (length < 3) {
+ return;
+ }
+
+ // If the length is odd, we want both sides to process the center pixel,
+ // so we use two different midpoints (to account for < and <= in the different
+ // loops).
+ const int32_t mid2 = length / 2;
+ const int32_t mid1 = mid2 + (length % 2);
+
+ uint32_t max_alpha = 0;
+ for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) {
+ uint32_t alpha = get_alpha(image_line->GetColor(i));
+ if (alpha > max_alpha) {
+ max_alpha = alpha;
+ *out_start = i;
+ }
+ }
+
+ max_alpha = 0;
+ for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) {
+ uint32_t alpha = get_alpha(image_line->GetColor(i));
+ if (alpha > max_alpha) {
+ max_alpha = alpha;
+ *out_end = length - (i + 1);
+ }
+ }
+ return;
+}
+
+template <typename ImageLine>
+static uint32_t FindMaxAlpha(const ImageLine* image_line) {
+ const int32_t length = image_line->GetLength();
+ uint32_t max_alpha = 0;
+ for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) {
+ uint32_t alpha = get_alpha(image_line->GetColor(idx));
+ if (alpha > max_alpha) {
+ max_alpha = alpha;
+ }
+ }
+ return max_alpha;
+}
+
+// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
+uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
+ return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
+}
+
+std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows,
+ const int32_t width,
+ const int32_t height,
+ std::string* out_err) {
+ if (width < 3 || height < 3) {
+ *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
+ return {};
+ }
+
+ std::vector<Range> horizontal_padding;
+ std::vector<Range> horizontal_layout_bounds;
+ std::vector<Range> vertical_padding;
+ std::vector<Range> vertical_layout_bounds;
+ std::vector<Range> unexpected_ranges;
+ std::unique_ptr<ColorValidator> color_validator;
+
+ if (rows[0][3] == 0) {
+ color_validator = util::make_unique<TransparentNeutralColorValidator>();
+ } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
+ color_validator = util::make_unique<WhiteNeutralColorValidator>();
+ } else {
+ *out_err =
+ "top-left corner pixel must be either opaque white or transparent";
+ return {};
+ }
+
+ // Private constructor, can't use make_unique.
+ auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
+
+ HorizontalImageLine top_row(rows, 0, 0, width);
+ if (!FillRanges(&top_row, color_validator.get(),
+ &nine_patch->horizontal_stretch_regions, &unexpected_ranges,
+ out_err)) {
+ return {};
+ }
+
+ if (!unexpected_ranges.empty()) {
+ const Range& range = unexpected_ranges[0];
+ std::stringstream err_stream;
+ err_stream << "found unexpected optical bounds (red pixel) on top border "
+ << "at x=" << range.start + 1;
+ *out_err = err_stream.str();
+ return {};
+ }
+
+ VerticalImageLine left_col(rows, 0, 0, height);
+ if (!FillRanges(&left_col, color_validator.get(),
+ &nine_patch->vertical_stretch_regions, &unexpected_ranges,
+ out_err)) {
+ return {};
+ }
+
+ if (!unexpected_ranges.empty()) {
+ const Range& range = unexpected_ranges[0];
+ std::stringstream err_stream;
+ err_stream << "found unexpected optical bounds (red pixel) on left border "
+ << "at y=" << range.start + 1;
+ return {};
+ }
+
+ HorizontalImageLine bottom_row(rows, 0, height - 1, width);
+ if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding,
+ &horizontal_layout_bounds, out_err)) {
+ return {};
+ }
+
+ if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
+ nine_patch->horizontal_stretch_regions, width - 2,
+ &nine_patch->padding.left, &nine_patch->padding.right,
+ &nine_patch->layout_bounds.left,
+ &nine_patch->layout_bounds.right, "bottom", out_err)) {
+ return {};
+ }
+
+ VerticalImageLine right_col(rows, width - 1, 0, height);
+ if (!FillRanges(&right_col, color_validator.get(), &vertical_padding,
+ &vertical_layout_bounds, out_err)) {
+ return {};
+ }
+
+ if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
+ nine_patch->vertical_stretch_regions, height - 2,
+ &nine_patch->padding.top, &nine_patch->padding.bottom,
+ &nine_patch->layout_bounds.top,
+ &nine_patch->layout_bounds.bottom, "right", out_err)) {
+ return {};
+ }
+
+ // Fill the region colors of the 9-patch.
+ const int32_t num_rows =
+ CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
+ const int32_t num_cols =
+ CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
+ if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
+ *out_err = "too many regions in 9-patch";
+ return {};
+ }
+
+ nine_patch->region_colors.reserve(num_rows * num_cols);
+ CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
+ nine_patch->vertical_stretch_regions, width - 2,
+ height - 2, &nine_patch->region_colors);
+
+ // Compute the outline based on opacity.
+
+ // Find left and right extent of 9-patch content on center row.
+ HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
+ FindOutlineInsets(&mid_row, &nine_patch->outline.left,
+ &nine_patch->outline.right);
+
+ // Find top and bottom extent of 9-patch content on center column.
+ VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
+ FindOutlineInsets(&mid_col, &nine_patch->outline.top,
+ &nine_patch->outline.bottom);
+
+ const int32_t outline_width =
+ (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
+ const int32_t outline_height =
+ (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
+
+ // Find the largest alpha value within the outline area.
+ HorizontalImageLine outline_mid_row(
+ rows, 1 + nine_patch->outline.left,
+ 1 + nine_patch->outline.top + (outline_height / 2), outline_width);
+ VerticalImageLine outline_mid_col(
+ rows, 1 + nine_patch->outline.left + (outline_width / 2),
+ 1 + nine_patch->outline.top, outline_height);
+ nine_patch->outline_alpha =
+ std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
+
+ // Assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center.
+ DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left,
+ 1 + nine_patch->outline.top, 1, 1,
+ std::min(outline_width, outline_height));
+ int32_t top_left, bottom_right;
+ FindOutlineInsets(&diagonal, &top_left, &bottom_right);
+
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ nine_patch->outline_radius = 3.4142f * top_left;
+ return nine_patch;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const {
+ android::Res_png_9patch data;
+ data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2;
+ data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2;
+ data.numColors = static_cast<uint8_t>(region_colors.size());
+ data.paddingLeft = padding.left;
+ data.paddingRight = padding.right;
+ data.paddingTop = padding.top;
+ data.paddingBottom = padding.bottom;
+
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
+ android::Res_png_9patch::serialize(
+ data, (const int32_t*)horizontal_stretch_regions.data(),
+ (const int32_t*)vertical_stretch_regions.data(), region_colors.data(),
+ buffer.get());
+ // Convert to file endianness.
+ reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
+
+ *outLen = data.serializedSize();
+ return buffer;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(
+ size_t* out_len) const {
+ size_t chunk_len = sizeof(uint32_t) * 4;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
+ uint8_t* cursor = buffer.get();
+
+ memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left));
+ cursor += sizeof(layout_bounds.left);
+
+ memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top));
+ cursor += sizeof(layout_bounds.top);
+
+ memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right));
+ cursor += sizeof(layout_bounds.right);
+
+ memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom));
+ cursor += sizeof(layout_bounds.bottom);
+
+ *out_len = chunk_len;
+ return buffer;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(
+ size_t* out_len) const {
+ size_t chunk_len = sizeof(uint32_t) * 6;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
+ uint8_t* cursor = buffer.get();
+
+ memcpy(cursor, &outline.left, sizeof(outline.left));
+ cursor += sizeof(outline.left);
+
+ memcpy(cursor, &outline.top, sizeof(outline.top));
+ cursor += sizeof(outline.top);
+
+ memcpy(cursor, &outline.right, sizeof(outline.right));
+ cursor += sizeof(outline.right);
+
+ memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
+ cursor += sizeof(outline.bottom);
+
+ *((float*)cursor) = outline_radius;
+ cursor += sizeof(outline_radius);
+
+ *((uint32_t*)cursor) = outline_alpha;
+
+ *out_len = chunk_len;
+ return buffer;
+}
+
+::std::ostream& operator<<(::std::ostream& out, const Range& range) {
+ return out << "[" << range.start << ", " << range.end << ")";
+}
+
+::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
+ return out << "l=" << bounds.left << " t=" << bounds.top
+ << " r=" << bounds.right << " b=" << bounds.bottom;
+}
+
+::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
+ return out << "horizontalStretch:"
+ << util::Joiner(nine_patch.horizontal_stretch_regions, " ")
+ << " verticalStretch:"
+ << util::Joiner(nine_patch.vertical_stretch_regions, " ")
+ << " padding: " << nine_patch.padding
+ << ", bounds: " << nine_patch.layout_bounds
+ << ", outline: " << nine_patch.outline
+ << " rad=" << nine_patch.outline_radius
+ << " alpha=" << nine_patch.outline_alpha;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/NinePatch_test.cpp b/tools/aapt2/compile/NinePatch_test.cpp
new file mode 100644
index 000000000000..f54bb2e62842
--- /dev/null
+++ b/tools/aapt2/compile/NinePatch_test.cpp
@@ -0,0 +1,377 @@
+/*
+ * 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/Image.h"
+
+#include "test/Test.h"
+
+namespace aapt {
+
+// Pixels are in RGBA_8888 packing.
+
+#define RED "\xff\x00\x00\xff"
+#define BLUE "\x00\x00\xff\xff"
+#define GREEN "\xff\x00\x00\xff"
+#define GR_70 "\xff\x00\x00\xb3"
+#define GR_50 "\xff\x00\x00\x80"
+#define GR_20 "\xff\x00\x00\x33"
+#define BLACK "\x00\x00\x00\xff"
+#define WHITE "\xff\xff\xff\xff"
+#define TRANS "\x00\x00\x00\x00"
+
+static uint8_t* k2x2[] = {
+ (uint8_t*)WHITE WHITE, (uint8_t*)WHITE WHITE,
+};
+
+static uint8_t* kMixedNeutralColor3x3[] = {
+ (uint8_t*)WHITE BLACK TRANS, (uint8_t*)TRANS RED TRANS,
+ (uint8_t*)WHITE WHITE WHITE,
+};
+
+static uint8_t* kTransparentNeutralColor3x3[] = {
+ (uint8_t*)TRANS BLACK TRANS, (uint8_t*)BLACK RED BLACK,
+ (uint8_t*)TRANS BLACK TRANS,
+};
+
+static uint8_t* kSingleStretch7x6[] = {
+ (uint8_t*)WHITE WHITE BLACK BLACK BLACK WHITE WHITE,
+ (uint8_t*)WHITE RED RED RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED RED RED WHITE,
+ (uint8_t*)WHITE RED RED RED RED RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kMultipleStretch10x7[] = {
+ (uint8_t*)WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kPadding6x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE,
+};
+
+static uint8_t* kLayoutBoundsWrongEdge3x3[] = {
+ (uint8_t*)WHITE RED WHITE, (uint8_t*)RED WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE,
+};
+
+static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE RED WHITE WHITE,
+};
+
+static uint8_t* kLayoutBounds5x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE RED WHITE RED WHITE,
+};
+
+static uint8_t* kAsymmetricLayoutBounds5x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE RED WHITE WHITE WHITE,
+};
+
+static uint8_t* kPaddingAndLayoutBounds5x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE BLACK,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE RED BLACK RED WHITE,
+};
+
+static uint8_t* kColorfulImage5x5[] = {
+ (uint8_t*)WHITE BLACK WHITE BLACK WHITE,
+ (uint8_t*)BLACK RED BLUE GREEN WHITE,
+ (uint8_t*)BLACK RED GREEN GREEN WHITE,
+ (uint8_t*)WHITE TRANS BLUE GREEN WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineOpaque10x10[] = {
+ (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineTranslucent10x10[] = {
+ (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineOffsetTranslucent12x10[] = {
+ (uint8_t*)
+ WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineRadius5x5[] = {
+ (uint8_t*)WHITE BLACK BLACK BLACK WHITE,
+ (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)BLACK GREEN GREEN GREEN WHITE,
+ (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kStretchAndPadding5x5[] = {
+ (uint8_t*)WHITE WHITE BLACK WHITE WHITE, (uint8_t*)WHITE RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED BLACK, (uint8_t*)WHITE RED RED RED WHITE,
+ (uint8_t*)WHITE WHITE BLACK WHITE WHITE,
+};
+
+TEST(NinePatchTest, Minimum3x3) {
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::Create(k2x2, 2, 2, &err));
+ EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, MixedNeutralColors) {
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::Create(kMixedNeutralColor3x3, 3, 3, &err));
+ EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, TransparentNeutralColor) {
+ std::string err;
+ EXPECT_NE(nullptr,
+ NinePatch::Create(kTransparentNeutralColor3x3, 3, 3, &err));
+}
+
+TEST(NinePatchTest, SingleStretchRegion) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kSingleStretch7x6, 7, 6, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ ASSERT_EQ(1u, nine_patch->horizontal_stretch_regions.size());
+ ASSERT_EQ(1u, nine_patch->vertical_stretch_regions.size());
+
+ EXPECT_EQ(Range(1, 4), nine_patch->horizontal_stretch_regions.front());
+ EXPECT_EQ(Range(1, 3), nine_patch->vertical_stretch_regions.front());
+}
+
+TEST(NinePatchTest, MultipleStretchRegions) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ ASSERT_EQ(3u, nine_patch->horizontal_stretch_regions.size());
+ ASSERT_EQ(2u, nine_patch->vertical_stretch_regions.size());
+
+ EXPECT_EQ(Range(1, 2), nine_patch->horizontal_stretch_regions[0]);
+ EXPECT_EQ(Range(3, 5), nine_patch->horizontal_stretch_regions[1]);
+ EXPECT_EQ(Range(6, 7), nine_patch->horizontal_stretch_regions[2]);
+
+ EXPECT_EQ(Range(0, 2), nine_patch->vertical_stretch_regions[0]);
+ EXPECT_EQ(Range(3, 5), nine_patch->vertical_stretch_regions[1]);
+}
+
+TEST(NinePatchTest, InferPaddingFromStretchRegions) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 0, 1, 0), nine_patch->padding);
+}
+
+TEST(NinePatchTest, Padding) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kPadding6x5, 6, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
+}
+
+TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) {
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::Create(kLayoutBoundsWrongEdge3x3, 3, 3, &err));
+ EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, LayoutBoundsMustTouchEdges) {
+ std::string err;
+ EXPECT_EQ(nullptr,
+ NinePatch::Create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
+ EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, LayoutBounds) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
+
+ nine_patch = NinePatch::Create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 0, 0), nine_patch->layout_bounds);
+}
+
+TEST(NinePatchTest, PaddingAndLayoutBounds) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
+}
+
+TEST(NinePatchTest, RegionColorsAreCorrect) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kColorfulImage5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ std::vector<uint32_t> expected_colors = {
+ NinePatch::PackRGBA((uint8_t*)RED),
+ (uint32_t)android::Res_png_9patch::NO_COLOR,
+ NinePatch::PackRGBA((uint8_t*)GREEN),
+ (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
+ NinePatch::PackRGBA((uint8_t*)BLUE),
+ NinePatch::PackRGBA((uint8_t*)GREEN),
+ };
+ EXPECT_EQ(expected_colors, nine_patch->region_colors);
+}
+
+TEST(NinePatchTest, OutlineFromOpaqueImage) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(2, 2, 2, 2), nine_patch->outline);
+ EXPECT_EQ(0x000000ffu, nine_patch->outline_alpha);
+ EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineFromTranslucentImage) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(3, 3, 3, 3), nine_patch->outline);
+ EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
+ EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineFromOffCenterImage) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineOffsetTranslucent12x10, 12, 10, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the
+ // middle for each inset. If the outline is shifted, the search may not find a
+ // closer bounds.
+ // This check should be:
+ // EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline);
+ // but until I know what behavior I'm breaking, I will leave it at the
+ // incorrect:
+ EXPECT_EQ(Bounds(4, 3, 3, 3), nine_patch->outline);
+
+ EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
+ EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineRadius) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineRadius5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(0, 0, 0, 0), nine_patch->outline);
+ EXPECT_EQ(3.4142f, nine_patch->outline_radius);
+}
+
+::testing::AssertionResult BigEndianOne(uint8_t* cursor) {
+ if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
+ return ::testing::AssertionSuccess();
+ }
+ return ::testing::AssertionFailure() << "Not BigEndian 1";
+}
+
+TEST(NinePatchTest, SerializePngEndianness) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ size_t len;
+ std::unique_ptr<uint8_t[]> data = nine_patch->SerializeBase(&len);
+ ASSERT_NE(nullptr, data);
+ ASSERT_NE(0u, len);
+
+ // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset +
+ // yDivsOffset
+ // (12 bytes)
+ uint8_t* cursor = data.get() + 12;
+
+ // Check that padding is big-endian. Expecting value 1.
+ EXPECT_TRUE(BigEndianOne(cursor));
+ EXPECT_TRUE(BigEndianOne(cursor + 4));
+ EXPECT_TRUE(BigEndianOne(cursor + 8));
+ EXPECT_TRUE(BigEndianOne(cursor + 12));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index bbf7f411d07e..7ab05b58b8b9 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-#include "util/BigBuffer.h"
#include "Png.h"
#include "Source.h"
+#include "util/BigBuffer.h"
#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
-#include <iostream>
#include <png.h>
+#include <zlib.h>
+#include <iostream>
#include <sstream>
#include <string>
#include <vector>
-#include <zlib.h>
namespace aapt {
@@ -33,158 +33,166 @@ constexpr bool kDebug = false;
constexpr size_t kPngSignatureSize = 8u;
struct PngInfo {
- ~PngInfo() {
- for (png_bytep row : rows) {
- if (row != nullptr) {
- delete[] row;
- }
- }
-
- delete[] xDivs;
- delete[] yDivs;
- }
-
- void* serialize9Patch() {
- void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs,
- colors.data());
- reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
- return serialized;
- }
-
- uint32_t width = 0;
- uint32_t height = 0;
- std::vector<png_bytep> rows;
-
- bool is9Patch = false;
- android::Res_png_9patch info9Patch;
- int32_t* xDivs = nullptr;
- int32_t* yDivs = nullptr;
- std::vector<uint32_t> colors;
-
- // Layout padding.
- bool haveLayoutBounds = false;
- int32_t layoutBoundsLeft;
- int32_t layoutBoundsTop;
- int32_t layoutBoundsRight;
- int32_t layoutBoundsBottom;
-
- // Round rect outline description.
- int32_t outlineInsetsLeft;
- int32_t outlineInsetsTop;
- int32_t outlineInsetsRight;
- int32_t outlineInsetsBottom;
- float outlineRadius;
- uint8_t outlineAlpha;
+ ~PngInfo() {
+ for (png_bytep row : rows) {
+ if (row != nullptr) {
+ delete[] row;
+ }
+ }
+
+ delete[] xDivs;
+ delete[] yDivs;
+ }
+
+ void* serialize9Patch() {
+ void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs,
+ yDivs, colors.data());
+ reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
+ return serialized;
+ }
+
+ uint32_t width = 0;
+ uint32_t height = 0;
+ std::vector<png_bytep> rows;
+
+ bool is9Patch = false;
+ android::Res_png_9patch info9Patch;
+ int32_t* xDivs = nullptr;
+ int32_t* yDivs = nullptr;
+ std::vector<uint32_t> colors;
+
+ // Layout padding.
+ bool haveLayoutBounds = false;
+ int32_t layoutBoundsLeft;
+ int32_t layoutBoundsTop;
+ int32_t layoutBoundsRight;
+ int32_t layoutBoundsBottom;
+
+ // Round rect outline description.
+ int32_t outlineInsetsLeft;
+ int32_t outlineInsetsTop;
+ int32_t outlineInsetsRight;
+ int32_t outlineInsetsBottom;
+ float outlineRadius;
+ uint8_t outlineAlpha;
};
-static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
- std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
- if (!input->read(reinterpret_cast<char*>(data), length)) {
- png_error(readPtr, strerror(errno));
- }
+static void readDataFromStream(png_structp readPtr, png_bytep data,
+ png_size_t length) {
+ std::istream* input =
+ reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+ if (!input->read(reinterpret_cast<char*>(data), length)) {
+ png_error(readPtr, strerror(errno));
+ }
}
-static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
- BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
- png_bytep buf = outBuffer->nextBlock<png_byte>(length);
- memcpy(buf, data, length);
+static void writeDataToStream(png_structp writePtr, png_bytep data,
+ png_size_t length) {
+ BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
+ png_bytep buf = outBuffer->NextBlock<png_byte>(length);
+ memcpy(buf, data, length);
}
-static void flushDataToStream(png_structp /*writePtr*/) {
-}
+static void flushDataToStream(png_structp /*writePtr*/) {}
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
- IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
- diag->warn(DiagMessage() << warningMessage);
+ IDiagnostics* diag =
+ reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
+ diag->Warn(DiagMessage() << warningMessage);
}
-
-static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
- if (setjmp(png_jmpbuf(readPtr))) {
- diag->error(DiagMessage() << "failed reading png");
- return false;
- }
-
- png_set_sig_bytes(readPtr, kPngSignatureSize);
- png_read_info(readPtr, infoPtr);
-
- int colorType, bitDepth, interlaceType, compressionType;
- png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
- &interlaceType, &compressionType, nullptr);
-
- if (colorType == PNG_COLOR_TYPE_PALETTE) {
- png_set_palette_to_rgb(readPtr);
- }
-
- if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
- png_set_expand_gray_1_2_4_to_8(readPtr);
- }
-
- if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
- png_set_tRNS_to_alpha(readPtr);
- }
-
- if (bitDepth == 16) {
- png_set_strip_16(readPtr);
- }
-
- if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
- png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
- }
-
- if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- png_set_gray_to_rgb(readPtr);
- }
-
- png_set_interlace_handling(readPtr);
- png_read_update_info(readPtr, infoPtr);
-
- const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
- outInfo->rows.resize(outInfo->height);
- for (size_t i = 0; i < outInfo->height; i++) {
- outInfo->rows[i] = new png_byte[rowBytes];
- }
-
- png_read_image(readPtr, outInfo->rows.data());
- png_read_end(readPtr, infoPtr);
- return true;
+static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr,
+ PngInfo* outInfo) {
+ if (setjmp(png_jmpbuf(readPtr))) {
+ diag->Error(DiagMessage() << "failed reading png");
+ return false;
+ }
+
+ png_set_sig_bytes(readPtr, kPngSignatureSize);
+ png_read_info(readPtr, infoPtr);
+
+ int colorType, bitDepth, interlaceType, compressionType;
+ png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth,
+ &colorType, &interlaceType, &compressionType, nullptr);
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(readPtr);
+ }
+
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(readPtr);
+ }
+
+ if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(readPtr);
+ }
+
+ if (bitDepth == 16) {
+ png_set_strip_16(readPtr);
+ }
+
+ if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+ }
+
+ if (colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(readPtr);
+ }
+
+ png_set_interlace_handling(readPtr);
+ png_read_update_info(readPtr, infoPtr);
+
+ const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+ outInfo->rows.resize(outInfo->height);
+ for (size_t i = 0; i < outInfo->height; i++) {
+ outInfo->rows[i] = new png_byte[rowBytes];
+ }
+
+ png_read_image(readPtr, outInfo->rows.data());
+ png_read_end(readPtr, infoPtr);
+ return true;
}
-static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* data) {
- size_t patchSize = inPatch->serializedSize();
- void* newData = malloc(patchSize);
- memcpy(newData, data, patchSize);
- android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
- outPatch->fileToDevice();
- // deserialization is done in place, so outPatch == newData
- assert(outPatch == newData);
- assert(outPatch->numXDivs == inPatch->numXDivs);
- assert(outPatch->numYDivs == inPatch->numYDivs);
- assert(outPatch->paddingLeft == inPatch->paddingLeft);
- assert(outPatch->paddingRight == inPatch->paddingRight);
- assert(outPatch->paddingTop == inPatch->paddingTop);
- assert(outPatch->paddingBottom == inPatch->paddingBottom);
-/* for (int i = 0; i < outPatch->numXDivs; i++) {
- assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
- }
- for (int i = 0; i < outPatch->numYDivs; i++) {
- assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
- }
- for (int i = 0; i < outPatch->numColors; i++) {
- assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
- }*/
- free(newData);
+static void checkNinePatchSerialization(android::Res_png_9patch* inPatch,
+ void* data) {
+ size_t patchSize = inPatch->serializedSize();
+ void* newData = malloc(patchSize);
+ memcpy(newData, data, patchSize);
+ android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
+ outPatch->fileToDevice();
+ // deserialization is done in place, so outPatch == newData
+ assert(outPatch == newData);
+ assert(outPatch->numXDivs == inPatch->numXDivs);
+ assert(outPatch->numYDivs == inPatch->numYDivs);
+ assert(outPatch->paddingLeft == inPatch->paddingLeft);
+ assert(outPatch->paddingRight == inPatch->paddingRight);
+ assert(outPatch->paddingTop == inPatch->paddingTop);
+ assert(outPatch->paddingBottom == inPatch->paddingBottom);
+ /* for (int i = 0; i < outPatch->numXDivs; i++) {
+ assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numYDivs; i++) {
+ assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numColors; i++) {
+ assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
+ }*/
+ free(newData);
}
-/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) {
+/*static void dump_image(int w, int h, const png_byte* const* rows, int
+color_type) {
int i, j, rr, gg, bb, aa;
int bpp;
- if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+ if (color_type == PNG_COLOR_TYPE_PALETTE || color_type ==
+PNG_COLOR_TYPE_GRAY) {
bpp = 1;
} else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
bpp = 2;
- } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+ } else if (color_type == PNG_COLOR_TYPE_RGB || color_type ==
+PNG_COLOR_TYPE_RGB_ALPHA) {
// We use a padding byte even when there is no alpha
bpp = 4;
} else {
@@ -224,1055 +232,1090 @@ 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(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
- png_colorp rgbPalette, png_bytep alphaPalette,
- int *paletteEntries, 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 maxGrayDeviation = 0;
-
- bool isOpaque = true;
- bool isPalette = true;
- bool isGrayscale = true;
-
- // Scan the entire image and determine if:
- // 1. Every pixel has R == G == B (grayscale)
- // 2. Every pixel has A == 255 (opaque)
- // 3. There are no more than 256 distinct RGBA colors
-
- if (kDebug) {
- printf("Initial image data:\n");
- //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
- }
-
- for (j = 0; j < h; j++) {
- const png_byte* row = imageInfo.rows[j];
- png_bytep out = outRows[j];
- for (i = 0; i < w; i++) {
- rr = *row++;
- gg = *row++;
- bb = *row++;
- aa = *row++;
-
- int odev = maxGrayDeviation;
- maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
- maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
- maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
- if (maxGrayDeviation > odev) {
- if (kDebug) {
- printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
- maxGrayDeviation, i, j, rr, gg, bb, aa);
- }
- }
-
- // Check if image is really grayscale
- if (isGrayscale) {
- if (rr != gg || rr != bb) {
- if (kDebug) {
- printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
- i, j, rr, gg, bb, aa);
- }
- isGrayscale = false;
- }
- }
-
- // Check if image is really opaque
- if (isOpaque) {
- if (aa != 0xff) {
- if (kDebug) {
- printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
- i, j, rr, gg, bb, aa);
- }
- isOpaque = false;
- }
- }
-
- // Check if image is really <= 256 colors
- 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;
- }
- }
-
- // 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 (kDebug) {
- printf("Found 257th color at %d, %d\n", i, j);
- }
- isPalette = false;
- } else {
- colors[num_colors++] = col;
- }
- }
- }
- }
- }
-
- *paletteEntries = 0;
- *hasTransparency = !isOpaque;
- int bpp = isOpaque ? 3 : 4;
- int paletteSize = w * h + bpp * num_colors;
-
- if (kDebug) {
- printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
- printf("isOpaque = %s\n", isOpaque ? "true" : "false");
- printf("isPalette = %s\n", isPalette ? "true" : "false");
- printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
- paletteSize, 2 * w * h, bpp * w * h);
- printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
- }
-
- // Choose the best color type for the image.
- // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
- // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
- // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
- // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
- // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
- if (isGrayscale) {
- if (isOpaque) {
- *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
- } else {
- // Use a simple heuristic to determine whether using a palette will
- // save space versus using gray + alpha for each pixel.
- // This doesn't take into account chunk overhead, filtering, LZ
- // compression, etc.
- if (isPalette && (paletteSize < 2 * w * h)) {
- *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
- } else {
- *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
- }
- }
- } else if (isPalette && (paletteSize < bpp * w * h)) {
- *colorType = PNG_COLOR_TYPE_PALETTE;
- } else {
- if (maxGrayDeviation <= grayscaleTolerance) {
- 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;
- }
- }
-
- // Perform postprocessing of the image or palette data based on the final
- // color type chosen
+#ifdef MAX
+#undef MAX
+#endif
+#ifdef ABS
+#undef ABS
+#endif
- if (*colorType == PNG_COLOR_TYPE_PALETTE) {
- // Create separate RGB and Alpha palettes and set the number of colors
- *paletteEntries = num_colors;
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define ABS(a) ((a) < 0 ? -(a) : (a))
- // Create the RGB and alpha palettes
- for (int idx = 0; idx < num_colors; 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);
+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) {
+ 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 maxGrayDeviation = 0;
+
+ bool isOpaque = true;
+ bool isPalette = true;
+ bool isGrayscale = true;
+
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors
+
+ if (kDebug) {
+ printf("Initial image data:\n");
+ // dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
+ }
+
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+ if (maxGrayDeviation > odev) {
+ if (kDebug) {
+ printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+ maxGrayDeviation, i, j, rr, gg, bb, aa);
}
- } 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
- for (j = 0; j < h; j++) {
- const png_byte* row = imageInfo.rows[j];
- png_bytep out = outRows[j];
- for (i = 0; i < w; i++) {
- rr = *row++;
- gg = *row++;
- bb = *row++;
- aa = *row++;
-
- if (isGrayscale) {
- *out++ = rr;
- } else {
- *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
- }
- if (!isOpaque) {
- *out++ = aa;
- }
- }
+ }
+
+ // Check if image is really grayscale
+ if (isGrayscale) {
+ if (rr != gg || rr != bb) {
+ if (kDebug) {
+ printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j,
+ rr, gg, bb, aa);
+ }
+ isGrayscale = false;
}
- }
-}
-
-static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
- int grayScaleTolerance) {
- if (setjmp(png_jmpbuf(writePtr))) {
- diag->error(DiagMessage() << "failed to write png");
- return false;
- }
-
- uint32_t width, height;
- int colorType, bitDepth, interlaceType, compressionType;
-
- png_unknown_chunk unknowns[3];
- unknowns[0].data = nullptr;
- unknowns[1].data = nullptr;
- unknowns[2].data = nullptr;
-
- png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep));
- if (outRows == (png_bytepp) 0) {
- printf("Can't allocate output buffer!\n");
- exit(1);
- }
- for (uint32_t i = 0; i < info->height; i++) {
- outRows[i] = (png_bytep) malloc(2 * (int) info->width);
- if (outRows[i] == (png_bytep) 0) {
- printf("Can't allocate output buffer!\n");
- exit(1);
+ }
+
+ // Check if image is really opaque
+ if (isOpaque) {
+ if (aa != 0xff) {
+ if (kDebug) {
+ printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j,
+ rr, gg, bb, aa);
+ }
+ isOpaque = false;
}
- }
-
- png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
-
- if (kDebug) {
- diag->note(DiagMessage()
- << "writing image: w = " << info->width
- << ", h = " << info->height);
- }
-
- png_color rgbPalette[256];
- png_byte alphaPalette[256];
- bool hasTransparency;
- int paletteEntries;
-
- 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
- // sure the pixels will not be pre-dithered/clamped until we decide they are
- if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB ||
- colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) {
- colorType = PNG_COLOR_TYPE_RGB_ALPHA;
- }
-
- if (kDebug) {
- switch (colorType) {
- case PNG_COLOR_TYPE_PALETTE:
- diag->note(DiagMessage()
- << "has " << paletteEntries
- << " colors" << (hasTransparency ? " (with alpha)" : "")
- << ", using PNG_COLOR_TYPE_PALLETTE");
- break;
- case PNG_COLOR_TYPE_GRAY:
- diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
- break;
- case PNG_COLOR_TYPE_GRAY_ALPHA:
- diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
- break;
- case PNG_COLOR_TYPE_RGB:
- diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
- break;
- case PNG_COLOR_TYPE_RGB_ALPHA:
- diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
+ }
+
+ // Check if image is really <= 256 colors
+ 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;
+ }
}
- }
-
- png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
- if (colorType == PNG_COLOR_TYPE_PALETTE) {
- png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
- if (hasTransparency) {
- png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0);
+ // 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 (kDebug) {
+ printf("Found 257th color at %d, %d\n", i, j);
+ }
+ isPalette = false;
+ } else {
+ colors[num_colors++] = col;
+ }
}
- png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+ }
+ }
+ }
+
+ *paletteEntries = 0;
+ *hasTransparency = !isOpaque;
+ int bpp = isOpaque ? 3 : 4;
+ int paletteSize = w * h + bpp * num_colors;
+
+ if (kDebug) {
+ printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
+ printf("isOpaque = %s\n", isOpaque ? "true" : "false");
+ printf("isPalette = %s\n", isPalette ? "true" : "false");
+ printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize,
+ 2 * w * h, bpp * w * h);
+ printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation,
+ grayscaleTolerance);
+ }
+
+ // Choose the best color type for the image.
+ // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+ // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct
+ // combinations
+ // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+ // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is
+ // sufficiently
+ // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+ if (isGrayscale) {
+ if (isOpaque) {
+ *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
} else {
- png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ // Use a simple heuristic to determine whether using a palette will
+ // save space versus using gray + alpha for each pixel.
+ // This doesn't take into account chunk overhead, filtering, LZ
+ // compression, etc.
+ if (isPalette && (paletteSize < 2 * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+ }
+ }
+ } else if (isPalette && (paletteSize < bpp * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ 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;
}
+ }
- if (info->is9Patch) {
- int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
- int pIndex = info->haveLayoutBounds ? 2 : 1;
- int bIndex = 1;
- int oIndex = 0;
-
- // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
- png_bytep chunkNames = info->haveLayoutBounds
- ? (png_bytep)"npOl\0npLb\0npTc\0"
- : (png_bytep)"npOl\0npTc";
+ // Perform postprocessing of the image or palette data based on the final
+ // color type chosen
- // base 9 patch data
- if (kDebug) {
- diag->note(DiagMessage() << "adding 9-patch info..");
- }
- strcpy((char*)unknowns[pIndex].name, "npTc");
- unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
- unknowns[pIndex].size = info->info9Patch.serializedSize();
- // TODO: remove the check below when everything works
- checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
-
- // automatically generated 9 patch outline data
- int chunkSize = sizeof(png_uint_32) * 6;
- strcpy((char*)unknowns[oIndex].name, "npOl");
- unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1);
- png_byte outputData[chunkSize];
- memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
- ((float*) outputData)[4] = info->outlineRadius;
- ((png_uint_32*) outputData)[5] = info->outlineAlpha;
- memcpy(unknowns[oIndex].data, &outputData, chunkSize);
- unknowns[oIndex].size = chunkSize;
-
- // optional optical inset / layout bounds data
- if (info->haveLayoutBounds) {
- int chunkSize = sizeof(png_uint_32) * 4;
- strcpy((char*)unknowns[bIndex].name, "npLb");
- unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1);
- memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
- unknowns[bIndex].size = chunkSize;
- }
+ if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Create separate RGB and Alpha palettes and set the number of colors
+ *paletteEntries = num_colors;
- for (int i = 0; i < chunkCount; i++) {
- unknowns[i].location = PNG_HAVE_PLTE;
- }
- png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS,
- chunkNames, chunkCount);
- png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
-
-#if PNG_LIBPNG_VER < 10600
- // Deal with unknown chunk location bug in 1.5.x and earlier.
- png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
- if (info->haveLayoutBounds) {
- png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
- }
-#endif
+ // Create the RGB and alpha palettes
+ for (int idx = 0; idx < num_colors; 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);
}
-
- png_write_info(writePtr, infoPtr);
-
- png_bytepp rows;
- if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
- if (colorType == PNG_COLOR_TYPE_RGB) {
- png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
+ } 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
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ if (isGrayscale) {
+ *out++ = rr;
+ } else {
+ *out++ = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
}
- rows = info->rows.data();
- } else {
- rows = outRows;
+ if (!isOpaque) {
+ *out++ = aa;
+ }
+ }
}
- png_write_image(writePtr, rows);
+ }
+}
+static bool writePng(IDiagnostics* diag, png_structp writePtr,
+ png_infop infoPtr, PngInfo* info, int grayScaleTolerance) {
+ if (setjmp(png_jmpbuf(writePtr))) {
+ diag->Error(DiagMessage() << "failed to write png");
+ return false;
+ }
+
+ uint32_t width, height;
+ int colorType, bitDepth, interlaceType, compressionType;
+
+ png_unknown_chunk unknowns[3];
+ unknowns[0].data = nullptr;
+ unknowns[1].data = nullptr;
+ unknowns[2].data = nullptr;
+
+ png_bytepp outRows =
+ (png_bytepp)malloc((int)info->height * sizeof(png_bytep));
+ if (outRows == (png_bytepp)0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ for (uint32_t i = 0; i < info->height; i++) {
+ outRows[i] = (png_bytep)malloc(2 * (int)info->width);
+ if (outRows[i] == (png_bytep)0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ }
+
+ png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+
+ if (kDebug) {
+ diag->Note(DiagMessage() << "writing image: w = " << info->width
+ << ", h = " << info->height);
+ }
+
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
+
+ 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
+ // sure the pixels will not be pre-dithered/clamped until we decide they are
+ if (info->is9Patch &&
+ (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_PALETTE)) {
+ colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ if (kDebug) {
+ switch (colorType) {
+ case PNG_COLOR_TYPE_PALETTE:
+ diag->Note(DiagMessage() << "has " << paletteEntries << " colors"
+ << (hasTransparency ? " (with alpha)" : "")
+ << ", using PNG_COLOR_TYPE_PALLETTE");
+ break;
+ case PNG_COLOR_TYPE_GRAY:
+ diag->Note(DiagMessage()
+ << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ diag->Note(DiagMessage()
+ << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ diag->Note(DiagMessage()
+ << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
+ break;
+ }
+ }
+
+ png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
+ if (hasTransparency) {
+ png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries,
+ (png_color_16p)0);
+ }
+ png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ }
+
+ if (info->is9Patch) {
+ int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
+ int pIndex = info->haveLayoutBounds ? 2 : 1;
+ int bIndex = 1;
+ int oIndex = 0;
+
+ // Chunks ordered thusly because older platforms depend on the base 9 patch
+ // data being last
+ png_bytep chunkNames = info->haveLayoutBounds
+ ? (png_bytep) "npOl\0npLb\0npTc\0"
+ : (png_bytep) "npOl\0npTc";
+
+ // base 9 patch data
if (kDebug) {
- printf("Final image data:\n");
- //dump_image(info->width, info->height, rows, colorType);
- }
-
- png_write_end(writePtr, infoPtr);
-
- for (uint32_t i = 0; i < info->height; i++) {
- free(outRows[i]);
- }
- free(outRows);
- free(unknowns[0].data);
- free(unknowns[1].data);
- free(unknowns[2].data);
+ diag->Note(DiagMessage() << "adding 9-patch info..");
+ }
+ strcpy((char*)unknowns[pIndex].name, "npTc");
+ unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
+ unknowns[pIndex].size = info->info9Patch.serializedSize();
+ // TODO: remove the check below when everything works
+ checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
+
+ // automatically generated 9 patch outline data
+ int chunkSize = sizeof(png_uint_32) * 6;
+ strcpy((char*)unknowns[oIndex].name, "npOl");
+ unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1);
+ png_byte outputData[chunkSize];
+ memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
+ ((float*)outputData)[4] = info->outlineRadius;
+ ((png_uint_32*)outputData)[5] = info->outlineAlpha;
+ memcpy(unknowns[oIndex].data, &outputData, chunkSize);
+ unknowns[oIndex].size = chunkSize;
+
+ // optional optical inset / layout bounds data
+ if (info->haveLayoutBounds) {
+ int chunkSize = sizeof(png_uint_32) * 4;
+ strcpy((char*)unknowns[bIndex].name, "npLb");
+ unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1);
+ memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
+ unknowns[bIndex].size = chunkSize;
+ }
+
+ for (int i = 0; i < chunkCount; i++) {
+ unknowns[i].location = PNG_HAVE_PLTE;
+ }
+ png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames,
+ chunkCount);
+ png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
- png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
- &compressionType, nullptr);
-
- if (kDebug) {
- diag->note(DiagMessage()
- << "image written: w = " << width << ", h = " << height
- << ", d = " << bitDepth << ", colors = " << colorType
- << ", inter = " << interlaceType << ", comp = " << compressionType);
+#if PNG_LIBPNG_VER < 10600
+ // Deal with unknown chunk location bug in 1.5.x and earlier.
+ png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
+ if (info->haveLayoutBounds) {
+ png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
}
- return true;
+#endif
+ }
+
+ png_write_info(writePtr, infoPtr);
+
+ png_bytepp rows;
+ if (colorType == PNG_COLOR_TYPE_RGB ||
+ colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+ if (colorType == PNG_COLOR_TYPE_RGB) {
+ png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
+ }
+ rows = info->rows.data();
+ } else {
+ rows = outRows;
+ }
+ png_write_image(writePtr, rows);
+
+ if (kDebug) {
+ printf("Final image data:\n");
+ // dump_image(info->width, info->height, rows, colorType);
+ }
+
+ png_write_end(writePtr, infoPtr);
+
+ for (uint32_t i = 0; i < info->height; i++) {
+ free(outRows[i]);
+ }
+ free(outRows);
+ free(unknowns[0].data);
+ free(unknowns[1].data);
+ free(unknowns[2].data);
+
+ png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType,
+ &interlaceType, &compressionType, nullptr);
+
+ if (kDebug) {
+ diag->Note(DiagMessage() << "image written: w = " << width
+ << ", h = " << height << ", d = " << bitDepth
+ << ", colors = " << colorType
+ << ", inter = " << interlaceType
+ << ", comp = " << compressionType);
+ }
+ return true;
}
constexpr uint32_t kColorWhite = 0xffffffffu;
constexpr uint32_t kColorTick = 0xff000000u;
constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
-enum class TickType {
- kNone,
- kTick,
- kLayoutBounds,
- kBoth
-};
+enum class TickType { kNone, kTick, kLayoutBounds, kBoth };
static TickType tickType(png_bytep p, bool transparent, const char** outError) {
- png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
-
- if (transparent) {
- if (p[3] == 0) {
- return TickType::kNone;
- }
- if (color == kColorLayoutBoundsTick) {
- return TickType::kLayoutBounds;
- }
- if (color == kColorTick) {
- return TickType::kTick;
- }
-
- // Error cases
- if (p[3] != 0xff) {
- *outError = "Frame pixels must be either solid or transparent "
- "(not intermediate alphas)";
- return TickType::kNone;
- }
+ png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
- if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
- *outError = "Ticks in transparent frame must be black or red";
- }
- return TickType::kTick;
+ if (transparent) {
+ if (p[3] == 0) {
+ return TickType::kNone;
}
-
- if (p[3] != 0xFF) {
- *outError = "White frame must be a solid color (no alpha)";
- }
- if (color == kColorWhite) {
- return TickType::kNone;
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
}
if (color == kColorTick) {
- return TickType::kTick;
+ return TickType::kTick;
}
- if (color == kColorLayoutBoundsTick) {
- return TickType::kLayoutBounds;
+
+ // Error cases
+ if (p[3] != 0xff) {
+ *outError =
+ "Frame pixels must be either solid or transparent "
+ "(not intermediate alphas)";
+ return TickType::kNone;
}
if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
- *outError = "Ticks in white frame must be black or red";
- return TickType::kNone;
+ *outError = "Ticks in transparent frame must be black or red";
}
return TickType::kTick;
+ }
+
+ if (p[3] != 0xFF) {
+ *outError = "White frame must be a solid color (no alpha)";
+ }
+ if (color == kColorWhite) {
+ return TickType::kNone;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in white frame must be black or red";
+ return TickType::kNone;
+ }
+ return TickType::kTick;
}
-enum class TickState {
- kStart,
- kInside1,
- kOutside1
-};
+enum class TickState { kStart, kInside1, kOutside1 };
-static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
- int32_t* outLeft, int32_t* outRight, const char** outError,
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent,
+ bool required, int32_t* outLeft,
+ int32_t* outRight, const char** outError,
uint8_t* outDivs, bool multipleAllowed) {
- *outLeft = *outRight = -1;
- TickState state = TickState::kStart;
- bool found = false;
-
- for (int i = 1; i < width - 1; i++) {
- if (tickType(row+i*4, transparent, outError) == TickType::kTick) {
- if (state == TickState::kStart ||
- (state == TickState::kOutside1 && multipleAllowed)) {
- *outLeft = i-1;
- *outRight = width-2;
- found = true;
- if (outDivs != NULL) {
- *outDivs += 2;
- }
- state = TickState::kInside1;
- } else if (state == TickState::kOutside1) {
- *outError = "Can't have more than one marked region along edge";
- *outLeft = i;
- return false;
- }
- } else if (!*outError) {
- if (state == TickState::kInside1) {
- // We're done with this div. Move on to the next.
- *outRight = i-1;
- outRight += 2;
- outLeft += 2;
- state = TickState::kOutside1;
- }
- } else {
- *outLeft = i;
- return false;
+ *outLeft = *outRight = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
+
+ for (int i = 1; i < width - 1; i++) {
+ if (tickType(row + i * 4, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outLeft = i - 1;
+ *outRight = width - 2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
}
- }
-
- if (required && !found) {
- *outError = "No marked region found along edge";
- *outLeft = -1;
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outLeft = i;
return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outRight = i - 1;
+ outRight += 2;
+ outLeft += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outLeft = i;
+ return false;
}
- return true;
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outLeft = -1;
+ return false;
+ }
+ return true;
}
-static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
- bool required, int32_t* outTop, int32_t* outBottom,
- const char** outError, uint8_t* outDivs, bool multipleAllowed) {
- *outTop = *outBottom = -1;
- TickState state = TickState::kStart;
- bool found = false;
-
- for (int i = 1; i < height - 1; i++) {
- if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) {
- if (state == TickState::kStart ||
- (state == TickState::kOutside1 && multipleAllowed)) {
- *outTop = i-1;
- *outBottom = height-2;
- found = true;
- if (outDivs != NULL) {
- *outDivs += 2;
- }
- state = TickState::kInside1;
- } else if (state == TickState::kOutside1) {
- *outError = "Can't have more than one marked region along edge";
- *outTop = i;
- return false;
- }
- } else if (!*outError) {
- if (state == TickState::kInside1) {
- // We're done with this div. Move on to the next.
- *outBottom = i-1;
- outTop += 2;
- outBottom += 2;
- state = TickState::kOutside1;
- }
- } else {
- *outTop = i;
- return false;
+static bool getVerticalTicks(png_bytepp rows, int offset, int height,
+ bool transparent, bool required, int32_t* outTop,
+ int32_t* outBottom, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed) {
+ *outTop = *outBottom = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
+
+ for (int i = 1; i < height - 1; i++) {
+ if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outTop = i - 1;
+ *outBottom = height - 2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
}
- }
-
- if (required && !found) {
- *outError = "No marked region found along edge";
- *outTop = -1;
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outTop = i;
return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outBottom = i - 1;
+ outTop += 2;
+ outBottom += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outTop = i;
+ return false;
}
- return true;
-}
+ }
-static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
- bool /* required */, int32_t* outLeft,
- int32_t* outRight, const char** outError) {
- *outLeft = *outRight = 0;
-
- // Look for left tick
- if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
- // Starting with a layout padding tick
- int i = 1;
- while (i < width - 1) {
- (*outLeft)++;
- i++;
- if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
- }
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outTop = -1;
+ return false;
+ }
+ return true;
+}
- // Look for right tick
- if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
- // Ending with a layout padding tick
- int i = width - 2;
- while (i > 1) {
- (*outRight)++;
- i--;
- if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
- }
- return true;
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width,
+ bool transparent,
+ bool /* required */,
+ int32_t* outLeft, int32_t* outRight,
+ const char** outError) {
+ *outLeft = *outRight = 0;
+
+ // Look for left tick
+ if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < width - 1) {
+ (*outLeft)++;
+ i++;
+ if (tickType(row + i * 4, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+
+ // Look for right tick
+ if (tickType(row + (width - 2) * 4, transparent, outError) ==
+ TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = width - 2;
+ while (i > 1) {
+ (*outRight)++;
+ i--;
+ if (tickType(row + i * 4, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+ return true;
}
-static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
- bool /* required */, int32_t* outTop, int32_t* outBottom,
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset,
+ int height, bool transparent,
+ bool /* required */, int32_t* outTop,
+ int32_t* outBottom,
const char** outError) {
- *outTop = *outBottom = 0;
-
- // Look for top tick
- if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
- // Starting with a layout padding tick
- int i = 1;
- while (i < height - 1) {
- (*outTop)++;
- i++;
- if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
- }
-
- // Look for bottom tick
- if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
- // Ending with a layout padding tick
- int i = height - 2;
- while (i > 1) {
- (*outBottom)++;
- i--;
- if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
- }
- return true;
+ *outTop = *outBottom = 0;
+
+ // Look for top tick
+ if (tickType(rows[1] + offset, transparent, outError) ==
+ TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < height - 1) {
+ (*outTop)++;
+ i++;
+ if (tickType(rows[i] + offset, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+
+ // Look for bottom tick
+ if (tickType(rows[height - 2] + offset, transparent, outError) ==
+ TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = height - 2;
+ while (i > 1) {
+ (*outBottom)++;
+ i--;
+ if (tickType(rows[i] + offset, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+ return true;
}
-static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY,
- int dX, int dY, int* outInset) {
- uint8_t maxOpacity = 0;
- int inset = 0;
- *outInset = 0;
- for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
- png_byte* color = rows[y] + x * 4;
- uint8_t opacity = color[3];
- if (opacity > maxOpacity) {
- maxOpacity = opacity;
- *outInset = inset;
- }
- if (opacity == 0xff) return;
- }
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX,
+ int endY, int dX, int dY, int* outInset) {
+ uint8_t maxOpacity = 0;
+ int inset = 0;
+ *outInset = 0;
+ for (int x = startX, y = startY; x != endX && y != endY;
+ x += dX, y += dY, inset++) {
+ png_byte* color = rows[y] + x * 4;
+ uint8_t opacity = color[3];
+ if (opacity > maxOpacity) {
+ maxOpacity = opacity;
+ *outInset = inset;
+ }
+ if (opacity == 0xff) return;
+ }
}
static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
- uint8_t maxAlpha = 0;
- for (int x = startX; x < endX; x++) {
- uint8_t alpha = (row + x * 4)[3];
- if (alpha > maxAlpha) maxAlpha = alpha;
- }
- return maxAlpha;
+ uint8_t maxAlpha = 0;
+ for (int x = startX; x < endX; x++) {
+ uint8_t alpha = (row + x * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
}
-static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
- uint8_t maxAlpha = 0;
- for (int y = startY; y < endY; y++) {
- uint8_t alpha = (rows[y] + offsetX * 4)[3];
- if (alpha > maxAlpha) maxAlpha = alpha;
- }
- return maxAlpha;
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY,
+ int endY) {
+ uint8_t maxAlpha = 0;
+ for (int y = startY; y < endY; y++) {
+ uint8_t alpha = (rows[y] + offsetX * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
}
static void getOutline(PngInfo* image) {
- int midX = image->width / 2;
- int midY = image->height / 2;
- int endX = image->width - 2;
- int endY = image->height - 2;
-
- // find left and right extent of nine patch content on center row
- if (image->width > 4) {
- findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
- findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
- &image->outlineInsetsRight);
- } else {
- image->outlineInsetsLeft = 0;
- image->outlineInsetsRight = 0;
- }
-
- // find top and bottom extent of nine patch content on center column
- if (image->height > 4) {
- findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
- findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
- &image->outlineInsetsBottom);
- } else {
- image->outlineInsetsTop = 0;
- image->outlineInsetsBottom = 0;
- }
-
- int innerStartX = 1 + image->outlineInsetsLeft;
- int innerStartY = 1 + image->outlineInsetsTop;
- int innerEndX = endX - image->outlineInsetsRight;
- int innerEndY = endY - image->outlineInsetsBottom;
- int innerMidX = (innerEndX + innerStartX) / 2;
- int innerMidY = (innerEndY + innerStartY) / 2;
-
- // assuming the image is a round rect, compute the radius by marching
- // diagonally from the top left corner towards the center
- image->outlineAlpha = std::max(
- maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
- maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
-
- int diagonalInset = 0;
- findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
- &diagonalInset);
-
- /* Determine source radius based upon inset:
- * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
- * sqrt(2) * r = sqrt(2) * i + r
- * (sqrt(2) - 1) * r = sqrt(2) * i
- * r = sqrt(2) / (sqrt(2) - 1) * i
- */
- image->outlineRadius = 3.4142f * diagonalInset;
-
- if (kDebug) {
- printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
- image->outlineInsetsLeft,
- image->outlineInsetsTop,
- image->outlineInsetsRight,
- image->outlineInsetsBottom,
- image->outlineRadius,
- image->outlineAlpha);
- }
+ int midX = image->width / 2;
+ int midY = image->height / 2;
+ int endX = image->width - 2;
+ int endY = image->height - 2;
+
+ // find left and right extent of nine patch content on center row
+ if (image->width > 4) {
+ findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0,
+ &image->outlineInsetsLeft);
+ findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
+ &image->outlineInsetsRight);
+ } else {
+ image->outlineInsetsLeft = 0;
+ image->outlineInsetsRight = 0;
+ }
+
+ // find top and bottom extent of nine patch content on center column
+ if (image->height > 4) {
+ findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1,
+ &image->outlineInsetsTop);
+ findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
+ &image->outlineInsetsBottom);
+ } else {
+ image->outlineInsetsTop = 0;
+ image->outlineInsetsBottom = 0;
+ }
+
+ int innerStartX = 1 + image->outlineInsetsLeft;
+ int innerStartY = 1 + image->outlineInsetsTop;
+ int innerEndX = endX - image->outlineInsetsRight;
+ int innerEndY = endY - image->outlineInsetsBottom;
+ int innerMidX = (innerEndX + innerStartX) / 2;
+ int innerMidY = (innerEndY + innerStartY) / 2;
+
+ // assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center
+ image->outlineAlpha = std::max(
+ maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+ maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+
+ int diagonalInset = 0;
+ findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX,
+ innerMidY, 1, 1, &diagonalInset);
+
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ image->outlineRadius = 3.4142f * diagonalInset;
+
+ if (kDebug) {
+ printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
+ image->outlineInsetsLeft, image->outlineInsetsTop,
+ image->outlineInsetsRight, image->outlineInsetsBottom,
+ image->outlineRadius, image->outlineAlpha);
+ }
}
-static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
- png_bytep color = rows[top] + left*4;
+static uint32_t getColor(png_bytepp rows, int left, int top, int right,
+ int bottom) {
+ png_bytep color = rows[top] + left * 4;
- if (left > right || top > bottom) {
- return android::Res_png_9patch::TRANSPARENT_COLOR;
- }
+ if (left > right || top > bottom) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
- while (top <= bottom) {
- for (int i = left; i <= right; i++) {
- png_bytep p = rows[top]+i*4;
- if (color[3] == 0) {
- if (p[3] != 0) {
- return android::Res_png_9patch::NO_COLOR;
- }
- } else if (p[0] != color[0] || p[1] != color[1] ||
- p[2] != color[2] || p[3] != color[3]) {
- return android::Res_png_9patch::NO_COLOR;
- }
+ while (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ png_bytep p = rows[top] + i * 4;
+ if (color[3] == 0) {
+ if (p[3] != 0) {
+ return android::Res_png_9patch::NO_COLOR;
}
- top++;
- }
-
- if (color[3] == 0) {
- return android::Res_png_9patch::TRANSPARENT_COLOR;
- }
- return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+ } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] ||
+ p[3] != color[3]) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ }
+ top++;
+ }
+
+ if (color[3] == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2];
}
static bool do9Patch(PngInfo* image, std::string* outError) {
- image->is9Patch = true;
-
- int W = image->width;
- int H = image->height;
- int i, j;
-
- const int maxSizeXDivs = W * sizeof(int32_t);
- const int maxSizeYDivs = H * sizeof(int32_t);
- int32_t* xDivs = image->xDivs = new int32_t[W];
- int32_t* yDivs = image->yDivs = new int32_t[H];
- uint8_t numXDivs = 0;
- uint8_t numYDivs = 0;
-
- int8_t numColors;
- int numRows;
- int numCols;
- int top;
- int left;
- int right;
- int bottom;
- memset(xDivs, -1, maxSizeXDivs);
- memset(yDivs, -1, maxSizeYDivs);
- image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
- image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
- image->layoutBoundsLeft = image->layoutBoundsRight = 0;
- image->layoutBoundsTop = image->layoutBoundsBottom = 0;
-
- png_bytep p = image->rows[0];
- bool transparent = p[3] == 0;
- bool hasColor = false;
-
- const char* errorMsg = nullptr;
- int errorPixel = -1;
- const char* errorEdge = nullptr;
-
- int colorIndex = 0;
- std::vector<png_bytep> newRows;
-
- // Validate size...
- if (W < 3 || H < 3) {
- errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
- goto getout;
- }
-
- // Validate frame...
- if (!transparent &&
- (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
- errorMsg = "Must have one-pixel frame that is either transparent or white";
- goto getout;
- }
-
- // Find left and right of sizing areas...
- if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
- true)) {
- errorPixel = xDivs[0];
- errorEdge = "top";
- goto getout;
- }
-
- // Find top and bottom of sizing areas...
- if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
- &errorMsg, &numYDivs, true)) {
- errorPixel = yDivs[0];
- errorEdge = "left";
- goto getout;
- }
-
- // Copy patch size data into image...
- image->info9Patch.numXDivs = numXDivs;
- image->info9Patch.numYDivs = numYDivs;
-
- // Find left and right of padding area...
- if (!getHorizontalTicks(image->rows[H-1], W, transparent, false,
- &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight,
- &errorMsg, nullptr, false)) {
- errorPixel = image->info9Patch.paddingLeft;
- errorEdge = "bottom";
- goto getout;
- }
-
- // Find top and bottom of padding area...
- if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false,
- &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom,
- &errorMsg, nullptr, false)) {
- errorPixel = image->info9Patch.paddingTop;
- errorEdge = "right";
- goto getout;
- }
-
- // Find left and right of layout padding...
- getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false,
- &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
-
- getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false,
- &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
-
- image->haveLayoutBounds = image->layoutBoundsLeft != 0
- || image->layoutBoundsRight != 0
- || image->layoutBoundsTop != 0
- || image->layoutBoundsBottom != 0;
-
- if (image->haveLayoutBounds) {
- if (kDebug) {
- printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
- image->layoutBoundsRight, image->layoutBoundsBottom);
- }
- }
-
- // use opacity of pixels to estimate the round rect outline
- getOutline(image);
-
- // If padding is not yet specified, take values from size.
- if (image->info9Patch.paddingLeft < 0) {
- image->info9Patch.paddingLeft = xDivs[0];
- image->info9Patch.paddingRight = W - 2 - xDivs[1];
- } else {
- // Adjust value to be correct!
- image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
- }
- if (image->info9Patch.paddingTop < 0) {
- image->info9Patch.paddingTop = yDivs[0];
- image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+ image->is9Patch = true;
+
+ int W = image->width;
+ int H = image->height;
+ int i, j;
+
+ const int maxSizeXDivs = W * sizeof(int32_t);
+ const int maxSizeYDivs = H * sizeof(int32_t);
+ int32_t* xDivs = image->xDivs = new int32_t[W];
+ int32_t* yDivs = image->yDivs = new int32_t[H];
+ uint8_t numXDivs = 0;
+ uint8_t numYDivs = 0;
+
+ int8_t numColors;
+ int numRows;
+ int numCols;
+ int top;
+ int left;
+ int right;
+ int bottom;
+ memset(xDivs, -1, maxSizeXDivs);
+ memset(yDivs, -1, maxSizeYDivs);
+ image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
+ image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+ image->layoutBoundsLeft = image->layoutBoundsRight = 0;
+ image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+
+ png_bytep p = image->rows[0];
+ bool transparent = p[3] == 0;
+ bool hasColor = false;
+
+ const char* errorMsg = nullptr;
+ int errorPixel = -1;
+ const char* errorEdge = nullptr;
+
+ int colorIndex = 0;
+ std::vector<png_bytep> newRows;
+
+ // Validate size...
+ if (W < 3 || H < 3) {
+ errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+ goto getout;
+ }
+
+ // Validate frame...
+ if (!transparent &&
+ (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+ errorMsg = "Must have one-pixel frame that is either transparent or white";
+ goto getout;
+ }
+
+ // Find left and right of sizing areas...
+ if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1],
+ &errorMsg, &numXDivs, true)) {
+ errorPixel = xDivs[0];
+ errorEdge = "top";
+ goto getout;
+ }
+
+ // Find top and bottom of sizing areas...
+ if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0],
+ &yDivs[1], &errorMsg, &numYDivs, true)) {
+ errorPixel = yDivs[0];
+ errorEdge = "left";
+ goto getout;
+ }
+
+ // Copy patch size data into image...
+ image->info9Patch.numXDivs = numXDivs;
+ image->info9Patch.numYDivs = numYDivs;
+
+ // Find left and right of padding area...
+ if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false,
+ &image->info9Patch.paddingLeft,
+ &image->info9Patch.paddingRight, &errorMsg, nullptr,
+ false)) {
+ errorPixel = image->info9Patch.paddingLeft;
+ errorEdge = "bottom";
+ goto getout;
+ }
+
+ // Find top and bottom of padding area...
+ if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
+ &image->info9Patch.paddingTop,
+ &image->info9Patch.paddingBottom, &errorMsg, nullptr,
+ false)) {
+ errorPixel = image->info9Patch.paddingTop;
+ errorEdge = "right";
+ goto getout;
+ }
+
+ // Find left and right of layout padding...
+ getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false,
+ &image->layoutBoundsLeft,
+ &image->layoutBoundsRight, &errorMsg);
+
+ getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent,
+ false, &image->layoutBoundsTop,
+ &image->layoutBoundsBottom, &errorMsg);
+
+ image->haveLayoutBounds =
+ image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 ||
+ image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0;
+
+ if (image->haveLayoutBounds) {
+ if (kDebug) {
+ printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft,
+ image->layoutBoundsTop, image->layoutBoundsRight,
+ image->layoutBoundsBottom);
+ }
+ }
+
+ // use opacity of pixels to estimate the round rect outline
+ getOutline(image);
+
+ // If padding is not yet specified, take values from size.
+ if (image->info9Patch.paddingLeft < 0) {
+ image->info9Patch.paddingLeft = xDivs[0];
+ image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ }
+ if (image->info9Patch.paddingTop < 0) {
+ image->info9Patch.paddingTop = yDivs[0];
+ image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+ }
+
+ /* if (kDebug) {
+ printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+ xDivs[0], xDivs[1],
+ yDivs[0], yDivs[1]);
+ printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+ image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+ image->info9Patch.paddingTop,
+ image->info9Patch.paddingBottom);
+ }*/
+
+ // Remove frame from image.
+ newRows.resize(H - 2);
+ for (i = 0; i < H - 2; i++) {
+ newRows[i] = image->rows[i + 1];
+ memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
+ }
+ image->rows.swap(newRows);
+
+ image->width -= 2;
+ W = image->width;
+ image->height -= 2;
+ H = image->height;
+
+ // Figure out the number of rows and columns in the N-patch
+ numCols = numXDivs + 1;
+ if (xDivs[0] == 0) { // Column 1 is strechable
+ numCols--;
+ }
+ if (xDivs[numXDivs - 1] == W) {
+ numCols--;
+ }
+ numRows = numYDivs + 1;
+ if (yDivs[0] == 0) { // Row 1 is strechable
+ numRows--;
+ }
+ if (yDivs[numYDivs - 1] == H) {
+ numRows--;
+ }
+
+ // Make sure the amount of rows and columns will fit in the number of
+ // colors we can use in the 9-patch format.
+ if (numRows * numCols > 0x7F) {
+ errorMsg = "Too many rows and columns in 9-patch perimeter";
+ goto getout;
+ }
+
+ numColors = numRows * numCols;
+ image->info9Patch.numColors = numColors;
+ image->colors.resize(numColors);
+
+ // Fill in color information for each patch.
+
+ uint32_t c;
+ top = 0;
+
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
+
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
+ if (j == numYDivs) {
+ bottom = H;
} else {
- // Adjust value to be correct!
- image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
- }
-
-/* if (kDebug) {
- printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
- xDivs[0], xDivs[1],
- yDivs[0], yDivs[1]);
- printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
- image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
- image->info9Patch.paddingTop, image->info9Patch.paddingBottom);
- }*/
-
- // Remove frame from image.
- newRows.resize(H - 2);
- for (i = 0; i < H - 2; i++) {
- newRows[i] = image->rows[i + 1];
- memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
- }
- image->rows.swap(newRows);
-
- image->width -= 2;
- W = image->width;
- image->height -= 2;
- H = image->height;
-
- // Figure out the number of rows and columns in the N-patch
- numCols = numXDivs + 1;
- if (xDivs[0] == 0) { // Column 1 is strechable
- numCols--;
- }
- if (xDivs[numXDivs - 1] == W) {
- numCols--;
- }
- numRows = numYDivs + 1;
- if (yDivs[0] == 0) { // Row 1 is strechable
- numRows--;
- }
- if (yDivs[numYDivs - 1] == H) {
- numRows--;
- }
-
- // Make sure the amount of rows and columns will fit in the number of
- // colors we can use in the 9-patch format.
- if (numRows * numCols > 0x7F) {
- errorMsg = "Too many rows and columns in 9-patch perimeter";
- goto getout;
- }
-
- numColors = numRows * numCols;
- image->info9Patch.numColors = numColors;
- image->colors.resize(numColors);
-
- // Fill in color information for each patch.
-
- uint32_t c;
- top = 0;
-
- // The first row always starts with the top being at y=0 and the bottom
- // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
- // the first row is stretchable along the Y axis, otherwise it is fixed.
- // The last row always ends with the bottom being bitmap.height and the top
- // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
- // yDivs[numYDivs-1]. In the former case the last row is stretchable along
- // the Y axis, otherwise it is fixed.
- //
- // The first and last columns are similarly treated with respect to the X
- // axis.
- //
- // The above is to help explain some of the special casing that goes on the
- // code below.
-
- // The initial yDiv and whether the first row is considered stretchable or
- // not depends on whether yDiv[0] was zero or not.
- for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
- if (j == numYDivs) {
- bottom = H;
- } else {
- bottom = yDivs[j];
- }
- left = 0;
- // The initial xDiv and whether the first column is considered
- // stretchable or not depends on whether xDiv[0] was zero or not.
- for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
- if (i == numXDivs) {
- right = W;
- } else {
- right = xDivs[i];
- }
- c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
- image->colors[colorIndex++] = c;
- if (kDebug) {
- if (c != android::Res_png_9patch::NO_COLOR) {
- hasColor = true;
- }
- }
- left = right;
+ bottom = yDivs[j];
+ }
+ left = 0;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
+ if (i == numXDivs) {
+ right = W;
+ } else {
+ right = xDivs[i];
+ }
+ c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
+ image->colors[colorIndex++] = c;
+ if (kDebug) {
+ if (c != android::Res_png_9patch::NO_COLOR) {
+ hasColor = true;
}
- top = bottom;
+ }
+ left = right;
}
+ top = bottom;
+ }
- assert(colorIndex == numColors);
+ assert(colorIndex == numColors);
- if (kDebug && hasColor) {
- for (i = 0; i < numColors; i++) {
- if (i == 0) printf("Colors:\n");
- printf(" #%08x", image->colors[i]);
- if (i == numColors - 1) printf("\n");
- }
+ if (kDebug && hasColor) {
+ for (i = 0; i < numColors; i++) {
+ if (i == 0) printf("Colors:\n");
+ printf(" #%08x", image->colors[i]);
+ if (i == numColors - 1) printf("\n");
}
+ }
getout:
- if (errorMsg) {
- std::stringstream err;
- err << "9-patch malformed: " << errorMsg;
- if (errorEdge) {
- err << "." << std::endl;
- if (errorPixel >= 0) {
- err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
- } else {
- err << "Found along " << errorEdge << " edge";
- }
- }
- *outError = err.str();
- return false;
- }
- return true;
+ if (errorMsg) {
+ std::stringstream err;
+ err << "9-patch malformed: " << errorMsg;
+ if (errorEdge) {
+ err << "." << std::endl;
+ if (errorPixel >= 0) {
+ err << "Found at pixel #" << errorPixel << " along " << errorEdge
+ << " edge";
+ } else {
+ err << "Found along " << errorEdge << " edge";
+ }
+ }
+ *outError = err.str();
+ return false;
+ }
+ return true;
}
-
-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)) {
- mDiag->error(DiagMessage() << strerror(errno));
- return false;
- }
-
- // If the PNG signature doesn't match, bail early.
- if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- mDiag->error(DiagMessage() << "not a valid png file");
- return false;
- }
-
- bool result = false;
- png_structp readPtr = nullptr;
- png_infop infoPtr = nullptr;
- png_structp writePtr = nullptr;
- png_infop writeInfoPtr = nullptr;
- PngInfo pngInfo = {};
-
- readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
- if (!readPtr) {
- mDiag->error(DiagMessage() << "failed to allocate read ptr");
- goto bail;
- }
-
- infoPtr = png_create_info_struct(readPtr);
- if (!infoPtr) {
- mDiag->error(DiagMessage() << "failed to allocate info ptr");
- goto bail;
- }
-
- 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);
-
- if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
- goto bail;
- }
-
- if (util::stringEndsWith<char>(source.path, ".9.png")) {
- 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) {
- mDiag->error(DiagMessage() << "failed to allocate write ptr");
- goto bail;
- }
-
- writeInfoPtr = png_create_info_struct(writePtr);
- if (!writeInfoPtr) {
- mDiag->error(DiagMessage() << "failed to allocate write info ptr");
- goto bail;
- }
-
- png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
-
- // Set the write function to write to std::ostream.
- png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
-
- if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) {
- goto bail;
- }
-
- result = true;
+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)) {
+ mDiag->Error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ // If the PNG signature doesn't match, bail early.
+ if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ mDiag->Error(DiagMessage() << "not a valid png file");
+ return false;
+ }
+
+ bool result = false;
+ png_structp readPtr = nullptr;
+ png_infop infoPtr = nullptr;
+ png_structp writePtr = nullptr;
+ png_infop writeInfoPtr = nullptr;
+ PngInfo pngInfo = {};
+
+ readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!readPtr) {
+ mDiag->Error(DiagMessage() << "failed to allocate read ptr");
+ goto bail;
+ }
+
+ infoPtr = png_create_info_struct(readPtr);
+ if (!infoPtr) {
+ mDiag->Error(DiagMessage() << "failed to allocate info ptr");
+ goto bail;
+ }
+
+ 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);
+
+ if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
+ goto bail;
+ }
+
+ if (util::EndsWith(source.path, ".9.png")) {
+ 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) {
+ mDiag->Error(DiagMessage() << "failed to allocate write ptr");
+ goto bail;
+ }
+
+ writeInfoPtr = png_create_info_struct(writePtr);
+ if (!writeInfoPtr) {
+ mDiag->Error(DiagMessage() << "failed to allocate write info ptr");
+ goto bail;
+ }
+
+ png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
+
+ // Set the write function to write to std::ostream.
+ png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream,
+ flushDataToStream);
+
+ if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo,
+ options.grayscale_tolerance)) {
+ goto bail;
+ }
+
+ result = true;
bail:
- if (readPtr) {
- png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
- }
-
- if (writePtr) {
- png_destroy_write_struct(&writePtr, &writeInfoPtr);
- }
- return result;
+ if (readPtr) {
+ png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
+ }
+
+ if (writePtr) {
+ png_destroy_write_struct(&writePtr, &writeInfoPtr);
+ }
+ return result;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index 345ff6c56870..aff1da3f05d2 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -17,31 +17,81 @@
#ifndef AAPT_PNG_H
#define AAPT_PNG_H
-#include "util/BigBuffer.h"
-#include "Diagnostics.h"
-#include "Source.h"
-
#include <iostream>
#include <string>
+#include "android-base/macros.h"
+
+#include "Diagnostics.h"
+#include "Source.h"
+#include "compile/Image.h"
+#include "io/Io.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+
namespace aapt {
struct PngOptions {
- int grayScaleTolerance = 0;
+ int grayscale_tolerance = 0;
};
+/**
+ * Deprecated. Removing once new PNG crunching code is proved to be correct.
+ */
class Png {
-public:
- Png(IDiagnostics* diag) : mDiag(diag) {
- }
+ public:
+ explicit Png(IDiagnostics* diag) : mDiag(diag) {}
+
+ bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+ const PngOptions& options);
+
+ private:
+ IDiagnostics* mDiag;
- bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
- const PngOptions& options);
+ DISALLOW_COPY_AND_ASSIGN(Png);
+};
+
+/**
+ * An InputStream that filters out unimportant PNG chunks.
+ */
+class PngChunkFilter : public io::InputStream {
+ public:
+ explicit PngChunkFilter(const StringPiece& data);
+
+ bool Next(const void** buffer, int* len) override;
+ void BackUp(int count) override;
+ bool Skip(int count) override;
+
+ google::protobuf::int64 ByteCount() const override {
+ return static_cast<google::protobuf::int64>(window_start_);
+ }
+
+ bool HadError() const override { return error_; }
-private:
- IDiagnostics* mDiag;
+ private:
+ bool ConsumeWindow(const void** buffer, int* len);
+
+ StringPiece data_;
+ size_t window_start_ = 0;
+ size_t window_end_ = 0;
+ bool error_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
};
-} // namespace aapt
+/**
+ * Reads a PNG from the InputStream into memory as an RGBA Image.
+ */
+std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in);
+
+/**
+ * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream
+ * as a PNG.
+ */
+bool WritePng(IAaptContext* context, const Image* image,
+ const NinePatch* nine_patch, io::OutputStream* out,
+ const PngOptions& options);
+
+} // namespace aapt
-#endif // AAPT_PNG_H
+#endif // AAPT_PNG_H
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
new file mode 100644
index 000000000000..4cbefb9496ae
--- /dev/null
+++ b/tools/aapt2/compile/PngChunkFilter.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 "compile/Png.h"
+
+#include "io/Io.h"
+#include "util/StringPiece.h"
+
+namespace aapt {
+
+static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
+
+// Useful helper function that encodes individual bytes into a uint32
+// at compile time.
+constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+ return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) |
+ ((uint32_t)d);
+}
+
+// Whitelist of PNG chunk types that we want to keep in the resulting PNG.
+enum PngChunkTypes {
+ kPngChunkIHDR = u32(73, 72, 68, 82),
+ kPngChunkIDAT = u32(73, 68, 65, 84),
+ kPngChunkIEND = u32(73, 69, 78, 68),
+ kPngChunkPLTE = u32(80, 76, 84, 69),
+ kPngChunktRNS = u32(116, 82, 78, 83),
+ kPngChunksRGB = u32(115, 82, 71, 66),
+};
+
+static uint32_t Peek32LE(const char* data) {
+ uint32_t word = ((uint32_t)data[0]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[1]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[2]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[3]) & 0x000000ff;
+ return word;
+}
+
+static bool IsPngChunkWhitelisted(uint32_t type) {
+ switch (type) {
+ case kPngChunkIHDR:
+ case kPngChunkIDAT:
+ case kPngChunkIEND:
+ case kPngChunkPLTE:
+ case kPngChunktRNS:
+ case kPngChunksRGB:
+ return true;
+ default:
+ return false;
+ }
+}
+
+PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
+ if (util::StartsWith(data_, kPngSignature)) {
+ window_start_ = 0;
+ window_end_ = strlen(kPngSignature);
+ } else {
+ error_ = true;
+ }
+}
+
+bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) {
+ if (window_start_ != window_end_) {
+ // We have bytes to give from our window.
+ const int bytes_read = (int)(window_end_ - window_start_);
+ *buffer = data_.data() + window_start_;
+ *len = bytes_read;
+ window_start_ = window_end_;
+ return true;
+ }
+ return false;
+}
+
+bool PngChunkFilter::Next(const void** buffer, int* len) {
+ if (error_) {
+ return false;
+ }
+
+ // In case BackUp was called, we must consume the window.
+ if (ConsumeWindow(buffer, len)) {
+ return true;
+ }
+
+ // Advance the window as far as possible (until we meet a chunk that
+ // we want to strip).
+ while (window_end_ < data_.size()) {
+ // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
+ const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
+
+ // Is there enough room for a chunk header?
+ if (data_.size() - window_start_ < kMinChunkHeaderSize) {
+ error_ = true;
+ return false;
+ }
+
+ // Verify the chunk length.
+ const uint32_t chunk_len = Peek32LE(data_.data() + window_end_);
+ if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) >
+ data_.size()) {
+ // Overflow.
+ error_ = true;
+ return false;
+ }
+
+ // Do we strip this chunk?
+ const uint32_t chunk_type =
+ Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
+ if (IsPngChunkWhitelisted(chunk_type)) {
+ // Advance the window to include this chunk.
+ window_end_ += kMinChunkHeaderSize + chunk_len;
+ } else {
+ // We want to strip this chunk. If we accumulated a window,
+ // we must return the window now.
+ if (window_start_ != window_end_) {
+ break;
+ }
+
+ // The window is empty, so we can advance past this chunk
+ // and keep looking for the next good chunk,
+ window_end_ += kMinChunkHeaderSize + chunk_len;
+ window_start_ = window_end_;
+ }
+ }
+
+ if (ConsumeWindow(buffer, len)) {
+ return true;
+ }
+ return false;
+}
+
+void PngChunkFilter::BackUp(int count) {
+ if (error_) {
+ return;
+ }
+ window_start_ -= count;
+}
+
+bool PngChunkFilter::Skip(int count) {
+ if (error_) {
+ return false;
+ }
+
+ const void* buffer;
+ int len;
+ while (count > 0) {
+ if (!Next(&buffer, &len)) {
+ return false;
+ }
+ if (len > count) {
+ BackUp(len - count);
+ count = 0;
+ } else {
+ count -= len;
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
new file mode 100644
index 000000000000..3b46d8b4c782
--- /dev/null
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -0,0 +1,767 @@
+/*
+ * 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/Png.h"
+
+#include <png.h>
+#include <zlib.h>
+
+#include <algorithm>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "android-base/errors.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
+namespace aapt {
+
+// Size in bytes of the PNG signature.
+constexpr size_t kPngSignatureSize = 8u;
+
+/**
+ * Custom deleter that destroys libpng read and info structs.
+ */
+class PngReadStructDeleter {
+ public:
+ PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
+ : read_ptr_(read_ptr), info_ptr_(info_ptr) {}
+
+ ~PngReadStructDeleter() {
+ png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
+ }
+
+ private:
+ png_structp read_ptr_;
+ png_infop info_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
+};
+
+/**
+ * Custom deleter that destroys libpng write and info structs.
+ */
+class PngWriteStructDeleter {
+ public:
+ PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
+ : write_ptr_(write_ptr), info_ptr_(info_ptr) {}
+
+ ~PngWriteStructDeleter() {
+ png_destroy_write_struct(&write_ptr_, &info_ptr_);
+ }
+
+ private:
+ png_structp write_ptr_;
+ png_infop info_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
+};
+
+// Custom warning logging method that uses IDiagnostics.
+static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) {
+ IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr);
+ diag->Warn(DiagMessage() << warning_msg);
+}
+
+// Custom error logging method that uses IDiagnostics.
+static void LogError(png_structp png_ptr, png_const_charp error_msg) {
+ IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr);
+ diag->Error(DiagMessage() << error_msg);
+}
+
+static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer,
+ png_size_t len) {
+ io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr);
+
+ const void* in_buffer;
+ int in_len;
+ if (!in->Next(&in_buffer, &in_len)) {
+ if (in->HadError()) {
+ std::string err = in->GetError();
+ png_error(png_ptr, err.c_str());
+ }
+ return;
+ }
+
+ const size_t bytes_read = std::min(static_cast<size_t>(in_len), len);
+ memcpy(buffer, in_buffer, bytes_read);
+ if (bytes_read != static_cast<size_t>(in_len)) {
+ in->BackUp(in_len - static_cast<int>(bytes_read));
+ }
+}
+
+static void WriteDataToStream(png_structp png_ptr, png_bytep buffer,
+ png_size_t len) {
+ io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr);
+
+ void* out_buffer;
+ int out_len;
+ while (len > 0) {
+ if (!out->Next(&out_buffer, &out_len)) {
+ if (out->HadError()) {
+ std::string err = out->GetError();
+ png_error(png_ptr, err.c_str());
+ }
+ return;
+ }
+
+ const size_t bytes_written = std::min(static_cast<size_t>(out_len), len);
+ memcpy(out_buffer, buffer, bytes_written);
+
+ // Advance the input buffer.
+ buffer += bytes_written;
+ len -= bytes_written;
+
+ // Advance the output buffer.
+ out_len -= static_cast<int>(bytes_written);
+ }
+
+ // If the entire output buffer wasn't used, backup.
+ if (out_len > 0) {
+ out->BackUp(out_len);
+ }
+}
+
+std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) {
+ // Read the first 8 bytes of the file looking for the PNG signature.
+ // Bail early if it does not match.
+ const png_byte* signature;
+ int buffer_size;
+ if (!in->Next((const void**)&signature, &buffer_size)) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << android::base::SystemErrorCodeToString(errno));
+ return {};
+ }
+
+ if (static_cast<size_t>(buffer_size) < kPngSignatureSize ||
+ png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "file signature does not match PNG signature");
+ return {};
+ }
+
+ // Start at the beginning of the first chunk.
+ in->BackUp(buffer_size - static_cast<int>(kPngSignatureSize));
+
+ // Create and initialize the png_struct with the default error and warning
+ // handlers.
+ // The header version is also passed in to ensure that this was built against
+ // the same
+ // version of libpng.
+ png_structp read_ptr =
+ png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (read_ptr == nullptr) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create libpng read png_struct");
+ return {};
+ }
+
+ // Create and initialize the memory for image header and data.
+ png_infop info_ptr = png_create_info_struct(read_ptr);
+ if (info_ptr == nullptr) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create libpng read png_info");
+ png_destroy_read_struct(&read_ptr, nullptr, nullptr);
+ return {};
+ }
+
+ // Automatically release PNG resources at end of scope.
+ PngReadStructDeleter png_read_deleter(read_ptr, info_ptr);
+
+ // libpng uses longjmp to jump to an error handling routine.
+ // setjmp will only return true if it was jumped to, aka there was
+ // an error.
+ if (setjmp(png_jmpbuf(read_ptr))) {
+ return {};
+ }
+
+ // Handle warnings ourselves via IDiagnostics.
+ png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError,
+ LogWarning);
+
+ // Set up the read functions which read from our custom data sources.
+ png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
+
+ // Skip the signature that we already read.
+ png_set_sig_bytes(read_ptr, kPngSignatureSize);
+
+ // Read the chunk headers.
+ png_read_info(read_ptr, info_ptr);
+
+ // Extract image meta-data from the various chunk headers.
+ uint32_t width, height;
+ int bit_depth, color_type, interlace_method, compression_method,
+ filter_method;
+ png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+ &interlace_method, &compression_method, &filter_method);
+
+ // When the image is read, expand it so that it is in RGBA 8888 format
+ // so that image handling is uniform.
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(read_ptr);
+ }
+
+ if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
+ png_set_expand_gray_1_2_4_to_8(read_ptr);
+ }
+
+ if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(read_ptr);
+ }
+
+ if (bit_depth == 16) {
+ png_set_strip_16(read_ptr);
+ }
+
+ if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
+ }
+
+ if (color_type == PNG_COLOR_TYPE_GRAY ||
+ color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(read_ptr);
+ }
+
+ if (interlace_method != PNG_INTERLACE_NONE) {
+ png_set_interlace_handling(read_ptr);
+ }
+
+ // Once all the options for reading have been set, we need to flush
+ // them to libpng.
+ png_read_update_info(read_ptr, info_ptr);
+
+ // 9-patch uses int32_t to index images, so we cap the image dimensions to
+ // something
+ // that can always be represented by 9-patch.
+ if (width > std::numeric_limits<int32_t>::max() ||
+ height > std::numeric_limits<int32_t>::max()) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "PNG image dimensions are too large: "
+ << width << "x" << height);
+ return {};
+ }
+
+ std::unique_ptr<Image> output_image = util::make_unique<Image>();
+ output_image->width = static_cast<int32_t>(width);
+ output_image->height = static_cast<int32_t>(height);
+
+ const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr);
+ CHECK(row_bytes == 4 * width); // RGBA
+
+ // Allocate one large block to hold the image.
+ output_image->data =
+ std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
+
+ // Create an array of rows that index into the data block.
+ output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
+ for (uint32_t h = 0; h < height; h++) {
+ output_image->rows[h] = output_image->data.get() + (h * row_bytes);
+ }
+
+ // Actually read the image pixels.
+ png_read_image(read_ptr, output_image->rows.get());
+
+ // Finish reading. This will read any other chunks after the image data.
+ png_read_end(read_ptr, info_ptr);
+
+ return output_image;
+}
+
+/**
+ * Experimentally chosen constant to be added to the overhead of using color
+ * type
+ * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette
+ * chunk.
+ * Without this, many small PNGs encoded with palettes are larger after
+ * compression than
+ * the same PNGs encoded as RGBA.
+ */
+constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
+
+// Pick a color type by which to encode the image, based on which color type
+// will take
+// the least amount of disk space.
+//
+// 9-patch images traditionally have not been encoded with palettes.
+// The original rationale was to avoid dithering until after scaling,
+// but I don't think this would be an issue with palettes. Either way,
+// our naive size estimation tends to be wrong for small images like 9-patches
+// and using palettes balloons the size of the resulting 9-patch.
+// In order to not regress in size, restrict 9-patch to not use palettes.
+
+// The options are:
+//
+// - RGB
+// - RGBA
+// - RGB + cheap alpha
+// - Color palette
+// - Color palette + cheap alpha
+// - Color palette + alpha palette
+// - Grayscale
+// - Grayscale + cheap alpha
+// - Grayscale + alpha
+//
+static int PickColorType(int32_t width, int32_t height, bool grayscale,
+ bool convertible_to_grayscale, bool has_nine_patch,
+ size_t color_palette_size, size_t alpha_palette_size) {
+ const size_t palette_chunk_size = 16 + color_palette_size * 3;
+ const size_t alpha_chunk_size = 16 + alpha_palette_size;
+ const size_t color_alpha_data_chunk_size = 16 + 4 * width * height;
+ const size_t color_data_chunk_size = 16 + 3 * width * height;
+ const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height;
+ const size_t palette_data_chunk_size = 16 + width * height;
+
+ if (grayscale) {
+ if (alpha_palette_size == 0) {
+ // This is the smallest the data can be.
+ return PNG_COLOR_TYPE_GRAY;
+ } else if (color_palette_size <= 256 && !has_nine_patch) {
+ // This grayscale has alpha and can fit within a palette.
+ // See if it is worth fitting into a palette.
+ const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
+ palette_data_chunk_size +
+ kPaletteOverheadConstant;
+ if (grayscale_alpha_data_chunk_size > palette_threshold) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
+ }
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+
+ if (color_palette_size <= 256 && !has_nine_patch) {
+ // This image can fit inside a palette. Let's see if it is worth it.
+ size_t total_size_with_palette =
+ palette_data_chunk_size + palette_chunk_size;
+ size_t total_size_without_palette = color_data_chunk_size;
+ if (alpha_palette_size > 0) {
+ total_size_with_palette += alpha_palette_size;
+ total_size_without_palette = color_alpha_data_chunk_size;
+ }
+
+ if (total_size_without_palette >
+ total_size_with_palette + kPaletteOverheadConstant) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
+ }
+
+ if (convertible_to_grayscale) {
+ if (alpha_palette_size == 0) {
+ return PNG_COLOR_TYPE_GRAY;
+ } else {
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+ }
+
+ if (alpha_palette_size == 0) {
+ return PNG_COLOR_TYPE_RGB;
+ }
+ return PNG_COLOR_TYPE_RGBA;
+}
+
+// Assigns indices to the color and alpha palettes, encodes them, and then
+// invokes
+// png_set_PLTE/png_set_tRNS.
+// This must be done before writing image data.
+// Image data must be transformed to use the indices assigned within the
+// palette.
+static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
+ std::unordered_map<uint32_t, int>* color_palette,
+ std::unordered_set<uint32_t>* alpha_palette) {
+ CHECK(color_palette->size() <= 256);
+ CHECK(alpha_palette->size() <= 256);
+
+ // Populate the PNG palette struct and assign indices to the color
+ // palette.
+
+ // Colors in the alpha palette should have smaller indices.
+ // This will ensure that we can truncate the alpha palette if it is
+ // smaller than the color palette.
+ int index = 0;
+ for (uint32_t color : *alpha_palette) {
+ (*color_palette)[color] = index++;
+ }
+
+ // Assign the rest of the entries.
+ for (auto& entry : *color_palette) {
+ if (entry.second == -1) {
+ entry.second = index++;
+ }
+ }
+
+ // Create the PNG color palette struct.
+ auto color_palette_bytes =
+ std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
+
+ std::unique_ptr<png_byte[]> alpha_palette_bytes;
+ if (!alpha_palette->empty()) {
+ alpha_palette_bytes =
+ std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
+ }
+
+ for (const auto& entry : *color_palette) {
+ const uint32_t color = entry.first;
+ const int index = entry.second;
+ CHECK(index >= 0);
+ CHECK(static_cast<size_t>(index) < color_palette->size());
+
+ png_colorp slot = color_palette_bytes.get() + index;
+ slot->red = color >> 24;
+ slot->green = color >> 16;
+ slot->blue = color >> 8;
+
+ const png_byte alpha = color & 0x000000ff;
+ if (alpha != 0xff && alpha_palette_bytes) {
+ CHECK(static_cast<size_t>(index) < alpha_palette->size());
+ alpha_palette_bytes[index] = alpha;
+ }
+ }
+
+ // The bytes get copied here, so it is safe to release color_palette_bytes at
+ // the end of function
+ // scope.
+ png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(),
+ color_palette->size());
+
+ if (alpha_palette_bytes) {
+ png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(),
+ alpha_palette->size(), nullptr);
+ }
+}
+
+// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
+// before
+// writing image data.
+static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
+ const NinePatch* nine_patch) {
+ // The order of the chunks is important.
+ // 9-patch code in older platforms expects the 9-patch chunk to
+ // be last.
+
+ png_unknown_chunk unknown_chunks[3];
+ memset(unknown_chunks, 0, sizeof(unknown_chunks));
+
+ size_t index = 0;
+ size_t chunk_len = 0;
+
+ std::unique_ptr<uint8_t[]> serialized_outline =
+ nine_patch->SerializeRoundedRectOutline(&chunk_len);
+ strcpy((char*)unknown_chunks[index].name, "npOl");
+ unknown_chunks[index].size = chunk_len;
+ unknown_chunks[index].data = (png_bytep)serialized_outline.get();
+ unknown_chunks[index].location = PNG_HAVE_PLTE;
+ index++;
+
+ std::unique_ptr<uint8_t[]> serialized_layout_bounds;
+ if (nine_patch->layout_bounds.nonZero()) {
+ serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len);
+ strcpy((char*)unknown_chunks[index].name, "npLb");
+ unknown_chunks[index].size = chunk_len;
+ unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get();
+ unknown_chunks[index].location = PNG_HAVE_PLTE;
+ index++;
+ }
+
+ std::unique_ptr<uint8_t[]> serialized_nine_patch =
+ nine_patch->SerializeBase(&chunk_len);
+ strcpy((char*)unknown_chunks[index].name, "npTc");
+ unknown_chunks[index].size = chunk_len;
+ unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
+ unknown_chunks[index].location = PNG_HAVE_PLTE;
+ index++;
+
+ // Handle all unknown chunks. We are manually setting the chunks here,
+ // so we will only ever handle our custom chunks.
+ png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
+
+ // Set the actual chunks here. The data gets copied, so our buffers can
+ // safely go out of scope.
+ png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index);
+}
+
+bool WritePng(IAaptContext* context, const Image* image,
+ const NinePatch* nine_patch, io::OutputStream* out,
+ const PngOptions& options) {
+ // Create and initialize the write png_struct with the default error and
+ // warning handlers.
+ // The header version is also passed in to ensure that this was built against
+ // the same
+ // version of libpng.
+ png_structp write_ptr =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (write_ptr == nullptr) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create libpng write png_struct");
+ return false;
+ }
+
+ // Allocate memory to store image header data.
+ png_infop write_info_ptr = png_create_info_struct(write_ptr);
+ if (write_info_ptr == nullptr) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create libpng write png_info");
+ png_destroy_write_struct(&write_ptr, nullptr);
+ return false;
+ }
+
+ // Automatically release PNG resources at end of scope.
+ PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr);
+
+ // libpng uses longjmp to jump to error handling routines.
+ // setjmp will return true only if it was jumped to, aka, there was an error.
+ if (setjmp(png_jmpbuf(write_ptr))) {
+ return false;
+ }
+
+ // Handle warnings with our IDiagnostics.
+ png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError,
+ LogWarning);
+
+ // Set up the write functions which write to our custom data sources.
+ png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
+
+ // We want small files and can take the performance hit to achieve this goal.
+ png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
+
+ // Begin analysis of the image data.
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors (palette).
+ std::unordered_map<uint32_t, int> color_palette;
+ std::unordered_set<uint32_t> alpha_palette;
+ bool needs_to_zero_rgb_channels_of_transparent_pixels = false;
+ bool grayscale = true;
+ int max_gray_deviation = 0;
+
+ for (int32_t y = 0; y < image->height; y++) {
+ const uint8_t* row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int red = *row++;
+ int green = *row++;
+ int blue = *row++;
+ int alpha = *row++;
+
+ if (alpha == 0) {
+ // The color is completely transparent.
+ // For purposes of palettes and grayscale optimization,
+ // treat all channels as 0x00.
+ needs_to_zero_rgb_channels_of_transparent_pixels =
+ needs_to_zero_rgb_channels_of_transparent_pixels ||
+ (red != 0 || green != 0 || blue != 0);
+ red = green = blue = 0;
+ }
+
+ // Insert the color into the color palette.
+ const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
+ color_palette[color] = -1;
+
+ // If the pixel has non-opaque alpha, insert it into the
+ // alpha palette.
+ if (alpha != 0xff) {
+ alpha_palette.insert(color);
+ }
+
+ // Check if the image is indeed grayscale.
+ if (grayscale) {
+ if (red != green || red != blue) {
+ grayscale = false;
+ }
+ }
+
+ // Calculate the gray scale deviation so that it can be compared
+ // with the threshold.
+ max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation);
+ max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation);
+ max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation);
+ }
+ }
+
+ if (context->IsVerbose()) {
+ DiagMessage msg;
+ msg << " paletteSize=" << color_palette.size()
+ << " alphaPaletteSize=" << alpha_palette.size()
+ << " maxGrayDeviation=" << max_gray_deviation
+ << " grayScale=" << (grayscale ? "true" : "false");
+ context->GetDiagnostics()->Note(msg);
+ }
+
+ const bool convertible_to_grayscale =
+ max_gray_deviation <= options.grayscale_tolerance;
+
+ const int new_color_type = PickColorType(
+ image->width, image->height, grayscale, convertible_to_grayscale,
+ nine_patch != nullptr, color_palette.size(), alpha_palette.size());
+
+ if (context->IsVerbose()) {
+ DiagMessage msg;
+ msg << "encoding PNG ";
+ if (nine_patch) {
+ msg << "(with 9-patch) as ";
+ }
+ switch (new_color_type) {
+ case PNG_COLOR_TYPE_GRAY:
+ msg << "GRAY";
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ msg << "GRAY + ALPHA";
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ msg << "RGB";
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ msg << "RGBA";
+ break;
+ case PNG_COLOR_TYPE_PALETTE:
+ msg << "PALETTE";
+ break;
+ default:
+ msg << "unknown type " << new_color_type;
+ break;
+ }
+ context->GetDiagnostics()->Note(msg);
+ }
+
+ png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8,
+ new_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ if (new_color_type & PNG_COLOR_MASK_PALETTE) {
+ // Assigns indices to the palette, and writes the encoded palette to the
+ // libpng writePtr.
+ WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette);
+ png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
+ }
+
+ if (nine_patch) {
+ WriteNinePatch(write_ptr, write_info_ptr, nine_patch);
+ }
+
+ // Flush our updates to the header.
+ png_write_info(write_ptr, write_info_ptr);
+
+ // Write out each row of image data according to its encoding.
+ if (new_color_type == PNG_COLOR_TYPE_PALETTE) {
+ // 1 byte/pixel.
+ auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep in_row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *in_row++;
+ int gg = *in_row++;
+ int bb = *in_row++;
+ int aa = *in_row++;
+ if (aa == 0) {
+ // Zero out color channels when transparent.
+ rr = gg = bb = 0;
+ }
+
+ const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
+ const int idx = color_palette[color];
+ CHECK(idx != -1);
+ out_row[x] = static_cast<png_byte>(idx);
+ }
+ png_write_row(write_ptr, out_row.get());
+ }
+ } else if (new_color_type == PNG_COLOR_TYPE_GRAY ||
+ new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
+ auto out_row =
+ std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep in_row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = in_row[x * 4];
+ int gg = in_row[x * 4 + 1];
+ int bb = in_row[x * 4 + 2];
+ int aa = in_row[x * 4 + 3];
+ if (aa == 0) {
+ // Zero out the gray channel when transparent.
+ rr = gg = bb = 0;
+ }
+
+ if (grayscale) {
+ // The image was already grayscale, red == green == blue.
+ out_row[x * bpp] = in_row[x * 4];
+ } else {
+ // The image is convertible to grayscale, use linear-luminance of
+ // sRGB colorspace:
+ // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
+ out_row[x * bpp] =
+ (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
+
+ if (bpp == 2) {
+ // Write out alpha if we have it.
+ out_row[x * bpp + 1] = aa;
+ }
+ }
+ png_write_row(write_ptr, out_row.get());
+ }
+ } else if (new_color_type == PNG_COLOR_TYPE_RGB ||
+ new_color_type == PNG_COLOR_TYPE_RGBA) {
+ const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
+ if (needs_to_zero_rgb_channels_of_transparent_pixels) {
+ // The source RGBA data can't be used as-is, because we need to zero out
+ // the RGB
+ // values of transparent pixels.
+ auto out_row =
+ std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep in_row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *in_row++;
+ int gg = *in_row++;
+ int bb = *in_row++;
+ int aa = *in_row++;
+ if (aa == 0) {
+ // Zero out the RGB channels when transparent.
+ rr = gg = bb = 0;
+ }
+ out_row[x * bpp] = rr;
+ out_row[x * bpp + 1] = gg;
+ out_row[x * bpp + 2] = bb;
+ if (bpp == 4) {
+ out_row[x * bpp + 3] = aa;
+ }
+ }
+ png_write_row(write_ptr, out_row.get());
+ }
+ } else {
+ // The source image can be used as-is, just tell libpng whether or not to
+ // ignore
+ // the alpha channel.
+ if (new_color_type == PNG_COLOR_TYPE_RGB) {
+ // Delete the extraneous alpha values that we appended to our buffer
+ // when reading the original values.
+ png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
+ }
+ png_write_image(write_ptr, image->rows.get());
+ }
+ } else {
+ LOG(FATAL) << "unreachable";
+ }
+
+ png_write_end(write_ptr, write_info_ptr);
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index d080e16c520b..055a725213b7 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -14,235 +14,249 @@
* limitations under the License.
*/
+#include "compile/PseudolocaleGenerator.h"
+
+#include <algorithm>
+
#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 });
+std::unique_ptr<StyledString> PseudolocalizeStyledString(
+ StyledString* string, Pseudolocalizer::Method method, StringPool* pool) {
+ Pseudolocalizer localizer(method);
+
+ const StringPiece original_text = *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.first_char, span.last_char});
+ }
+
+ // 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* update_start;
+ uint32_t* update_end;
+ };
+
+ 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{original_text.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.first_char, cmp);
+ if (iter != ranges.end() && iter->start == span.first_char) {
+ iter->update_start = &localized.spans[i].first_char;
+ } else {
+ ranges.insert(iter, Range{span.first_char, &localized.spans[i].first_char,
+ nullptr});
}
- // 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 });
- }
+ // Insert or update the Range marker for the end of this span.
+ iter = std::lower_bound(ranges.begin(), ranges.end(), span.last_char, cmp);
+ if (iter != ranges.end() && iter->start == span.last_char) {
+ iter->update_end = &localized.spans[i].last_char;
+ } else {
+ ranges.insert(
+ iter, Range{span.last_char, nullptr, &localized.spans[i].last_char});
}
+ }
- localized.str += localizer.start();
+ 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();
- }
+ // 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 = original_text.size() - start;
+ if (i + 1 < ranges.size()) {
+ len = ranges[i + 1].start - start;
+ }
- if (ranges[i].updateEnd) {
- *ranges[i].updateEnd = localized.str.size();
- }
+ if (ranges[i].update_start) {
+ *ranges[i].update_start = localized.str.size();
+ }
- localized.str += localizer.text(originalText.substr(start, len));
+ if (ranges[i].update_end) {
+ *ranges[i].update_end = localized.str.size();
}
- localized.str += localizer.end();
+ localized.str += localizer.Text(original_text.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;
+ std::unique_ptr<StyledString> localized_string =
+ util::make_unique<StyledString>(pool->MakeRef(localized));
+ localized_string->SetSource(string->GetSource());
+ return localized_string;
}
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(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));
- }
- }
+class Visitor : public RawValueVisitor {
+ public:
+ // Either value or item will be populated upon visiting the value.
+ std::unique_ptr<Value> value;
+ std::unique_ptr<Item> item;
+
+ Visitor(StringPool* pool, Pseudolocalizer::Method method)
+ : pool_(pool), method_(method), localizer_(method) {}
+
+ 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 sub_visitor(pool_, method_);
+ if (plural->values[i]) {
+ plural->values[i]->Accept(&sub_visitor);
+ if (sub_visitor.value) {
+ localized->values[i] = std::move(sub_visitor.item);
+ } else {
+ localized->values[i] =
+ std::unique_ptr<Item>(plural->values[i]->Clone(pool_));
}
- localized->setSource(plural->getSource());
- localized->setWeak(true);
- mValue = std::move(localized);
- }
-
- void visit(String* string) override {
- 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 {
- mItem = pseudolocalizeStyledString(string, mMethod, mPool);
- mItem->setWeak(true);
+ }
}
+ localized->SetSource(plural->GetSource());
+ localized->SetWeak(true);
+ value = std::move(localized);
+ }
+
+ void Visit(String* string) override {
+ std::string result =
+ localizer_.Start() + localizer_.Text(*string->value) + localizer_.End();
+ std::unique_ptr<String> localized =
+ util::make_unique<String>(pool_->MakeRef(result));
+ localized->SetSource(string->GetSource());
+ localized->SetWeak(true);
+ item = std::move(localized);
+ }
+
+ void Visit(StyledString* string) override {
+ item = PseudolocalizeStyledString(string, method_, pool_);
+ item->SetWeak(true);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Visitor);
+
+ StringPool* pool_;
+ Pseudolocalizer::Method method_;
+ Pseudolocalizer localizer_;
};
-ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
+ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base,
Pseudolocalizer::Method m) {
- ConfigDescription modified = base;
- switch (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;
+ 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;
+ modified.language[0] = 'a';
+ modified.language[1] = 'r';
+ modified.country[0] = 'X';
+ modified.country[1] = 'B';
+ break;
default:
- break;
- }
- return modified;
+ 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);
- }
+void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
+ ResourceConfigValue* original_value,
+ StringPool* pool, ResourceEntry* entry) {
+ Visitor visitor(pool, method);
+ original_value->value->Accept(&visitor);
+
+ std::unique_ptr<Value> localized_value;
+ if (visitor.value) {
+ localized_value = std::move(visitor.value);
+ } else if (visitor.item) {
+ localized_value = std::move(visitor.item);
+ }
+
+ if (!localized_value) {
+ return;
+ }
+
+ ConfigDescription config_with_accent =
+ ModifyConfigForPseudoLocale(original_value->config, method);
+
+ ResourceConfigValue* new_config_value =
+ entry->FindOrCreateValue(config_with_accent, original_value->product);
+ if (!new_config_value->value) {
+ // Only use auto-generated pseudo-localization if none is defined.
+ new_config_value->value = std::move(localized_value);
+ }
}
/**
- * A value is pseudolocalizable if it does not define a locale (or is the default locale)
+ * A value is pseudolocalizable if it does not define a locale (or is the
+ * default locale)
* and is translateable.
*/
-static bool isPseudolocalizable(ResourceConfigValue* configValue) {
- const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
- if (diff & ConfigDescription::CONFIG_LOCALE) {
- return false;
- }
- return configValue->value->isTranslateable();
+static bool IsPseudolocalizable(ResourceConfigValue* config_value) {
+ const int diff =
+ config_value->config.diff(ConfigDescription::DefaultConfig());
+ if (diff & ConfigDescription::CONFIG_LOCALE) {
+ return false;
+ }
+ return config_value->value->IsTranslateable();
}
-} // 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->findValuesIf(isPseudolocalizable);
-
- for (ResourceConfigValue* value : values) {
- pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
- &table->stringPool, entry.get());
- pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
- &table->stringPool, entry.get());
- }
- }
+} // 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->FindValuesIf(IsPseudolocalizable);
+
+ for (ResourceConfigValue* value : values) {
+ PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
+ &table->string_pool, entry.get());
+ PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
+ &table->string_pool, entry.get());
}
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h
index 4fbc51607595..ace378603f65 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.h
+++ b/tools/aapt2/compile/PseudolocaleGenerator.h
@@ -23,14 +23,13 @@
namespace aapt {
-std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
- Pseudolocalizer::Method method,
- StringPool* pool);
+std::unique_ptr<StyledString> PseudolocalizeStyledString(
+ StyledString* string, Pseudolocalizer::Method method, StringPool* pool);
struct PseudolocaleGenerator : public IResourceTableConsumer {
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H */
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index 4cb6ea2db565..5a9884d34dd1 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -15,109 +15,119 @@
*/
#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>
+#include "test/Test.h"
+#include "util/Util.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 } };
+ StringPool pool;
+ StyleString original_style;
+ original_style.str = "Hello world!";
+ original_style.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}};
- std::unique_ptr<StyledString> newString = pseudolocalizeStyledString(
- util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
- Pseudolocalizer::Method::kNone, &pool);
+ std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
+ Pseudolocalizer::Method::kNone, &pool);
- EXPECT_EQ(originalStyle.str, *newString->value->str);
- ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
+ EXPECT_EQ(original_style.str, *new_string->value->str);
+ ASSERT_EQ(original_style.spans.size(), new_string->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(std::string("He").size(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::string("Hel").size(), new_string->value->spans[0].last_char);
+ EXPECT_EQ(std::string("b"), *new_string->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(std::string("Hello ").size(),
+ new_string->value->spans[1].first_char);
+ EXPECT_EQ(std::string("Hello w").size(),
+ new_string->value->spans[1].last_char);
+ EXPECT_EQ(std::string("b"), *new_string->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);
+ EXPECT_EQ(std::string("H").size(), new_string->value->spans[2].first_char);
+ EXPECT_EQ(std::string("Hello worl").size(),
+ new_string->value->spans[2].last_char);
+ EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name);
- originalStyle.spans.push_back(Span{ u"em", 0, 11u });
+ original_style.spans.push_back(Span{"em", 0, 11u});
- newString = pseudolocalizeStyledString(
- util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
- Pseudolocalizer::Method::kAccent, &pool);
+ new_string = PseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.MakeRef(original_style)).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(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *new_string->value->str);
+ ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size());
- EXPECT_EQ(3u, newString->value->spans[0].firstChar);
- EXPECT_EQ(4u, newString->value->spans[0].lastChar);
+ EXPECT_EQ(std::string("[Ĥé").size(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::string("[Ĥéļ").size(), new_string->value->spans[0].last_char);
- EXPECT_EQ(7u, newString->value->spans[1].firstChar);
- EXPECT_EQ(8u, newString->value->spans[1].lastChar);
+ EXPECT_EQ(std::string("[Ĥéļļö ").size(),
+ new_string->value->spans[1].first_char);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(),
+ new_string->value->spans[1].last_char);
- EXPECT_EQ(2u, newString->value->spans[2].firstChar);
- EXPECT_EQ(11u, newString->value->spans[2].lastChar);
+ EXPECT_EQ(std::string("[Ĥ").size(), new_string->value->spans[2].first_char);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(),
+ new_string->value->spans[2].last_char);
- EXPECT_EQ(1u, newString->value->spans[3].firstChar);
- EXPECT_EQ(12u, newString->value->spans[3].lastChar);
+ EXPECT_EQ(std::string("[").size(), new_string->value->spans[3].first_char);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(),
+ new_string->value->spans[3].last_char);
}
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")));
-
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/one", "one")
+ .AddString("android:string/two", ResourceId{},
+ test::ParseConfigOrDie("en"), "two")
+ .AddString("android:string/three", "three")
+ .AddString("android:string/three", ResourceId{},
+ test::ParseConfigOrDie("en-rXA"), "three")
+ .AddString("android:string/four", "four")
+ .Build();
+
+ String* val = test::GetValue<String>(table.get(), "android:string/four");
+ ASSERT_NE(nullptr, val);
+ 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(), "android:string/one",
+ test::ParseConfigOrDie("en-rXA")));
+ ASSERT_NE(nullptr,
+ test::GetValueForConfig<String>(table.get(), "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(), "android:string/two",
+ test::ParseConfigOrDie("en-rXA")));
+ ASSERT_EQ(nullptr,
+ test::GetValueForConfig<String>(table.get(), "android:string/two",
+ test::ParseConfigOrDie("ar-rXB")));
+
+ // Check that we didn't override manual pseudolocalization.
+ val = test::GetValueForConfig<String>(table.get(), "android:string/three",
+ test::ParseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, val);
+ EXPECT_EQ(std::string("three"), *val->value);
+
+ ASSERT_NE(nullptr,
+ test::GetValueForConfig<String>(table.get(), "android:string/three",
+ test::ParseConfigOrDie("ar-rXB")));
+
+ // Check that four's translateable marker was honored.
+ ASSERT_EQ(nullptr,
+ test::GetValueForConfig<String>(table.get(), "android:string/four",
+ test::ParseConfigOrDie("en-rXA")));
+ ASSERT_EQ(nullptr,
+ test::GetValueForConfig<String>(table.get(), "android:string/four",
+ test::ParseConfigOrDie("ar-rXB")));
}
-} // namespace aapt
-
+} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
index eae52d778744..f89288f73333 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -15,251 +15,333 @@
*/
#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";
+static const std::string kExpansionString =
+ "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";
+static const std::string kRlm = "\u200f";
+static const std::string kRlo = "\u202e";
+static const std::string kPdf = "\u202c";
// Placeholder marks
-static const std::u16string k_placeholder_open = u"\u00bb";
-static const std::u16string k_placeholder_close = u"\u00ab";
+static const std::string kPlaceholderOpen = "\u00bb";
+static const std::string kPlaceholderClose = "\u00ab";
-static const char16_t k_arg_start = u'{';
-static const char16_t k_arg_end = u'}';
+static const char kArgStart = '{';
+static const char kArgEnd = '}';
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(); }
+ public:
+ std::string Text(const StringPiece& text) override { return text.ToString(); }
+ std::string Placeholder(const StringPiece& text) override {
+ return text.ToString();
+ }
};
class PseudoMethodBidi : public PseudoMethodImpl {
-public:
- std::u16string text(const StringPiece16& text) override;
- std::u16string placeholder(const StringPiece16& text) override;
+ public:
+ std::string Text(const StringPiece& text) override;
+ std::string Placeholder(const StringPiece& 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;
+ public:
+ PseudoMethodAccent() : depth_(0), word_count_(0), length_(0) {}
+ std::string Start() override;
+ std::string End() override;
+ std::string Text(const StringPiece& text) override;
+ std::string Placeholder(const StringPiece& text) override;
+
+ private:
+ size_t depth_;
+ size_t word_count_;
+ size_t length_;
};
-Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) {
- setMethod(method);
+Pseudolocalizer::Pseudolocalizer(Method method) : last_depth_(0) {
+ SetMethod(method);
}
-void Pseudolocalizer::setMethod(Method method) {
- switch (method) {
+void Pseudolocalizer::SetMethod(Method method) {
+ switch (method) {
case Method::kNone:
- mImpl = util::make_unique<PseudoMethodNone>();
- break;
+ impl_ = util::make_unique<PseudoMethodNone>();
+ break;
case Method::kAccent:
- mImpl = util::make_unique<PseudoMethodAccent>();
- break;
+ impl_ = util::make_unique<PseudoMethodAccent>();
+ break;
case Method::kBidi:
- mImpl = util::make_unique<PseudoMethodBidi>();
- break;
- }
+ impl_ = 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;
- }
+std::string Pseudolocalizer::Text(const StringPiece& text) {
+ std::string out;
+ size_t depth = last_depth_;
+ size_t lastpos, pos;
+ const size_t length = text.size();
+ const char* 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 (c == kArgStart) {
+ depth++;
+ } else if (c == kArgEnd && 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;
+ if (last_depth_ != depth || pos == length - 1) {
+ bool pseudo = ((last_depth_ % 2) == 0);
+ size_t nextpos = pos;
+ if (!pseudo || depth == last_depth_) {
+ nextpos++;
+ }
+ size_t size = nextpos - lastpos;
+ if (size) {
+ std::string chunk = text.substr(lastpos, size).ToString();
+ if (pseudo) {
+ chunk = impl_->Text(chunk);
+ } else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) {
+ chunk = impl_->Placeholder(chunk);
}
+ out.append(chunk);
+ }
+ if (pseudo && depth < last_depth_) { // End of message
+ out.append(impl_->End());
+ } else if (!pseudo && depth > last_depth_) { // Start of message
+ out.append(impl_->Start());
+ }
+ lastpos = nextpos;
+ last_depth_ = depth;
}
- return out;
+ }
+ 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 const char* PseudolocalizeChar(const char c) {
+ switch (c) {
+ case 'a':
+ return "\u00e5";
+ case 'b':
+ return "\u0253";
+ case 'c':
+ return "\u00e7";
+ case 'd':
+ return "\u00f0";
+ case 'e':
+ return "\u00e9";
+ case 'f':
+ return "\u0192";
+ case 'g':
+ return "\u011d";
+ case 'h':
+ return "\u0125";
+ case 'i':
+ return "\u00ee";
+ case 'j':
+ return "\u0135";
+ case 'k':
+ return "\u0137";
+ case 'l':
+ return "\u013c";
+ case 'm':
+ return "\u1e3f";
+ case 'n':
+ return "\u00f1";
+ case 'o':
+ return "\u00f6";
+ case 'p':
+ return "\u00fe";
+ case 'q':
+ return "\u0051";
+ case 'r':
+ return "\u0155";
+ case 's':
+ return "\u0161";
+ case 't':
+ return "\u0163";
+ case 'u':
+ return "\u00fb";
+ case 'v':
+ return "\u0056";
+ case 'w':
+ return "\u0175";
+ case 'x':
+ return "\u0445";
+ case 'y':
+ return "\u00fd";
+ case 'z':
+ return "\u017e";
+ case 'A':
+ return "\u00c5";
+ case 'B':
+ return "\u03b2";
+ case 'C':
+ return "\u00c7";
+ case 'D':
+ return "\u00d0";
+ case 'E':
+ return "\u00c9";
+ case 'G':
+ return "\u011c";
+ case 'H':
+ return "\u0124";
+ case 'I':
+ return "\u00ce";
+ case 'J':
+ return "\u0134";
+ case 'K':
+ return "\u0136";
+ case 'L':
+ return "\u013b";
+ case 'M':
+ return "\u1e3e";
+ case 'N':
+ return "\u00d1";
+ case 'O':
+ return "\u00d6";
+ case 'P':
+ return "\u00de";
+ case 'Q':
+ return "\u0071";
+ case 'R':
+ return "\u0154";
+ case 'S':
+ return "\u0160";
+ case 'T':
+ return "\u0162";
+ case 'U':
+ return "\u00db";
+ case 'V':
+ return "\u03bd";
+ case 'W':
+ return "\u0174";
+ case 'X':
+ return "\u00d7";
+ case 'Y':
+ return "\u00dd";
+ case 'Z':
+ return "\u017d";
+ case '!':
+ return "\u00a1";
+ case '?':
+ return "\u00bf";
+ case '$':
+ return "\u20ac";
+ default:
+ return nullptr;
+ }
}
-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 bool IsPossibleNormalPlaceholderEnd(const char 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);
+static std::string PseudoGenerateExpansion(const unsigned int length) {
+ std::string result = kExpansionString;
+ const char* s = result.data();
+ if (result.size() < length) {
+ result += " ";
+ 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;
+ }
}
- return result;
+ 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::string PseudoMethodAccent::Start() {
+ std::string result;
+ if (depth_ == 0) {
+ result = "[";
+ }
+ word_count_ = length_ = 0;
+ depth_++;
+ 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;
+std::string PseudoMethodAccent::End() {
+ std::string result;
+ if (length_) {
+ result += " ";
+ result += PseudoGenerateExpansion(word_count_ > 3 ? length_ : length_ / 2);
+ }
+ word_count_ = length_ = 0;
+ depth_--;
+ if (depth_ == 0) {
+ result += "]";
+ }
+ return result;
}
/**
@@ -267,128 +349,125 @@ std::u16string PseudoMethodAccent::end() {
*
* 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];
+std::string PseudoMethodAccent::Text(const StringPiece& source) {
+ const char* s = source.data();
+ std::string result;
+ const size_t I = source.size();
+ bool lastspace = true;
+ for (size_t i = 0; i < I; i++) {
+ char c = s[i];
+ if (c == '%') {
+ // Placeholder syntax, no need to pseudolocalize
+ std::string chunk;
+ bool end = false;
+ chunk.append(&c, 1);
+ while (!end && i + 1 < I) {
+ ++i;
+ c = s[i];
+ chunk.append(&c, 1);
+ if (IsPossibleNormalPlaceholderEnd(c)) {
+ end = true;
+ } else if (i + 1 < I && 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::string escape_text;
+ escape_text.append(&c, 1);
+ bool end = false;
+ size_t html_code_pos = i;
+ while (!end && html_code_pos < I) {
+ ++html_code_pos;
+ c = s[html_code_pos];
+ escape_text.append(&c, 1);
+ // Valid html code
+ if (c == ';') {
+ end = true;
+ i = html_code_pos;
}
- } 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);
+ // Wrong html code
+ else if (!((c == '#' || (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')))) {
+ end = true;
}
- // Count only pseudolocalizable chars and delimiters
- mLength++;
+ }
+ result += escape_text;
+ if (escape_text != "&lt;") {
+ tag_closed = true;
+ }
+ continue;
}
- }
- 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 (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 char* p = PseudolocalizeChar(c);
+ if (p != nullptr) {
+ result += p;
+ } else {
+ bool space = isspace(c);
if (lastspace && !space) {
- // Word start
- result += k_rlm + k_rlo;
- } else if (!lastspace && space) {
- // Word end
- result += k_pdf + k_rlm;
+ word_count_++;
}
lastspace = space;
result.append(&c, 1);
+ }
+ // Count only pseudolocalizable chars and delimiters
+ length_++;
}
- if (!lastspace) {
- // End of last word
- result += k_pdf + k_rlm;
+ }
+ return result;
+}
+
+std::string PseudoMethodAccent::Placeholder(const StringPiece& source) {
+ // Surround a placeholder with brackets
+ return kPlaceholderOpen + source.ToString() + kPlaceholderClose;
+}
+
+std::string PseudoMethodBidi::Text(const StringPiece& source) {
+ const char* s = source.data();
+ std::string result;
+ bool lastspace = true;
+ bool space = true;
+ for (size_t i = 0; i < source.size(); i++) {
+ char c = s[i];
+ space = isspace(c);
+ if (lastspace && !space) {
+ // Word start
+ result += kRlm + kRlo;
+ } else if (!lastspace && space) {
+ // Word end
+ result += kPdf + kRlm;
}
- return result;
+ lastspace = space;
+ result.append(&c, 1);
+ }
+ if (!lastspace) {
+ // End of last word
+ result += kPdf + kRlm;
+ }
+ 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;
+std::string PseudoMethodBidi::Placeholder(const StringPiece& source) {
+ // Surround a placeholder with directionality change sequence
+ return kRlm + kRlo + source.ToString() + kPdf + kRlm;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
index 8818c1725617..a6d2ad037d50 100644
--- a/tools/aapt2/compile/Pseudolocalizer.h
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -17,42 +17,44 @@
#ifndef AAPT_COMPILE_PSEUDOLOCALIZE_H
#define AAPT_COMPILE_PSEUDOLOCALIZE_H
+#include <memory>
+
+#include "android-base/macros.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;
+ public:
+ virtual ~PseudoMethodImpl() {}
+ virtual std::string Start() { return {}; }
+ virtual std::string End() { return {}; }
+ virtual std::string Text(const StringPiece& text) = 0;
+ virtual std::string Placeholder(const StringPiece& 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;
+ public:
+ enum class Method {
+ kNone,
+ kAccent,
+ kBidi,
+ };
+
+ explicit Pseudolocalizer(Method method);
+ void SetMethod(Method method);
+ std::string Start() { return impl_->Start(); }
+ std::string End() { return impl_->End(); }
+ std::string Text(const StringPiece& text);
+
+ private:
+ std::unique_ptr<PseudoMethodImpl> impl_;
+ size_t last_depth_;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_PSEUDOLOCALIZE_H */
diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp
index b0bc2c10fbe0..92eb3b58515f 100644
--- a/tools/aapt2/compile/Pseudolocalizer_test.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp
@@ -15,213 +15,216 @@
*/
#include "compile/Pseudolocalizer.h"
-#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-#include <gtest/gtest.h>
+#include "test/Test.h"
+#include "util/Util.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,
+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();
+ Pseudolocalizer pseudo(method);
+ std::string result = pseudo.Start() + pseudo.Text(input) + pseudo.End();
+ if (result != expected) {
+ 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();
+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 = pseudo.Start() + pseudo.Text(in1) + pseudo.Text(in2) +
+ pseudo.Text(in3) + pseudo.End();
+ if (result != expected) {
+ 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(SimpleHelper("", "", Pseudolocalizer::Method::kNone));
+ EXPECT_TRUE(SimpleHelper("Hello, world", "Hello, world",
+ Pseudolocalizer::Method::kNone));
- EXPECT_TRUE(compoundHelper("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));
+ 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(
+ SimpleHelper("^1 %", "[^1 % one]", 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));
+ 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}«]",
+ // 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(simpleHelper("{USER} is offline",
- "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}",
+ EXPECT_TRUE(CompoundHelper("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));
+ // 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]",
+ // 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));
-
- // 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} " \
+ 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));
-
- 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));
+ 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));
+ 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));
+ Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent);
+ std::string result = pseudo.Text("Hello, ");
+ pseudo.SetMethod(Pseudolocalizer::Method::kNone);
+ result += pseudo.Text("world!");
+ ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
index f40689eaeb47..d61a15af0d85 100644
--- a/tools/aapt2/compile/XmlIdCollector.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -14,57 +14,61 @@
* limitations under the License.
*/
-#include "ResourceUtils.h"
-#include "ResourceValues.h"
#include "compile/XmlIdCollector.h"
-#include "xml/XmlDom.h"
#include <algorithm>
#include <vector>
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "xml/XmlDom.h"
+
namespace aapt {
namespace {
-static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
- return a.name < b;
+static bool cmp_name(const SourcedResourceName& a, const ResourceNameRef& b) {
+ return a.name < b;
}
struct IdCollector : public xml::Visitor {
- using xml::Visitor::visit;
+ public:
+ using xml::Visitor::Visit;
- std::vector<SourcedResourceName>* mOutSymbols;
+ explicit IdCollector(std::vector<SourcedResourceName>* out_symbols)
+ : out_symbols_(out_symbols) {}
- 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::ParseReference(attr.value, &name, &create, nullptr)) {
+ if (create && name.type == ResourceType::kId) {
+ auto iter = std::lower_bound(out_symbols_->begin(),
+ out_symbols_->end(), name, cmp_name);
+ if (iter == out_symbols_->end() || iter->name != name) {
+ out_symbols_->insert(iter,
+ SourcedResourceName{name.ToResourceName(),
+ element->line_number});
+ }
+ }
+ }
}
- 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);
+ }
- xml::Visitor::visit(element);
- }
+ private:
+ std::vector<SourcedResourceName>* out_symbols_;
};
-} // namespace
+} // namespace
-bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
- xmlRes->file.exportedSymbols.clear();
- IdCollector collector(&xmlRes->file.exportedSymbols);
- xmlRes->root->accept(&collector);
- return true;
+bool XmlIdCollector::Consume(IAaptContext* context, xml::XmlResource* xmlRes) {
+ xmlRes->file.exported_symbols.clear();
+ IdCollector collector(&xmlRes->file.exported_symbols);
+ xmlRes->root->Accept(&collector);
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h
index 1b149449de2c..8febf0f250dd 100644
--- a/tools/aapt2/compile/XmlIdCollector.h
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -23,9 +23,9 @@
namespace aapt {
struct XmlIdCollector : public IXmlResourceConsumer {
- bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
+ bool Consume(IAaptContext* context, xml::XmlResource* xml_res) override;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_XMLIDCOLLECTOR_H */
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
index a37ea86c317f..98da56d03ae3 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -15,18 +15,17 @@
*/
#include "compile/XmlIdCollector.h"
-#include "test/Builders.h"
-#include "test/Context.h"
#include <algorithm>
-#include <gtest/gtest.h>
+
+#include "test/Test.h"
namespace aapt {
TEST(XmlIdCollectorTest, CollectsIds) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ 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">
@@ -34,28 +33,35 @@ TEST(XmlIdCollectorTest, CollectsIds) {
class="@+id/bar"/>
</View>)EOF");
- XmlIdCollector collector;
- ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+ 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.exported_symbols.begin(),
+ doc->file.exported_symbols.end(),
+ SourcedResourceName{test::ParseNameOrDie("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.exported_symbols.begin(),
+ doc->file.exported_symbols.end(),
+ SourcedResourceName{test::ParseNameOrDie("id/bar"), 3u}));
- EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
- SourcedResourceName{ test::parseNameOrDie(u"@id/car"), 6u }));
+ EXPECT_EQ(
+ 1, std::count(doc->file.exported_symbols.begin(),
+ doc->file.exported_symbols.end(),
+ SourcedResourceName{test::ParseNameOrDie("id/car"), 6u}));
}
TEST(XmlIdCollectorTest, DontCollectNonIds) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDom("<View foo=\"@+string/foo\"/>");
- XmlIdCollector collector;
- ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+ XmlIdCollector collector;
+ ASSERT_TRUE(collector.Consume(context.get(), doc.get()));
- EXPECT_TRUE(doc->file.exportedSymbols.empty());
+ EXPECT_TRUE(doc->file.exported_symbols.empty());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp
index 20b7b59642ca..593e7ab119f1 100644
--- a/tools/aapt2/diff/Diff.cpp
+++ b/tools/aapt2/diff/Diff.cpp
@@ -14,398 +14,419 @@
* limitations under the License.
*/
+#include "android-base/macros.h"
+
#include "Flags.h"
#include "ResourceTable.h"
+#include "ValueVisitor.h"
#include "io/ZipArchive.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "unflatten/BinaryResourceParser.h"
-#include <android-base/macros.h>
-
namespace aapt {
class DiffContext : public IAaptContext {
-public:
- const std::u16string& getCompilationPackage() override {
- return mEmpty;
- }
+ public:
+ const std::string& GetCompilationPackage() override { return empty_; }
- uint8_t getPackageId() override {
- return 0x0;
- }
+ uint8_t GetPackageId() override { return 0x0; }
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
- NameMangler* getNameMangler() override {
- return &mNameMangler;
- }
+ NameMangler* GetNameMangler() override { return &name_mangler_; }
- SymbolTable* getExternalSymbols() override {
- return &mSymbolTable;
- }
+ SymbolTable* GetExternalSymbols() override { return &symbol_table_; }
- bool verbose() override {
- return false;
- }
+ bool IsVerbose() override { return false; }
+
+ int GetMinSdkVersion() override { return 0; }
-private:
- std::u16string mEmpty;
- StdErrDiagnostics mDiagnostics;
- NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
- SymbolTable mSymbolTable;
+ private:
+ std::string empty_;
+ StdErrDiagnostics diagnostics_;
+ NameMangler name_mangler_ = NameMangler(NameManglerPolicy{});
+ SymbolTable symbol_table_;
};
class LoadedApk {
-public:
- LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
- std::unique_ptr<ResourceTable> table) :
- mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {
- }
+ public:
+ LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+ std::unique_ptr<ResourceTable> table)
+ : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
- io::IFileCollection* getFileCollection() {
- return mApk.get();
- }
+ io::IFileCollection* GetFileCollection() { return apk_.get(); }
- ResourceTable* getResourceTable() {
- return mTable.get();
- }
+ ResourceTable* GetResourceTable() { return table_.get(); }
- const Source& getSource() {
- return mSource;
- }
+ const Source& GetSource() { return source_; }
-private:
- Source mSource;
- std::unique_ptr<io::IFileCollection> mApk;
- std::unique_ptr<ResourceTable> mTable;
+ private:
+ Source source_;
+ std::unique_ptr<io::IFileCollection> apk_;
+ std::unique_ptr<ResourceTable> table_;
- DISALLOW_COPY_AND_ASSIGN(LoadedApk);
+ DISALLOW_COPY_AND_ASSIGN(LoadedApk);
};
-static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) {
- Source source(path);
- std::string error;
- std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error);
- if (!apk) {
- context->getDiagnostics()->error(DiagMessage(source) << error);
- return {};
- }
-
- io::IFile* file = apk->findFile("resources.arsc");
- if (!file) {
- context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found");
- return {};
- }
-
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc");
- return {};
- }
-
- std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
- if (!parser.parse()) {
- return {};
- }
-
- return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
+static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
+ const StringPiece& path) {
+ Source source(path);
+ std::string error;
+ std::unique_ptr<io::ZipFileCollection> apk =
+ io::ZipFileCollection::Create(path, &error);
+ if (!apk) {
+ context->GetDiagnostics()->Error(DiagMessage(source) << error);
+ return {};
+ }
+
+ io::IFile* file = apk->FindFile("resources.arsc");
+ if (!file) {
+ context->GetDiagnostics()->Error(DiagMessage(source)
+ << "no resources.arsc found");
+ return {};
+ }
+
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ if (!data) {
+ context->GetDiagnostics()->Error(DiagMessage(source)
+ << "could not open resources.arsc");
+ return {};
+ }
+
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+ BinaryResourceParser parser(context, table.get(), source, data->data(),
+ data->size());
+ if (!parser.Parse()) {
+ return {};
+ }
+
+ return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
}
-static void emitDiffLine(const Source& source, const StringPiece& message) {
- std::cerr << source << ": " << message << "\n";
+static void EmitDiffLine(const Source& source, const StringPiece& message) {
+ std::cerr << source << ": " << message << "\n";
}
-static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
- return symbolA.state != symbolB.state;
+static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a,
+ const Symbol& symbol_b) {
+ return symbol_a.state != symbol_b.state;
}
template <typename Id>
-static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA,
- const Symbol& symbolB, const Maybe<Id>& idB) {
- if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) {
- return idA != idB;
- }
- return false;
+static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a,
+ const Symbol& symbol_b, const Maybe<Id>& id_b) {
+ if (symbol_a.state == SymbolState::kPublic ||
+ symbol_b.state == SymbolState::kPublic) {
+ return id_a != id_b;
+ }
+ return false;
}
-static bool emitResourceConfigValueDiff(IAaptContext* context,
- LoadedApk* apkA,
- ResourceTablePackage* pkgA,
- ResourceTableType* typeA,
- ResourceEntry* entryA,
- ResourceConfigValue* configValueA,
- LoadedApk* apkB,
- ResourceTablePackage* pkgB,
- ResourceTableType* typeB,
- ResourceEntry* entryB,
- ResourceConfigValue* configValueB) {
- Value* valueA = configValueA->value.get();
- Value* valueB = configValueB->value.get();
- if (!valueA->equals(valueB)) {
- std::stringstream strStream;
- strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name
- << " config=" << configValueA->config << " does not match:\n";
- valueA->print(&strStream);
- strStream << "\n vs \n";
- valueB->print(&strStream);
- emitDiffLine(apkB->getSource(), strStream.str());
- return true;
- }
- return false;
+static bool EmitResourceConfigValueDiff(
+ IAaptContext* context, LoadedApk* apk_a, ResourceTablePackage* pkg_a,
+ ResourceTableType* type_a, ResourceEntry* entry_a,
+ ResourceConfigValue* config_value_a, LoadedApk* apk_b,
+ ResourceTablePackage* pkg_b, ResourceTableType* type_b,
+ ResourceEntry* entry_b, ResourceConfigValue* config_value_b) {
+ Value* value_a = config_value_a->value.get();
+ Value* value_b = config_value_b->value.get();
+ if (!value_a->Equals(value_b)) {
+ std::stringstream str_stream;
+ str_stream << "value " << pkg_a->name << ":" << type_a->type << "/"
+ << entry_a->name << " config=" << config_value_a->config
+ << " does not match:\n";
+ value_a->Print(&str_stream);
+ str_stream << "\n vs \n";
+ value_b->Print(&str_stream);
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ return true;
+ }
+ return false;
}
-static bool emitResourceEntryDiff(IAaptContext* context,
- LoadedApk* apkA,
- ResourceTablePackage* pkgA,
- ResourceTableType* typeA,
- ResourceEntry* entryA,
- LoadedApk* apkB,
- ResourceTablePackage* pkgB,
- ResourceTableType* typeB,
- ResourceEntry* entryB) {
- bool diff = false;
- for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
- ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
- if (!configValueB) {
- std::stringstream strStream;
- strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name
- << " config=" << configValueA->config;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- } else {
- diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
- configValueA.get(), apkB, pkgB, typeB, entryB,
- configValueB);
- }
+static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
+ ResourceTablePackage* pkg_a,
+ ResourceTableType* type_a,
+ ResourceEntry* entry_a, LoadedApk* apk_b,
+ ResourceTablePackage* pkg_b,
+ ResourceTableType* type_b,
+ ResourceEntry* entry_b) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
+ ResourceConfigValue* config_value_b =
+ entry_b->FindValue(config_value_a->config);
+ if (!config_value_b) {
+ std::stringstream str_stream;
+ str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/"
+ << entry_a->name << " config=" << config_value_a->config;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else {
+ diff |= EmitResourceConfigValueDiff(
+ context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(), apk_b,
+ pkg_b, type_b, entry_b, config_value_b);
}
-
- // Check for any newly added config values.
- for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
- ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
- if (!configValueA) {
- std::stringstream strStream;
- strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name
- << " config=" << configValueB->config;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
+ }
+
+ // Check for any newly added config values.
+ for (std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
+ ResourceConfigValue* config_value_a =
+ entry_a->FindValue(config_value_b->config);
+ if (!config_value_a) {
+ std::stringstream str_stream;
+ str_stream << "new config " << pkg_b->name << ":" << type_b->type << "/"
+ << entry_b->name << " config=" << config_value_b->config;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
}
- return false;
+ }
+ return false;
}
-static bool emitResourceTypeDiff(IAaptContext* context,
- LoadedApk* apkA,
- ResourceTablePackage* pkgA,
- ResourceTableType* typeA,
- LoadedApk* apkB,
- ResourceTablePackage* pkgB,
- ResourceTableType* typeB) {
- bool diff = false;
- for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
- ResourceEntry* entryB = typeB->findEntry(entryA->name);
- if (!entryB) {
- std::stringstream strStream;
- strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
+static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
+ ResourceTablePackage* pkg_a,
+ ResourceTableType* type_a, LoadedApk* apk_b,
+ ResourceTablePackage* pkg_b,
+ ResourceTableType* type_b) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceEntry>& entry_a : type_a->entries) {
+ ResourceEntry* entry_b = type_b->FindEntry(entry_a->name);
+ if (!entry_b) {
+ std::stringstream str_stream;
+ str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/"
+ << entry_a->name;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else {
+ if (IsSymbolVisibilityDifferent(entry_a->symbol_status,
+ entry_b->symbol_status)) {
+ std::stringstream str_stream;
+ str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
+ << " has different visibility (";
+ if (entry_b->symbol_status.state == SymbolState::kPublic) {
+ str_stream << "PUBLIC";
} else {
- if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) {
- std::stringstream strStream;
- strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
- << " has different visibility (";
- if (entryB->symbolStatus.state == SymbolState::kPublic) {
- strStream << "PUBLIC";
- } else {
- strStream << "PRIVATE";
- }
- strStream << " vs ";
- if (entryA->symbolStatus.state == SymbolState::kPublic) {
- strStream << "PUBLIC";
- } else {
- strStream << "PRIVATE";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- } else if (isIdDiff(entryA->symbolStatus, entryA->id,
- entryB->symbolStatus, entryB->id)) {
- std::stringstream strStream;
- strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
- << " has different public ID (";
- if (entryB->id) {
- strStream << "0x" << std::hex << entryB->id.value();
- } else {
- strStream << "none";
- }
- strStream << " vs ";
- if (entryA->id) {
- strStream << "0x " << std::hex << entryA->id.value();
- } else {
- strStream << "none";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
- diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
- apkB, pkgB, typeB, entryB);
+ str_stream << "PRIVATE";
}
- }
-
- // Check for any newly added entries.
- for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
- ResourceEntry* entryA = typeA->findEntry(entryB->name);
- if (!entryA) {
- std::stringstream strStream;
- strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
+ str_stream << " vs ";
+ if (entry_a->symbol_status.state == SymbolState::kPublic) {
+ str_stream << "PUBLIC";
+ } else {
+ str_stream << "PRIVATE";
+ }
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else if (IsIdDiff(entry_a->symbol_status, entry_a->id,
+ entry_b->symbol_status, entry_b->id)) {
+ std::stringstream str_stream;
+ str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
+ << " has different public ID (";
+ if (entry_b->id) {
+ str_stream << "0x" << std::hex << entry_b->id.value();
+ } else {
+ str_stream << "none";
+ }
+ str_stream << " vs ";
+ if (entry_a->id) {
+ str_stream << "0x " << std::hex << entry_a->id.value();
+ } else {
+ str_stream << "none";
}
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ diff |=
+ EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a.get(),
+ apk_b, pkg_b, type_b, entry_b);
}
- return diff;
+ }
+
+ // Check for any newly added entries.
+ for (std::unique_ptr<ResourceEntry>& entry_b : type_b->entries) {
+ ResourceEntry* entry_a = type_a->FindEntry(entry_b->name);
+ if (!entry_a) {
+ std::stringstream str_stream;
+ str_stream << "new entry " << pkg_b->name << ":" << type_b->type << "/"
+ << entry_b->name;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ }
+ return diff;
}
-static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
- ResourceTablePackage* pkgA,
- LoadedApk* apkB, ResourceTablePackage* pkgB) {
- bool diff = false;
- for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
- ResourceTableType* typeB = pkgB->findType(typeA->type);
- if (!typeB) {
- std::stringstream strStream;
- strStream << "missing " << pkgA->name << ":" << typeA->type;
- emitDiffLine(apkA->getSource(), strStream.str());
- diff = true;
+static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
+ ResourceTablePackage* pkg_a,
+ LoadedApk* apk_b,
+ ResourceTablePackage* pkg_b) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceTableType>& type_a : pkg_a->types) {
+ ResourceTableType* type_b = pkg_b->FindType(type_a->type);
+ if (!type_b) {
+ std::stringstream str_stream;
+ str_stream << "missing " << pkg_a->name << ":" << type_a->type;
+ EmitDiffLine(apk_a->GetSource(), str_stream.str());
+ diff = true;
+ } else {
+ if (IsSymbolVisibilityDifferent(type_a->symbol_status,
+ type_b->symbol_status)) {
+ std::stringstream str_stream;
+ str_stream << pkg_a->name << ":" << type_a->type
+ << " has different visibility (";
+ if (type_b->symbol_status.state == SymbolState::kPublic) {
+ str_stream << "PUBLIC";
} else {
- if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) {
- std::stringstream strStream;
- strStream << pkgA->name << ":" << typeA->type << " has different visibility (";
- if (typeB->symbolStatus.state == SymbolState::kPublic) {
- strStream << "PUBLIC";
- } else {
- strStream << "PRIVATE";
- }
- strStream << " vs ";
- if (typeA->symbolStatus.state == SymbolState::kPublic) {
- strStream << "PUBLIC";
- } else {
- strStream << "PRIVATE";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) {
- std::stringstream strStream;
- strStream << pkgA->name << ":" << typeA->type << " has different public ID (";
- if (typeB->id) {
- strStream << "0x" << std::hex << typeB->id.value();
- } else {
- strStream << "none";
- }
- strStream << " vs ";
- if (typeA->id) {
- strStream << "0x " << std::hex << typeA->id.value();
- } else {
- strStream << "none";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
- diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB);
+ str_stream << "PRIVATE";
}
- }
-
- // Check for any newly added types.
- for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
- ResourceTableType* typeA = pkgA->findType(typeB->type);
- if (!typeA) {
- std::stringstream strStream;
- strStream << "new type " << pkgB->name << ":" << typeB->type;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
+ str_stream << " vs ";
+ if (type_a->symbol_status.state == SymbolState::kPublic) {
+ str_stream << "PUBLIC";
+ } else {
+ str_stream << "PRIVATE";
+ }
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else if (IsIdDiff(type_a->symbol_status, type_a->id,
+ type_b->symbol_status, type_b->id)) {
+ std::stringstream str_stream;
+ str_stream << pkg_a->name << ":" << type_a->type
+ << " has different public ID (";
+ if (type_b->id) {
+ str_stream << "0x" << std::hex << type_b->id.value();
+ } else {
+ str_stream << "none";
}
+ str_stream << " vs ";
+ if (type_a->id) {
+ str_stream << "0x " << std::hex << type_a->id.value();
+ } else {
+ str_stream << "none";
+ }
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b,
+ pkg_b, type_b);
}
- return diff;
+ }
+
+ // Check for any newly added types.
+ for (std::unique_ptr<ResourceTableType>& type_b : pkg_b->types) {
+ ResourceTableType* type_a = pkg_a->FindType(type_b->type);
+ if (!type_a) {
+ std::stringstream str_stream;
+ str_stream << "new type " << pkg_b->name << ":" << type_b->type;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ }
+ return diff;
}
-static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) {
- ResourceTable* tableA = apkA->getResourceTable();
- ResourceTable* tableB = apkB->getResourceTable();
-
- bool diff = false;
- for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
- ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
- if (!pkgB) {
- std::stringstream strStream;
- strStream << "missing package " << pkgA->name;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
+static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a,
+ LoadedApk* apk_b) {
+ ResourceTable* table_a = apk_a->GetResourceTable();
+ ResourceTable* table_b = apk_b->GetResourceTable();
+
+ bool diff = false;
+ for (std::unique_ptr<ResourceTablePackage>& pkg_a : table_a->packages) {
+ ResourceTablePackage* pkg_b = table_b->FindPackage(pkg_a->name);
+ if (!pkg_b) {
+ std::stringstream str_stream;
+ str_stream << "missing package " << pkg_a->name;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else {
+ if (pkg_a->id != pkg_b->id) {
+ std::stringstream str_stream;
+ str_stream << "package '" << pkg_a->name << "' has different id (";
+ if (pkg_b->id) {
+ str_stream << "0x" << std::hex << pkg_b->id.value();
} else {
- if (pkgA->id != pkgB->id) {
- std::stringstream strStream;
- strStream << "package '" << pkgA->name << "' has different id (";
- if (pkgB->id) {
- strStream << "0x" << std::hex << pkgB->id.value();
- } else {
- strStream << "none";
- }
- strStream << " vs ";
- if (pkgA->id) {
- strStream << "0x" << std::hex << pkgA->id.value();
- } else {
- strStream << "none";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
- diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
+ str_stream << "none";
}
- }
-
- // Check for any newly added packages.
- for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
- ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
- if (!pkgA) {
- std::stringstream strStream;
- strStream << "new package " << pkgB->name;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
+ str_stream << " vs ";
+ if (pkg_a->id) {
+ str_stream << "0x" << std::hex << pkg_a->id.value();
+ } else {
+ str_stream << "none";
}
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ diff |=
+ EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b);
+ }
+ }
+
+ // Check for any newly added packages.
+ for (std::unique_ptr<ResourceTablePackage>& pkg_b : table_b->packages) {
+ ResourceTablePackage* pkg_a = table_a->FindPackage(pkg_b->name);
+ if (!pkg_a) {
+ std::stringstream str_stream;
+ str_stream << "new package " << pkg_b->name;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
}
- return diff;
+ }
+ return diff;
}
-int diff(const std::vector<StringPiece>& args) {
- DiffContext context;
+class ZeroingReferenceVisitor : public ValueVisitor {
+ public:
+ using ValueVisitor::Visit;
- Flags flags;
- if (!flags.parse("aapt2 diff", args, &std::cerr)) {
- return 1;
- }
-
- if (flags.getArgs().size() != 2u) {
- std::cerr << "must have two apks as arguments.\n\n";
- flags.usage("aapt2 diff", &std::cerr);
- return 1;
+ void Visit(Reference* ref) override {
+ if (ref->name && ref->id) {
+ if (ref->id.value().package_id() == 0x7f) {
+ ref->id = {};
+ }
}
+ }
+};
- std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]);
- std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]);
- if (!apkA || !apkB) {
- return 1;
- }
+static void ZeroOutAppReferences(ResourceTable* table) {
+ ZeroingReferenceVisitor visitor;
+ VisitAllValuesInTable(table, &visitor);
+}
- if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
- // We emitted a diff, so return 1 (failure).
- return 1;
- }
- return 0;
+int Diff(const std::vector<StringPiece>& args) {
+ DiffContext context;
+
+ Flags flags;
+ if (!flags.Parse("aapt2 diff", args, &std::cerr)) {
+ return 1;
+ }
+
+ if (flags.GetArgs().size() != 2u) {
+ std::cerr << "must have two apks as arguments.\n\n";
+ flags.Usage("aapt2 diff", &std::cerr);
+ return 1;
+ }
+
+ std::unique_ptr<LoadedApk> apk_a =
+ LoadApkFromPath(&context, flags.GetArgs()[0]);
+ std::unique_ptr<LoadedApk> apk_b =
+ LoadApkFromPath(&context, flags.GetArgs()[1]);
+ if (!apk_a || !apk_b) {
+ return 1;
+ }
+
+ // Zero out Application IDs in references.
+ ZeroOutAppReferences(apk_a->GetResourceTable());
+ ZeroOutAppReferences(apk_b->GetResourceTable());
+
+ if (EmitResourceTableDiff(&context, apk_a.get(), apk_b.get())) {
+ // We emitted a diff, so return 1 (failure).
+ return 1;
+ }
+ return 0;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp
index 56b9f9a3e081..2920c2abb57a 100644
--- a/tools/aapt2/dump/Dump.cpp
+++ b/tools/aapt2/dump/Dump.cpp
@@ -14,157 +14,191 @@
* limitations under the License.
*/
+#include <vector>
+
#include "Debug.h"
#include "Diagnostics.h"
#include "Flags.h"
#include "io/ZipArchive.h"
#include "process/IResourceTableConsumer.h"
#include "proto/ProtoSerialize.h"
+#include "unflatten/BinaryResourceParser.h"
#include "util/Files.h"
#include "util/StringPiece.h"
-#include <vector>
-
namespace aapt {
-//struct DumpOptions {
-//
-//};
+void DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data,
+ size_t len, const Source& source, IAaptContext* context) {
+ std::unique_ptr<ResourceFile> file =
+ DeserializeCompiledFileFromPb(pb_file, source, context->GetDiagnostics());
+ if (!file) {
+ context->GetDiagnostics()->Warn(DiagMessage()
+ << "failed to read compiled file");
+ return;
+ }
+
+ std::cout << "Resource: " << file->name << "\n"
+ << "Config: " << file->config << "\n"
+ << "Source: " << file->source << "\n";
+}
-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) {
+void TryDumpFile(IAaptContext* context, const std::string& file_path) {
+ std::unique_ptr<ResourceTable> table;
+
+ std::string err;
+ std::unique_ptr<io::ZipFileCollection> zip =
+ io::ZipFileCollection::Create(file_path, &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(file_path) << "failed to open resources.arsc.flat");
return;
- }
+ }
- std::cout << "Resource: " << file->name << "\n"
- << "Config: " << file->config << "\n"
- << "Source: " << file->source << "\n";
-}
+ pb::ResourceTable pb_table;
+ if (!pb_table.ParseFromArray(data->data(), data->size())) {
+ context->GetDiagnostics()->Error(DiagMessage(file_path)
+ << "invalid resources.arsc.flat");
+ return;
+ }
-void dumpCompiledTable(const pb::ResourceTable& pbTable, const Source& source,
- IAaptContext* context) {
- std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
- context->getDiagnostics());
- if (!table) {
+ table = DeserializeTableFromPb(pb_table, Source(file_path),
+ context->GetDiagnostics());
+ if (!table) {
return;
+ }
}
- Debug::printTable(table.get());
-}
+ if (!table) {
+ file = zip->FindFile("resources.arsc");
+ if (file) {
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ if (!data) {
+ context->GetDiagnostics()->Error(DiagMessage(file_path)
+ << "failed to open resources.arsc");
+ return;
+ }
-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);
- }
+ table = util::make_unique<ResourceTable>();
+ BinaryResourceParser parser(context, table.get(), Source(file_path),
+ data->data(), data->size());
+ if (!parser.Parse()) {
+ return;
}
- return;
+ }
}
+ }
- Maybe<android::FileMap> file = file::mmapPath(filePath, &err);
+ if (!table) {
+ Maybe<android::FileMap> file = file::MmapPath(file_path, &err);
if (!file) {
- context->getDiagnostics()->error(DiagMessage(filePath) << err);
- return;
+ context->GetDiagnostics()->Error(DiagMessage(file_path) << err);
+ return;
}
- android::FileMap* fileMap = &file.value();
+ android::FileMap* file_map = &file.value();
// Try as a compiled table.
- pb::ResourceTable pbTable;
- if (pbTable.ParseFromArray(fileMap->getDataPtr(), fileMap->getDataLength())) {
- dumpCompiledTable(pbTable, Source(filePath), context);
- return;
+ pb::ResourceTable pb_table;
+ if (pb_table.ParseFromArray(file_map->getDataPtr(),
+ file_map->getDataLength())) {
+ table = DeserializeTableFromPb(pb_table, Source(file_path),
+ context->GetDiagnostics());
}
- // 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;
+ if (!table) {
+ // Try as a compiled file.
+ CompiledFileInputStream input(file_map->getDataPtr(),
+ file_map->getDataLength());
+
+ uint32_t num_files = 0;
+ if (!input.ReadLittleEndian32(&num_files)) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < num_files; i++) {
+ pb::CompiledFile compiled_file;
+ if (!input.ReadCompiledFile(&compiled_file)) {
+ context->GetDiagnostics()->Warn(DiagMessage()
+ << "failed to read compiled file");
+ return;
+ }
+
+ uint64_t offset, len;
+ if (!input.ReadDataMetaData(&offset, &len)) {
+ context->GetDiagnostics()->Warn(DiagMessage()
+ << "failed to read meta data");
+ return;
+ }
+
+ const void* data =
+ static_cast<const uint8_t*>(file_map->getDataPtr()) + offset;
+ DumpCompiledFile(compiled_file, data, len, Source(file_path), context);
+ }
}
+ }
+
+ if (table) {
+ DebugPrintTableOptions options;
+ options.show_sources = true;
+ Debug::PrintTable(table.get(), options);
+ }
}
class DumpContext : public IAaptContext {
-public:
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ public:
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
- NameMangler* getNameMangler() override {
- abort();
- return nullptr;
- }
+ NameMangler* GetNameMangler() override {
+ abort();
+ return nullptr;
+ }
- const std::u16string& getCompilationPackage() override {
- static std::u16string empty;
- return empty;
- }
+ const std::string& GetCompilationPackage() override {
+ static std::string empty;
+ return empty;
+ }
- uint8_t getPackageId() override {
- return 0;
- }
+ uint8_t GetPackageId() override { return 0; }
- SymbolTable* getExternalSymbols() override {
- abort();
- return nullptr;
- }
+ SymbolTable* GetExternalSymbols() override {
+ abort();
+ return nullptr;
+ }
- bool verbose() override {
- return mVerbose;
- }
+ bool IsVerbose() override { return verbose_; }
- void setVerbose(bool val) {
- mVerbose = val;
- }
+ void SetVerbose(bool val) { verbose_ = val; }
+
+ int GetMinSdkVersion() override { return 0; }
-private:
- StdErrDiagnostics mDiagnostics;
- bool mVerbose = false;
+ private:
+ StdErrDiagnostics diagnostics_;
+ bool verbose_ = 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;
+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
+} // namespace aapt
diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp
index 68a017d247f6..66aff829d71c 100644
--- a/tools/aapt2/filter/ConfigFilter.cpp
+++ b/tools/aapt2/filter/ConfigFilter.cpp
@@ -14,64 +14,69 @@
* limitations under the License.
*/
-#include "ConfigDescription.h"
#include "filter/ConfigFilter.h"
-#include <androidfw/ResourceTypes.h>
+#include "androidfw/ResourceTypes.h"
+
+#include "ConfigDescription.h"
namespace aapt {
-void AxisConfigFilter::addConfig(ConfigDescription config) {
- uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
+void AxisConfigFilter::AddConfig(ConfigDescription config) {
+ uint32_t diff_mask = ConfigDescription::DefaultConfig().diff(config);
- // Ignore the version
- diffMask &= ~android::ResTable_config::CONFIG_VERSION;
+ // Ignore the version
+ diff_mask &= ~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;
- }
+ // Ignore any densities. Those are best handled in --preferred-density
+ if ((diff_mask & android::ResTable_config::CONFIG_DENSITY) != 0) {
+ config.density = 0;
+ diff_mask &= ~android::ResTable_config::CONFIG_DENSITY;
+ }
- mConfigs.insert(std::make_pair(config, diffMask));
- mConfigMask |= diffMask;
+ configs_.insert(std::make_pair(config, diff_mask));
+ config_mask_ |= diff_mask;
}
-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;
- }
+bool AxisConfigFilter::Match(const ConfigDescription& config) const {
+ const uint32_t mask = ConfigDescription::DefaultConfig().diff(config);
+ if ((config_mask_ & 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;
- }
+ uint32_t matched_axis = 0;
+ for (const auto& entry : configs_) {
+ const ConfigDescription& target = entry.first;
+ const uint32_t diff_mask = entry.second;
+ uint32_t diff = target.diff(config);
+ if ((diff & diff_mask) == 0) {
+ // Mark the axis that was matched.
+ matched_axis |= diff_mask;
+ } else if ((diff & diff_mask) == 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) {
+ matched_axis |= android::ResTable_config::CONFIG_LOCALE;
}
+ }
+ } else if ((diff & diff_mask) ==
+ 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) {
+ matched_axis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
+ }
}
- return matchedAxis == (mConfigMask & mask);
+ }
+ return matched_axis == (config_mask_ & mask);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h
index 36e9c44255e4..3f1341684912 100644
--- a/tools/aapt2/filter/ConfigFilter.h
+++ b/tools/aapt2/filter/ConfigFilter.h
@@ -17,45 +17,48 @@
#ifndef AAPT_FILTER_CONFIGFILTER_H
#define AAPT_FILTER_CONFIGFILTER_H
-#include "ConfigDescription.h"
-
#include <set>
#include <utility>
+#include "ConfigDescription.h"
+
namespace aapt {
/**
* Matches ConfigDescriptions based on some pattern.
*/
class IConfigFilter {
-public:
- virtual ~IConfigFilter() = default;
+ public:
+ virtual ~IConfigFilter() = default;
- /**
- * Returns true if the filter matches the configuration, false otherwise.
- */
- virtual bool match(const ConfigDescription& config) const = 0;
+ /**
+ * 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
+ * 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);
+ public:
+ void AddConfig(ConfigDescription config);
- bool match(const ConfigDescription& config) const override;
+ bool Match(const ConfigDescription& config) const override;
-private:
- std::set<std::pair<ConfigDescription, uint32_t>> mConfigs;
- uint32_t mConfigMask = 0;
+ private:
+ std::set<std::pair<ConfigDescription, uint32_t>> configs_;
+ uint32_t config_mask_ = 0;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FILTER_CONFIGFILTER_H */
diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp
index f6b49557306d..586dd5f68f1b 100644
--- a/tools/aapt2/filter/ConfigFilter_test.cpp
+++ b/tools/aapt2/filter/ConfigFilter_test.cpp
@@ -15,98 +15,98 @@
*/
#include "filter/ConfigFilter.h"
-#include "test/Common.h"
-#include <gtest/gtest.h>
+#include "test/Test.h"
namespace aapt {
TEST(ConfigFilterTest, EmptyFilterMatchesAnything) {
- AxisConfigFilter filter;
+ AxisConfigFilter filter;
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("320dpi")));
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("fr")));
}
TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr"));
+ AxisConfigFilter filter;
+ filter.AddConfig(test::ParseConfigOrDie("fr"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("320dpi")));
}
TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr"));
+ AxisConfigFilter filter;
+ filter.AddConfig(test::ParseConfigOrDie("fr"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("fr")));
}
TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr"));
+ AxisConfigFilter filter;
+ filter.AddConfig(test::ParseConfigOrDie("fr"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi")));
+ 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"));
+ 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")));
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("en")));
}
TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr"));
+ AxisConfigFilter filter;
+ filter.AddConfig(test::ParseConfigOrDie("fr"));
- EXPECT_FALSE(filter.match(test::parseConfigOrDie("de")));
+ 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")));
+ 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"));
+ AxisConfigFilter filter;
+ filter.AddConfig(test::ParseConfigOrDie("sw600dp"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13")));
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("fr-sw320dp-v13")));
}
TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("de-rDE"));
+ AxisConfigFilter filter;
+ filter.AddConfig(test::ParseConfigOrDie("de-rDE"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("de")));
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("de")));
}
TEST(ConfigFilterTest, IgnoresVersion) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("normal-v4"));
+ 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")));
+ // 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"));
+ 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")));
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("kok-rIN")));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 3a244c05efec..47de0a3b8d6c 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -15,170 +15,186 @@
*/
#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>
+
+#include "android-base/macros.h"
+#include "ziparchive/zip_writer.h"
+
+#include "util/Files.h"
+#include "util/StringPiece.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;
+class DirectoryWriter : public IArchiveWriter {
+ public:
+ DirectoryWriter() = default;
+
+ bool Open(IDiagnostics* diag, const StringPiece& out_dir) {
+ dir_ = out_dir.ToString();
+ file::FileType type = file::GetFileType(dir_);
+ if (type == file::FileType::kNonexistant) {
+ diag->Error(DiagMessage() << "directory " << dir_ << " does not exist");
+ return false;
+ } else if (type != file::FileType::kDirectory) {
+ diag->Error(DiagMessage() << dir_ << " is not a directory");
+ return false;
+ }
+ return true;
+ }
+
+ bool StartEntry(const StringPiece& path, uint32_t flags) override {
+ if (file_) {
+ return false;
+ }
+
+ std::string full_path = dir_;
+ file::AppendPath(&full_path, path);
+ file::mkdirs(file::GetStem(full_path));
+
+ file_ = {fopen(full_path.data(), "wb"), fclose};
+ if (!file_) {
+ return false;
+ }
+ return true;
+ }
+
+ bool WriteEntry(const BigBuffer& buffer) override {
+ if (!file_) {
+ return false;
}
-};
-struct ZipFileWriter : public IArchiveWriter {
- std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
- std::unique_ptr<ZipWriter> mWriter;
+ for (const BigBuffer::Block& b : buffer) {
+ if (fwrite(b.buffer.get(), 1, b.size, file_.get()) != b.size) {
+ file_.reset(nullptr);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool WriteEntry(const void* data, size_t len) override {
+ if (fwrite(data, 1, len, file_.get()) != len) {
+ file_.reset(nullptr);
+ return false;
+ }
+ return true;
+ }
- 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 FinishEntry() override {
+ if (!file_) {
+ return false;
}
+ file_.reset(nullptr);
+ return true;
+ }
- bool startEntry(const StringPiece& path, uint32_t flags) override {
- if (!mWriter) {
- return false;
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DirectoryWriter);
- size_t zipFlags = 0;
- if (flags & ArchiveEntry::kCompress) {
- zipFlags |= ZipWriter::kCompress;
- }
+ std::string dir_;
+ std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
+};
- if (flags & ArchiveEntry::kAlign) {
- zipFlags |= ZipWriter::kAlign32;
- }
+class ZipFileWriter : public IArchiveWriter {
+ public:
+ ZipFileWriter() = default;
- int32_t result = mWriter->StartEntry(path.data(), zipFlags);
- if (result != 0) {
- return false;
- }
- return true;
+ bool Open(IDiagnostics* diag, const StringPiece& path) {
+ file_ = {fopen(path.data(), "w+b"), fclose};
+ if (!file_) {
+ diag->Error(DiagMessage() << "failed to Open " << path << ": "
+ << strerror(errno));
+ return false;
}
+ writer_ = util::make_unique<ZipWriter>(file_.get());
+ 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 StartEntry(const StringPiece& path, uint32_t flags) override {
+ if (!writer_) {
+ return false;
}
- 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;
+ size_t zip_flags = 0;
+ if (flags & ArchiveEntry::kCompress) {
+ zip_flags |= ZipWriter::kCompress;
}
- bool finishEntry() override {
- int32_t result = mWriter->FinishEntry();
- if (result != 0) {
- return false;
- }
- return true;
+ if (flags & ArchiveEntry::kAlign) {
+ zip_flags |= ZipWriter::kAlign32;
}
- virtual ~ZipFileWriter() {
- if (mWriter) {
- mWriter->Finish();
- }
+ int32_t result = writer_->StartEntry(path.data(), zip_flags);
+ if (result != 0) {
+ return false;
}
-};
+ return true;
+ }
-} // namespace
+ bool WriteEntry(const void* data, size_t len) override {
+ int32_t result = writer_->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 = writer_->WriteBytes(b.buffer.get(), b.size);
+ if (result != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
- const StringPiece& path) {
+ bool FinishEntry() override {
+ int32_t result = writer_->FinishEntry();
+ if (result != 0) {
+ return false;
+ }
+ return true;
+ }
- std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
- if (!writer->open(diag, path)) {
- return {};
+ virtual ~ZipFileWriter() {
+ if (writer_) {
+ writer_->Finish();
}
- return std::move(writer);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ZipFileWriter);
+
+ std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
+ std::unique_ptr<ZipWriter> writer_;
+};
+
+} // 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);
+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
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index 34c10ad40365..4fcb3ffa2b78 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -17,50 +17,52 @@
#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>
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+#include "Diagnostics.h"
+#include "util/BigBuffer.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
namespace aapt {
struct ArchiveEntry {
- enum : uint32_t {
- kCompress = 0x01,
- kAlign = 0x02,
- };
+ enum : uint32_t {
+ kCompress = 0x01,
+ kAlign = 0x02,
+ };
- std::string path;
- uint32_t flags;
- size_t uncompressedSize;
+ std::string path;
+ uint32_t flags;
+ size_t uncompressed_size;
};
-struct IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
- virtual ~IArchiveWriter() = default;
+class IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
+ public:
+ 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;
+ 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);
- }
+ // 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> CreateDirectoryArchiveWriter(
+ IDiagnostics* diag, const StringPiece& path);
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
- const StringPiece& path);
+std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(
+ IDiagnostics* diag, const StringPiece& path);
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FLATTEN_ARCHIVE_H */
diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h
index de1d87a57e6d..968d3eef48ec 100644
--- a/tools/aapt2/flatten/ChunkWriter.h
+++ b/tools/aapt2/flatten/ChunkWriter.h
@@ -17,71 +17,64 @@
#ifndef AAPT_FLATTEN_CHUNKWRITER_H
#define AAPT_FLATTEN_CHUNKWRITER_H
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.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;
- }
+ public:
+ explicit inline ChunkWriter(BigBuffer* buffer) : buffer_(buffer) {}
+ ChunkWriter(ChunkWriter&&) = default;
+ ChunkWriter& operator=(ChunkWriter&&) = default;
+
+ template <typename T>
+ inline T* StartChunk(uint16_t type) {
+ start_size_ = buffer_->size();
+ T* chunk = buffer_->NextBlock<T>();
+ header_ = &chunk->header;
+ header_->type = util::HostToDevice16(type);
+ header_->headerSize = util::HostToDevice16(sizeof(T));
+ return chunk;
+ }
+
+ template <typename T>
+ inline T* NextBlock(size_t count = 1) {
+ return buffer_->NextBlock<T>(count);
+ }
+
+ inline BigBuffer* buffer() { return buffer_; }
+
+ inline android::ResChunk_header* chunk_header() { return header_; }
+
+ inline size_t size() { return buffer_->size() - start_size_; }
+
+ inline android::ResChunk_header* Finish() {
+ buffer_->Align4();
+ header_->size = util::HostToDevice32(buffer_->size() - start_size_);
+ return header_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChunkWriter);
+
+ BigBuffer* buffer_;
+ size_t start_size_ = 0;
+ android::ResChunk_header* header_ = nullptr;
};
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;
+inline android::ResChunk_header* ChunkWriter::StartChunk(uint16_t type) {
+ start_size_ = buffer_->size();
+ header_ = buffer_->NextBlock<android::ResChunk_header>();
+ header_->type = util::HostToDevice16(type);
+ header_->headerSize = util::HostToDevice16(sizeof(android::ResChunk_header));
+ return header_;
}
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FLATTEN_CHUNKWRITER_H */
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index 3e20ad643eb6..6359b4143f1e 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -17,20 +17,21 @@
#ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H
#define AAPT_RESOURCE_TYPE_EXTENSIONS_H
-#include <androidfw/ResourceTypes.h>
+#include "androidfw/ResourceTypes.h"
namespace aapt {
/**
- * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout
+ * 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;
+ android::ResTable_entry entry;
+ android::ResTable_ref parent;
+ uint32_t count;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
+#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 28a792820de3..19d030ec4a25 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -14,20 +14,23 @@
* limitations under the License.
*/
+#include "flatten/TableFlattener.h"
+
+#include <algorithm>
+#include <numeric>
+#include <sstream>
+#include <type_traits>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
#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 {
@@ -35,438 +38,458 @@ namespace aapt {
namespace {
template <typename T>
-static bool cmpIds(const T* a, const T* b) {
- return a->id.value() < b->id.value();
+static bool cmp_ids(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;
+ if (len == 0) {
+ return;
+ }
+
+ size_t i;
+ const char16_t* src_data = src.data();
+ for (i = 0; i < len - 1 && i < src.size(); i++) {
+ dst[i] = util::HostToDevice16((uint16_t)src_data[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;
+static bool cmp_style_entries(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;
+ ResourceEntry* entry;
+ Value* value;
- // The entry string pool index to the entry's name.
- uint32_t entryKey;
+ // The entry string pool index to the entry's name.
+ uint32_t entry_key;
};
class MapFlattenVisitor : public RawValueVisitor {
-public:
- using RawValueVisitor::visit;
+ public:
+ using RawValueVisitor::Visit;
- MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) :
- mOutEntry(outEntry), mBuffer(buffer) {
- }
+ MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer)
+ : out_entry_(out_entry), buffer_(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(Attribute* attr) override {
+ {
+ Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask);
+ FlattenEntry(&key, &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());
- }
+ if (attr->min_int != std::numeric_limits<int32_t>::min()) {
+ Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC,
+ static_cast<uint32_t>(attr->min_int));
+ FlattenEntry(&key, &val);
}
- void visit(Styleable* styleable) override {
- for (auto& attrRef : styleable->entries) {
- BinaryPrimitive val(Res_value{});
- flattenEntry(&attrRef, &val);
- }
-
+ if (attr->max_int != std::numeric_limits<int32_t>::max()) {
+ Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC,
+ static_cast<uint32_t>(attr->max_int));
+ FlattenEntry(&key, &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++;
- }
+ for (Attribute::Symbol& s : attr->symbols) {
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
+ FlattenEntry(&s.symbol, &val);
}
+ }
- 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());
- }
+ void Visit(Style* style) override {
+ if (style->parent) {
+ const Reference& parent_ref = style->parent.value();
+ CHECK(bool(parent_ref.id)) << "parent has no ID";
+ out_entry_->parent.ident = util::HostToDevice32(parent_ref.id.value().id);
}
- /**
- * 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);
- }
+ // Sort the style.
+ std::sort(style->entries.begin(), style->entries.end(), cmp_style_entries);
-private:
- void flattenKey(Reference* key, ResTable_map* outEntry) {
- assert(key->id && "key has no ID");
- outEntry->name.ident = util::hostToDevice32(key->id.value().id);
+ for (Style::Entry& entry : style->entries) {
+ FlattenEntry(&entry.key, entry.value.get());
}
+ }
- void flattenValue(Item* value, ResTable_map* outEntry) {
- bool result = value->flatten(&outEntry->value);
- assert(result && "flatten failed");
+ void Visit(Styleable* styleable) override {
+ for (auto& attr_ref : styleable->entries) {
+ BinaryPrimitive val(Res_value{});
+ FlattenEntry(&attr_ref, &val);
}
-
- 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++;
+ }
+
+ void Visit(Array* array) override {
+ for (auto& item : array->items) {
+ ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
+ FlattenValue(item.get(), out_entry);
+ out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
+ entry_count_++;
}
-
- ResTable_entry_ext* mOutEntry;
- BigBuffer* mBuffer;
- size_t mEntryCount = 0;
+ }
+
+ 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:
+ LOG(FATAL) << "unhandled plural type";
+ 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() { out_entry_->count = util::HostToDevice32(entry_count_); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor);
+
+ void FlattenKey(Reference* key, ResTable_map* out_entry) {
+ CHECK(bool(key->id)) << "key has no ID";
+ out_entry->name.ident = util::HostToDevice32(key->id.value().id);
+ }
+
+ void FlattenValue(Item* value, ResTable_map* out_entry) {
+ CHECK(value->Flatten(&out_entry->value)) << "flatten failed";
+ }
+
+ void FlattenEntry(Reference* key, Item* value) {
+ ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
+ FlattenKey(key, out_entry);
+ FlattenValue(value, out_entry);
+ out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
+ entry_count_++;
+ }
+
+ ResTable_entry_ext* out_entry_;
+ BigBuffer* buffer_;
+ size_t entry_count_ = 0;
};
class PackageFlattener {
-public:
- PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) :
- mDiag(diag), mPackage(package) {
+ public:
+ PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package)
+ : diag_(diag), package_(package) {}
+
+ bool FlattenPackage(BigBuffer* buffer) {
+ ChunkWriter pkg_writer(buffer);
+ ResTable_package* pkg_header =
+ pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
+ pkg_header->id = util::HostToDevice32(package_->id.value());
+
+ if (package_->name.size() >= arraysize(pkg_header->name)) {
+ diag_->Error(DiagMessage() << "package name '" << package_->name
+ << "' is too long");
+ return false;
}
- 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());
+ // Copy the package name in device endianness.
+ strcpy16_htod(pkg_header->name, arraysize(pkg_header->name),
+ util::Utf8ToUtf16(package_->name));
- if (mPackage->name.size() >= arraysize(pkgHeader->name)) {
- mDiag->error(DiagMessage() <<
- "package name '" << mPackage->name << "' is too long");
- return false;
- }
+ // Serialize the types. We do this now so that our type and key strings
+ // are populated. We write those first.
+ BigBuffer type_buffer(1024);
+ FlattenTypes(&type_buffer);
- // Copy the package name in device endianness.
- strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name), mPackage->name);
+ pkg_header->typeStrings = util::HostToDevice32(pkg_writer.size());
+ StringPool::FlattenUtf16(pkg_writer.buffer(), type_pool_);
- // 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);
+ pkg_header->keyStrings = util::HostToDevice32(pkg_writer.size());
+ StringPool::FlattenUtf8(pkg_writer.buffer(), key_pool_);
- pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
- StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
+ // Append the types.
+ buffer->AppendBuffer(std::move(type_buffer));
- pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
- StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
-
- // Append the types.
- buffer->appendBuffer(std::move(typeBuffer));
-
- pkgWriter.finish();
- return true;
- }
+ pkg_writer.Finish();
+ return true;
+ }
-private:
- IDiagnostics* mDiag;
- ResourceTablePackage* mPackage;
- StringPool mTypePool;
- StringPool mKeyPool;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
- template <typename T, bool IsItem>
- T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
- static_assert(std::is_same<ResTable_entry, T>::value ||
+ 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 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;
- }
+ T* result = buffer->NextBlock<T>();
+ ResTable_entry* out_entry = (ResTable_entry*)result;
+ if (entry->entry->symbol_status.state == SymbolState::kPublic) {
+ out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
+ }
- outEntry->flags = util::hostToDevice16(outEntry->flags);
- outEntry->key.index = util::hostToDevice32(entry->entryKey);
- outEntry->size = util::hostToDevice16(sizeof(T));
- return result;
+ if (entry->value->IsWeak()) {
+ out_entry->flags |= ResTable_entry::FLAG_WEAK;
}
- 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;
+ if (!IsItem) {
+ out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
}
- 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;
+ out_entry->flags = util::HostToDevice16(out_entry->flags);
+ out_entry->key.index = util::HostToDevice32(entry->entry_key);
+ out_entry->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>();
+ CHECK(item->Flatten(outValue)) << "flatten failed";
+ outValue->size = util::HostToDevice16(sizeof(*outValue));
+ } else {
+ ResTable_entry_ext* out_entry =
+ WriteEntry<ResTable_entry_ext, false>(entry, buffer);
+ MapFlattenVisitor visitor(out_entry, buffer);
+ entry->value->Accept(&visitor);
+ visitor.Finish();
}
+ return true;
+ }
+
+ bool FlattenConfig(const ResourceTableType* type,
+ const ConfigDescription& config,
+ std::vector<FlatEntry>* entries, BigBuffer* buffer) {
+ ChunkWriter type_writer(buffer);
+ ResTable_type* type_header =
+ type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
+ type_header->id = type->id.value();
+ type_header->config = config;
+ type_header->config.swapHtoD();
+
+ auto max_accum = [](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 entry_count =
+ std::accumulate(type->entries.begin(), type->entries.end(), 0,
+ max_accum) +
+ 1;
+
+ type_header->entryCount = util::HostToDevice32(entry_count);
+ uint32_t* indices = type_writer.NextBlock<uint32_t>(entry_count);
+
+ CHECK((size_t)entry_count <= std::numeric_limits<uint16_t>::max());
+ memset(indices, 0xff, entry_count * sizeof(uint32_t));
+
+ type_header->entriesStart = util::HostToDevice32(type_writer.size());
+
+ const size_t entry_start = type_writer.buffer()->size();
+ for (FlatEntry& flat_entry : *entries) {
+ CHECK(flat_entry.entry->id.value() < entry_count);
+ indices[flat_entry.entry->id.value()] =
+ util::HostToDevice32(type_writer.buffer()->size() - entry_start);
+ if (!FlattenValue(&flat_entry, type_writer.buffer())) {
+ diag_->Error(DiagMessage()
+ << "failed to flatten resource '"
+ << ResourceNameRef(package_->name, type->type,
+ flat_entry.entry->name)
+ << "' for configuration '" << config << "'");
+ return false;
+ }
+ }
+ type_writer.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;
- }
+ std::vector<ResourceTableType*> CollectAndSortTypes() {
+ std::vector<ResourceTableType*> sorted_types;
+ for (auto& type : package_->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");
+ CHECK(bool(type->id)) << "type must have an ID set";
- sortedTypes.push_back(type.get());
- }
- std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>);
- return sortedTypes;
+ sorted_types.push_back(type.get());
}
-
- 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;
+ std::sort(sorted_types.begin(), sorted_types.end(),
+ cmp_ids<ResourceTableType>);
+ return sorted_types;
+ }
+
+ std::vector<ResourceEntry*> CollectAndSortEntries(ResourceTableType* type) {
+ // Sort the entries by entry ID.
+ std::vector<ResourceEntry*> sorted_entries;
+ for (auto& entry : type->entries) {
+ CHECK(bool(entry->id)) << "entry must have an ID set";
+ sorted_entries.push_back(entry.get());
+ }
+ std::sort(sorted_entries.begin(), sorted_entries.end(),
+ cmp_ids<ResourceEntry>);
+ return sorted_entries;
+ }
+
+ bool FlattenTypeSpec(ResourceTableType* type,
+ std::vector<ResourceEntry*>* sorted_entries,
+ BigBuffer* buffer) {
+ ChunkWriter type_spec_writer(buffer);
+ ResTable_typeSpec* spec_header =
+ type_spec_writer.StartChunk<ResTable_typeSpec>(
+ RES_TABLE_TYPE_SPEC_TYPE);
+ spec_header->id = type->id.value();
+
+ if (sorted_entries->empty()) {
+ type_spec_writer.Finish();
+ return true;
}
- 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;
+ // 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 num_entries = sorted_entries->back()->id.value() + 1;
- specHeader->entryCount = util::hostToDevice32(numEntries);
+ spec_header->entryCount = util::HostToDevice32(num_entries);
- // 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);
+ // Reserve space for the masks of each resource in this type. These
+ // show for which configuration axis the resource changes.
+ uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
- const size_t actualNumEntries = sortedEntries->size();
- for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
- ResourceEntry* entry = sortedEntries->at(entryIndex);
+ const size_t actual_num_entries = sorted_entries->size();
+ for (size_t entryIndex = 0; entryIndex < actual_num_entries; entryIndex++) {
+ ResourceEntry* entry = sorted_entries->at(entryIndex);
- // Populate the config masks for this entry.
+ // Populate the config masks for this entry.
- if (entry->symbolStatus.state == SymbolState::kPublic) {
- configMasks[entry->id.value()] |=
- util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
- }
+ if (entry->symbol_status.state == SymbolState::kPublic) {
+ config_masks[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));
- }
- }
+ const size_t config_count = entry->values.size();
+ for (size_t i = 0; i < config_count; i++) {
+ const ConfigDescription& config = entry->values[i]->config;
+ for (size_t j = i + 1; j < config_count; j++) {
+ config_masks[entry->id.value()] |=
+ util::HostToDevice32(config.diff(entry->values[j]->config));
}
- typeSpecWriter.finish();
- return true;
+ }
}
+ type_spec_writer.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*> sorted_types = CollectAndSortTypes();
+
+ size_t expected_type_id = 1;
+ for (ResourceTableType* type : sorted_types) {
+ // 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() > expected_type_id) {
+ std::stringstream type_name;
+ type_name << "?" << expected_type_id;
+ type_pool_.MakeRef(type_name.str());
+ expected_type_id++;
+ }
+ expected_type_id++;
+ type_pool_.MakeRef(ToString(type->type));
+
+ std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
+
+ if (!FlattenTypeSpec(type, &sorted_entries, 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>>
+ config_to_entry_list_map;
+ for (ResourceEntry* entry : sorted_entries) {
+ const uint32_t key_index =
+ (uint32_t)key_pool_.MakeRef(entry->name).index();
+
+ // Group values by configuration.
+ for (auto& config_value : entry->values) {
+ config_to_entry_list_map[config_value->config].push_back(
+ FlatEntry{entry, config_value->value.get(), key_index});
+ }
+ }
- 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;
- }
- }
+ // Flatten a configuration value.
+ for (auto& entry : config_to_entry_list_map) {
+ if (!FlattenConfig(type, entry.first, &entry.second, buffer)) {
+ return false;
}
- return true;
+ }
}
+ return true;
+ }
+
+ IDiagnostics* diag_;
+ ResourceTablePackage* package_;
+ StringPool type_pool_;
+ StringPool key_pool_;
};
-} // namespace
+} // 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 {
+bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
+ // We must do this before writing the resources, since the string pool IDs may
+ // change.
+ table->string_pool.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;
@@ -474,31 +497,32 @@ bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
if (diff < 0) return true;
if (diff > 0) return false;
return a.value < b.value;
- });
- table->stringPool.prune();
+ });
+ table->string_pool.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());
+ // Write the ResTable header.
+ ChunkWriter table_writer(buffer_);
+ ResTable_header* table_header =
+ table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
+ table_header->packageCount = util::HostToDevice32(table->packages.size());
- // Flatten the values string pool.
- StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
+ // Flatten the values string pool.
+ StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool);
- BigBuffer packageBuffer(1024);
+ BigBuffer package_buffer(1024);
- // Flatten each package.
- for (auto& package : table->packages) {
- PackageFlattener flattener(context->getDiagnostics(), package.get());
- if (!flattener.flattenPackage(&packageBuffer)) {
- return false;
- }
+ // Flatten each package.
+ for (auto& package : table->packages) {
+ PackageFlattener flattener(context->GetDiagnostics(), package.get());
+ if (!flattener.FlattenPackage(&package_buffer)) {
+ return false;
}
+ }
- // Finally merge all the packages into the main buffer.
- tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
- tableWriter.finish();
- return true;
+ // Finally merge all the packages into the main buffer.
+ table_writer.buffer()->AppendBuffer(std::move(package_buffer));
+ table_writer.Finish();
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h
index 0ab01974044b..53f52c29a6a3 100644
--- a/tools/aapt2/flatten/TableFlattener.h
+++ b/tools/aapt2/flatten/TableFlattener.h
@@ -17,24 +17,26 @@
#ifndef AAPT_FLATTEN_TABLEFLATTENER_H
#define AAPT_FLATTEN_TABLEFLATTENER_H
+#include "android-base/macros.h"
+
+#include "ResourceTable.h"
#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
namespace aapt {
-class BigBuffer;
-class ResourceTable;
-
class TableFlattener : public IResourceTableConsumer {
-public:
- TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {
- }
+ public:
+ explicit TableFlattener(BigBuffer* buffer) : buffer_(buffer) {}
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TableFlattener);
-private:
- BigBuffer* mBuffer;
+ BigBuffer* buffer_;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 39c4fd318508..c72624066fb8 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -15,217 +15,222 @@
*/
#include "flatten/TableFlattener.h"
-#include "test/Builders.h"
-#include "test/Context.h"
+
+#include "ResourceUtils.h"
+#include "test/Test.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();
+ public:
+ void SetUp() override {
+ context_ = test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .Build();
+ }
+
+ ::testing::AssertionResult Flatten(ResourceTable* table,
+ ResTable* out_table) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.Consume(context_.get(), table)) {
+ return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+ }
+
+ std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
+ if (out_table->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* out_table) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.Consume(context_.get(), table)) {
+ return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+ }
+
+ std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
+ BinaryResourceParser parser(context_.get(), out_table, {}, data.get(),
+ buffer.size());
+ if (!parser.Parse()) {
+ return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ ::testing::AssertionResult Exists(ResTable* table,
+ const StringPiece& expected_name,
+ const ResourceId& expected_id,
+ const ConfigDescription& expected_config,
+ const uint8_t expected_data_type,
+ const uint32_t expected_data,
+ const uint32_t expected_spec_flags) {
+ const ResourceName expected_res_name = test::ParseNameOrDie(expected_name);
+
+ table->setParameters(&expected_config);
+
+ ResTable_config config;
+ Res_value val;
+ uint32_t spec_flags;
+ if (table->getResource(expected_id.id, &val, false, 0, &spec_flags,
+ &config) < 0) {
+ return ::testing::AssertionFailure() << "could not find resource with";
+ }
+
+ if (expected_data_type != val.dataType) {
+ return ::testing::AssertionFailure()
+ << "expected data type " << std::hex << (int)expected_data_type
+ << " but got data type " << (int)val.dataType << std::dec
+ << " instead";
+ }
+
+ if (expected_data != val.data) {
+ return ::testing::AssertionFailure()
+ << "expected data " << std::hex << expected_data
+ << " but got data " << val.data << std::dec << " instead";
+ }
+
+ if (expected_spec_flags != spec_flags) {
+ return ::testing::AssertionFailure()
+ << "expected specFlags " << std::hex << expected_spec_flags
+ << " but got specFlags " << spec_flags << std::dec << " instead";
}
- ::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();
+ ResTable::resource_name actual_name;
+ if (!table->getResourceName(expected_id.id, false, &actual_name)) {
+ return ::testing::AssertionFailure() << "failed to find resource name";
}
- ::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();
+ Maybe<ResourceName> resName = ResourceUtils::ToResourceName(actual_name);
+ if (!resName) {
+ return ::testing::AssertionFailure()
+ << "expected name '" << expected_res_name << "' but got '"
+ << StringPiece16(actual_name.package, actual_name.packageLen)
+ << ":" << StringPiece16(actual_name.type, actual_name.typeLen)
+ << "/" << StringPiece16(actual_name.name, actual_name.nameLen)
+ << "'";
}
- ::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();
+ if (expected_config != config) {
+ return ::testing::AssertionFailure() << "expected config '"
+ << expected_config << "' but got '"
+ << ConfigDescription(config) << "'";
}
+ return ::testing::AssertionSuccess();
+ }
-private:
- std::unique_ptr<IAaptContext> mContext;
+ private:
+ std::unique_ptr<IAaptContext> context_;
};
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));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
+ .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
+ .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
+ test::BuildReference("com.app.test:id/one",
+ ResourceId(0x7f020000)))
+ .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
+ util::make_unique<BinaryPrimitive>(
+ uint8_t(Res_value::TYPE_INT_DEC), 1u))
+ .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
+ ResourceId(0x7f030000),
+ util::make_unique<BinaryPrimitive>(
+ uint8_t(Res_value::TYPE_INT_DEC), 2u))
+ .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
+ .AddString("com.app.test:layout/bar", ResourceId(0x7f050000),
+ "res/layout/bar.xml")
+ .Build();
+
+ ResTable res_table;
+ ASSERT_TRUE(Flatten(table.get(), &res_table));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000),
+ {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001),
+ {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three",
+ ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE,
+ 0x7f020000u, 0u));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one",
+ ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
+ ResTable_config::CONFIG_VERSION));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one",
+ ResourceId(0x7f030000), test::ParseConfigOrDie("v1"),
+ Res_value::TYPE_INT_DEC, 2u,
+ ResTable_config::CONFIG_VERSION));
+
+ std::u16string foo_str = u"foo";
+ ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(),
+ foo_str.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test",
+ ResourceId(0x7f040000), {}, Res_value::TYPE_STRING,
+ (uint32_t)idx, 0u));
+
+ std::u16string bar_path = u"res/layout/bar.xml";
+ idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(),
+ bar_path.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(Exists(&res_table, "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));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddSimple("com.app.test:id/one", ResourceId(0x7f020001))
+ .AddSimple("com.app.test:id/three", ResourceId(0x7f020003))
+ .Build();
+
+ ResTable res_table;
+ ASSERT_TRUE(Flatten(table.get(), &res_table));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001),
+ {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+ EXPECT_TRUE(Exists(&res_table, "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);
+ Attribute attr(false);
+ attr.type_mask = android::ResTable_map::TYPE_INTEGER;
+ attr.min_int = 10;
+ attr.max_int = 23;
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddValue("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, "android:attr/foo");
+ ASSERT_NE(nullptr, actualAttr);
+ EXPECT_EQ(attr.IsWeak(), actualAttr->IsWeak());
+ EXPECT_EQ(attr.type_mask, actualAttr->type_mask);
+ EXPECT_EQ(attr.min_int, actualAttr->min_int);
+ EXPECT_EQ(attr.max_int, actualAttr->max_int);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index 570cd9635de3..366c373223dc 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -14,17 +14,22 @@
* 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 <map>
#include <vector>
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+#include "utils/misc.h"
+
+#include "SdkConstants.h"
+#include "flatten/ChunkWriter.h"
+#include "flatten/ResourceTypeExtensions.h"
+#include "xml/XmlDom.h"
+
using namespace android;
namespace aapt {
@@ -33,283 +38,323 @@ 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) {
+static bool cmp_xml_attribute_by_id(const xml::Attribute* a,
+ const xml::Attribute* b) {
+ if (a->compiled_attribute && a->compiled_attribute.value().id) {
+ if (b->compiled_attribute && b->compiled_attribute.value().id) {
+ return a->compiled_attribute.value().id.value() <
+ b->compiled_attribute.value().id.value();
}
-
- 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);
- }
+ return true;
+ } else if (!b->compiled_attribute) {
+ int diff = a->namespace_uri.compare(b->namespace_uri);
+ if (diff < 0) {
+ return true;
+ } else if (diff > 0) {
+ return false;
}
+ return a->name < b->name;
+ }
+ return false;
+}
- void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
- mStringRefs.push_back(StringFlattenDest{ ref, dest });
- }
+class XmlFlattenerVisitor : public xml::Visitor {
+ public:
+ using xml::Visitor::Visit;
+
+ StringPool pool;
+ std::map<uint8_t, StringPool> package_pools;
- void writeNamespace(xml::Namespace* node, uint16_t type) {
- ChunkWriter writer(mBuffer);
+ struct StringFlattenDest {
+ StringPool::Ref ref;
+ ResStringPool_ref* dest;
+ };
- ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
- flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
- flatNode->comment.index = util::hostToDevice32(-1);
+ std::vector<StringFlattenDest> string_refs;
- ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>();
- addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
- addString(node->namespaceUri, kLowPriority, &flatNs->uri);
+ XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)
+ : buffer_(buffer), options_(options) {}
- writer.finish();
+ void Visit(xml::Namespace* node) override {
+ if (node->namespace_uri == xml::kSchemaTools) {
+ // Skip dedicated tools namespace.
+ xml::Visitor::Visit(node);
+ } else {
+ WriteNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
+ xml::Visitor::Visit(node);
+ WriteNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
}
+ }
- 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;
}
- void visit(xml::Text* node) override {
- if (util::trimWhitespace(node->text).empty()) {
- // Skip whitespace only text nodes.
- return;
- }
+ ChunkWriter writer(buffer_);
+ ResXMLTree_node* flat_node =
+ writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
+ flat_node->lineNumber = util::HostToDevice32(node->line_number);
+ flat_node->comment.index = util::HostToDevice32(-1);
- 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* flat_text = writer.NextBlock<ResXMLTree_cdataExt>();
+ AddString(node->text, kLowPriority, &flat_text->data);
- ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
- addString(node->text, kLowPriority, &flatText->data);
+ writer.Finish();
+ }
- 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);
+ void Visit(xml::Element* node) override {
+ {
+ ChunkWriter start_writer(buffer_);
+ ResXMLTree_node* flat_node =
+ start_writer.StartChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE);
+ flat_node->lineNumber = util::HostToDevice32(node->line_number);
+ flat_node->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));
+ ResXMLTree_attrExt* flat_elem =
+ start_writer.NextBlock<ResXMLTree_attrExt>();
- writeAttributes(node, flatElem, &startWriter);
+ // A missing namespace must be null, not an empty string. Otherwise the
+ // runtime complains.
+ AddString(node->namespace_uri, kLowPriority, &flat_elem->ns,
+ true /* treat_empty_string_as_null */);
+ AddString(node->name, kLowPriority, &flat_elem->name,
+ true /* treat_empty_string_as_null */);
- startWriter.finish();
- }
+ flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem));
+ flat_elem->attributeSize =
+ util::HostToDevice16(sizeof(ResXMLTree_attribute));
- xml::Visitor::visit(node);
+ WriteAttributes(node, flat_elem, &start_writer);
- {
- 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);
+ start_writer.Finish();
+ }
- ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
- addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
- addString(node->name, kLowPriority, &flatEndElem->name);
+ xml::Visitor::Visit(node);
- endWriter.finish();
+ {
+ ChunkWriter end_writer(buffer_);
+ ResXMLTree_node* flat_end_node =
+ end_writer.StartChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE);
+ flat_end_node->lineNumber = util::HostToDevice32(node->line_number);
+ flat_end_node->comment.index = util::HostToDevice32(-1);
+
+ ResXMLTree_endElementExt* flat_end_elem =
+ end_writer.NextBlock<ResXMLTree_endElementExt>();
+ AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns,
+ true /* treat_empty_string_as_null */);
+ AddString(node->name, kLowPriority, &flat_end_elem->name);
+
+ end_writer.Finish();
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlFlattenerVisitor);
+
+ void AddString(const StringPiece& str, uint32_t priority,
+ android::ResStringPool_ref* dest,
+ bool treat_empty_string_as_null = false) {
+ if (str.empty() && treat_empty_string_as_null) {
+ // Some parts of the runtime treat null differently than empty string.
+ dest->index = util::DeviceToHost32(-1);
+ } else {
+ string_refs.push_back(StringFlattenDest{
+ pool.MakeRef(str, StringPool::Context(priority)), dest});
+ }
+ }
+
+ void AddString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
+ string_refs.push_back(StringFlattenDest{ref, dest});
+ }
+
+ void WriteNamespace(xml::Namespace* node, uint16_t type) {
+ ChunkWriter writer(buffer_);
+
+ ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type);
+ flatNode->lineNumber = util::HostToDevice32(node->line_number);
+ flatNode->comment.index = util::HostToDevice32(-1);
+
+ ResXMLTree_namespaceExt* flat_ns =
+ writer.NextBlock<ResXMLTree_namespaceExt>();
+ AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix);
+ AddString(node->namespace_uri, kLowPriority, &flat_ns->uri);
+
+ writer.Finish();
+ }
+
+ void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem,
+ ChunkWriter* writer) {
+ filtered_attrs_.clear();
+ filtered_attrs_.reserve(node->attributes.size());
+
+ // Filter the attributes.
+ for (xml::Attribute& attr : node->attributes) {
+ if (options_.max_sdk_level && attr.compiled_attribute &&
+ attr.compiled_attribute.value().id) {
+ size_t sdk_level =
+ FindAttributeSdkLevel(attr.compiled_attribute.value().id.value());
+ if (sdk_level > options_.max_sdk_level.value()) {
+ continue;
}
+ }
+ if (attr.namespace_uri == xml::kSchemaTools) {
+ continue;
+ }
+ filtered_attrs_.push_back(&attr);
}
- 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;
+ if (filtered_attrs_.empty()) {
+ return;
}
- 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);
+ const ResourceId kIdAttr(0x010100d0);
+
+ std::sort(filtered_attrs_.begin(), filtered_attrs_.end(),
+ cmp_xml_attribute_by_id);
+
+ flat_elem->attributeCount = util::HostToDevice16(filtered_attrs_.size());
+
+ ResXMLTree_attribute* flat_attr =
+ writer->NextBlock<ResXMLTree_attribute>(filtered_attrs_.size());
+ uint16_t attribute_index = 1;
+ for (const xml::Attribute* xml_attr : filtered_attrs_) {
+ // Assign the indices for specific attributes.
+ if (xml_attr->compiled_attribute &&
+ xml_attr->compiled_attribute.value().id &&
+ xml_attr->compiled_attribute.value().id.value() == kIdAttr) {
+ flat_elem->idIndex = util::HostToDevice16(attribute_index);
+ } else if (xml_attr->namespace_uri.empty()) {
+ if (xml_attr->name == "class") {
+ flat_elem->classIndex = util::HostToDevice16(attribute_index);
+ } else if (xml_attr->name == "style") {
+ flat_elem->styleIndex = util::HostToDevice16(attribute_index);
}
+ }
+ attribute_index++;
+
+ // Add the namespaceUri to the list of StringRefs to encode. Use null if
+ // the namespace
+ // is empty (doesn't exist).
+ AddString(xml_attr->namespace_uri, kLowPriority, &flat_attr->ns,
+ true /* treat_empty_string_as_null */);
+
+ flat_attr->rawValue.index = util::HostToDevice32(-1);
+
+ if (!xml_attr->compiled_attribute ||
+ !xml_attr->compiled_attribute.value().id) {
+ // The attribute has no associated ResourceID, so the string order
+ // doesn't matter.
+ AddString(xml_attr->name, kLowPriority, &flat_attr->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& aapt_attr =
+ xml_attr->compiled_attribute.value();
+
+ StringPool::Ref name_ref =
+ package_pools[aapt_attr.id.value().package_id()].MakeRef(
+ xml_attr->name, StringPool::Context(aapt_attr.id.value().id));
+
+ // Add it to the list of strings to flatten.
+ AddString(name_ref, &flat_attr->name);
+ }
+
+ if (options_.keep_raw_values || !xml_attr->compiled_value) {
+ // Keep raw values if the value is not compiled or
+ // if we're building a static library (need symbols).
+ AddString(xml_attr->value, kLowPriority, &flat_attr->rawValue);
+ }
+
+ if (xml_attr->compiled_value) {
+ CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue));
+ } else {
+ // Flatten as a regular string type.
+ flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ AddString(xml_attr->value, kLowPriority,
+ (ResStringPool_ref*)&flat_attr->typedValue.data);
+ }
+
+ flat_attr->typedValue.size =
+ util::HostToDevice16(sizeof(flat_attr->typedValue));
+ flat_attr++;
+ }
+ }
- if (mFilteredAttrs.empty()) {
- return;
- }
+ BigBuffer* buffer_;
+ XmlFlattenerOptions options_;
- 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++;
- }
- }
+ // Scratch vector to filter attributes. We avoid allocations
+ // making this a member.
+ std::vector<xml::Attribute*> filtered_attrs_;
};
-} // namespace
+} // namespace
-bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
- BigBuffer nodeBuffer(1024);
- XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
- node->accept(&visitor);
+bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) {
+ BigBuffer node_buffer(1024);
+ XmlFlattenerVisitor visitor(&node_buffer, options_);
+ node->Accept(&visitor);
- // Merge the package pools into the main pool.
- for (auto& packagePoolEntry : visitor.mPackagePools) {
- visitor.mPool.merge(std::move(packagePoolEntry.second));
- }
+ // Merge the package pools into the main pool.
+ for (auto& package_pool_entry : visitor.package_pools) {
+ visitor.pool.Merge(std::move(package_pool_entry.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 {
+ // Sort the string pool so that attribute resource IDs show up first.
+ visitor.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 : visitor.mStringRefs) {
- refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
+ });
+
+ // Now we flatten the string pool references into the correct places.
+ for (const auto& ref_entry : visitor.string_refs) {
+ ref_entry.dest->index = util::HostToDevice32(ref_entry.ref.index());
+ }
+
+ // Write the XML header.
+ ChunkWriter xml_header_writer(buffer_);
+ xml_header_writer.StartChunk<ResXMLTree_header>(RES_XML_TYPE);
+
+ // Flatten the StringPool.
+ StringPool::FlattenUtf8(buffer_, visitor.pool);
+
+ {
+ // Write the array of resource IDs, indexed by StringPool order.
+ ChunkWriter res_id_map_writer(buffer_);
+ res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
+ for (const auto& str : visitor.pool) {
+ ResourceId id = {str->context.priority};
+ if (id.id == kLowPriority || !id.is_valid()) {
+ // When we see the first non-resource ID,
+ // we're done.
+ break;
+ }
+
+ *res_id_map_writer.NextBlock<uint32_t>() = id.id;
}
+ res_id_map_writer.Finish();
+ }
- // Write the XML header.
- ChunkWriter xmlHeaderWriter(mBuffer);
- xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
+ // Move the nodeBuffer and append it to the out buffer.
+ buffer_->AppendBuffer(std::move(node_buffer));
- // 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;
+ // Finish the xml header.
+ xml_header_writer.Finish();
+ return true;
}
-bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
- if (!resource->root) {
- return false;
- }
- return flatten(context, resource->root.get());
+bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) {
+ if (!resource->root) {
+ return false;
+ }
+ return Flatten(context, resource->root.get());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
index a688ac965b0d..f5129fd40e99 100644
--- a/tools/aapt2/flatten/XmlFlattener.h
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -17,6 +17,8 @@
#ifndef AAPT_FLATTEN_XMLFLATTENER_H
#define AAPT_FLATTEN_XMLFLATTENER_H
+#include "android-base/macros.h"
+
#include "process/IResourceTableConsumer.h"
#include "util/BigBuffer.h"
#include "xml/XmlDom.h"
@@ -24,32 +26,33 @@
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;
+ /**
+ * Keep attribute raw string values along with typed values.
+ */
+ bool keep_raw_values = false;
+
+ /**
+ * If set, the max SDK level of attribute to flatten. All others are ignored.
+ */
+ Maybe<size_t> max_sdk_level;
};
class XmlFlattener : public IXmlResourceConsumer {
-public:
- XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) :
- mBuffer(buffer), mOptions(options) {
- }
+ public:
+ XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options)
+ : buffer_(buffer), options_(options) {}
+
+ bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlFlattener);
-private:
- BigBuffer* mBuffer;
- XmlFlattenerOptions mOptions;
+ bool Flatten(IAaptContext* context, xml::Node* node);
- bool flatten(IAaptContext* context, xml::Node* node);
+ BigBuffer* buffer_;
+ XmlFlattenerOptions options_;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FLATTEN_XMLFLATTENER_H */
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index 4e6eb811e572..2c83bb384cc5 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -15,196 +15,251 @@
*/
#include "flatten/XmlFlattener.h"
+
+#include "androidfw/ResourceTypes.h"
+
#include "link/Linkers.h"
-#include "test/Builders.h"
-#include "test/Context.h"
+#include "test/Test.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();
+ public:
+ void SetUp() override {
+ context_ =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .AddSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .AddSymbol("android:attr/id", ResourceId(0x010100d0),
+ test::AttributeBuilder().Build())
+ .AddSymbol("com.app.test:id/id", ResourceId(0x7f020000))
+ .AddSymbol("android:attr/paddingStart",
+ ResourceId(0x010103b3),
+ test::AttributeBuilder().Build())
+ .AddSymbol("android:attr/colorAccent",
+ ResourceId(0x01010435),
+ test::AttributeBuilder().Build())
+ .Build())
+ .Build();
+ }
+
+ ::testing::AssertionResult Flatten(xml::XmlResource* doc,
+ android::ResXMLTree* out_tree,
+ const 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(context_.get(), doc)) {
+ return ::testing::AssertionFailure() << "failed to flatten XML Tree";
}
- ::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();
+ std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
+ if (out_tree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
+ return ::testing::AssertionFailure() << "flattened XML is corrupt";
}
+ return ::testing::AssertionSuccess();
+ }
-protected:
- std::unique_ptr<IAaptContext> mContext;
+ protected:
+ std::unique_ptr<IAaptContext> context_;
};
TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ 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));
- android::ResXMLTree tree;
- ASSERT_TRUE(flatten(doc.get(), &tree));
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
- ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
+ size_t len;
+ const char16_t* namespace_prefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespace_prefix, len), u"test");
- size_t len;
- const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
- EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+ const char16_t* namespace_uri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespace_uri, len), u"http://com.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.next(), android::ResXMLTree::START_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ const char16_t* tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, len), u"View");
- 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* attr_name = tree.getAttributeName(0, &len);
+ EXPECT_EQ(StringPiece16(attr_name, len), u"attr");
- 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()));
- EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size()));
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
- ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, len), u"Layout");
- 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* attr_namespace = tree.getAttributeNamespace(0, &len);
+ EXPECT_EQ(StringPiece16(attr_namespace, len), u"http://com.test");
- ASSERT_EQ(1u, tree.getAttributeCount());
- const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len);
- EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test");
+ attr_name = tree.getAttributeName(0, &len);
+ EXPECT_EQ(StringPiece16(attr_name, len), u"hello");
- 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.next(), android::ResXMLTree::END_TAG);
- ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, len), u"Layout");
+ ASSERT_EQ(0u, tree.getAttributeCount());
- 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::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);
+ tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, 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"Layout");
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, len), u"View");
- 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);
+ namespace_prefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespace_prefix, len), u"test");
- ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
- namespacePrefix = tree.getNamespacePrefix(&len);
- EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+ namespace_uri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespace_uri, len), u"http://com.test");
- namespaceUri = tree.getNamespaceUri(&len);
- ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
-
- ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
}
TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ 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);
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+ ASSERT_TRUE(linker.sdk_levels().count(17) == 1);
+ ASSERT_TRUE(linker.sdk_levels().count(21) == 1);
- android::ResXMLTree tree;
- XmlFlattenerOptions options;
- options.maxSdkLevel = 17;
- ASSERT_TRUE(flatten(doc.get(), &tree, options));
+ android::ResXMLTree tree;
+ XmlFlattenerOptions options;
+ options.max_sdk_level = 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);
- }
+ 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));
+ ASSERT_EQ(1u, tree.getAttributeCount());
+ EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
+}
+
+TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
+ <View xmlns:tools="http://schemas.android.com/tools"
+ xmlns:foo="http://schemas.android.com/foo"
+ foo:bar="Foo"
+ tools:ignore="MissingTranslation"/>)EOF");
+
+ android::ResXMLTree tree;
+ ASSERT_TRUE(Flatten(doc.get(), &tree));
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
+
+ size_t len;
+ const char16_t* namespace_prefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo");
+
+ const char16_t* namespace_uri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespace_uri, len),
+ u"http://schemas.android.com/foo");
+
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+ EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
+ android::NAME_NOT_FOUND);
+ EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0);
}
TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ 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));
+ 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);
- }
+ 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);
+ EXPECT_EQ(tree.indexOfClass(), 0);
+ EXPECT_EQ(tree.indexOfStyle(), 1);
}
/*
- * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
+ * 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\"/>");
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDom("<View package=\"android\"/>");
- android::ResXMLTree tree;
- ASSERT_TRUE(flatten(doc.get(), &tree));
+ 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);
- }
+ 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);
+}
+
+TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDom("<View package=\"\"/>");
+
+ 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";
+ ssize_t idx =
+ tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
+ ASSERT_GE(idx, 0);
- const StringPiece16 kPackage = u"package";
- EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
+ size_t len;
+ EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml
index b6d8f2d1b748..1156b01fe70d 100644
--- a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml
+++ b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml
@@ -14,4 +14,7 @@
limitations under the License.
-->
-<manifest package="com.android.aapt.app.one" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.aapt.app.one" coreApp="true">
+ <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png b/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png
new file mode 100644
index 000000000000..0522a9979db9
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png
new file mode 100644
index 000000000000..baf9fff13ab5
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png
new file mode 100644
index 000000000000..7b331e16fcbd
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png
new file mode 100644
index 000000000000..0ec6c70a2b9f
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png
new file mode 100644
index 000000000000..e05708a089a3
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png
new file mode 100644
index 000000000000..a11377a0d670
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png
new file mode 100644
index 000000000000..6803e4243484
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png
new file mode 100644
index 000000000000..1a3731bbc8b8
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png
new file mode 100644
index 000000000000..489ace292e1f
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf b/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf
diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf b/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf
diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml b/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml
new file mode 100644
index 000000000000..1fb67914894e
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/myfont-normal" />
+ <font android:fontStyle="italic" android:fontWeight="400" android:font="@font/myfont-italic" />
+</font-family>
diff --git a/tools/aapt2/integration-tests/AppOne/res/layout/special.xml b/tools/aapt2/integration-tests/AppOne/res/layout/special.xml
new file mode 100644
index 000000000000..28c85ca92019
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/layout/special.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <include>
+ <aapt:attr name="layout" xmlns:aapt="http://schemas.android.com/aapt">
+ <RelativeLayout android:id="@+id/hello" />
+ </aapt:attr>
+ </include>
+</View>
diff --git a/tools/aapt2/integration-tests/AppOne/res/values/test.xml b/tools/aapt2/integration-tests/AppOne/res/values/test.xml
index f4b7471aefae..91f8bfd0dd14 100644
--- a/tools/aapt2/integration-tests/AppOne/res/values/test.xml
+++ b/tools/aapt2/integration-tests/AppOne/res/values/test.xml
@@ -29,4 +29,11 @@
<flag name="pub" value="2" />
<flag name="weak" value="4" />
</attr>
+
+ <!-- Override the Widget styleable declared in StaticLibOne.
+ This should merge the two when built in overlay mode. -->
+ <declare-styleable name="Widget">
+ <attr name="android:text" />
+ <attr name="layout_width" />
+ </declare-styleable>
</resources>
diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml
index d09a4851f7b4..b4dc90b3e640 100644
--- a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml
+++ b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml
@@ -23,5 +23,6 @@
<declare-styleable name="Widget">
<attr name="StaticLibOne_attr" />
+ <attr name="android:text" />
</declare-styleable>
</resources>
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 467e60464a68..fdc044d86e5a 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -17,84 +17,97 @@
#ifndef AAPT_IO_DATA_H
#define AAPT_IO_DATA_H
-#include <utils/FileMap.h>
-
#include <memory>
+#include "android-base/macros.h"
+#include "utils/FileMap.h"
+
namespace aapt {
namespace io {
/**
- * Interface for a block of contiguous memory. An instance of this interface owns the data.
+ * Interface for a block of contiguous memory. An instance of this interface
+ * owns the data.
*/
class IData {
-public:
- virtual ~IData() = default;
+ public:
+ virtual ~IData() = default;
+
+ virtual const void* data() const = 0;
+ virtual size_t size() const = 0;
+};
+
+class DataSegment : public IData {
+ public:
+ explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len)
+ : data_(std::move(data)), offset_(offset), len_(len) {}
+
+ const void* data() const override {
+ return static_cast<const uint8_t*>(data_->data()) + offset_;
+ }
- virtual const void* data() const = 0;
- virtual size_t size() const = 0;
+ size_t size() const override { return len_; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DataSegment);
+
+ std::unique_ptr<IData> data_;
+ size_t offset_;
+ size_t len_;
};
/**
- * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this
+ * 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)) {
- }
+ public:
+ explicit MmappedData(android::FileMap&& map)
+ : map_(std::forward<android::FileMap>(map)) {}
- const void* data() const override {
- return mMap.getDataPtr();
- }
+ const void* data() const override { return map_.getDataPtr(); }
- size_t size() const override {
- return mMap.getDataLength();
- }
+ size_t size() const override { return map_.getDataLength(); }
-private:
- android::FileMap mMap;
+ private:
+ android::FileMap map_;
};
/**
- * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The
+ * 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;
+ public:
+ MallocData(std::unique_ptr<const uint8_t[]> data, size_t size)
+ : data_(std::move(data)), size_(size) {}
+
+ const void* data() const override { return data_.get(); }
+
+ size_t size() const override { return size_; }
+
+ private:
+ std::unique_ptr<const uint8_t[]> data_;
+ size_t size_;
};
/**
- * When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0.
+ * 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;
- }
+ 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
+} // namespace io
+} // namespace aapt
#endif /* AAPT_IO_DATA_H */
diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp
new file mode 100644
index 000000000000..ee737280ec81
--- /dev/null
+++ b/tools/aapt2/io/File.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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 "io/File.h"
+
+#include <memory>
+
+namespace aapt {
+namespace io {
+
+IFile* IFile::CreateFileSegment(size_t offset, size_t len) {
+ FileSegment* file_segment = new FileSegment(this, offset, len);
+ segments_.push_back(std::unique_ptr<IFile>(file_segment));
+ return file_segment;
+}
+
+std::unique_ptr<IData> FileSegment::OpenAsData() {
+ std::unique_ptr<IData> data = file_->OpenAsData();
+ if (!data) {
+ return {};
+ }
+
+ if (offset_ <= data->size() - len_) {
+ return util::make_unique<DataSegment>(std::move(data), offset_, len_);
+ }
+ return {};
+}
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index b4d49719aa3e..644f59f8f3ba 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -17,62 +17,104 @@
#ifndef AAPT_IO_FILE_H
#define AAPT_IO_FILE_H
-#include "Source.h"
-#include "io/Data.h"
-
+#include <list>
#include <memory>
#include <vector>
+#include "android-base/macros.h"
+
+#include "Source.h"
+#include "io/Data.h"
+#include "util/Util.h"
+
namespace aapt {
namespace io {
/**
- * Interface for a file, which could be a real file on the file system, or a file inside
+ * 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;
+ 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;
+
+ IFile* CreateFileSegment(size_t offset, size_t len);
+
+ private:
+ // Any segments created from this IFile need to be owned by this IFile, so
+ // keep them
+ // in a list. This will never be read, so we prefer better insertion
+ // performance
+ // than cache locality, hence the list.
+ std::list<std::unique_ptr<IFile>> segments_;
+};
+
+/**
+ * An IFile that wraps an underlying IFile but limits it to a subsection of that
+ * file.
+ */
+class FileSegment : public IFile {
+ public:
+ explicit FileSegment(IFile* file, size_t offset, size_t len)
+ : file_(file), offset_(offset), len_(len) {}
+
+ std::unique_ptr<IData> OpenAsData() override;
+
+ const Source& GetSource() const override { return file_->GetSource(); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileSegment);
+
+ IFile* file_;
+ size_t offset_;
+ size_t len_;
};
class IFileCollectionIterator {
-public:
- virtual ~IFileCollectionIterator() = default;
+ public:
+ virtual ~IFileCollectionIterator() = default;
- virtual bool hasNext() = 0;
- virtual IFile* next() = 0;
+ virtual bool HasNext() = 0;
+ virtual IFile* Next() = 0;
};
/**
- * Interface for a collection of files, all of which share a common source. That source may
+ * 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;
+ public:
+ virtual ~IFileCollection() = default;
- virtual IFile* findFile(const StringPiece& path) = 0;
- virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0;
+ virtual IFile* FindFile(const StringPiece& path) = 0;
+ virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0;
};
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
#endif /* AAPT_IO_FILE_H */
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index e758d8a421e1..828f34e9c883 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -14,65 +14,62 @@
* limitations under the License.
*/
-#include "Source.h"
#include "io/FileSystem.h"
+
+#include "utils/FileMap.h"
+
+#include "Source.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) {
-}
+RegularFile::RegularFile(const Source& source) : source_(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>();
+std::unique_ptr<IData> RegularFile::OpenAsData() {
+ android::FileMap map;
+ if (Maybe<android::FileMap> map = file::MmapPath(source_.path, nullptr)) {
+ if (map.value().getDataPtr() && map.value().getDataLength() > 0) {
+ return util::make_unique<MmappedData>(std::move(map.value()));
}
- return {};
+ return util::make_unique<EmptyData>();
+ }
+ return {};
}
-const Source& RegularFile::getSource() const {
- return mSource;
-}
+const Source& RegularFile::GetSource() const { return source_; }
-FileCollectionIterator::FileCollectionIterator(FileCollection* collection) :
- mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) {
-}
+FileCollectionIterator::FileCollectionIterator(FileCollection* collection)
+ : current_(collection->files_.begin()), end_(collection->files_.end()) {}
-bool FileCollectionIterator::hasNext() {
- return mCurrent != mEnd;
-}
+bool FileCollectionIterator::HasNext() { return current_ != end_; }
-IFile* FileCollectionIterator::next() {
- IFile* result = mCurrent->second.get();
- ++mCurrent;
- return result;
+IFile* FileCollectionIterator::Next() {
+ IFile* result = current_->second.get();
+ ++current_;
+ return result;
}
-IFile* FileCollection::insertFile(const StringPiece& path) {
- return (mFiles[path.toString()] = util::make_unique<RegularFile>(Source(path))).get();
+IFile* FileCollection::InsertFile(const StringPiece& path) {
+ return (files_[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;
+IFile* FileCollection::FindFile(const StringPiece& path) {
+ auto iter = files_.find(path.ToString());
+ if (iter != files_.end()) {
+ return iter->second.get();
+ }
+ return nullptr;
}
-std::unique_ptr<IFileCollectionIterator> FileCollection::iterator() {
- return util::make_unique<FileCollectionIterator>(this);
+std::unique_ptr<IFileCollectionIterator> FileCollection::Iterator() {
+ return util::make_unique<FileCollectionIterator>(this);
}
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index f0559c03a8b8..84f851ff694b 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -17,10 +17,10 @@
#ifndef AAPT_IO_FILESYSTEM_H
#define AAPT_IO_FILESYSTEM_H
-#include "io/File.h"
-
#include <map>
+#include "io/File.h"
+
namespace aapt {
namespace io {
@@ -28,47 +28,47 @@ namespace io {
* A regular file from the file system. Uses mmap to open the data.
*/
class RegularFile : public IFile {
-public:
- RegularFile(const Source& source);
+ public:
+ explicit RegularFile(const Source& source);
- std::unique_ptr<IData> openAsData() override;
- const Source& getSource() const override;
+ std::unique_ptr<IData> OpenAsData() override;
+ const Source& GetSource() const override;
-private:
- Source mSource;
+ private:
+ Source source_;
};
class FileCollection;
class FileCollectionIterator : public IFileCollectionIterator {
-public:
- FileCollectionIterator(FileCollection* collection);
+ public:
+ explicit FileCollectionIterator(FileCollection* collection);
- bool hasNext() override;
- io::IFile* next() override;
+ bool HasNext() override;
+ io::IFile* Next() override;
-private:
- std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
+ private:
+ std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_;
};
/**
* 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;
+ 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;
+ private:
+ friend class FileCollectionIterator;
+ std::map<std::string, std::unique_ptr<IFile>> files_;
};
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
-#endif // AAPT_IO_FILESYSTEM_H
+#endif // AAPT_IO_FILESYSTEM_H
diff --git a/tools/aapt2/io/Io.cpp b/tools/aapt2/io/Io.cpp
new file mode 100644
index 000000000000..cab4b65f2f5a
--- /dev/null
+++ b/tools/aapt2/io/Io.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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 "io/Io.h"
+
+#include <algorithm>
+#include <cstring>
+
+namespace aapt {
+namespace io {
+
+bool Copy(OutputStream* out, InputStream* in) {
+ const void* in_buffer;
+ int in_len;
+ while (in->Next(&in_buffer, &in_len)) {
+ void* out_buffer;
+ int out_len;
+ if (!out->Next(&out_buffer, &out_len)) {
+ return !out->HadError();
+ }
+
+ const int bytes_to_copy = std::min(in_len, out_len);
+ memcpy(out_buffer, in_buffer, bytes_to_copy);
+ out->BackUp(out_len - bytes_to_copy);
+ in->BackUp(in_len - bytes_to_copy);
+ }
+ return !in->HadError();
+}
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h
new file mode 100644
index 000000000000..33cdc7bbe498
--- /dev/null
+++ b/tools/aapt2/io/Io.h
@@ -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.
+ */
+
+#ifndef AAPT_IO_IO_H
+#define AAPT_IO_IO_H
+
+#include <string>
+
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+namespace aapt {
+namespace io {
+
+/**
+ * InputStream interface that inherits from protobuf's ZeroCopyInputStream,
+ * but adds error handling methods to better report issues.
+ *
+ * The code style here matches the protobuf style.
+ */
+class InputStream : public ::google::protobuf::io::ZeroCopyInputStream {
+ public:
+ virtual std::string GetError() const { return {}; }
+
+ virtual bool HadError() const = 0;
+};
+
+/**
+ * OutputStream interface that inherits from protobuf's ZeroCopyOutputStream,
+ * but adds error handling methods to better report issues.
+ *
+ * The code style here matches the protobuf style.
+ */
+class OutputStream : public ::google::protobuf::io::ZeroCopyOutputStream {
+ public:
+ virtual std::string GetError() const { return {}; }
+
+ virtual bool HadError() const = 0;
+};
+
+/**
+ * Copies the data from in to out. Returns true if there was no error.
+ * If there was an error, check the individual streams' HadError/GetError
+ * methods.
+ */
+bool Copy(OutputStream* out, InputStream* in);
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_IO_H */
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index b3e7a02102e4..f4a128eca9d1 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -14,129 +14,128 @@
* limitations under the License.
*/
-#include "Source.h"
#include "io/ZipArchive.h"
-#include "util/Util.h"
-#include <utils/FileMap.h>
-#include <ziparchive/zip_archive.h>
+#include "utils/FileMap.h"
+#include "ziparchive/zip_archive.h"
+
+#include "Source.h"
+#include "util/Util.h"
namespace aapt {
namespace io {
-ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) :
- mZipHandle(handle), mZipEntry(entry), mSource(source) {
-}
+ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry,
+ const Source& source)
+ : zip_handle_(handle), zip_entry_(entry), source_(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);
- }
-}
+std::unique_ptr<IData> ZipFile::OpenAsData() {
+ if (zip_entry_.method == kCompressStored) {
+ int fd = GetFileDescriptor(zip_handle_);
-const Source& ZipFile::getSource() const {
- return mSource;
+ android::FileMap file_map;
+ bool result = file_map.create(nullptr, fd, zip_entry_.offset,
+ zip_entry_.uncompressed_length, true);
+ if (!result) {
+ return {};
+ }
+ return util::make_unique<MmappedData>(std::move(file_map));
+
+ } else {
+ std::unique_ptr<uint8_t[]> data =
+ std::unique_ptr<uint8_t[]>(new uint8_t[zip_entry_.uncompressed_length]);
+ int32_t result =
+ ExtractToMemory(zip_handle_, &zip_entry_, data.get(),
+ static_cast<uint32_t>(zip_entry_.uncompressed_length));
+ if (result != 0) {
+ return {};
+ }
+ return util::make_unique<MallocData>(std::move(data),
+ zip_entry_.uncompressed_length);
+ }
}
-ZipFileCollectionIterator::ZipFileCollectionIterator(ZipFileCollection* collection) :
- mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) {
-}
+const Source& ZipFile::GetSource() const { return source_; }
-bool ZipFileCollectionIterator::hasNext() {
- return mCurrent != mEnd;
-}
+ZipFileCollectionIterator::ZipFileCollectionIterator(
+ ZipFileCollection* collection)
+ : current_(collection->files_.begin()), end_(collection->files_.end()) {}
-IFile* ZipFileCollectionIterator::next() {
- IFile* result = mCurrent->second.get();
- ++mCurrent;
- return result;
-}
+bool ZipFileCollectionIterator::HasNext() { return current_ != end_; }
-ZipFileCollection::ZipFileCollection() : mHandle(nullptr) {
+IFile* ZipFileCollectionIterator::Next() {
+ IFile* result = current_->second.get();
+ ++current_;
+ return result;
}
-std::unique_ptr<ZipFileCollection> ZipFileCollection::create(const StringPiece& path,
- std::string* outError) {
- constexpr static const int32_t kEmptyArchive = -6;
+ZipFileCollection::ZipFileCollection() : handle_(nullptr) {}
- std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>(
- new ZipFileCollection());
+std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
+ const StringPiece& path, std::string* out_error) {
+ constexpr static const int32_t kEmptyArchive = -6;
- 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 {};
- }
+ std::unique_ptr<ZipFileCollection> collection =
+ std::unique_ptr<ZipFileCollection>(new ZipFileCollection());
- void* cookie = nullptr;
- result = StartIteration(collection->mHandle, &cookie, nullptr, nullptr);
- if (result != 0) {
- if (outError) *outError = ErrorCodeString(result);
- return {};
+ int32_t result = OpenArchive(path.data(), &collection->handle_);
+ 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;
}
- 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;
+ if (out_error) *out_error = ErrorCodeString(result);
+ return {};
+ }
+
+ void* cookie = nullptr;
+ result = StartIteration(collection->handle_, &cookie, nullptr, nullptr);
+ if (result != 0) {
+ if (out_error) *out_error = ErrorCodeString(result);
+ return {};
+ }
+
+ using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
+ IterationEnder iteration_ender(cookie, EndIteration);
+
+ ZipString zip_entry_name;
+ ZipEntry zip_data;
+ while ((result = Next(cookie, &zip_data, &zip_entry_name)) == 0) {
+ std::string zip_entry_path =
+ std::string(reinterpret_cast<const char*>(zip_entry_name.name),
+ zip_entry_name.name_length);
+ std::string nested_path = path.ToString() + "@" + zip_entry_path;
+ collection->files_[zip_entry_path] = util::make_unique<ZipFile>(
+ collection->handle_, zip_data, Source(nested_path));
+ }
+
+ if (result != -1) {
+ if (out_error) *out_error = 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;
+IFile* ZipFileCollection::FindFile(const StringPiece& path) {
+ auto iter = files_.find(path.ToString());
+ if (iter != files_.end()) {
+ return iter->second.get();
+ }
+ return nullptr;
}
-std::unique_ptr<IFileCollectionIterator> ZipFileCollection::iterator() {
- return util::make_unique<ZipFileCollectionIterator>(this);
+std::unique_ptr<IFileCollectionIterator> ZipFileCollection::Iterator() {
+ return util::make_unique<ZipFileCollectionIterator>(this);
}
ZipFileCollection::~ZipFileCollection() {
- if (mHandle) {
- CloseArchive(mHandle);
- }
+ if (handle_) {
+ CloseArchive(handle_);
+ }
}
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 5ad119d1d6d4..85ca1aed4edc 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -17,67 +17,70 @@
#ifndef AAPT_IO_ZIPARCHIVE_H
#define AAPT_IO_ZIPARCHIVE_H
-#include "io/File.h"
-#include "util/StringPiece.h"
+#include "ziparchive/zip_archive.h"
#include <map>
-#include <ziparchive/zip_archive.h>
+
+#include "io/File.h"
+#include "util/StringPiece.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.
+ * 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);
+ public:
+ ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source);
- std::unique_ptr<IData> openAsData() override;
- const Source& getSource() const override;
+ std::unique_ptr<IData> OpenAsData() override;
+ const Source& GetSource() const override;
-private:
- ZipArchiveHandle mZipHandle;
- ZipEntry mZipEntry;
- Source mSource;
+ private:
+ ZipArchiveHandle zip_handle_;
+ ZipEntry zip_entry_;
+ Source source_;
};
class ZipFileCollection;
class ZipFileCollectionIterator : public IFileCollectionIterator {
-public:
- ZipFileCollectionIterator(ZipFileCollection* collection);
+ public:
+ explicit ZipFileCollectionIterator(ZipFileCollection* collection);
- bool hasNext() override;
- io::IFile* next() override;
+ bool HasNext() override;
+ io::IFile* Next() override;
-private:
- std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
+ private:
+ std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_;
};
/**
* 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);
+ 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;
+ io::IFile* FindFile(const StringPiece& path) override;
+ std::unique_ptr<IFileCollectionIterator> Iterator() override;
- ~ZipFileCollection() override;
+ ~ZipFileCollection() override;
-private:
- friend class ZipFileCollectionIterator;
- ZipFileCollection();
+ private:
+ friend class ZipFileCollectionIterator;
+ ZipFileCollection();
- ZipArchiveHandle mHandle;
- std::map<std::string, std::unique_ptr<IFile>> mFiles;
+ ZipArchiveHandle handle_;
+ std::map<std::string, std::unique_ptr<IFile>> files_;
};
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
#endif /* AAPT_IO_ZIPARCHIVE_H */
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index b7e7f903a2b1..2951e5cff6d7 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -15,79 +15,71 @@
*/
#include "java/AnnotationProcessor.h"
-#include "util/Util.h"
#include <algorithm>
+#include "util/Util.h"
+
namespace aapt {
-void AnnotationProcessor::appendCommentLine(std::string& comment) {
- static const std::string sDeprecated = "@deprecated";
- static const std::string sSystemApi = "@SystemApi";
+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;
- }
+ if (comment.find(sDeprecated) != std::string::npos) {
+ annotation_bit_mask_ |= 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());
- }
+ std::string::size_type idx = comment.find(sSystemApi);
+ if (idx != std::string::npos) {
+ annotation_bit_mask_ |= kSystemApi;
+ comment.erase(comment.begin() + idx,
+ comment.begin() + idx + sSystemApi.size());
+ }
- if (util::trimWhitespace(comment).empty()) {
- return;
- }
+ if (util::TrimWhitespace(comment).empty()) {
+ return;
+ }
- if (!mHasComments) {
- mHasComments = true;
- mComment << "/**";
- }
+ if (!has_comments_) {
+ has_comments_ = true;
+ comment_ << "/**";
+ }
- mComment << "\n * " << std::move(comment);
+ comment_ << "\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) {
+ // We need to process line by line to clean-up whitespace and append prefixes.
+ for (StringPiece line : util::Tokenize(comment, '\n')) {
+ line = util::TrimWhitespace(line);
+ if (!line.empty()) {
+ std::string lineCopy = line.ToString();
+ AppendCommentLine(lineCopy);
}
+ }
}
-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::AppendNewLine() { comment_ << "\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";
+void AnnotationProcessor::WriteToStream(std::ostream* out,
+ const StringPiece& prefix) const {
+ if (has_comments_) {
+ std::string result = comment_.str();
+ for (StringPiece line : util::Tokenize(result, '\n')) {
+ *out << prefix << line << "\n";
}
+ *out << prefix << " */"
+ << "\n";
+ }
- if (mAnnotationBitMask & kDeprecated) {
- *out << prefix << "@Deprecated\n";
- }
+ if (annotation_bit_mask_ & kDeprecated) {
+ *out << prefix << "@Deprecated\n";
+ }
- if (mAnnotationBitMask & kSystemApi) {
- *out << prefix << "@android.annotation.SystemApi\n";
- }
+ if (annotation_bit_mask_ & kSystemApi) {
+ *out << prefix << "@android.annotation.SystemApi\n";
+ }
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index 8309dd978175..666a7f356768 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -17,11 +17,11 @@
#ifndef AAPT_JAVA_ANNOTATIONPROCESSOR_H
#define AAPT_JAVA_ANNOTATIONPROCESSOR_H
-#include "util/StringPiece.h"
-
#include <sstream>
#include <string>
+#include "util/StringPiece.h"
+
namespace aapt {
/**
@@ -52,35 +52,36 @@ namespace aapt {
*
*/
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);
+ public:
+ /**
+ * Adds more comments. Since resources can have various values with different
+ * configurations,
+ * we need to collect all the comments.
+ */
+ void AppendComment(const StringPiece& comment);
- void appendNewLine();
+ 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;
+ /**
+ * 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,
- };
+ private:
+ enum : uint32_t {
+ kDeprecated = 0x01,
+ kSystemApi = 0x02,
+ };
- std::stringstream mComment;
- std::stringstream mAnnotations;
- bool mHasComments = false;
- uint32_t mAnnotationBitMask = 0;
+ std::stringstream comment_;
+ std::stringstream mAnnotations;
+ bool has_comments_ = false;
+ uint32_t annotation_bit_mask_ = 0;
- void appendCommentLine(std::string& line);
+ void AppendCommentLine(std::string& line);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index 5a39add48fbd..3e43c4295c07 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -15,38 +15,39 @@
*/
#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! }";
+ 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);
+ AnnotationProcessor processor;
+ processor.AppendComment(comment);
- std::stringstream result;
- processor.writeToStream(&result, "");
- std::string annotations = result.str();
+ std::stringstream result;
+ processor.WriteToStream(&result, "");
+ std::string annotations = result.str();
- EXPECT_NE(std::string::npos, annotations.find("@Deprecated"));
+ EXPECT_NE(std::string::npos, annotations.find("@Deprecated"));
}
TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) {
- AnnotationProcessor processor;
- processor.appendComment("@SystemApi This is a system API");
+ AnnotationProcessor processor;
+ processor.AppendComment("@SystemApi This is a system API");
- std::stringstream result;
- processor.writeToStream(&result, "");
- std::string annotations = result.str();
+ 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"));
+ 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
-
-
+} // namespace aapt
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index 08f2c8b9805c..f1f1f925480c 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -15,61 +15,59 @@
*/
#include "java/ClassDefinition.h"
-#include "util/StringPiece.h"
-#include <ostream>
+#include "util/StringPiece.h"
namespace aapt {
bool ClassDefinition::empty() const {
- for (const std::unique_ptr<ClassMember>& member : mMembers) {
- if (!member->empty()) {
- return false;
- }
+ for (const std::unique_ptr<ClassMember>& member : members_) {
+ if (!member->empty()) {
+ return false;
}
- return true;
+ }
+ return true;
}
-void ClassDefinition::writeToStream(const StringPiece& prefix, bool final,
+void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final,
std::ostream* out) const {
- if (mMembers.empty() && !mCreateIfEmpty) {
- return;
- }
+ if (members_.empty() && !create_if_empty_) {
+ return;
+ }
- ClassMember::writeToStream(prefix, final, out);
+ ClassMember::WriteToStream(prefix, final, out);
- *out << prefix << "public ";
- if (mQualifier == ClassQualifier::Static) {
- *out << "static ";
- }
- *out << "final class " << mName << " {\n";
+ *out << prefix << "public ";
+ if (qualifier_ == ClassQualifier::Static) {
+ *out << "static ";
+ }
+ *out << "final class " << name_ << " {\n";
- std::string newPrefix = prefix.toString();
- newPrefix.append(kIndent);
+ std::string new_prefix = prefix.ToString();
+ new_prefix.append(kIndent);
- for (const std::unique_ptr<ClassMember>& member : mMembers) {
- member->writeToStream(newPrefix, final, out);
- *out << "\n";
- }
+ for (const std::unique_ptr<ClassMember>& member : members_) {
+ member->WriteToStream(new_prefix, final, out);
+ *out << "\n";
+ }
- *out << prefix << "}";
+ *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";
+ "/* 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,
+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);
+ *out << sWarningHeader << "package " << package << ";\n\n";
+ def->WriteToStream("", final, out);
+ return bool(*out);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index d45328fedba2..d8b61d919fd8 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -17,15 +17,16 @@
#ifndef AAPT_JAVA_CLASSDEFINITION_H
#define AAPT_JAVA_CLASSDEFINITION_H
+#include <ostream>
+#include <string>
+
+#include "android-base/macros.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.
@@ -33,46 +34,43 @@ constexpr static size_t kAttribsPerLine = 4;
constexpr static const char* kIndent = " ";
class ClassMember {
-public:
- virtual ~ClassMember() = default;
+ public:
+ virtual ~ClassMember() = default;
- AnnotationProcessor* getCommentBuilder() {
- return &mProcessor;
- }
+ AnnotationProcessor* GetCommentBuilder() { return &processor_; }
- virtual bool empty() const = 0;
+ virtual bool empty() const = 0;
- virtual void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const {
- mProcessor.writeToStream(out, prefix);
- }
+ virtual void WriteToStream(const StringPiece& prefix, bool final,
+ std::ostream* out) const {
+ processor_.WriteToStream(out, prefix);
+ }
-private:
- AnnotationProcessor mProcessor;
+ private:
+ AnnotationProcessor processor_;
};
template <typename T>
class PrimitiveMember : public ClassMember {
-public:
- PrimitiveMember(const StringPiece& name, const T& val) :
- mName(name.toString()), mVal(val) {
- }
+ public:
+ PrimitiveMember(const StringPiece& name, const T& val)
+ : name_(name.ToString()), val_(val) {}
- bool empty() const override {
- return false;
- }
+ bool empty() const override { return false; }
- void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
- ClassMember::writeToStream(prefix, final, out);
+ 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 << ";";
- }
+ *out << prefix << "public static " << (final ? "final " : "") << "int "
+ << name_ << "=" << val_ << ";";
+ }
-private:
- std::string mName;
- T mVal;
+ private:
+ std::string name_;
+ T val_;
- DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
};
/**
@@ -80,27 +78,25 @@ private:
*/
template <>
class PrimitiveMember<std::string> : public ClassMember {
-public:
- PrimitiveMember(const StringPiece& name, const std::string& val) :
- mName(name.toString()), mVal(val) {
- }
+ public:
+ PrimitiveMember(const StringPiece& name, const std::string& val)
+ : name_(name.ToString()), val_(val) {}
- bool empty() const override {
- return false;
- }
+ bool empty() const override { return false; }
- void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
- ClassMember::writeToStream(prefix, final, out);
+ 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 << "\";";
- }
+ *out << prefix << "public static " << (final ? "final " : "") << "String "
+ << name_ << "=\"" << val_ << "\";";
+ }
-private:
- std::string mName;
- std::string mVal;
+ private:
+ std::string name_;
+ std::string val_;
- DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
};
using IntMember = PrimitiveMember<uint32_t>;
@@ -109,80 +105,75 @@ using StringMember = PrimitiveMember<std::string>;
template <typename T>
class PrimitiveArrayMember : public ClassMember {
-public:
- PrimitiveArrayMember(const StringPiece& name) :
- mName(name.toString()) {
- }
+ public:
+ explicit PrimitiveArrayMember(const StringPiece& name)
+ : name_(name.ToString()) {}
- void addElement(const T& val) {
- mElements.push_back(val);
- }
+ void AddElement(const T& val) { elements_.push_back(val); }
- bool empty() const override {
- return false;
- }
+ bool empty() const override { return false; }
- void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
- ClassMember::writeToStream(prefix, final, out);
+ void WriteToStream(const StringPiece& prefix, bool final,
+ std::ostream* out) const override {
+ ClassMember::WriteToStream(prefix, final, out);
- *out << prefix << "public static final int[] " << mName << "={";
+ *out << prefix << "public static final int[] " << name_ << "={";
- 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;
- }
+ const auto begin = elements_.begin();
+ const auto end = elements_.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 <<"};";
+ *out << *current;
+ if (std::distance(current, end) > 1) {
+ *out << ", ";
+ }
}
+ *out << "\n" << prefix << kIndent << "};";
+ }
-private:
- std::string mName;
- std::vector<T> mElements;
+ private:
+ std::string name_;
+ std::vector<T> elements_;
- DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
};
using ResourceArrayMember = PrimitiveArrayMember<ResourceId>;
-enum class ClassQualifier {
- None,
- Static
-};
+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);
+ public:
+ static bool WriteJavaFile(const ClassDefinition* def,
+ const StringPiece& package, bool final,
+ std::ostream* out);
+
+ ClassDefinition(const StringPiece& name, ClassQualifier qualifier,
+ bool createIfEmpty)
+ : name_(name.ToString()),
+ qualifier_(qualifier),
+ create_if_empty_(createIfEmpty) {}
+
+ void AddMember(std::unique_ptr<ClassMember> member) {
+ members_.push_back(std::move(member));
+ }
+
+ bool empty() const override;
+ void WriteToStream(const StringPiece& prefix, bool final,
+ std::ostream* out) const override;
+
+ private:
+ std::string name_;
+ ClassQualifier qualifier_;
+ bool create_if_empty_;
+ std::vector<std::unique_ptr<ClassMember>> members_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_JAVA_CLASSDEFINITION_H */
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 84df0b429fc5..6e7c707847b9 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -14,60 +14,57 @@
* limitations under the License.
*/
+#include "java/JavaClassGenerator.h"
+
+#include <algorithm>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <tuple>
+
+#include "android-base/logging.h"
+
#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();
+static const std::set<StringPiece> sJavaIdentifiers = {
+ "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"};
+
+static bool IsValidSymbol(const StringPiece& 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 = '_';
- }
+static std::string Transform(const StringPiece& symbol) {
+ std::string output = symbol.ToString();
+ for (char& c : output) {
+ if (c == '.' || c == '-') {
+ c = '_';
}
- return output;
+ }
+ return output;
}
/**
@@ -81,477 +78,519 @@ static std::string transform(const StringPiece16& symbol) {
* 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 std::string TransformNestedAttr(
+ const ResourceNameRef& attr_name, const std::string& styleable_class_name,
+ const StringPiece& package_name_to_generate) {
+ std::string output = styleable_class_name;
+
+ // We may reference IDs from other packages, so prefix the entry name with
+ // the package.
+ if (!attr_name.package.empty() &&
+ package_name_to_generate != attr_name.package) {
+ output += "_" + Transform(attr_name.package);
+ }
+ output += "_" + Transform(attr_name.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>\".");
+static void AddAttributeFormatDoc(AnnotationProcessor* processor,
+ Attribute* attr) {
+ const uint32_t type_mask = attr->type_mask;
+ if (type_mask & 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 (type_mask & 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 (type_mask & android::ResTable_map::TYPE_INTEGER) {
+ processor->AppendComment(
+ "<p>May be an integer value, such as \"<code>100</code>\".");
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_BOOLEAN) {
+ processor->AppendComment(
+ "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
+ "\"<code>false</code>\".");
+ }
+
+ if (type_mask & 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 (type_mask & android::ResTable_map::TYPE_FLOAT) {
+ processor->AppendComment(
+ "<p>May be a floating point value, such as \"<code>1.2</code>\".");
+ }
+
+ if (type_mask & 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 (type_mask & 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 (type_mask &
+ (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
+ if (type_mask & 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>");
}
- 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;");
+ 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>");
+ }
+}
- if (typeMask & android::ResTable_map::TYPE_INTEGER) {
- processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
- }
+JavaClassGenerator::JavaClassGenerator(IAaptContext* context,
+ ResourceTable* table,
+ const JavaClassGeneratorOptions& options)
+ : context_(context), table_(table), options_(options) {}
- 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>\".");
- }
+bool JavaClassGenerator::SkipSymbol(SymbolState state) {
+ switch (options_.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;
+}
- 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>\".");
- }
+struct StyleableAttr {
+ const Reference* attr_ref;
+ std::string field_name;
+ std::unique_ptr<SymbolTable::Symbol> symbol;
+};
- if (typeMask & android::ResTable_map::TYPE_FLOAT) {
- processor->appendComment(
- "<p>May be a floating point value, such as \"<code>1.2</code>\".");
- }
+static bool less_styleable_attr(const StyleableAttr& lhs,
+ const StyleableAttr& rhs) {
+ const ResourceId lhs_id =
+ lhs.attr_ref->id ? lhs.attr_ref->id.value() : ResourceId(0);
+ const ResourceId rhs_id =
+ rhs.attr_ref->id ? rhs.attr_ref->id.value() : ResourceId(0);
+ if (lhs_id < rhs_id) {
+ return true;
+ } else if (lhs_id > rhs_id) {
+ return false;
+ } else {
+ return lhs.attr_ref->name.value() < rhs.attr_ref->name.value();
+ }
+}
- 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).");
+void JavaClassGenerator::AddMembersToStyleableClass(
+ const StringPiece& package_name_to_generate, const std::string& entry_name,
+ const Styleable* styleable, ClassDefinition* out_styleable_class_def) {
+ const std::string class_name = Transform(entry_name);
+
+ std::unique_ptr<ResourceArrayMember> styleable_array_def =
+ util::make_unique<ResourceArrayMember>(class_name);
+
+ // This must be sorted by resource ID.
+ std::vector<StyleableAttr> sorted_attributes;
+ sorted_attributes.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.
+ CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
+ CHECK(bool(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 styleable_attr = {};
+ styleable_attr.attr_ref = &attr;
+ styleable_attr.field_name = TransformNestedAttr(
+ attr.name.value(), class_name, package_name_to_generate);
+
+ Reference mangled_reference;
+ mangled_reference.id = attr.id;
+ mangled_reference.name = attr.name;
+ if (mangled_reference.name.value().package.empty()) {
+ mangled_reference.name.value().package =
+ context_->GetCompilationPackage();
}
- 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 (Maybe<ResourceName> mangled_name =
+ context_->GetNameMangler()->MangleName(
+ mangled_reference.name.value())) {
+ mangled_reference.name = mangled_name;
}
- 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>");
+ // 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 =
+ context_->GetExternalSymbols()->FindByReference(mangled_reference);
+ if (symbol && symbol->attribute) {
+ // Copy the symbol data structure because the returned instance can be
+ // destroyed.
+ styleable_attr.symbol = util::make_unique<SymbolTable::Symbol>(*symbol);
}
-}
-
-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;
+ sorted_attributes.push_back(std::move(styleable_attr));
+ }
+
+ // Sort the attributes by ID.
+ std::sort(sorted_attributes.begin(), sorted_attributes.end(),
+ less_styleable_attr);
+
+ const size_t attr_count = sorted_attributes.size();
+ if (attr_count > 0) {
+ // Build the comment string for the Styleable. It includes details about the
+ // child attributes.
+ std::stringstream styleable_comment;
+ if (!styleable->GetComment().empty()) {
+ styleable_comment << styleable->GetComment() << "\n";
+ } else {
+ styleable_comment << "Attributes that can be used with a " << class_name
+ << ".\n";
}
- 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();
+ styleable_comment << "<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 : sorted_attributes) {
+ if (!entry.symbol) {
+ continue;
+ }
+
+ if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+ !entry.symbol->is_public) {
+ // Don't write entries for non-public attributes.
+ continue;
+ }
+
+ StringPiece attr_comment_line = entry.symbol->attribute->GetComment();
+ if (attr_comment_line.contains("@removed")) {
+ // Removed attributes are public but hidden from the documentation, so
+ // don't emit
+ // them as part of the class documentation.
+ continue;
+ }
+
+ const ResourceName& attr_name = entry.attr_ref->name.value();
+ styleable_comment << "<tr><td>";
+ styleable_comment << "<code>{@link #" << entry.field_name << " "
+ << (!attr_name.package.empty()
+ ? attr_name.package
+ : context_->GetCompilationPackage())
+ << ":" << attr_name.entry << "}</code>";
+ styleable_comment << "</td>";
+
+ styleable_comment << "<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(attr_comment_line.begin(), attr_comment_line.end(), u'.');
+ if (iter != attr_comment_line.end()) {
+ attr_comment_line =
+ attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1);
+ }
+ styleable_comment << attr_comment_line << "</td></tr>\n";
+ }
+ styleable_comment << "</table>\n";
+
+ for (const StyleableAttr& entry : sorted_attributes) {
+ if (!entry.symbol) {
+ continue;
+ }
+
+ if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+ !entry.symbol->is_public) {
+ // Don't write entries for non-public attributes.
+ continue;
+ }
+ styleable_comment << "@see #" << entry.field_name << "\n";
}
-}
-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();
- }
+ styleable_array_def->GetCommentBuilder()->AppendComment(
+ styleable_comment.str());
+ }
- if (Maybe<ResourceName> mangledName =
- mContext->getNameMangler()->mangleName(mangledReference.name.value())) {
- mangledReference.name = mangledName;
- }
+ // Add the ResourceIds to the array member.
+ for (const StyleableAttr& styleable_attr : sorted_attributes) {
+ styleable_array_def->AddElement(styleable_attr.attr_ref->id
+ ? styleable_attr.attr_ref->id.value()
+ : ResourceId(0));
+ }
- // 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));
- }
+ // Add the Styleable array to the Styleable class.
+ out_styleable_class_def->AddMember(std::move(styleable_array_def));
- // 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";
- }
+ // Now we emit the indices into the array.
+ for (size_t i = 0; i < attr_count; i++) {
+ const StyleableAttr& styleable_attr = sorted_attributes[i];
- 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";
- }
+ if (!styleable_attr.symbol) {
+ continue;
+ }
- styleableArrayDef->getCommentBuilder()->appendComment(styleableComment.str());
+ if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+ !styleable_attr.symbol->is_public) {
+ // Don't write entries for non-public attributes.
+ continue;
}
- // Add the ResourceIds to the array member.
- for (const StyleableAttr& styleableAttr : sortedAttributes) {
- styleableArrayDef->addElement(
- styleableAttr.attrRef->id ? styleableAttr.attrRef->id.value() : ResourceId(0));
+ StringPiece comment = styleable_attr.attr_ref->GetComment();
+ if (styleable_attr.symbol->attribute && comment.empty()) {
+ comment = styleable_attr.symbol->attribute->GetComment();
}
- // Add the Styleable array to the Styleable class.
- outStyleableClassDef->addMember(std::move(styleableArrayDef));
+ if (comment.contains("@removed")) {
+ // Removed attributes are public but hidden from the documentation, so
+ // don't emit them
+ // as part of the class documentation.
+ continue;
+ }
- // Now we emit the indices into the array.
- for (size_t i = 0; i < attrCount; i++) {
- const StyleableAttr& styleableAttr = sortedAttributes[i];
+ const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
- if (!styleableAttr.symbol) {
- continue;
- }
+ StringPiece package_name = attr_name.package;
+ if (package_name.empty()) {
+ package_name = context_->GetCompilationPackage();
+ }
- if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
- !styleableAttr.symbol->isPublic) {
- // Don't write entries for non-public attributes.
- continue;
- }
+ std::unique_ptr<IntMember> index_member = util::make_unique<IntMember>(
+ sorted_attributes[i].field_name, static_cast<uint32_t>(i));
- StringPiece16 comment = styleableAttr.attrRef->getComment();
- if (styleableAttr.symbol->attribute && comment.empty()) {
- comment = styleableAttr.symbol->attribute->getComment();
- }
+ AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
- 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;
- }
+ if (!comment.empty()) {
+ attr_processor->AppendComment("<p>\n@attr description");
+ attr_processor->AppendComment(comment);
+ } else {
+ std::stringstream default_comment;
+ default_comment << "<p>This symbol is the offset where the "
+ << "{@link " << package_name << ".R.attr#"
+ << Transform(attr_name.entry) << "}\n"
+ << "attribute's value can be found in the "
+ << "{@link #" << class_name << "} array.";
+ attr_processor->AppendComment(default_comment.str());
+ }
- const ResourceName& attrName = styleableAttr.attrRef->name.value();
+ attr_processor->AppendNewLine();
- StringPiece16 packageName = attrName.package;
- if (packageName.empty()) {
- packageName = mContext->getCompilationPackage();
- }
+ AddAttributeFormatDoc(attr_processor,
+ styleable_attr.symbol->attribute.get());
+ attr_processor->AppendNewLine();
- 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());
- }
+ std::stringstream doclava_name;
+ doclava_name << "@attr name " << package_name << ":" << attr_name.entry;
- attrProcessor->appendNewLine();
+ attr_processor->AppendComment(doclava_name.str());
- addAttributeFormatDoc(attrProcessor, styleableAttr.symbol->attribute.get());
- attrProcessor->appendNewLine();
+ out_styleable_class_def->AddMember(std::move(index_member));
+ }
+}
- std::stringstream doclavaName;
- doclavaName << "@attr name " << packageName << ":" << attrName.entry;;
- attrProcessor->appendComment(doclavaName.str());
+bool JavaClassGenerator::AddMembersToTypeClass(
+ const StringPiece& package_name_to_generate,
+ const ResourceTablePackage* package, const ResourceTableType* type,
+ ClassDefinition* out_type_class_def) {
+ for (const auto& entry : type->entries) {
+ if (SkipSymbol(entry->symbol_status.state)) {
+ continue;
+ }
- outStyleableClassDef->addMember(std::move(indexMember));
+ ResourceId id;
+ if (package->id && type->id && entry->id) {
+ id = ResourceId(package->id.value(), type->id.value(), entry->id.value());
}
-}
-bool JavaClassGenerator::addMembersToTypeClass(const StringPiece16& packageNameToGenerate,
- const ResourceTablePackage* package,
- const ResourceTableType* type,
- ClassDefinition* outTypeClassDef) {
+ std::string unmangled_package;
+ std::string unmangled_name = entry->name;
+ if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
+ // The entry name was mangled, and we successfully unmangled it.
+ // Check that we want to emit this symbol.
+ if (package->name != unmangled_package) {
+ // Skip the entry if it doesn't belong to the package we're writing.
+ continue;
+ }
+ } else if (package_name_to_generate != package->name) {
+ // We are processing a mangled package name,
+ // but this is a non-mangled resource.
+ continue;
+ }
- for (const auto& entry : type->entries) {
- if (skipSymbol(entry->symbolStatus.state)) {
- continue;
- }
+ if (!IsValidSymbol(unmangled_name)) {
+ ResourceNameRef resource_name(package_name_to_generate, type->type,
+ unmangled_name);
+ std::stringstream err;
+ err << "invalid symbol name '" << resource_name << "'";
+ error_ = err.str();
+ return false;
+ }
- ResourceId id;
- if (package->id && type->id && entry->id) {
- id = ResourceId(package->id.value(), type->id.value(), entry->id.value());
- }
+ if (type->type == ResourceType::kStyleable) {
+ CHECK(!entry->values.empty());
- 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;
- }
+ const Styleable* styleable =
+ static_cast<const Styleable*>(entry->values.front()->value.get());
- if (!isValidSymbol(unmangledName)) {
- ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName);
- std::stringstream err;
- err << "invalid symbol name '" << resourceName << "'";
- mError = err.str();
- return false;
+ // Comments are handled within this method.
+ AddMembersToStyleableClass(package_name_to_generate, unmangled_name,
+ styleable, out_type_class_def);
+ } else {
+ std::unique_ptr<ResourceMember> resource_member =
+ util::make_unique<ResourceMember>(Transform(unmangled_name), id);
+
+ // Build the comments and annotations for this entry.
+ AnnotationProcessor* processor = resource_member->GetCommentBuilder();
+
+ // Add the comments from any <public> tags.
+ if (entry->symbol_status.state != SymbolState::kUndefined) {
+ processor->AppendComment(entry->symbol_status.comment);
+ }
+
+ // Add the comments from all configurations of this entry.
+ for (const auto& config_value : entry->values) {
+ processor->AppendComment(config_value->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);
}
+ }
- 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));
- }
+ out_type_class_def->AddMember(std::move(resource_member));
}
- return true;
+ }
+ return true;
}
-bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
- return generate(packageNameToGenerate, packageNameToGenerate, out);
+bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
+ std::ostream* out) {
+ return Generate(package_name_to_generate, package_name_to_generate, 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);
- }
+static void AppendJavaDocAnnotations(
+ const std::vector<std::string>& annotations,
+ AnnotationProcessor* processor) {
+ for (const std::string& annotation : annotations) {
+ std::string proper_annotation = "@";
+ proper_annotation += annotation;
+ processor->AppendComment(proper_annotation);
+ }
}
-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));
- }
- }
+bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
+ const StringPiece& out_package_name,
+ std::ostream* out) {
+ ClassDefinition r_class("R", ClassQualifier::None, true);
+
+ for (const auto& package : table_->packages) {
+ for (const auto& type : package->types) {
+ if (type->type == ResourceType::kAttrPrivate) {
+ continue;
+ }
- appendJavaDocAnnotations(mOptions.javadocAnnotations, rClass.getCommentBuilder());
+ const bool force_creation_if_empty =
+ (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
- if (!ClassDefinition::writeJavaFile(&rClass, util::utf16ToUtf8(outPackageName),
- mOptions.useFinal, out)) {
+ std::unique_ptr<ClassDefinition> class_def =
+ util::make_unique<ClassDefinition>(ToString(type->type),
+ ClassQualifier::Static,
+ force_creation_if_empty);
+
+ bool result = AddMembersToTypeClass(
+ package_name_to_generate, package.get(), type.get(), class_def.get());
+ if (!result) {
return false;
+ }
+
+ if (type->type == ResourceType::kAttr) {
+ // Also include private attributes in this same class.
+ ResourceTableType* priv_type =
+ package->FindType(ResourceType::kAttrPrivate);
+ if (priv_type) {
+ result =
+ AddMembersToTypeClass(package_name_to_generate, package.get(),
+ priv_type, class_def.get());
+ if (!result) {
+ return false;
+ }
+ }
+ }
+
+ if (type->type == ResourceType::kStyleable &&
+ options_.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.
+ class_def->GetCommentBuilder()->AppendComment("@doconly");
+ }
+
+ AppendJavaDocAnnotations(options_.javadoc_annotations,
+ class_def->GetCommentBuilder());
+
+ r_class.AddMember(std::move(class_def));
}
+ }
- out->flush();
- return true;
+ AppendJavaDocAnnotations(options_.javadoc_annotations,
+ r_class.GetCommentBuilder());
+
+ if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name,
+ options_.use_final, out)) {
+ return false;
+ }
+
+ out->flush();
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 77e0ed76143a..190e73b66b9e 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -17,86 +17,88 @@
#ifndef AAPT_JAVA_CLASS_GENERATOR_H
#define AAPT_JAVA_CLASS_GENERATOR_H
+#include <ostream>
+#include <string>
+
#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;
+ /*
+ * Specifies whether to use the 'final' modifier
+ * on resource entries. Default is true.
+ */
+ bool use_final = 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> javadoc_annotations;
};
/*
* 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;
+ 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 StringPiece& packageNameToGenerate, std::ostream* out);
+
+ bool Generate(const StringPiece& packageNameToGenerate,
+ const StringPiece& outputPackageName, std::ostream* out);
+
+ const std::string& getError() const;
+
+ private:
+ bool AddMembersToTypeClass(const StringPiece& packageNameToGenerate,
+ const ResourceTablePackage* package,
+ const ResourceTableType* type,
+ ClassDefinition* outTypeClassDef);
+
+ void AddMembersToStyleableClass(const StringPiece& packageNameToGenerate,
+ const std::string& entryName,
+ const Styleable* styleable,
+ ClassDefinition* outStyleableClassDef);
+
+ bool SkipSymbol(SymbolState state);
+
+ IAaptContext* context_;
+ ResourceTable* table_;
+ JavaClassGeneratorOptions options_;
+ std::string error_;
};
inline const std::string& JavaClassGenerator::getError() const {
- return mError;
+ return error_;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_JAVA_CLASS_GENERATOR_H
+#endif // AAPT_JAVA_CLASS_GENERATOR_H
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 46266b3f3e89..3d3d24e6aab5 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -15,154 +15,181 @@
*/
#include "java/JavaClassGenerator.h"
-#include "test/Test.h"
-#include "util/Util.h"
#include <sstream>
#include <string>
+#include "test/Test.h"
+#include "util/Util.h"
+
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));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/class", ResourceId(0x01020000))
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ std::stringstream out;
+ EXPECT_FALSE(generator.Generate("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;"));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/hey-man", ResourceId(0x01020000))
+ .AddValue("android:attr/cool.attr", ResourceId(0x01010000),
+ test::AttributeBuilder(false).Build())
+ .AddValue(
+ "android:styleable/hey.dude", ResourceId(0x01030000),
+ test::StyleableBuilder()
+ .AddItem("android:attr/cool.attr", ResourceId(0x01010000))
+ .Build())
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.Generate("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;"));
+}
- EXPECT_NE(std::string::npos,
- output.find("public static final int[] hey_dude={"));
+TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/one", ResourceId(0x01020000))
+ .AddSimple("android:id/com.foo$two", ResourceId(0x01020001))
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", "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"));
+}
- EXPECT_NE(std::string::npos,
- output.find("public static final int hey_dude_cool_attr=0;"));
+TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:attr/two", ResourceId(0x01010001))
+ .AddSimple("android:^attr-private/one", ResourceId(0x01010000))
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("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, 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(), {});
+TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
+ StdErrDiagnostics diag;
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/one", ResourceId(0x01020000))
+ .AddSimple("android:id/two", ResourceId(0x01020001))
+ .AddSimple("android:id/three", ResourceId(0x01020002))
+ .SetSymbolState("android:id/one", ResourceId(0x01020000),
+ SymbolState::kPublic)
+ .SetSymbolState("android:id/two", ResourceId(0x01020001),
+ SymbolState::kPrivate)
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"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", u"com.android.internal", &out));
-
+ ASSERT_TRUE(generator.Generate("android", &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_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"));
-}
+ EXPECT_EQ(std::string::npos, output.find("three"));
+ }
-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(), {});
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+ {
+ JavaClassGenerator generator(context.get(), table.get(), options);
std::stringstream out;
- ASSERT_TRUE(generator.generate(u"android", &out));
-
+ ASSERT_TRUE(generator.Generate("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"));
-}
+ 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"));
+ }
-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;"));
- }
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+ {
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("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;"));
+ }
}
/*
@@ -172,12 +199,15 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
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(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>()),
+ std::make_shared<MockResolver>(mTable, std::map<ResourceName,
+ResourceId>()),
{});
ASSERT_TRUE(linker.linkAndValidate());
@@ -197,129 +227,139 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
}*/
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="));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .SetPackageId("com.lib", 0x02)
+ .AddValue("android:attr/bar", ResourceId(0x01010000),
+ test::AttributeBuilder(false).Build())
+ .AddValue("com.lib:attr/bar", ResourceId(0x02010000),
+ test::AttributeBuilder(false).Build())
+ .AddValue("android:styleable/foo", ResourceId(0x01030000),
+ test::StyleableBuilder()
+ .AddItem("android:attr/bar", ResourceId(0x01010000))
+ .AddItem("com.lib:attr/bar", ResourceId(0x02010000))
+ .Build())
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.Generate("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(/**
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/foo", ResourceId(0x01010000))
+ .Build();
+ test::GetValue<Id>(table.get(), "android:id/foo")
+ ->SetComment(std::string("This is a comment\n@deprecated"));
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("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));
+ 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, CommentsForEnumAndFlagAttributesArePresent) {}
+
+TEST(JavaClassGeneratorTest,
+ CommentsForStyleablesAndNestedAttributesArePresent) {
+ Attribute attr(false);
+ attr.SetComment(StringPiece("This is an attribute"));
+
+ Styleable styleable;
+ styleable.entries.push_back(
+ Reference(test::ParseNameOrDie("android:attr/one")));
+ styleable.SetComment(StringPiece("This is a styleable"));
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddValue("android:attr/one", util::make_unique<Attribute>(attr))
+ .AddValue("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{"android"})
+ .Build();
+ JavaClassGeneratorOptions options;
+ options.use_final = false;
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("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(attr.GetComment().data()));
+ EXPECT_NE(std::string::npos, actual.find(styleable.GetComment().data()));
}
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));
+ Attribute attr(false);
+ attr.SetComment(StringPiece("removed"));
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddValue("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{"android"})
+ .Build();
+ JavaClassGeneratorOptions options;
+ options.use_final = false;
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", &out));
+ std::string actual = out.str();
+
+ 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
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index be8955ecdf83..db84f295db2a 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -14,111 +14,120 @@
* limitations under the License.
*/
+#include "java/ManifestClassGenerator.h"
+
+#include <algorithm>
+
#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 Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag,
+ const Source& source,
+ const StringPiece& value) {
+ const StringPiece sep = ".";
+ auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end());
+
+ StringPiece 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, "_");
+ if (iter != result.end()) {
+ diag->Error(DiagMessage(source) << "invalid character '"
+ << StringPiece(iter, 1) << "' in '"
+ << result << "'");
+ return {};
+ }
+
+ if (*result.begin() >= '0' && *result.begin() <= '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;
+static bool WriteSymbol(const Source& source, IDiagnostics* diag,
+ xml::Element* el, ClassDefinition* class_def) {
+ xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+ if (!attr) {
+ diag->Error(DiagMessage(source) << "<" << el->name
+ << "> must define 'android:name'");
+ return false;
+ }
+
+ Maybe<StringPiece> result = ExtractJavaIdentifier(
+ diag, source.WithLine(el->line_number), attr->value);
+ if (!result) {
+ return false;
+ }
+
+ std::unique_ptr<StringMember> string_member =
+ util::make_unique<StringMember>(result.value(), attr->value);
+ string_member->GetCommentBuilder()->AppendComment(el->comment);
+
+ class_def->AddMember(std::move(string_member));
+ 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 {};
+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 != "manifest" && !el->namespace_uri.empty()) {
+ diag->Error(DiagMessage(res->file.source)
+ << "no <manifest> root tag defined");
+ return {};
+ }
+
+ std::unique_ptr<ClassDefinition> permission_class =
+ util::make_unique<ClassDefinition>("permission", ClassQualifier::Static,
+ false);
+ std::unique_ptr<ClassDefinition> permission_group_class =
+ util::make_unique<ClassDefinition>("permission_group",
+ ClassQualifier::Static, false);
+
+ bool error = false;
+ std::vector<xml::Element*> children = el->GetChildElements();
+ for (xml::Element* child_el : children) {
+ if (child_el->namespace_uri.empty()) {
+ if (child_el->name == "permission") {
+ error |= !WriteSymbol(res->file.source, diag, child_el,
+ permission_class.get());
+ } else if (child_el->name == "permission-group") {
+ error |= !WriteSymbol(res->file.source, diag, child_el,
+ permission_group_class.get());
+ }
}
-
- 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;
+ }
+
+ if (error) {
+ return {};
+ }
+
+ std::unique_ptr<ClassDefinition> manifest_class =
+ util::make_unique<ClassDefinition>("Manifest", ClassQualifier::None,
+ false);
+ manifest_class->AddMember(std::move(permission_class));
+ manifest_class->AddMember(std::move(permission_group_class));
+ return manifest_class;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
index f565289393fb..b12202a8d137 100644
--- a/tools/aapt2/java/ManifestClassGenerator.h
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -19,15 +19,13 @@
#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);
+std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag,
+ xml::XmlResource* res);
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index d3bca7068cb2..5ebf508807e8 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -15,33 +15,33 @@
*/
#include "java/ManifestClassGenerator.h"
-#include "test/Builders.h"
-#include "test/Context.h"
-#include <gtest/gtest.h>
+#include "test/Test.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();
+static ::testing::AssertionResult GetManifestClassText(IAaptContext* context,
+ xml::XmlResource* res,
+ std::string* out_str) {
+ std::unique_ptr<ClassDefinition> manifest_class =
+ GenerateManifestClass(context->GetDiagnostics(), res);
+ if (!manifest_class) {
+ return ::testing::AssertionFailure() << "manifest_class == nullptr";
+ }
+
+ std::stringstream out;
+ if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true,
+ &out)) {
+ return ::testing::AssertionFailure() << "failed to write java file";
+ }
+
+ *out_str = 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(
+ 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" />
@@ -49,46 +49,51 @@ TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
<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);
+ std::string actual;
+ ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual));
+
+ const size_t permission_class_pos =
+ actual.find("public static final class permission {");
+ const size_t permission_croup_class_pos =
+ actual.find("public static final class permission_group {");
+ ASSERT_NE(std::string::npos, permission_class_pos);
+ ASSERT_NE(std::string::npos, permission_croup_class_pos);
+
+ //
+ // 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, permission_class_pos);
+ EXPECT_LT(pos, permission_croup_class_pos);
+
+ pos = actual.find(
+ "public static final String DO_DANGEROUS_THINGS="
+ "\"android.DO_DANGEROUS_THINGS\";");
+ EXPECT_GT(pos, permission_class_pos);
+ EXPECT_LT(pos, permission_croup_class_pos);
+
+ pos = actual.find(
+ "public static final String HUH=\"com.test.sample.permission.HUH\";");
+ EXPECT_GT(pos, permission_class_pos);
+ EXPECT_LT(pos, permission_croup_class_pos);
+
+ //
+ // Make sure these permissions are in the permission_group class
+ //
+
+ pos = actual.find(
+ "public static final String PERMISSION="
+ "\"foo.bar.PERMISSION\";");
+ EXPECT_GT(pos, permission_croup_class_pos);
+ 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(
+ 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. -->
@@ -101,36 +106,36 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
<permission android:name="android.permission.SECRET" />
</manifest>)EOF");
- std::string actual;
- ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual));
+ std::string actual;
+ ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual));
- const char* expectedAccessInternet =
-R"EOF( /**
+ const char* expected_access_internet =
+ 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));
+ EXPECT_NE(std::string::npos, actual.find(expected_access_internet));
- const char* expectedPlayOutside =
-R"EOF( /**
+ const char* expected_play_outside =
+ 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));
+ EXPECT_NE(std::string::npos, actual.find(expected_play_outside));
- const char* expectedSecret =
-R"EOF( /**
+ const char* expected_secret =
+ 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));
+ EXPECT_NE(std::string::npos, actual.find(expected_secret));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index c610bb0f2ff2..624a559c4dae 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -15,231 +15,281 @@
*/
#include "java/ProguardRules.h"
-#include "util/Util.h"
-#include "xml/XmlDom.h"
#include <memory>
#include <string>
+#include "android-base/macros.h"
+
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+
namespace aapt {
namespace proguard {
class BaseVisitor : public xml::Visitor {
-public:
- BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
- }
+ public:
+ BaseVisitor(const Source& source, KeepSet* keep_set)
+ : source_(source), keep_set_(keep_set) {}
- virtual void visit(xml::Text*) override {};
+ virtual void Visit(xml::Text*) override{};
- virtual void visit(xml::Namespace* node) override {
- for (const auto& child : node->children) {
- child->accept(this);
- }
+ virtual void Visit(xml::Namespace* node) override {
+ for (const auto& child : node->children) {
+ child->Accept(this);
}
-
- virtual void visit(xml::Element* node) override {
- if (!node->namespaceUri.empty()) {
- 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().package + u"." + node->name;
- if (util::isJavaClassName(package)) {
- addClass(node->lineNumber, package);
- }
- }
- } else if (util::isJavaClassName(node->name)) {
- addClass(node->lineNumber, node->name);
- }
-
- for (const auto& child: node->children) {
- child->accept(this);
+ }
+
+ virtual void Visit(xml::Element* node) override {
+ if (!node->namespace_uri.empty()) {
+ Maybe<xml::ExtractedPackage> maybe_package =
+ xml::ExtractPackageFromNamespace(node->namespace_uri);
+ if (maybe_package) {
+ // This is a custom view, let's figure out the class name from this.
+ std::string package = maybe_package.value().package + "." + node->name;
+ if (util::IsJavaClassName(package)) {
+ AddClass(node->line_number, package);
}
+ }
+ } else if (util::IsJavaClassName(node->name)) {
+ AddClass(node->line_number, node->name);
}
-protected:
- void addClass(size_t lineNumber, const std::u16string& className) {
- mKeepSet->addClass(Source(mSource.path, lineNumber), className);
+ for (const auto& child : node->children) {
+ child->Accept(this);
}
+ }
- void addMethod(size_t lineNumber, const std::u16string& methodName) {
- mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName);
- }
+ protected:
+ void AddClass(size_t line_number, const std::string& class_name) {
+ keep_set_->AddClass(Source(source_.path, line_number), class_name);
+ }
-private:
- Source mSource;
- KeepSet* mKeepSet;
+ void AddMethod(size_t line_number, const std::string& method_name) {
+ keep_set_->AddMethod(Source(source_.path, line_number), method_name);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BaseVisitor);
+
+ Source source_;
+ KeepSet* keep_set_;
};
-struct LayoutVisitor : public BaseVisitor {
- LayoutVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+class LayoutVisitor : public BaseVisitor {
+ public:
+ LayoutVisitor(const Source& source, KeepSet* keep_set)
+ : BaseVisitor(source, keep_set) {}
+
+ virtual void Visit(xml::Element* node) override {
+ bool check_class = false;
+ bool check_name = false;
+ if (node->namespace_uri.empty()) {
+ check_class = node->name == "view" || node->name == "fragment";
+ } else if (node->namespace_uri == xml::kSchemaAndroid) {
+ check_name = node->name == "fragment";
}
- virtual void visit(xml::Element* node) override {
- bool checkClass = false;
- bool checkName = false;
- if (node->namespaceUri.empty()) {
- checkClass = node->name == u"view" || node->name == u"fragment";
- } else if (node->namespaceUri == xml::kSchemaAndroid) {
- checkName = node->name == u"fragment";
- }
+ for (const auto& attr : node->attributes) {
+ if (check_class && attr.namespace_uri.empty() && attr.name == "class" &&
+ util::IsJavaClassName(attr.value)) {
+ AddClass(node->line_number, attr.value);
+ } else if (check_name && attr.namespace_uri == xml::kSchemaAndroid &&
+ attr.name == "name" && util::IsJavaClassName(attr.value)) {
+ AddClass(node->line_number, attr.value);
+ } else if (attr.namespace_uri == xml::kSchemaAndroid &&
+ attr.name == "onClick") {
+ AddMethod(node->line_number, attr.value);
+ }
+ }
- for (const auto& attr : node->attributes) {
- if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" &&
- util::isJavaClassName(attr.value)) {
- addClass(node->lineNumber, 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 == xml::kSchemaAndroid && attr.name == u"onClick") {
- addMethod(node->lineNumber, attr.value);
- }
- }
+ BaseVisitor::Visit(node);
+ }
- BaseVisitor::visit(node);
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LayoutVisitor);
};
-struct XmlResourceVisitor : public BaseVisitor {
- XmlResourceVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+class XmlResourceVisitor : public BaseVisitor {
+ public:
+ XmlResourceVisitor(const Source& source, KeepSet* keep_set)
+ : BaseVisitor(source, keep_set) {}
+
+ virtual void Visit(xml::Element* node) override {
+ bool check_fragment = false;
+ if (node->namespace_uri.empty()) {
+ check_fragment =
+ node->name == "PreferenceScreen" || node->name == "header";
}
- virtual void visit(xml::Element* node) override {
- bool checkFragment = false;
- if (node->namespaceUri.empty()) {
- checkFragment = node->name == u"PreferenceScreen" || node->name == u"header";
- }
+ if (check_fragment) {
+ xml::Attribute* attr =
+ node->FindAttribute(xml::kSchemaAndroid, "fragment");
+ if (attr && util::IsJavaClassName(attr->value)) {
+ AddClass(node->line_number, attr->value);
+ }
+ }
- if (checkFragment) {
- xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"fragment");
- if (attr && util::isJavaClassName(attr->value)) {
- addClass(node->lineNumber, attr->value);
- }
- }
+ BaseVisitor::Visit(node);
+ }
- BaseVisitor::visit(node);
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlResourceVisitor);
};
-struct TransitionVisitor : public BaseVisitor {
- TransitionVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+class TransitionVisitor : public BaseVisitor {
+ public:
+ TransitionVisitor(const Source& source, KeepSet* keep_set)
+ : BaseVisitor(source, keep_set) {}
+
+ virtual void Visit(xml::Element* node) override {
+ bool check_class =
+ node->namespace_uri.empty() &&
+ (node->name == "transition" || node->name == "pathMotion");
+ if (check_class) {
+ xml::Attribute* attr = node->FindAttribute({}, "class");
+ if (attr && util::IsJavaClassName(attr->value)) {
+ AddClass(node->line_number, attr->value);
+ }
}
- virtual void visit(xml::Element* node) override {
- bool checkClass = node->namespaceUri.empty() &&
- (node->name == u"transition" || node->name == u"pathMotion");
- if (checkClass) {
- xml::Attribute* attr = node->findAttribute({}, u"class");
- if (attr && util::isJavaClassName(attr->value)) {
- addClass(node->lineNumber, attr->value);
- }
- }
+ BaseVisitor::Visit(node);
+ }
- BaseVisitor::visit(node);
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TransitionVisitor);
};
-struct ManifestVisitor : public BaseVisitor {
- ManifestVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
- }
+class ManifestVisitor : public BaseVisitor {
+ public:
+ ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only)
+ : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {}
+
+ virtual void Visit(xml::Element* node) override {
+ if (node->namespace_uri.empty()) {
+ bool get_name = false;
+ if (node->name == "manifest") {
+ xml::Attribute* attr = node->FindAttribute({}, "package");
+ if (attr) {
+ package_ = attr->value;
+ }
+ } else if (node->name == "application") {
+ get_name = true;
+ xml::Attribute* attr =
+ node->FindAttribute(xml::kSchemaAndroid, "backupAgent");
+ if (attr) {
+ Maybe<std::string> result =
+ util::GetFullyQualifiedClassName(package_, attr->value);
+ if (result) {
+ AddClass(node->line_number, result.value());
+ }
+ }
+ if (main_dex_only_) {
+ xml::Attribute* default_process =
+ node->FindAttribute(xml::kSchemaAndroid, "process");
+ if (default_process) {
+ default_process_ = default_process->value;
+ }
+ }
+ } else if (node->name == "activity" || node->name == "service" ||
+ node->name == "receiver" || node->name == "provider") {
+ get_name = true;
+
+ if (main_dex_only_) {
+ xml::Attribute* component_process =
+ node->FindAttribute(xml::kSchemaAndroid, "process");
- virtual void visit(xml::Element* node) override {
- if (node->namespaceUri.empty()) {
- bool getName = false;
- if (node->name == u"manifest") {
- xml::Attribute* attr = node->findAttribute({}, u"package");
- if (attr) {
- mPackage = attr->value;
- }
- } else if (node->name == u"application") {
- getName = true;
- xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"backupAgent");
- if (attr) {
- Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
- attr->value);
- if (result) {
- addClass(node->lineNumber, result.value());
- }
- }
- } else if (node->name == u"activity" || node->name == u"service" ||
- node->name == u"receiver" || node->name == u"provider" ||
- node->name == u"instrumentation") {
- getName = true;
- }
-
- if (getName) {
- xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"name");
- if (attr) {
- Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
- attr->value);
- if (result) {
- addClass(node->lineNumber, result.value());
- }
- }
- }
+ const std::string& process =
+ component_process ? component_process->value : default_process_;
+ get_name = !process.empty() && process[0] != ':';
}
- BaseVisitor::visit(node);
+ } else if (node->name == "instrumentation") {
+ get_name = true;
+ }
+
+ if (get_name) {
+ xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "name");
+ get_name = attr != nullptr;
+
+ if (get_name) {
+ Maybe<std::string> result =
+ util::GetFullyQualifiedClassName(package_, attr->value);
+ if (result) {
+ AddClass(node->line_number, result.value());
+ }
+ }
+ }
}
+ BaseVisitor::Visit(node);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ManifestVisitor);
- std::u16string mPackage;
+ std::string package_;
+ const bool main_dex_only_;
+ std::string default_process_;
};
-bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res,
- KeepSet* keepSet) {
- ManifestVisitor visitor(source, keepSet);
- if (res->root) {
- res->root->accept(&visitor);
- return true;
- }
- return false;
+bool CollectProguardRulesForManifest(const Source& source,
+ xml::XmlResource* res, KeepSet* keep_set,
+ bool main_dex_only) {
+ ManifestVisitor visitor(source, keep_set, main_dex_only);
+ if (res->root) {
+ res->root->Accept(&visitor);
+ return true;
+ }
+ return false;
}
-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);
- res->root->accept(&visitor);
- break;
- }
+bool CollectProguardRules(const Source& source, xml::XmlResource* res,
+ KeepSet* keep_set) {
+ if (!res->root) {
+ return false;
+ }
- case ResourceType::kXml: {
- XmlResourceVisitor visitor(source, keepSet);
- res->root->accept(&visitor);
- break;
- }
+ switch (res->file.name.type) {
+ case ResourceType::kLayout: {
+ LayoutVisitor visitor(source, keep_set);
+ res->root->Accept(&visitor);
+ break;
+ }
- case ResourceType::kTransition: {
- TransitionVisitor visitor(source, keepSet);
- res->root->accept(&visitor);
- break;
- }
+ case ResourceType::kXml: {
+ XmlResourceVisitor visitor(source, keep_set);
+ res->root->Accept(&visitor);
+ break;
+ }
- default:
- break;
+ case ResourceType::kTransition: {
+ TransitionVisitor visitor(source, keep_set);
+ res->root->Accept(&visitor);
+ break;
}
- return true;
+
+ default:
+ break;
+ }
+ return true;
}
-bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
- for (const auto& entry : keepSet.mKeepSet) {
- for (const Source& source : entry.second) {
- *out << "# Referenced at " << source << "\n";
- }
- *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) {
+ for (const auto& entry : keep_set.keep_set_) {
+ 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 Source& source : entry.second) {
- *out << "# Referenced at " << source << "\n";
- }
- *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
+ for (const auto& entry : keep_set.keep_method_set_) {
+ for (const Source& source : entry.second) {
+ *out << "# Referenced at " << source << "\n";
}
- return true;
+ *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n"
+ << std::endl;
+ }
+ return true;
}
-} // namespace proguard
-} // namespace aapt
+} // namespace proguard
+} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index aafffd39d84e..3c349bab1217 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -17,41 +17,44 @@
#ifndef AAPT_PROGUARD_RULES_H
#define AAPT_PROGUARD_RULES_H
-#include "Resource.h"
-#include "Source.h"
-#include "xml/XmlDom.h"
-
#include <map>
#include <ostream>
#include <set>
#include <string>
+#include "Resource.h"
+#include "Source.h"
+#include "xml/XmlDom.h"
+
namespace aapt {
namespace proguard {
class KeepSet {
-public:
- inline void addClass(const Source& source, const std::u16string& className) {
- mKeepSet[className].insert(source);
- }
+ public:
+ inline void AddClass(const Source& source, const std::string& class_name) {
+ keep_set_[class_name].insert(source);
+ }
- inline void addMethod(const Source& source, const std::u16string& methodName) {
- mKeepMethodSet[methodName].insert(source);
- }
+ inline void AddMethod(const Source& source, const std::string& method_name) {
+ keep_method_set_[method_name].insert(source);
+ }
-private:
- friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+ private:
+ friend bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
- std::map<std::u16string, std::set<Source>> mKeepSet;
- std::map<std::u16string, std::set<Source>> mKeepMethodSet;
+ std::map<std::string, std::set<Source>> keep_set_;
+ std::map<std::string, std::set<Source>> keep_method_set_;
};
-bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
-bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
+bool CollectProguardRulesForManifest(const Source& source,
+ xml::XmlResource* res, KeepSet* keep_set,
+ bool main_dex_only = false);
+bool CollectProguardRules(const Source& source, xml::XmlResource* res,
+ KeepSet* keep_set);
-bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
-} // namespace proguard
-} // namespace aapt
+} // namespace proguard
+} // namespace aapt
-#endif // AAPT_PROGUARD_RULES_H
+#endif // AAPT_PROGUARD_RULES_H
diff --git a/tools/aapt2/jni/ScopedUtfChars.h b/tools/aapt2/jni/ScopedUtfChars.h
new file mode 100644
index 000000000000..a8c4b136dcf6
--- /dev/null
+++ b/tools/aapt2/jni/ScopedUtfChars.h
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#ifndef SCOPED_UTF_CHARS_H_included
+#define SCOPED_UTF_CHARS_H_included
+
+#include <string.h>
+#include <jni.h>
+
+#include "android-base/logging.h"
+
+// This file was copied with some minor modifications from libnativehelper.
+// As soon as libnativehelper can be compiled for Windows, this file should be
+// replaced with libnativehelper's implementation.
+class ScopedUtfChars {
+ public:
+ ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) {
+ CHECK(s != nullptr);
+ utf_chars_ = env->GetStringUTFChars(s, nullptr);
+ }
+
+ ScopedUtfChars(ScopedUtfChars&& rhs) :
+ env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) {
+ rhs.env_ = nullptr;
+ rhs.string_ = nullptr;
+ rhs.utf_chars_ = nullptr;
+ }
+
+ ~ScopedUtfChars() {
+ if (utf_chars_) {
+ env_->ReleaseStringUTFChars(string_, utf_chars_);
+ }
+ }
+
+ ScopedUtfChars& operator=(ScopedUtfChars&& rhs) {
+ if (this != &rhs) {
+ // Delete the currently owned UTF chars.
+ this->~ScopedUtfChars();
+
+ // Move the rhs ScopedUtfChars and zero it out.
+ env_ = rhs.env_;
+ string_ = rhs.string_;
+ utf_chars_ = rhs.utf_chars_;
+ rhs.env_ = nullptr;
+ rhs.string_ = nullptr;
+ rhs.utf_chars_ = nullptr;
+ }
+ return *this;
+ }
+
+ const char* c_str() const {
+ return utf_chars_;
+ }
+
+ size_t size() const {
+ return strlen(utf_chars_);
+ }
+
+ const char& operator[](size_t n) const {
+ return utf_chars_[n];
+ }
+
+ private:
+ JNIEnv* env_;
+ jstring string_;
+ const char* utf_chars_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedUtfChars);
+};
+
+#endif // SCOPED_UTF_CHARS_H_included
diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp
new file mode 100644
index 000000000000..5518fe2cae37
--- /dev/null
+++ b/tools/aapt2/jni/aapt2_jni.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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 "com_android_tools_aapt2_Aapt2Jni.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "ScopedUtfChars.h"
+
+#include "util/Util.h"
+
+namespace aapt {
+extern int Compile(const std::vector<StringPiece> &args);
+extern int Link(const std::vector<StringPiece> &args);
+}
+
+/*
+ * Converts a java List<String> into C++ vector<ScopedUtfChars>.
+ */
+static std::vector<ScopedUtfChars> list_to_utfchars(JNIEnv *env, jobject obj) {
+ std::vector<ScopedUtfChars> converted;
+
+ // Call size() method on the list to know how many elements there are.
+ jclass list_cls = env->GetObjectClass(obj);
+ jmethodID size_method_id = env->GetMethodID(list_cls, "size", "()I");
+ CHECK(size_method_id != 0);
+ jint size = env->CallIntMethod(obj, size_method_id);
+ CHECK(size >= 0);
+
+ // Now, iterate all strings in the list
+ // (note: generic erasure means get() return an Object)
+ jmethodID get_method_id = env->GetMethodID(list_cls, "get", "(I)Ljava/lang/Object;");
+ CHECK(get_method_id != 0);
+ for (jint i = 0; i < size; i++) {
+ // Call get(i) to get the string in the ith position.
+ jobject string_obj_uncast = env->CallObjectMethod(obj, get_method_id, i);
+ CHECK(string_obj_uncast != nullptr);
+ jstring string_obj = static_cast<jstring>(string_obj_uncast);
+ converted.push_back(ScopedUtfChars(env, string_obj));
+ }
+
+ return converted;
+}
+
+/*
+ * Extracts all StringPiece from the ScopedUtfChars instances.
+ *
+ * The returned pieces can only be used while the original ones have not been
+ * destroyed.
+ */
+static std::vector<aapt::StringPiece> extract_pieces(
+ const std::vector<ScopedUtfChars> &strings) {
+ std::vector<aapt::StringPiece> pieces;
+
+ std::for_each(
+ strings.begin(), strings.end(),
+ [&pieces](const ScopedUtfChars &p) { pieces.push_back(p.c_str()); });
+
+ return pieces;
+}
+
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeCompile(
+ JNIEnv *env, jclass aapt_obj, jobject arguments_obj) {
+ std::vector<ScopedUtfChars> compile_args_jni =
+ list_to_utfchars(env, arguments_obj);
+ std::vector<aapt::StringPiece> compile_args =
+ extract_pieces(compile_args_jni);
+ aapt::Compile(compile_args);
+}
+
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(
+ JNIEnv *env, jclass aapt_obj, jobject arguments_obj) {
+ std::vector<ScopedUtfChars> link_args_jni =
+ list_to_utfchars(env, arguments_obj);
+ std::vector<aapt::StringPiece> link_args = extract_pieces(link_args_jni);
+ aapt::Link(link_args);
+}
+
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping(
+ JNIEnv *env, jclass aapt_obj) {
+ // This is just a dummy method to see if the library has been loaded.
+}
diff --git a/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h
new file mode 100644
index 000000000000..56c3c18e3a1e
--- /dev/null
+++ b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h
@@ -0,0 +1,37 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_android_tools_aapt2_Aapt2Jni */
+
+#ifndef _Included_com_android_tools_aapt2_Aapt2Jni
+#define _Included_com_android_tools_aapt2_Aapt2Jni
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_android_tools_aapt2_Aapt2Jni
+ * Method: ping
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_android_tools_aapt2_Aapt2Jni
+ * Method: nativeCompile
+ * Signature: (Ljava/util/List;)V
+ */
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeCompile
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: com_android_tools_aapt2_Aapt2Jni
+ * Method: nativeLink
+ * Signature: (Ljava/util/List;)V
+ */
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink
+ (JNIEnv *, jclass, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index 459c330cfbdc..77471ea5d0da 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -14,126 +14,142 @@
* limitations under the License.
*/
+#include "link/Linkers.h"
+
+#include <algorithm>
+
+#include "android-base/logging.h"
+
#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;
- }
+bool ShouldGenerateVersionedResource(const ResourceEntry* entry,
+ const ConfigDescription& config,
+ const int sdk_version_to_generate) {
+ // We assume the caller is trying to generate a version greater than the
+ // current configuration.
+ CHECK(sdk_version_to_generate > config.sdkVersion);
+
+ const auto end_iter = entry->values.end();
+ auto iter = entry->values.begin();
+ for (; iter != end_iter; ++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;
- }
+ }
+
+ // The source config came from this list, so it should be here.
+ CHECK(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 temp_config(config);
+ for (; iter != end_iter; ++iter) {
+ temp_config.sdkVersion = (*iter)->config.sdkVersion;
+ if (temp_config == (*iter)->config) {
+ // The two configs are the same, check the sdk version.
+ return sdk_version_to_generate < (*iter)->config.sdkVersion;
}
+ }
- // No match was found, so we should generate the versioned resource.
- return true;
+ // 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) {
+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* config_value = entry->values[i].get();
+ if (config_value->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>(config_value->value.get())) {
+ Maybe<size_t> min_sdk_stripped;
+ std::vector<Style::Entry> stripped;
+
+ auto iter = style->entries.begin();
+ while (iter != style->entries.end()) {
+ CHECK(bool(iter->key.id)) << "IDs must be assigned and linked";
+
+ // Find the SDK level that is higher than the configuration
+ // allows.
+ const size_t sdk_level =
+ FindAttributeSdkLevel(iter->key.id.value());
+ if (sdk_level >
+ std::max<size_t>(config_value->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 (min_sdk_stripped) {
+ min_sdk_stripped =
+ std::min(min_sdk_stripped.value(), sdk_level);
+ } else {
+ min_sdk_stripped = sdk_level;
+ }
+
+ // Erase this from this style.
+ iter = style->entries.erase(iter);
continue;
+ }
+ ++iter;
}
- 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);
- }
- }
- }
- }
+ if (min_sdk_stripped && !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(),
+ config_value->config,
+ min_sdk_stripped.value())) {
+ // Let's create a new Style for this versioned resource.
+ ConfigDescription new_config(config_value->config);
+ new_config.sdkVersion = min_sdk_stripped.value();
+
+ std::unique_ptr<Style> new_style(
+ style->Clone(&table->string_pool));
+ new_style->SetComment(style->GetComment());
+ new_style->SetSource(style->GetSource());
+
+ // Move the previously stripped attributes into this style.
+ new_style->entries.insert(
+ new_style->entries.end(),
+ std::make_move_iterator(stripped.begin()),
+ std::make_move_iterator(stripped.end()));
+
+ // Insert the new Resource into the correct place.
+ entry->FindOrCreateValue(new_config, {})->value =
+ std::move(new_style);
+ }
}
+ }
}
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
index 9b3a87c4eed0..755af0a1c6cc 100644
--- a/tools/aapt2/link/AutoVersioner_test.cpp
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -14,112 +14,124 @@
* limitations under the License.
*/
-#include "ConfigDescription.h"
#include "link/Linkers.h"
-#include "test/Builders.h"
-#include "test/Context.h"
-#include <gtest/gtest.h>
+#include "ConfigDescription.h"
+#include "test/Test.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));
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+ const ConfigDescription sw600dp_land_config =
+ test::ParseConfigOrDie("sw600dp-land");
+
+ ResourceEntry entry("foo");
+ entry.values.push_back(util::make_unique<ResourceConfigValue>(
+ ConfigDescription::DefaultConfig(), ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(land_config, ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dp_land_config, ""));
+
+ EXPECT_TRUE(ShouldGenerateVersionedResource(
+ &entry, ConfigDescription::DefaultConfig(), 17));
+ EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, land_config, 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));
+ const ConfigDescription sw600dp_v13_config =
+ test::ParseConfigOrDie("sw600dp-v13");
+ const ConfigDescription v21_config = test::ParseConfigOrDie("v21");
+
+ ResourceEntry entry("foo");
+ entry.values.push_back(util::make_unique<ResourceConfigValue>(
+ ConfigDescription::DefaultConfig(), ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dp_v13_config, ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(v21_config, ""));
+
+ EXPECT_TRUE(ShouldGenerateVersionedResource(
+ &entry, ConfigDescription::DefaultConfig(), 17));
+ EXPECT_FALSE(ShouldGenerateVersionedResource(
+ &entry, ConfigDescription::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"));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("app", 0x7f)
+ .AddValue(
+ "app:style/Foo", test::ParseConfigOrDie("v4"),
+ ResourceId(0x7f020000),
+ test::StyleBuilder()
+ .AddItem("android:attr/onClick", ResourceId(0x0101026f),
+ util::make_unique<Id>())
+ .AddItem("android:attr/paddingStart", ResourceId(0x010103b3),
+ util::make_unique<Id>())
+ .AddItem("android:attr/requiresSmallestWidthDp",
+ ResourceId(0x01010364), util::make_unique<Id>())
+ .AddItem("android:attr/colorAccent", ResourceId(0x01010435),
+ util::make_unique<Id>())
+ .Build())
+ .AddValue(
+ "app:style/Foo", test::ParseConfigOrDie("v21"),
+ ResourceId(0x7f020000),
+ test::StyleBuilder()
+ .AddItem("android:attr/paddingEnd", ResourceId(0x010103b4),
+ util::make_unique<Id>())
+ .Build())
+ .Build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("app")
+ .SetPackageId(0x7f)
+ .Build();
+
+ AutoVersioner versioner;
+ ASSERT_TRUE(versioner.Consume(context.get(), table.get()));
+
+ Style* style = test::GetValueForConfig<Style>(table.get(), "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("android:attr/onClick"));
+
+ style = test::GetValueForConfig<Style>(table.get(), "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("android:attr/onClick"));
+ AAPT_ASSERT_TRUE(style->entries[1].key.name);
+ EXPECT_EQ(style->entries[1].key.name.value(),
+ test::ParseNameOrDie("android:attr/requiresSmallestWidthDp"));
+
+ style = test::GetValueForConfig<Style>(table.get(), "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("android:attr/onClick"));
+ AAPT_ASSERT_TRUE(style->entries[1].key.name);
+ EXPECT_EQ(style->entries[1].key.name.value(),
+ test::ParseNameOrDie("android:attr/requiresSmallestWidthDp"));
+ AAPT_ASSERT_TRUE(style->entries[2].key.name);
+ EXPECT_EQ(style->entries[2].key.name.value(),
+ test::ParseNameOrDie("android:attr/paddingStart"));
+
+ style = test::GetValueForConfig<Style>(table.get(), "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("android:attr/paddingEnd"));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 49971201fb3c..b525758004f0 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -14,6 +14,17 @@
* limitations under the License.
*/
+#include <sys/stat.h>
+
+#include <fstream>
+#include <queue>
+#include <unordered_map>
+#include <vector>
+
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "google/protobuf/io/coded_stream.h"
+
#include "AppInfo.h"
#include "Debug.h"
#include "Flags.h"
@@ -31,9 +42,8 @@
#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/ReferenceLinker.h"
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
@@ -44,1546 +54,2059 @@
#include "util/StringPiece.h"
#include "xml/XmlDom.h"
-#include <google/protobuf/io/coded_stream.h>
-
-#include <fstream>
-#include <sys/stat.h>
-#include <vector>
+using ::google::protobuf::io::CopyingOutputStreamAdaptor;
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;
+ std::string output_path;
+ std::string manifest_path;
+ std::vector<std::string> include_paths;
+ std::vector<std::string> overlay_files;
+ bool output_to_directory = false;
+ bool auto_add_overlay = false;
+
+ // Java/Proguard options.
+ Maybe<std::string> generate_java_class_path;
+ Maybe<std::string> custom_java_package;
+ std::set<std::string> extra_java_packages;
+ Maybe<std::string> generate_proguard_rules_path;
+ Maybe<std::string> generate_main_dex_proguard_rules_path;
+ bool generate_non_final_ids = false;
+ std::vector<std::string> javadoc_annotations;
+ Maybe<std::string> private_symbols;
+
+ // Optimizations/features.
+ bool no_auto_version = false;
+ bool no_version_vectors = false;
+ bool no_resource_deduping = false;
+ bool no_xml_namespaces = false;
+ bool do_not_compress_anything = false;
+ std::unordered_set<std::string> extensions_to_not_compress;
+
+ // Static lib options.
+ bool static_lib = false;
+ bool no_static_lib_packages = false;
+
+ // AndroidManifest.xml massaging options.
+ ManifestFixerOptions manifest_fixer_options;
+
+ // Products to use/filter on.
+ std::unordered_set<std::string> products;
+
+ // Split APK options.
+ TableSplitterOptions table_splitter_options;
+ std::vector<SplitConstraints> split_constraints;
+ std::vector<std::string> split_paths;
+
+ // Stable ID options.
+ std::unordered_map<ResourceName, ResourceId> stable_id_map;
+ Maybe<std::string> resource_id_map_path;
};
class LinkContext : public IAaptContext {
-public:
- LinkContext() : mNameMangler({}) {
- }
+ public:
+ LinkContext() : name_mangler_({}) {}
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
- NameMangler* getNameMangler() override {
- return &mNameMangler;
- }
+ NameMangler* GetNameMangler() override { return &name_mangler_; }
- void setNameManglerPolicy(const NameManglerPolicy& policy) {
- mNameMangler = NameMangler(policy);
- }
+ void SetNameManglerPolicy(const NameManglerPolicy& policy) {
+ name_mangler_ = NameMangler(policy);
+ }
- const std::u16string& getCompilationPackage() override {
- return mCompilationPackage;
- }
+ const std::string& GetCompilationPackage() override {
+ return compilation_package_;
+ }
- void setCompilationPackage(const StringPiece16& packageName) {
- mCompilationPackage = packageName.toString();
- }
+ void SetCompilationPackage(const StringPiece& package_name) {
+ compilation_package_ = package_name.ToString();
+ }
- uint8_t getPackageId() override {
- return mPackageId;
- }
+ uint8_t GetPackageId() override { return package_id_; }
- void setPackageId(uint8_t id) {
- mPackageId = id;
- }
+ void SetPackageId(uint8_t id) { package_id_ = id; }
- SymbolTable* getExternalSymbols() override {
- return &mSymbols;
- }
+ SymbolTable* GetExternalSymbols() override { return &symbols_; }
- bool verbose() override {
- return mVerbose;
- }
+ bool IsVerbose() override { return verbose_; }
- void setVerbose(bool val) {
- mVerbose = val;
- }
+ void SetVerbose(bool val) { verbose_ = val; }
+
+ int GetMinSdkVersion() override { return min_sdk_version_; }
+
+ void SetMinSdkVersion(int minSdk) { min_sdk_version_ = minSdk; }
-private:
- StdErrDiagnostics mDiagnostics;
- NameMangler mNameMangler;
- std::u16string mCompilationPackage;
- uint8_t mPackageId = 0x0;
- SymbolTable mSymbols;
- bool mVerbose = false;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LinkContext);
+
+ StdErrDiagnostics diagnostics_;
+ NameMangler name_mangler_;
+ std::string compilation_package_;
+ uint8_t package_id_ = 0x0;
+ SymbolTable symbols_;
+ bool verbose_ = false;
+ int min_sdk_version_ = 0;
};
-static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
- uint32_t compressionFlags,
+static bool CopyFileToArchive(io::IFile* file, const std::string& out_path,
+ uint32_t compression_flags,
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;
- }
+ 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();
+ const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
+ const size_t buffer_size = 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 (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path
+ << " to archive");
+ }
- if (writer->startEntry(outPath, compressionFlags)) {
- if (writer->writeEntry(buffer, bufferSize)) {
- if (writer->finishEntry()) {
- return true;
- }
- }
+ if (writer->StartEntry(out_path, compression_flags)) {
+ if (writer->WriteEntry(buffer, buffer_size)) {
+ if (writer->FinishEntry()) {
+ return true;
+ }
}
+ }
- context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
- return false;
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to write file "
+ << out_path);
+ 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;
- }
+static bool FlattenXml(xml::XmlResource* xml_res, const StringPiece& path,
+ Maybe<size_t> max_sdk_level, bool keep_raw_values,
+ IArchiveWriter* writer, IAaptContext* context) {
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions options = {};
+ options.keep_raw_values = keep_raw_values;
+ options.max_sdk_level = max_sdk_level;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.Consume(context, xml_res)) {
+ 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 (context->IsVerbose()) {
+ DiagMessage msg;
+ msg << "writing " << path << " to archive";
+ if (max_sdk_level) {
+ msg << " maxSdkLevel=" << max_sdk_level.value()
+ << " keepRawValues=" << keep_raw_values;
}
+ context->GetDiagnostics()->Note(msg);
+ }
- if (writer->startEntry(path, ArchiveEntry::kCompress)) {
- if (writer->writeEntry(buffer)) {
- if (writer->finishEntry()) {
- return true;
- }
- }
+ 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;
+ }
+ 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,
+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;
+ pb::ResourceTable pb_table;
+ if (!pb_table.ParseFromArray(data, len)) {
+ diag->Error(DiagMessage(source) << "invalid compiled table");
+ return {};
+ }
+
+ std::unique_ptr<ResourceTable> table =
+ DeserializeTableFromPb(pb_table, 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> 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();
+struct ResourceFileFlattenerOptions {
+ bool no_auto_version = false;
+ bool no_version_vectors = false;
+ bool no_xml_namespaces = false;
+ bool keep_raw_values = false;
+ bool do_not_compress_anything = false;
+ bool update_proguard_spec = false;
+ std::unordered_set<std::string> extensions_to_not_compress;
+};
- std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
- if (!xmlRes) {
- return {};
- }
- return xmlRes;
-}
+class ResourceFileFlattener {
+ public:
+ ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
+ IAaptContext* context, proguard::KeepSet* keep_set)
+ : options_(options), context_(context), keep_set_(keep_set) {}
-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 {};
- }
+ bool Flatten(ResourceTable* table, IArchiveWriter* archive_writer);
- std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
- if (!resFile) {
- return {};
- }
- return resFile;
-}
+ private:
+ struct FileOperation {
+ ConfigDescription config;
-struct ResourceFileFlattenerOptions {
- bool noAutoVersion = false;
- bool noVersionVectors = false;
- bool keepRawValues = false;
- bool doNotCompressAnything = false;
- std::vector<std::string> extensionsToNotCompress;
-};
+ // The entry this file came from.
+ const ResourceEntry* entry;
-class ResourceFileFlattener {
-public:
- ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
- IAaptContext* context, proguard::KeepSet* keepSet) :
- mOptions(options), mContext(context), mKeepSet(keepSet) {
- }
+ // The file to copy as-is.
+ io::IFile* file_to_copy;
- bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
+ // The XML to process and flatten.
+ std::unique_ptr<xml::XmlResource> xml_to_flatten;
-private:
- struct FileOperation {
- io::IFile* fileToCopy;
- std::unique_ptr<xml::XmlResource> xmlToFlatten;
- std::string dstPath;
- bool skipVersion = false;
- };
+ // The destination to write this file to.
+ std::string dst_path;
+ bool skip_version = false;
+ };
- uint32_t getCompressionFlags(const StringPiece& str);
+ uint32_t GetCompressionFlags(const StringPiece& str);
- bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
- io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
+ bool LinkAndVersionXmlFile(ResourceTable* table, FileOperation* file_op,
+ std::queue<FileOperation>* out_file_op_queue);
- ResourceFileFlattenerOptions mOptions;
- IAaptContext* mContext;
- proguard::KeepSet* mKeepSet;
+ ResourceFileFlattenerOptions options_;
+ IAaptContext* context_;
+ proguard::KeepSet* keep_set_;
};
-uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
- if (mOptions.doNotCompressAnything) {
- return 0;
- }
+uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) {
+ if (options_.do_not_compress_anything) {
+ return 0;
+ }
- for (const std::string& extension : mOptions.extensionsToNotCompress) {
- if (util::stringEndsWith<char>(str, extension)) {
- return 0;
- }
+ for (const std::string& extension : options_.extensions_to_not_compress) {
+ if (util::EndsWith(str, extension)) {
+ return 0;
}
- return ArchiveEntry::kCompress;
+ }
+ 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);
- }
+bool ResourceFileFlattener::LinkAndVersionXmlFile(
+ ResourceTable* table, FileOperation* file_op,
+ std::queue<FileOperation>* out_file_op_queue) {
+ xml::XmlResource* doc = file_op->xml_to_flatten.get();
+ const Source& src = doc->file.source;
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
- return false;
- }
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage() << "linking " << src.path);
+ }
- 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());
+ XmlReferenceLinker xml_linker;
+ if (!xml_linker.Consume(context_, doc)) {
+ return false;
+ }
+
+ if (options_.update_proguard_spec &&
+ !proguard::CollectProguardRules(src, doc, keep_set_)) {
+ return false;
+ }
+
+ if (options_.no_xml_namespaces) {
+ XmlNamespaceRemover namespace_remover;
+ if (!namespace_remover.Consume(context_, doc)) {
+ return false;
}
+ }
- if (!outFileOp->xmlToFlatten) {
- return false;
+ if (!options_.no_auto_version) {
+ if (options_.no_version_vectors) {
+ // Skip this if it is a vector or animated-vector.
+ xml::Element* el = xml::FindRootElement(doc);
+ if (el && el->namespace_uri.empty()) {
+ if (el->name == "vector" || el->name == "animated-vector") {
+ // We are NOT going to version this file.
+ file_op->skip_version = true;
+ return true;
+ }
+ }
}
- // Copy the the file description header.
- outFileOp->xmlToFlatten->file = fileDesc;
+ const ConfigDescription& config = file_op->config;
- XmlReferenceLinker xmlLinker;
- if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
- return false;
- }
+ // Find the first SDK level used that is higher than this defined config and
+ // not superseded by a lower or equal SDK level resource.
+ const int min_sdk_version = context_->GetMinSdkVersion();
+ for (int sdk_level : xml_linker.sdk_levels()) {
+ if (sdk_level > min_sdk_version && sdk_level > config.sdkVersion) {
+ if (!ShouldGenerateVersionedResource(file_op->entry, config,
+ sdk_level)) {
+ // If we shouldn't generate a versioned resource, stop checking.
+ break;
+ }
- if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source,
- outFileOp->xmlToFlatten.get(), mKeepSet)) {
- return false;
- }
+ ResourceFile versioned_file_desc = doc->file;
+ versioned_file_desc.config.sdkVersion = (uint16_t)sdk_level;
- 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;
- }
- }
+ FileOperation new_file_op;
+ new_file_op.xml_to_flatten = util::make_unique<xml::XmlResource>(
+ versioned_file_desc, doc->root->Clone());
+ new_file_op.config = versioned_file_desc.config;
+ new_file_op.entry = file_op->entry;
+ new_file_op.dst_path = ResourceUtils::BuildResourceFileName(
+ versioned_file_desc, context_->GetNameMangler());
+
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage(versioned_file_desc.source)
+ << "auto-versioning resource from config '" << config << "' -> '"
+ << versioned_file_desc.config << "'");
}
- // 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;
- }
+ bool added = table->AddFileReferenceAllowMangled(
+ versioned_file_desc.name, versioned_file_desc.config,
+ versioned_file_desc.source, new_file_op.dst_path, nullptr,
+ context_->GetDiagnostics());
+ if (!added) {
+ return false;
}
+
+ out_file_op_queue->push(std::move(new_file_op));
+ break;
+ }
}
- return true;
+ }
+ return true;
}
/**
- * Do not insert or remove any resources while executing in this function. It will
+ * 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);
- }
+bool ResourceFileFlattener::Flatten(ResourceTable* table,
+ IArchiveWriter* archive_writer) {
+ bool error = false;
+ std::map<std::pair<ConfigDescription, StringPiece>, FileOperation>
+ config_sorted_files;
+
+ 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.
+ config_sorted_files.clear();
+ std::queue<FileOperation> file_operations;
+
+ // Populate the queue with all files in the ResourceTable.
+ for (auto& entry : type->entries) {
+ for (auto& config_value : entry->values) {
+ FileReference* file_ref =
+ ValueCast<FileReference>(config_value->value.get());
+ if (!file_ref) {
+ continue;
+ }
+
+ io::IFile* file = file_ref->file;
+ if (!file) {
+ context_->GetDiagnostics()->Error(DiagMessage(file_ref->GetSource())
+ << "file not found");
+ return false;
+ }
+
+ FileOperation file_op;
+ file_op.entry = entry.get();
+ file_op.dst_path = *file_ref->path;
+ file_op.config = config_value->config;
+
+ const StringPiece src_path = file->GetSource().path;
+ if (type->type != ResourceType::kRaw &&
+ (util::EndsWith(src_path, ".xml.flat") ||
+ util::EndsWith(src_path, ".xml"))) {
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ if (!data) {
+ context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+ << "failed to open file");
+ return false;
}
- if (error) {
- return false;
- }
+ file_op.xml_to_flatten =
+ xml::Inflate(data->data(), data->size(),
+ context_->GetDiagnostics(), file->GetSource());
- // 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;
- }
- }
+ if (!file_op.xml_to_flatten) {
+ return false;
}
+
+ file_op.xml_to_flatten->file.config = config_value->config;
+ file_op.xml_to_flatten->file.source = file_ref->GetSource();
+ file_op.xml_to_flatten->file.name =
+ ResourceName(pkg->name, type->type, entry->name);
+
+ // Enqueue the XML files to be processed.
+ file_operations.push(std::move(file_op));
+ } else {
+ file_op.file_to_copy = file;
+
+ // NOTE(adamlesinski): Explicitly construct a StringPiece here, or
+ // else we end up copying the string in the std::make_pair() method,
+ // then creating a StringPiece from the copy, which would cause us
+ // to end up referencing garbage in the map.
+ const StringPiece entry_name(entry->name);
+ config_sorted_files[std::make_pair(
+ config_value->config, entry_name)] = std::move(file_op);
+ }
+ }
+ }
+
+ // Now process the XML queue
+ for (; !file_operations.empty(); file_operations.pop()) {
+ FileOperation& file_op = file_operations.front();
+
+ if (!LinkAndVersionXmlFile(table, &file_op, &file_operations)) {
+ error = true;
+ continue;
+ }
+
+ // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
+ // we end up copying the string in the std::make_pair() method, then
+ // creating a StringPiece from the copy, which would cause us to end up
+ // referencing garbage in the map.
+ const StringPiece entry_name(file_op.entry->name);
+ config_sorted_files[std::make_pair(file_op.config, entry_name)] =
+ std::move(file_op);
+ }
+
+ if (error) {
+ return false;
+ }
+
+ // Now flatten the sorted values.
+ for (auto& map_entry : config_sorted_files) {
+ const ConfigDescription& config = map_entry.first.first;
+ const FileOperation& file_op = map_entry.second;
+
+ if (file_op.xml_to_flatten) {
+ Maybe<size_t> max_sdk_level;
+ if (!options_.no_auto_version && !file_op.skip_version) {
+ max_sdk_level =
+ std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u),
+ context_->GetMinSdkVersion());
+ }
+
+ bool result = FlattenXml(
+ file_op.xml_to_flatten.get(), file_op.dst_path, max_sdk_level,
+ options_.keep_raw_values, archive_writer, context_);
+ if (!result) {
+ error = true;
+ }
+ } else {
+ bool result = CopyFileToArchive(
+ file_op.file_to_copy, file_op.dst_path,
+ GetCompressionFlags(file_op.dst_path), archive_writer, context_);
+ if (!result) {
+ error = true;
+ }
}
+ }
}
- return !error;
+ }
+ 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");
- }
+static bool WriteStableIdMapToPath(
+ IDiagnostics* diag,
+ const std::unordered_map<ResourceName, ResourceId>& id_map,
+ const std::string& id_map_path) {
+ std::ofstream fout(id_map_path, std::ofstream::binary);
+ if (!fout) {
+ diag->Error(DiagMessage(id_map_path) << strerror(errno));
+ return false;
+ }
+
+ for (const auto& entry : id_map) {
+ const ResourceName& name = entry.first;
+ const ResourceId& id = entry.second;
+ fout << name << " = " << id << "\n";
+ }
+
+ if (!fout) {
+ diag->Error(DiagMessage(id_map_path)
+ << "failed writing to file: "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
- // 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;
- }
+ return true;
+}
- if (!assetSource->addAssetPath(path)) {
- mContext->getDiagnostics()->error(
- DiagMessage(path) << "failed to load include path");
- return false;
- }
- }
+static bool LoadStableIdMap(
+ IDiagnostics* diag, const std::string& path,
+ std::unordered_map<ResourceName, ResourceId>* out_id_map) {
+ std::string content;
+ if (!android::base::ReadFileToString(path, &content)) {
+ diag->Error(DiagMessage(path) << "failed reading stable ID file");
+ return false;
+ }
- mContext->getExternalSymbols()->appendSource(std::move(assetSource));
- return true;
+ out_id_map->clear();
+ size_t line_no = 0;
+ for (StringPiece line : util::Tokenize(content, '\n')) {
+ line_no++;
+ line = util::TrimWhitespace(line);
+ if (line.empty()) {
+ continue;
}
- 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 {};
+ auto iter = std::find(line.begin(), line.end(), '=');
+ if (iter == line.end()) {
+ diag->Error(DiagMessage(Source(path, line_no)) << "missing '='");
+ return false;
}
- /**
- * 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;
+ ResourceNameRef name;
+ StringPiece res_name_str =
+ util::TrimWhitespace(line.substr(0, std::distance(line.begin(), iter)));
+ if (!ResourceUtils::ParseResourceName(res_name_str, &name)) {
+ diag->Error(DiagMessage(Source(path, line_no))
+ << "invalid resource name '" << res_name_str << "'");
+ return false;
}
- std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
- if (mOptions.outputToDirectory) {
- return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
- } else {
- return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
- }
+ const size_t res_id_start_idx = std::distance(line.begin(), iter) + 1;
+ const size_t res_id_str_len = line.size() - res_id_start_idx;
+ StringPiece res_id_str =
+ util::TrimWhitespace(line.substr(res_id_start_idx, res_id_str_len));
+
+ Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(res_id_str);
+ if (!maybe_id) {
+ diag->Error(DiagMessage(Source(path, line_no)) << "invalid resource ID '"
+ << res_id_str << "'");
+ return false;
}
- bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
- BigBuffer buffer(1024);
- TableFlattener flattener(&buffer);
- if (!flattener.consume(mContext, table)) {
- return false;
- }
+ (*out_id_map)[name.ToResourceName()] = maybe_id.value();
+ }
+ return true;
+}
- if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
- if (writer->writeEntry(buffer)) {
- if (writer->finishEntry()) {
- return true;
- }
- }
- }
+static bool ParseSplitParameter(const StringPiece& arg, IDiagnostics* diag,
+ std::string* out_path,
+ SplitConstraints* out_split) {
+ std::vector<std::string> parts = util::Split(arg, ':');
+ if (parts.size() != 2) {
+ diag->Error(DiagMessage() << "invalid split parameter '" << arg << "'");
+ diag->Note(
+ DiagMessage()
+ << "should be --split path/to/output.apk:<config>[,<config>...]");
+ return false;
+ }
+ *out_path = parts[0];
+ std::vector<ConfigDescription> configs;
+ for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
+ configs.push_back({});
+ if (!ConfigDescription::Parse(config_str, &configs.back())) {
+ diag->Error(DiagMessage() << "invalid config '" << config_str
+ << "' in split parameter '" << arg << "'");
+ return false;
+ }
+ }
+ out_split->configs.insert(configs.begin(), configs.end());
+ return true;
+}
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed to write resources.arsc to archive");
+class LinkCommand {
+ public:
+ LinkCommand(LinkContext* context, const LinkOptions& options)
+ : options_(options),
+ context_(context),
+ final_table_(),
+ file_collection_(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> asset_source =
+ util::make_unique<AssetManagerSymbolSource>();
+ for (const std::string& path : options_.include_paths) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage(path)
+ << "loading include path");
+ }
+
+ // First try to load the file as a static lib.
+ std::string error_str;
+ std::unique_ptr<ResourceTable> static_include =
+ LoadStaticLibrary(path, &error_str);
+ if (static_include) {
+ if (!options_.static_lib) {
+ // Can't include static libraries when not building a static library.
+ context_->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 (options_.no_static_lib_packages) {
+ if (ResourceTablePackage* pkg =
+ static_include->FindPackageById(0x7f)) {
+ pkg->name = context_->GetCompilationPackage();
+ }
+ }
+
+ context_->GetExternalSymbols()->AppendSource(
+ util::make_unique<ResourceTableSymbolSource>(static_include.get()));
+
+ static_table_includes_.push_back(std::move(static_include));
+
+ } else if (!error_str.empty()) {
+ // We had an error with reading, so fail.
+ context_->GetDiagnostics()->Error(DiagMessage(path) << error_str);
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;
- }
+ if (!asset_source->AddAssetPath(path)) {
+ context_->GetDiagnostics()->Error(DiagMessage(path)
+ << "failed to load include path");
+ return false;
+ }
+ }
- std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
+ context_->GetExternalSymbols()->AppendSource(std::move(asset_source));
+ return true;
+ }
+
+ Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res,
+ IDiagnostics* diag) {
+ // Make sure the first element is <manifest> with package attribute.
+ if (xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get())) {
+ AppInfo app_info;
+
+ if (!manifest_el->namespace_uri.empty() ||
+ manifest_el->name != "manifest") {
+ diag->Error(DiagMessage(xml_res->file.source)
+ << "root tag must be <manifest>");
+ return {};
+ }
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- {
- google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+ xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
+ if (!package_attr) {
+ diag->Error(DiagMessage(xml_res->file.source)
+ << "<manifest> must have a 'package' attribute");
+ return {};
+ }
+
+ app_info.package = package_attr->value;
+
+ if (xml::Attribute* version_code_attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
+ Maybe<uint32_t> maybe_code =
+ ResourceUtils::ParseInt(version_code_attr->value);
+ if (!maybe_code) {
+ diag->Error(DiagMessage(xml_res->file.source.WithLine(
+ manifest_el->line_number))
+ << "invalid android:versionCode '"
+ << version_code_attr->value << "'");
+ return {};
+ }
+ app_info.version_code = maybe_code.value();
+ }
+
+ if (xml::Attribute* revision_code_attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
+ Maybe<uint32_t> maybe_code =
+ ResourceUtils::ParseInt(revision_code_attr->value);
+ if (!maybe_code) {
+ diag->Error(DiagMessage(xml_res->file.source.WithLine(
+ manifest_el->line_number))
+ << "invalid android:revisionCode '"
+ << revision_code_attr->value << "'");
+ return {};
+ }
+ app_info.revision_code = maybe_code.value();
+ }
+
+ if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+ if (xml::Attribute* min_sdk = uses_sdk_el->FindAttribute(
+ xml::kSchemaAndroid, "minSdkVersion")) {
+ app_info.min_sdk_version = min_sdk->value;
+ }
+ }
+ return app_info;
+ }
+ 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 is_ext_package_func =
+ [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
+ return context_->GetCompilationPackage() != pkg->name || !pkg->id ||
+ pkg->id.value() != context_->GetPackageId();
+ };
- if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
- return false;
+ bool error = false;
+ for (const auto& package : final_table_.packages) {
+ if (is_ext_package_func(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 res_name(package->name, type->type, entry->name);
+
+ for (const auto& config_value : 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>(config_value->value.get()) &&
+ package->name == "android") {
+ context_->GetDiagnostics()->Warn(
+ DiagMessage(config_value->value->GetSource())
+ << "generated id '" << res_name
+ << "' for external package '" << package->name << "'");
+ } else {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(config_value->value->GetSource())
+ << "defined resource '" << res_name
+ << "' for external package '" << package->name << "'");
+ error = true;
+ }
}
+ }
}
+ }
+ }
- if (!writer->finishEntry()) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
+ auto new_end_iter =
+ std::remove_if(final_table_.packages.begin(),
+ final_table_.packages.end(), is_ext_package_func);
+ final_table_.packages.erase(new_end_iter, final_table_.packages.end());
+ return !error;
+ }
+
+ /**
+ * Returns true if no IDs have been set, false otherwise.
+ */
+ bool VerifyNoIdsSet() {
+ for (const auto& package : final_table_.packages) {
+ for (const auto& type : package->types) {
+ if (type->id) {
+ context_->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 res_name(package->name, type->type, entry->name);
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "entry " << res_name << " has ID " << std::hex
+ << (int)entry->id.value() << std::dec
+ << " assigned");
return false;
+ }
}
- return true;
+ }
}
+ return true;
+ }
- bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
- const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
- if (!mOptions.generateJavaClassPath) {
- return true;
- }
+ std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) {
+ if (options_.output_to_directory) {
+ return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out);
+ } else {
+ return CreateZipFileArchiveWriter(context_->GetDiagnostics(), out);
+ }
+ }
- 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;
+ bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.Consume(context_, table)) {
+ return false;
+ }
+
+ if (writer->StartEntry("resources.arsc", ArchiveEntry::kAlign)) {
+ if (writer->WriteEntry(buffer)) {
+ if (writer->FinishEntry()) {
+ return true;
}
+ }
+ }
- file::appendPath(&outPath, "R.java");
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to write resources.arsc to archive");
+ return false;
+ }
- std::ofstream fout(outPath, std::ofstream::binary);
- if (!fout) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- return false;
- }
+ bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
+ // Create the file/zip entry.
+ if (!writer->StartEntry("resources.arsc.flat", 0)) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to open");
+ return false;
+ }
- JavaClassGenerator generator(mContext, table, javaOptions);
- if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
- mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
- return false;
- }
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->FinishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream interface.
+ CopyingOutputStreamAdaptor adaptor(writer);
- if (!fout) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- }
- return true;
+ std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table);
+ if (!pb_table->SerializeToZeroCopyStream(&adaptor)) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to write");
+ return false;
+ }
}
- bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
- if (!mOptions.generateJavaClassPath) {
- return true;
- }
+ if (!writer->FinishEntry()) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to finish entry");
+ return false;
+ }
+ return true;
+ }
- std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
- mContext->getDiagnostics(), manifestXml);
+ bool WriteJavaFile(ResourceTable* table,
+ const StringPiece& package_name_to_generate,
+ const StringPiece& out_package,
+ const JavaClassGeneratorOptions& java_options) {
+ if (!options_.generate_java_class_path) {
+ return true;
+ }
- if (!manifestClass) {
- // Something bad happened, but we already logged it, so exit.
- return false;
- }
+ std::string out_path = options_.generate_java_class_path.value();
+ file::AppendPath(&out_path, file::PackageToPath(out_package));
+ if (!file::mkdirs(out_path)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create directory '" << out_path << "'");
+ return false;
+ }
- if (manifestClass->empty()) {
- // Empty Manifest class, no need to generate it.
- return true;
- }
+ file::AppendPath(&out_path, "R.java");
- // Add any JavaDoc annotations to the generated class.
- for (const std::string& annotation : mOptions.javadocAnnotations) {
- std::string properAnnotation = "@";
- properAnnotation += annotation;
- manifestClass->getCommentBuilder()->appendComment(properAnnotation);
- }
+ std::ofstream fout(out_path, std::ofstream::binary);
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
- const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage());
+ JavaClassGenerator generator(context_, table, java_options);
+ if (!generator.Generate(package_name_to_generate, out_package, &fout)) {
+ context_->GetDiagnostics()->Error(DiagMessage(out_path)
+ << generator.getError());
+ return false;
+ }
- std::string outPath = mOptions.generateJavaClassPath.value();
- file::appendPath(&outPath, file::packageToPath(packageUtf8));
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ }
+ return true;
+ }
- if (!file::mkdirs(outPath)) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed to create directory '" << outPath << "'");
- return false;
- }
+ bool WriteManifestJavaFile(xml::XmlResource* manifest_xml) {
+ if (!options_.generate_java_class_path) {
+ return true;
+ }
- file::appendPath(&outPath, "Manifest.java");
+ std::unique_ptr<ClassDefinition> manifest_class =
+ GenerateManifestClass(context_->GetDiagnostics(), manifest_xml);
- std::ofstream fout(outPath, std::ofstream::binary);
- if (!fout) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- return false;
- }
+ if (!manifest_class) {
+ // Something bad happened, but we already logged it, so exit.
+ return false;
+ }
- if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- return false;
- }
- return true;
+ if (manifest_class->empty()) {
+ // Empty Manifest class, no need to generate it.
+ return true;
}
- bool writeProguardFile(const proguard::KeepSet& keepSet) {
- if (!mOptions.generateProguardRulesPath) {
- return true;
- }
+ // Add any JavaDoc annotations to the generated class.
+ for (const std::string& annotation : options_.javadoc_annotations) {
+ std::string proper_annotation = "@";
+ proper_annotation += annotation;
+ manifest_class->GetCommentBuilder()->AppendComment(proper_annotation);
+ }
- 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;
- }
+ const std::string& package_utf8 = context_->GetCompilationPackage();
- proguard::writeKeepSet(&fout, keepSet);
- if (!fout) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- return false;
- }
- return true;
+ std::string out_path = options_.generate_java_class_path.value();
+ file::AppendPath(&out_path, file::PackageToPath(package_utf8));
+
+ if (!file::mkdirs(out_path)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create directory '" << out_path << "'");
+ return false;
}
- 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());
+ file::AppendPath(&out_path, "Manifest.java");
+
+ std::ofstream fout(out_path, std::ofstream::binary);
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
}
- std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
- io::IFile* file = collection->findFile("resources.arsc.flat");
- if (!file) {
- return {};
- }
+ if (!ClassDefinition::WriteJavaFile(manifest_class.get(), package_utf8,
+ true, &fout)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+ return true;
+ }
- std::unique_ptr<io::IData> data = file->openAsData();
- return loadTableFromPb(file->getSource(), data->data(), data->size(),
- mContext->getDiagnostics());
+ bool WriteProguardFile(const Maybe<std::string>& out,
+ const proguard::KeepSet& keep_set) {
+ if (!out) {
+ return true;
}
- bool mergeStaticLibrary(const std::string& input, bool override) {
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
- }
+ const std::string& out_path = out.value();
+ std::ofstream fout(out_path, std::ofstream::binary);
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to open '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
- std::string errorStr;
- std::unique_ptr<io::ZipFileCollection> collection =
- io::ZipFileCollection::create(input, &errorStr);
- if (!collection) {
- mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
- return false;
- }
+ proguard::WriteKeepSet(&fout, keep_set);
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+ return true;
+ }
- std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
- if (!table) {
- mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
- return false;
- }
+ std::unique_ptr<ResourceTable> LoadStaticLibrary(const std::string& input,
+ std::string* out_error) {
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::Create(input, out_error);
+ if (!collection) {
+ return {};
+ }
+ return LoadTablePbFromCollection(collection.get());
+ }
- ResourceTablePackage* pkg = table->findPackageById(0x7f);
- if (!pkg) {
- mContext->getDiagnostics()->error(DiagMessage(input)
- << "static library has no package");
- return false;
- }
+ std::unique_ptr<ResourceTable> LoadTablePbFromCollection(
+ io::IFileCollection* collection) {
+ io::IFile* file = collection->FindFile("resources.arsc.flat");
+ if (!file) {
+ return {};
+ }
- bool result;
- if (mOptions.noStaticLibPackages) {
- // Merge all resources as if they were in the compilation package. This is the old
- // behaviour of aapt.
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ return LoadTableFromPb(file->GetSource(), data->data(), data->size(),
+ context_->GetDiagnostics());
+ }
- // 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);
- }
+ bool MergeStaticLibrary(const std::string& input, bool override) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage()
+ << "merging static library " << input);
+ }
- pkg->name = u"";
- if (override) {
- result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
- } else {
- result = mTableMerger->merge(Source(input), table.get(), collection.get());
- }
+ std::string error_str;
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::Create(input, &error_str);
+ if (!collection) {
+ context_->GetDiagnostics()->Error(DiagMessage(input) << error_str);
+ return false;
+ }
- } 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());
- }
+ std::unique_ptr<ResourceTable> table =
+ LoadTablePbFromCollection(collection.get());
+ if (!table) {
+ context_->GetDiagnostics()->Error(DiagMessage(input)
+ << "invalid static library");
+ return false;
+ }
+
+ ResourceTablePackage* pkg = table->FindPackageById(0x7f);
+ if (!pkg) {
+ context_->GetDiagnostics()->Error(DiagMessage(input)
+ << "static library has no package");
+ return false;
+ }
+
+ bool result;
+ if (options_.no_static_lib_packages) {
+ // Merge all resources as if they were in the compilation package. This is
+ // the old behavior 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()) {
+ options_.extra_java_packages.insert(pkg->name);
+ }
+
+ pkg->name = "";
+ if (override) {
+ result = table_merger_->MergeOverlay(Source(input), table.get(),
+ collection.get());
+ } else {
+ result =
+ table_merger_->Merge(Source(input), table.get(), collection.get());
+ }
- if (!result) {
- return false;
- }
+ } else {
+ // This is the proper way to merge libraries, where the package name is
+ // preserved and resource names are mangled.
+ result = table_merger_->MergeAndMangle(Source(input), pkg->name,
+ table.get(), collection.get());
+ }
- // Make sure to move the collection into the set of IFileCollections.
- mCollections.push_back(std::move(collection));
- return true;
+ if (!result) {
+ return false;
}
- bool mergeResourceTable(io::IFile* file, bool override) {
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
- << file->getSource());
- }
+ // Make sure to move the collection into the set of IFileCollections.
+ collections_.push_back(std::move(collection));
+ return true;
+ }
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- mContext->getDiagnostics()->error(DiagMessage(file->getSource())
- << "failed to open file");
- return false;
- }
+ bool MergeResourceTable(io::IFile* file, bool override) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage() << "merging resource table " << file->GetSource());
+ }
- std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
- data->data(), data->size(),
- mContext->getDiagnostics());
- if (!table) {
- return false;
- }
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ if (!data) {
+ context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+ << "failed to open file");
+ return false;
+ }
- bool result = false;
- if (override) {
- result = mTableMerger->mergeOverlay(file->getSource(), table.get());
- } else {
- result = mTableMerger->merge(file->getSource(), table.get());
- }
- return result;
+ std::unique_ptr<ResourceTable> table =
+ LoadTableFromPb(file->GetSource(), data->data(), data->size(),
+ context_->GetDiagnostics());
+ if (!table) {
+ return false;
}
- 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 = table_merger_->MergeOverlay(file->GetSource(), table.get());
+ } else {
+ result = table_merger_->Merge(file->GetSource(), table.get());
+ }
+ return result;
+ }
- bool result = false;
- if (override) {
- result = mTableMerger->mergeFileOverlay(*fileDesc, file);
- } else {
- result = mTableMerger->mergeFile(*fileDesc, file);
- }
+ bool MergeCompiledFile(io::IFile* file, ResourceFile* file_desc,
+ bool override) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage() << "merging '" << file_desc->name
+ << "' from compiled file " << file->GetSource());
+ }
- if (!result) {
- return false;
- }
+ bool result = false;
+ if (override) {
+ result = table_merger_->MergeFileOverlay(*file_desc, file);
+ } else {
+ result = table_merger_->MergeFile(*file_desc, file);
+ }
- // Add the exports of this file to the table.
- for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
- if (exportedSymbol.name.package.empty()) {
- exportedSymbol.name.package = mContext->getCompilationPackage();
- }
+ if (!result) {
+ return false;
+ }
- ResourceNameRef resName = exportedSymbol.name;
+ // Add the exports of this file to the table.
+ for (SourcedResourceName& exported_symbol : file_desc->exported_symbols) {
+ if (exported_symbol.name.package.empty()) {
+ exported_symbol.name.package = context_->GetCompilationPackage();
+ }
- Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
- exportedSymbol.name);
- if (mangledName) {
- resName = mangledName.value();
- }
+ ResourceNameRef res_name = exported_symbol.name;
- 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;
+ Maybe<ResourceName> mangled_name =
+ context_->GetNameMangler()->MangleName(exported_symbol.name);
+ if (mangled_name) {
+ res_name = mangled_name.value();
+ }
+
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->SetSource(file_desc->source.WithLine(exported_symbol.line));
+ bool result = final_table_.AddResourceAllowMangled(
+ res_name, ConfigDescription::DefaultConfig(), std::string(),
+ std::move(id), context_->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 (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage() << "merging archive "
+ << input);
+ }
+
+ std::string error_str;
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::Create(input, &error_str);
+ if (!collection) {
+ context_->GetDiagnostics()->Error(DiagMessage(input) << error_str);
+ return false;
}
- /**
- * 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);
- }
+ bool error = false;
+ for (auto iter = collection->Iterator(); iter->HasNext();) {
+ if (!MergeFile(iter->Next(), override)) {
+ error = true;
+ }
+ }
- std::string errorStr;
- std::unique_ptr<io::ZipFileCollection> collection =
- io::ZipFileCollection::create(input, &errorStr);
- if (!collection) {
- mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
- return false;
- }
+ // Make sure to move the collection into the set of IFileCollections.
+ collections_.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::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") ||
+ util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) {
+ return MergeArchive(path, override);
+ } else if (util::EndsWith(path, ".apk")) {
+ return MergeStaticLibrary(path, override);
+ }
+
+ io::IFile* file = file_collection_->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::EndsWith(src.path, ".arsc.flat")) {
+ return MergeResourceTable(file, override);
+
+ } else if (util::EndsWith(src.path, ".flat")) {
+ // Try opening the file and looking for an Export header.
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ if (!data) {
+ context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open");
+ return false;
+ }
- bool error = false;
- for (auto iter = collection->iterator(); iter->hasNext(); ) {
- if (!mergeFile(iter->next(), override)) {
- error = true;
- }
- }
+ CompiledFileInputStream input_stream(data->data(), data->size());
+ uint32_t num_files = 0;
+ if (!input_stream.ReadLittleEndian32(&num_files)) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "failed read num files");
+ return false;
+ }
+
+ for (uint32_t i = 0; i < num_files; i++) {
+ pb::CompiledFile compiled_file;
+ if (!input_stream.ReadCompiledFile(&compiled_file)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(src) << "failed to read compiled file header");
+ return false;
+ }
+
+ uint64_t offset, len;
+ if (!input_stream.ReadDataMetaData(&offset, &len)) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "failed to read data meta data");
+ return false;
+ }
+
+ std::unique_ptr<ResourceFile> resource_file =
+ DeserializeCompiledFileFromPb(compiled_file, file->GetSource(),
+ context_->GetDiagnostics());
+ if (!resource_file) {
+ return false;
+ }
+
+ if (!MergeCompiledFile(file->CreateFileSegment(offset, len),
+ resource_file.get(), override)) {
+ return false;
+ }
+ }
+ return true;
+ } else if (util::EndsWith(src.path, ".xml") ||
+ util::EndsWith(src.path, ".png")) {
+ // Since AAPT compiles these file types and appends .flat to them, seeing
+ // their raw extensions is a sign that they weren't compiled.
+ const StringPiece file_type =
+ util::EndsWith(src.path, ".xml") ? "XML" : "PNG";
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "uncompiled " << file_type
+ << " file passed as argument. Must be "
+ "compiled first into .flat file.");
+ return false;
+ }
+
+ // Ignore non .flat files. This could be classes.dex or something else that
+ // happens
+ // to be in an archive.
+ return true;
+ }
+
+ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(
+ const AppInfo& app_info, const SplitConstraints& constraints) {
+ std::unique_ptr<xml::XmlResource> doc =
+ util::make_unique<xml::XmlResource>();
+
+ std::unique_ptr<xml::Namespace> namespace_android =
+ util::make_unique<xml::Namespace>();
+ namespace_android->namespace_uri = xml::kSchemaAndroid;
+ namespace_android->namespace_prefix = "android";
+
+ std::unique_ptr<xml::Element> manifest_el =
+ util::make_unique<xml::Element>();
+ manifest_el->name = "manifest";
+ manifest_el->attributes.push_back(
+ xml::Attribute{"", "package", app_info.package});
+
+ if (app_info.version_code) {
+ manifest_el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionCode",
+ std::to_string(app_info.version_code.value())});
+ }
+
+ if (app_info.revision_code) {
+ manifest_el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "revisionCode",
+ std::to_string(app_info.revision_code.value())});
+ }
+
+ std::stringstream split_name;
+ split_name << "config." << util::Joiner(constraints.configs, "_");
+
+ manifest_el->attributes.push_back(
+ xml::Attribute{"", "split", split_name.str()});
+
+ std::unique_ptr<xml::Element> application_el =
+ util::make_unique<xml::Element>();
+ application_el->name = "application";
+ application_el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
+
+ manifest_el->AppendChild(std::move(application_el));
+ namespace_android->AppendChild(std::move(manifest_el));
+ doc->root = std::move(namespace_android);
+ return doc;
+ }
+
+ /**
+ * Writes the AndroidManifest, ResourceTable, and all XML files referenced by
+ * the ResourceTable to the IArchiveWriter.
+ */
+ bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set,
+ xml::XmlResource* manifest, ResourceTable* table) {
+ const bool keep_raw_values = options_.static_lib;
+ bool result = FlattenXml(manifest, "AndroidManifest.xml", {},
+ keep_raw_values, writer, context_);
+ if (!result) {
+ return false;
+ }
+
+ ResourceFileFlattenerOptions file_flattener_options;
+ file_flattener_options.keep_raw_values = keep_raw_values;
+ file_flattener_options.do_not_compress_anything =
+ options_.do_not_compress_anything;
+ file_flattener_options.extensions_to_not_compress =
+ options_.extensions_to_not_compress;
+ file_flattener_options.no_auto_version = options_.no_auto_version;
+ file_flattener_options.no_version_vectors = options_.no_version_vectors;
+ file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces;
+ file_flattener_options.update_proguard_spec =
+ static_cast<bool>(options_.generate_proguard_rules_path);
+
+ ResourceFileFlattener file_flattener(file_flattener_options, context_,
+ keep_set);
+
+ if (!file_flattener.Flatten(table, writer)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed linking file resources");
+ return false;
+ }
+
+ if (options_.static_lib) {
+ if (!FlattenTableToPb(table, writer)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to write resources.arsc.flat");
+ return false;
+ }
+ } else {
+ if (!FlattenTable(table, writer)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to write resources.arsc");
+ return false;
+ }
+ }
+ return 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);
- }
+ int Run(const std::vector<std::string>& input_files) {
+ // Load the AndroidManifest.xml
+ std::unique_ptr<xml::XmlResource> manifest_xml =
+ LoadXml(options_.manifest_path, context_->GetDiagnostics());
+ if (!manifest_xml) {
+ return 1;
+ }
- 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;
- }
+ // First extract the Package name without modifying it (via
+ // --rename-manifest-package).
+ if (Maybe<AppInfo> maybe_app_info = ExtractAppInfoFromManifest(
+ manifest_xml.get(), context_->GetDiagnostics())) {
+ const AppInfo& app_info = maybe_app_info.value();
+ context_->SetCompilationPackage(app_info.package);
+ }
- std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
- src, data->data(), data->size(), mContext->getDiagnostics());
- if (resourceFile) {
- return mergeCompiledFile(file, resourceFile.get(), override);
- }
- return false;
- }
+ ManifestFixer manifest_fixer(options_.manifest_fixer_options);
+ if (!manifest_fixer.Consume(context_, manifest_xml.get())) {
+ return 1;
+ }
- // Ignore non .flat files. This could be classes.dex or something else that happens
- // to be in an archive.
- return true;
+ Maybe<AppInfo> maybe_app_info = ExtractAppInfoFromManifest(
+ manifest_xml.get(), context_->GetDiagnostics());
+ if (!maybe_app_info) {
+ return 1;
}
- 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;
- }
+ const AppInfo& app_info = maybe_app_info.value();
+ if (app_info.min_sdk_version) {
+ if (Maybe<int> maybe_min_sdk_version = ResourceUtils::ParseSdkVersion(
+ app_info.min_sdk_version.value())) {
+ context_->SetMinSdkVersion(maybe_min_sdk_version.value());
+ }
+ }
- 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;
- }
+ context_->SetNameManglerPolicy(
+ NameManglerPolicy{context_->GetCompilationPackage()});
+ if (context_->GetCompilationPackage() == "android") {
+ context_->SetPackageId(0x01);
+ } else {
+ context_->SetPackageId(0x7f);
+ }
- if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
- mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
- << "invalid package name '"
- << mContext->getCompilationPackage()
- << "'");
- return 1;
- }
+ if (!LoadSymbolsFromIncludePaths()) {
+ return 1;
+ }
- mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
+ TableMergerOptions table_merger_options;
+ table_merger_options.auto_add_overlay = options_.auto_add_overlay;
+ table_merger_ = util::make_unique<TableMerger>(context_, &final_table_,
+ table_merger_options);
- if (mContext->getCompilationPackage() == u"android") {
- mContext->setPackageId(0x01);
- } else {
- mContext->setPackageId(0x7f);
- }
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage()
+ << "linking package '"
+ << context_->GetCompilationPackage()
+ << "' with package ID " << std::hex
+ << (int)context_->GetPackageId());
+ }
- if (!loadSymbolsFromIncludePaths()) {
- return 1;
- }
+ for (const std::string& input : input_files) {
+ if (!MergePath(input, false)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed parsing input");
+ return 1;
+ }
+ }
- TableMergerOptions tableMergerOptions;
- tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
- mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
+ for (const std::string& input : options_.overlay_files) {
+ if (!MergePath(input, true)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed parsing overlays");
+ return 1;
+ }
+ }
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(
- DiagMessage() << "linking package '" << mContext->getCompilationPackage()
- << "' with package ID " << std::hex
- << (int) mContext->getPackageId());
- }
+ if (!VerifyNoExternalPackages()) {
+ return 1;
+ }
+ if (!options_.static_lib) {
+ PrivateAttributeMover mover;
+ if (!mover.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed moving private attributes");
+ return 1;
+ }
- for (const std::string& input : inputFiles) {
- if (!mergePath(input, false)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
- return 1;
- }
- }
+ // Assign IDs if we are building a regular app.
+ IdAssigner id_assigner(&options_.stable_id_map);
+ if (!id_assigner.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed assigning IDs");
+ return 1;
+ }
- for (const std::string& input : mOptions.overlayFiles) {
- if (!mergePath(input, true)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
- return 1;
+ // Now grab each ID and emit it as a file.
+ if (options_.resource_id_map_path) {
+ for (auto& package : final_table_.packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ ResourceName name(package->name, type->type, entry->name);
+ // The IDs are guaranteed to exist.
+ options_.stable_id_map[std::move(name)] = ResourceId(
+ package->id.value(), type->id.value(), entry->id.value());
}
+ }
}
- if (!verifyNoExternalPackages()) {
- return 1;
+ if (!WriteStableIdMapToPath(context_->GetDiagnostics(),
+ options_.stable_id_map,
+ options_.resource_id_map_path.value())) {
+ 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;
+ }
+ }
- if (!mOptions.staticLib) {
- PrivateAttributeMover mover;
- if (!mover.consume(mContext, &mFinalTable)) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed moving private attributes");
- return 1;
- }
- }
+ // Add the names to mangle based on our source merge earlier.
+ context_->SetNameManglerPolicy(NameManglerPolicy{
+ context_->GetCompilationPackage(), table_merger_->merged_packages()});
- 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 our table to the symbol table.
+ context_->GetExternalSymbols()->PrependSource(
+ util::make_unique<ResourceTableSymbolSource>(&final_table_));
- // Add the names to mangle based on our source merge earlier.
- mContext->setNameManglerPolicy(NameManglerPolicy{
- mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
+ ReferenceLinker linker;
+ if (!linker.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed linking references");
+ return 1;
+ }
- // Add our table to the symbol table.
- mContext->getExternalSymbols()->prependSource(
- util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
+ if (options_.static_lib) {
+ if (!options_.products.empty()) {
+ context_->GetDiagnostics()
+ ->Warn(DiagMessage()
+ << "can't select products when building static library");
+ }
+ } else {
+ ProductFilter product_filter(options_.products);
+ if (!product_filter.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed stripping products");
+ return 1;
+ }
+ }
- {
- ReferenceLinker linker;
- if (!linker.consume(mContext, &mFinalTable)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
- return 1;
- }
+ if (!options_.no_auto_version) {
+ AutoVersioner versioner;
+ if (!versioner.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed versioning styles");
+ 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);
- }
- }
+ if (!options_.static_lib && context_->GetMinSdkVersion() > 0) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage() << "collapsing resource versions for minimum SDK "
+ << context_->GetMinSdkVersion());
+ }
- proguard::KeepSet proguardKeepSet;
+ VersionCollapser collapser;
+ if (!collapser.Consume(context_, &final_table_)) {
+ return 1;
+ }
+ }
- std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
- if (!archiveWriter) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
- return 1;
- }
+ if (!options_.no_resource_deduping) {
+ ResourceDeduper deduper;
+ if (!deduper.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed deduping resources");
+ return 1;
+ }
+ }
- bool error = false;
- {
- ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
- if (!manifestFixer.consume(mContext, manifestXml.get())) {
- error = true;
- }
+ proguard::KeepSet proguard_keep_set;
+ proguard::KeepSet proguard_main_dex_keep_set;
- // 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 (options_.static_lib) {
+ if (options_.table_splitter_options.config_filter != nullptr ||
+ options_.table_splitter_options.preferred_density) {
+ context_->GetDiagnostics()
+ ->Warn(DiagMessage()
+ << "can't strip resources when building static library");
+ }
+ } else {
+ // Adjust the SplitConstraints so that their SDK version is stripped if it
+ // is less
+ // than or equal to the minSdk. Otherwise the resources that have had
+ // their SDK version
+ // stripped due to minSdk won't ever match.
+ std::vector<SplitConstraints> adjusted_constraints_list;
+ adjusted_constraints_list.reserve(options_.split_constraints.size());
+ for (const SplitConstraints& constraints : options_.split_constraints) {
+ SplitConstraints adjusted_constraints;
+ for (const ConfigDescription& config : constraints.configs) {
+ if (config.sdkVersion <= context_->GetMinSdkVersion()) {
+ adjusted_constraints.configs.insert(config.CopyWithoutSdkVersion());
+ } else {
+ adjusted_constraints.configs.insert(config);
+ }
+ }
+ adjusted_constraints_list.push_back(std::move(adjusted_constraints));
+ }
+
+ TableSplitter table_splitter(adjusted_constraints_list,
+ options_.table_splitter_options);
+ if (!table_splitter.VerifySplitConstraints(context_)) {
+ return 1;
+ }
+ table_splitter.SplitTable(&final_table_);
- if (error) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
- return 1;
+ // Now we need to write out the Split APKs.
+ auto path_iter = options_.split_paths.begin();
+ auto split_constraints_iter = adjusted_constraints_list.begin();
+ for (std::unique_ptr<ResourceTable>& split_table :
+ table_splitter.splits()) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage(*path_iter)
+ << "generating split with configurations '"
+ << util::Joiner(split_constraints_iter->configs, ", ") << "'");
}
- 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;
+ std::unique_ptr<IArchiveWriter> archive_writer =
+ MakeArchiveWriter(*path_iter);
+ if (!archive_writer) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to create archive");
+ return 1;
}
- if (!mOptions.noAutoVersion) {
- AutoVersioner versioner;
- if (!versioner.consume(mContext, &mFinalTable)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
- return 1;
- }
- }
+ // Generate an AndroidManifest.xml for each split.
+ std::unique_ptr<xml::XmlResource> split_manifest =
+ GenerateSplitManifest(app_info, *split_constraints_iter);
- 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;
- }
+ XmlReferenceLinker linker;
+ if (!linker.Consume(context_, split_manifest.get())) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create Split AndroidManifest.xml");
+ 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 (!WriteApk(archive_writer.get(), &proguard_keep_set,
+ split_manifest.get(), split_table.get())) {
+ return 1;
+ }
- 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.
+ ++path_iter;
+ ++split_constraints_iter;
+ }
+ }
- options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
- if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
- outputPackage, options)) {
- return 1;
- }
+ // Start writing the base APK.
+ std::unique_ptr<IArchiveWriter> archive_writer =
+ MakeArchiveWriter(options_.output_path);
+ if (!archive_writer) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to create archive");
+ return 1;
+ }
- options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
- outputPackage = mOptions.privateSymbols.value();
- }
+ bool error = false;
+ {
+ // 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.
+ manifest_xml->file.name.package = context_->GetCompilationPackage();
- if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
- return 1;
- }
+ XmlReferenceLinker manifest_linker;
+ if (manifest_linker.Consume(context_, manifest_xml.get())) {
+ if (options_.generate_proguard_rules_path &&
+ !proguard::CollectProguardRulesForManifest(
+ Source(options_.manifest_path), manifest_xml.get(),
+ &proguard_keep_set)) {
+ error = true;
+ }
- for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
- if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
- return 1;
- }
- }
+ if (options_.generate_main_dex_proguard_rules_path &&
+ !proguard::CollectProguardRulesForManifest(
+ Source(options_.manifest_path), manifest_xml.get(),
+ &proguard_main_dex_keep_set, true)) {
+ error = true;
}
- if (mOptions.generateProguardRulesPath) {
- if (!writeProguardFile(proguardKeepSet)) {
- return 1;
- }
+ if (options_.generate_java_class_path) {
+ if (!WriteManifestJavaFile(manifest_xml.get())) {
+ error = true;
+ }
}
- if (mContext->verbose()) {
- DebugPrintTableOptions debugPrintTableOptions;
- debugPrintTableOptions.showSources = true;
- Debug::printTable(&mFinalTable, debugPrintTableOptions);
+ if (options_.no_xml_namespaces) {
+ // PackageParser will fail if URIs are removed from
+ // AndroidManifest.xml.
+ XmlNamespaceRemover namespace_remover(true /* keepUris */);
+ if (!namespace_remover.Consume(context_, manifest_xml.get())) {
+ error = true;
+ }
}
- return 0;
+ } else {
+ error = true;
+ }
}
-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;
+ if (error) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed processing manifest");
+ 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 (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(),
+ &final_table_)) {
+ return 1;
}
- if (verbose) {
- context.setVerbose(verbose);
- }
+ if (options_.generate_java_class_path) {
+ JavaClassGeneratorOptions options;
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+ options.javadoc_annotations = options_.javadoc_annotations;
- if (privateSymbolsPackage) {
- options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
- }
+ if (options_.static_lib || options_.generate_non_final_ids) {
+ options.use_final = false;
+ }
- if (minSdkVersion) {
- options.manifestFixerOptions.minSdkVersionDefault =
- util::utf8ToUtf16(minSdkVersion.value());
- }
+ const StringPiece actual_package = context_->GetCompilationPackage();
+ StringPiece output_package = context_->GetCompilationPackage();
+ if (options_.custom_java_package) {
+ // Override the output java package to the custom one.
+ output_package = options_.custom_java_package.value();
+ }
- if (targetSdkVersion) {
- options.manifestFixerOptions.targetSdkVersionDefault =
- util::utf8ToUtf16(targetSdkVersion.value());
- }
+ if (options_.private_symbols) {
+ // 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.
- if (renameManifestPackage) {
- options.manifestFixerOptions.renameManifestPackage =
- util::utf8ToUtf16(renameManifestPackage.value());
- }
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+ if (!WriteJavaFile(&final_table_, context_->GetCompilationPackage(),
+ output_package, options)) {
+ return 1;
+ }
- if (renameInstrumentationTargetPackage) {
- options.manifestFixerOptions.renameInstrumentationTargetPackage =
- util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
- }
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+ output_package = options_.private_symbols.value();
+ }
- if (versionCode) {
- options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
- }
+ if (!WriteJavaFile(&final_table_, actual_package, output_package,
+ options)) {
+ return 1;
+ }
- if (versionName) {
- options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
+ for (const std::string& extra_package : options_.extra_java_packages) {
+ if (!WriteJavaFile(&final_table_, actual_package, extra_package,
+ options)) {
+ return 1;
+ }
+ }
}
- if (customJavaPackage) {
- options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
+ if (!WriteProguardFile(options_.generate_proguard_rules_path,
+ proguard_keep_set)) {
+ return 1;
}
- // 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 (!WriteProguardFile(options_.generate_main_dex_proguard_rules_path,
+ proguard_main_dex_keep_set)) {
+ return 1;
}
- if (productList) {
- for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
- if (product != "" && product != "default") {
- options.products.insert(product.toString());
- }
- }
+ if (context_->IsVerbose()) {
+ DebugPrintTableOptions debug_print_table_options;
+ debug_print_table_options.show_sources = true;
+ Debug::PrintTable(&final_table_, debug_print_table_options);
}
+ return 0;
+ }
- 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);
- }
- }
+ private:
+ LinkOptions options_;
+ LinkContext* context_;
+ ResourceTable final_table_;
- options.tableSplitterOptions.configFilter = &filter;
- }
+ std::unique_ptr<TableMerger> table_merger_;
- if (preferredDensity) {
- ConfigDescription preferredDensityConfig;
- if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
- context.getDiagnostics()->error(DiagMessage() << "invalid density '"
- << preferredDensity.value()
- << "' for --preferred-density option");
- return 1;
- }
+ // A pointer to the FileCollection representing the filesystem (not archives).
+ std::unique_ptr<io::FileCollection> file_collection_;
- // Clear the version that can be automatically added.
- preferredDensityConfig.sdkVersion = 0;
+ // A vector of IFileCollections. This is mainly here to keep ownership of the
+ // collections.
+ std::vector<std::unique_ptr<io::IFileCollection>> collections_;
- 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;
- }
+ // A vector of ResourceTables. This is here to retain ownership, so that the
+ // SymbolTable
+ // can use these.
+ std::vector<std::unique_ptr<ResourceTable>> static_table_includes_;
+};
- // Turn off auto versioning for static-libs.
- if (options.staticLib) {
- options.noAutoVersion = true;
- options.noVersionVectors = true;
+int Link(const std::vector<StringPiece>& args) {
+ LinkContext context;
+ LinkOptions options;
+ std::vector<std::string> overlay_arg_list;
+ std::vector<std::string> extra_java_packages;
+ Maybe<std::string> configs;
+ Maybe<std::string> preferred_density;
+ Maybe<std::string> product_list;
+ bool legacy_x_flag = false;
+ bool require_localization = false;
+ bool verbose = false;
+ Maybe<std::string> stable_id_file_path;
+ std::vector<std::string> split_args;
+ Flags flags =
+ Flags()
+ .RequiredFlag("-o", "Output path", &options.output_path)
+ .RequiredFlag("--manifest", "Path to the Android manifest to build",
+ &options.manifest_path)
+ .OptionalFlagList("-I", "Adds an Android APK to link against",
+ &options.include_paths)
+ .OptionalFlagList(
+ "-R",
+ "Compilation unit to link, using `overlay` semantics.\n"
+ "The last conflicting resource given takes precedence.",
+ &overlay_arg_list)
+ .OptionalFlag("--java", "Directory in which to generate R.java",
+ &options.generate_java_class_path)
+ .OptionalFlag("--proguard",
+ "Output file for generated Proguard rules",
+ &options.generate_proguard_rules_path)
+ .OptionalFlag(
+ "--proguard-main-dex",
+ "Output file for generated Proguard rules for the main dex",
+ &options.generate_main_dex_proguard_rules_path)
+ .OptionalSwitch("--no-auto-version",
+ "Disables automatic style and layout SDK versioning",
+ &options.no_auto_version)
+ .OptionalSwitch("--no-version-vectors",
+ "Disables automatic versioning of vector drawables. "
+ "Use this only\n"
+ "when building with vector drawable support library",
+ &options.no_version_vectors)
+ .OptionalSwitch("--no-resource-deduping",
+ "Disables automatic deduping of resources with\n"
+ "identical values across compatible configurations.",
+ &options.no_resource_deduping)
+ .OptionalSwitch(
+ "-x",
+ "Legacy flag that specifies to use the package identifier 0x01",
+ &legacy_x_flag)
+ .OptionalSwitch("-z",
+ "Require localization of strings marked 'suggested'",
+ &require_localization)
+ .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.",
+ &preferred_density)
+ .OptionalFlag("--product",
+ "Comma separated list of product names to keep",
+ &product_list)
+ .OptionalSwitch("--output-to-dir",
+ "Outputs the APK contents to a directory specified "
+ "by -o",
+ &options.output_to_directory)
+ .OptionalSwitch("--no-xml-namespaces",
+ "Removes XML namespace prefix and URI "
+ "information from AndroidManifest.xml\nand XML "
+ "binaries in res/*.",
+ &options.no_xml_namespaces)
+ .OptionalFlag("--min-sdk-version",
+ "Default minimum SDK version to use for "
+ "AndroidManifest.xml",
+ &options.manifest_fixer_options.min_sdk_version_default)
+ .OptionalFlag(
+ "--target-sdk-version",
+ "Default target SDK version to use for "
+ "AndroidManifest.xml",
+ &options.manifest_fixer_options.target_sdk_version_default)
+ .OptionalFlag("--version-code",
+ "Version code (integer) to inject into the "
+ "AndroidManifest.xml if none is present",
+ &options.manifest_fixer_options.version_code_default)
+ .OptionalFlag("--version-name",
+ "Version name to inject into the AndroidManifest.xml "
+ "if none is present",
+ &options.manifest_fixer_options.version_name_default)
+ .OptionalSwitch("--static-lib", "Generate a static Android library",
+ &options.static_lib)
+ .OptionalSwitch("--no-static-lib-packages",
+ "Merge all library resources under the app's package",
+ &options.no_static_lib_packages)
+ .OptionalSwitch("--non-final-ids",
+ "Generates R.java without the final modifier.\n"
+ "This is implied when --static-lib is specified.",
+ &options.generate_non_final_ids)
+ .OptionalFlag("--stable-ids",
+ "File containing a list of name to ID mapping.",
+ &stable_id_file_path)
+ .OptionalFlag(
+ "--emit-ids",
+ "Emit a file at the given path with a list of name to ID\n"
+ "mappings, suitable for use with --stable-ids.",
+ &options.resource_id_map_path)
+ .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",
+ &options.private_symbols)
+ .OptionalFlag("--custom-package",
+ "Custom Java package under which to generate R.java",
+ &options.custom_java_package)
+ .OptionalFlagList("--extra-packages",
+ "Generate the same R.java but with different "
+ "package names",
+ &extra_java_packages)
+ .OptionalFlagList("--add-javadoc-annotation",
+ "Adds a JavaDoc annotation to all "
+ "generated Java classes",
+ &options.javadoc_annotations)
+ .OptionalSwitch("--auto-add-overlay",
+ "Allows the addition of new resources in "
+ "overlays without <add-resource> tags",
+ &options.auto_add_overlay)
+ .OptionalFlag("--rename-manifest-package",
+ "Renames the package in AndroidManifest.xml",
+ &options.manifest_fixer_options.rename_manifest_package)
+ .OptionalFlag(
+ "--rename-instrumentation-target-package",
+ "Changes the name of the target package for instrumentation. "
+ "Most useful "
+ "when used\nin conjunction with --rename-manifest-package",
+ &options.manifest_fixer_options
+ .rename_instrumentation_target_package)
+ .OptionalFlagList("-0", "File extensions not to compress",
+ &options.extensions_to_not_compress)
+ .OptionalFlagList(
+ "--split",
+ "Split resources matching a set of configs out to a "
+ "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
+ &split_args)
+ .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> arg_list;
+ for (const std::string& arg : flags.GetArgs()) {
+ if (util::StartsWith(arg, "@")) {
+ const std::string path = arg.substr(1, arg.size() - 1);
+ std::string error;
+ if (!file::AppendArgsFromFile(path, &arg_list, &error)) {
+ context.GetDiagnostics()->Error(DiagMessage(path) << error);
+ return 1;
+ }
+ } else {
+ arg_list.push_back(arg);
}
+ }
- LinkCommand cmd(&context, options);
- return cmd.run(argList);
+ // Expand all argument-files passed to -R.
+ for (const std::string& arg : overlay_arg_list) {
+ if (util::StartsWith(arg, "@")) {
+ const std::string path = arg.substr(1, arg.size() - 1);
+ std::string error;
+ if (!file::AppendArgsFromFile(path, &options.overlay_files, &error)) {
+ context.GetDiagnostics()->Error(DiagMessage(path) << error);
+ return 1;
+ }
+ } else {
+ options.overlay_files.push_back(arg);
+ }
+ }
+
+ if (verbose) {
+ context.SetVerbose(verbose);
+ }
+
+ // Populate the set of extra packages for which to generate R.java.
+ for (std::string& extra_package : extra_java_packages) {
+ // A given package can actually be a colon separated list of packages.
+ for (StringPiece package : util::Split(extra_package, ':')) {
+ options.extra_java_packages.insert(package.ToString());
+ }
+ }
+
+ if (product_list) {
+ for (StringPiece product : util::Tokenize(product_list.value(), ',')) {
+ if (product != "" && product != "default") {
+ options.products.insert(product.ToString());
+ }
+ }
+ }
+
+ AxisConfigFilter filter;
+ if (configs) {
+ for (const StringPiece& config_str : util::Tokenize(configs.value(), ',')) {
+ ConfigDescription config;
+ LocaleValue lv;
+ if (lv.InitFromFilterString(config_str)) {
+ lv.WriteTo(&config);
+ } else if (!ConfigDescription::Parse(config_str, &config)) {
+ context.GetDiagnostics()->Error(DiagMessage() << "invalid config '"
+ << config_str
+ << "' for -c option");
+ return 1;
+ }
+
+ if (config.density != 0) {
+ context.GetDiagnostics()->Warn(DiagMessage() << "ignoring density '"
+ << config
+ << "' for -c option");
+ } else {
+ filter.AddConfig(config);
+ }
+ }
+
+ options.table_splitter_options.config_filter = &filter;
+ }
+
+ if (preferred_density) {
+ ConfigDescription preferred_density_config;
+ if (!ConfigDescription::Parse(preferred_density.value(),
+ &preferred_density_config)) {
+ context.GetDiagnostics()->Error(
+ DiagMessage() << "invalid density '" << preferred_density.value()
+ << "' for --preferred-density option");
+ return 1;
+ }
+
+ // Clear the version that can be automatically added.
+ preferred_density_config.sdkVersion = 0;
+
+ if (preferred_density_config.diff(ConfigDescription::DefaultConfig()) !=
+ ConfigDescription::CONFIG_DENSITY) {
+ context.GetDiagnostics()->Error(
+ DiagMessage() << "invalid preferred density '"
+ << preferred_density.value() << "'. "
+ << "Preferred density must only be a density value");
+ return 1;
+ }
+ options.table_splitter_options.preferred_density =
+ preferred_density_config.density;
+ }
+
+ if (!options.static_lib && stable_id_file_path) {
+ if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path.value(),
+ &options.stable_id_map)) {
+ return 1;
+ }
+ }
+
+ // Populate some default no-compress extensions that are already compressed.
+ options.extensions_to_not_compress.insert(
+ {".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg",
+ ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl",
+ ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2",
+ ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
+
+ // Parse the split parameters.
+ for (const std::string& split_arg : split_args) {
+ options.split_paths.push_back({});
+ options.split_constraints.push_back({});
+ if (!ParseSplitParameter(split_arg, context.GetDiagnostics(),
+ &options.split_paths.back(),
+ &options.split_constraints.back())) {
+ return 1;
+ }
+ }
+
+ // Turn off auto versioning for static-libs.
+ if (options.static_lib) {
+ options.no_auto_version = true;
+ options.no_version_vectors = true;
+ }
+
+ LinkCommand cmd(&context, options);
+ return cmd.Run(arg_list);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index ec532aba465f..4687d2c01d68 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -17,12 +17,15 @@
#ifndef AAPT_LINKER_LINKERS_H
#define AAPT_LINKER_LINKERS_H
+#include <set>
+#include <unordered_set>
+
+#include "android-base/macros.h"
+
#include "Resource.h"
#include "process/IResourceTableConsumer.h"
#include "xml/XmlDom.h"
-#include <set>
-
namespace aapt {
class ResourceTable;
@@ -30,74 +33,174 @@ class ResourceEntry;
struct ConfigDescription;
/**
- * Defines the location in which a value exists. This determines visibility of other
- * package's private symbols.
+ * Defines the location in which a value exists. This determines visibility of
+ * other package's private symbols.
*/
struct CallSite {
- ResourceNameRef resource;
+ ResourceNameRef resource;
};
/**
- * Determines whether a versioned resource should be created. If a versioned resource already
- * exists, it takes precedence.
+ * 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);
+bool ShouldGenerateVersionedResource(const ResourceEntry* entry,
+ const ConfigDescription& config,
+ const int sdk_version_to_generate);
+
+class AutoVersioner : public IResourceTableConsumer {
+ public:
+ AutoVersioner() = default;
-struct AutoVersioner : public IResourceTableConsumer {
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AutoVersioner);
};
-struct XmlAutoVersioner : public IXmlResourceConsumer {
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+class VersionCollapser : public IResourceTableConsumer {
+ public:
+ VersionCollapser() = default;
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VersionCollapser);
};
/**
- * 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
+ * Removes duplicated key-value entries from dominated resources.
+ */
+class ResourceDeduper : public IResourceTableConsumer {
+ public:
+ ResourceDeduper() = default;
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceDeduper);
+};
+
+/**
+ * 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'
+ * 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
+ * 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
+ * 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
+ * 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;
+class PrivateAttributeMover : public IResourceTableConsumer {
+ public:
+ PrivateAttributeMover() = default;
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PrivateAttributeMover);
+};
+
+class ResourceConfigValue;
+
+class ProductFilter : public IResourceTableConsumer {
+ public:
+ using ResourceConfigValueIter =
+ std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
+
+ explicit ProductFilter(std::unordered_set<std::string> products)
+ : products_(products) {}
+
+ ResourceConfigValueIter SelectProductToKeep(
+ const ResourceNameRef& name, const ResourceConfigValueIter begin,
+ const ResourceConfigValueIter end, IDiagnostics* diag);
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ std::unordered_set<std::string> products_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProductFilter);
+};
+
+class XmlAutoVersioner : public IXmlResourceConsumer {
+ public:
+ XmlAutoVersioner() = default;
+
+ bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlAutoVersioner);
+};
+
+/**
+ * Removes namespace nodes and URI information from the XmlResource.
+ *
+ * Once an XmlResource is processed by this consumer, it is no longer able to
+ * have its attributes
+ * parsed. As such, this XmlResource must have already been processed by
+ * XmlReferenceLinker.
+ */
+class XmlNamespaceRemover : public IXmlResourceConsumer {
+ public:
+ explicit XmlNamespaceRemover(bool keep_uris = false)
+ : keep_uris_(keep_uris){};
+
+ bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlNamespaceRemover);
+
+ bool keep_uris_;
};
/**
- * Resolves attributes in the XmlResource and compiles string values to resource values.
+ * 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;
- }
+ public:
+ XmlReferenceLinker() = default;
+
+ 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>& sdk_levels() const { return sdk_levels_found_; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlReferenceLinker);
+
+ std::set<int> sdk_levels_found_;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_LINKER_LINKERS_H */
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 77a949f1339d..4185937e6e38 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-#include "ResourceUtils.h"
#include "link/ManifestFixer.h"
+
+#include <unordered_set>
+
+#include "android-base/logging.h"
+
+#include "ResourceUtils.h"
#include "util/Util.h"
#include "xml/XmlActionExecutor.h"
#include "xml/XmlDom.h"
@@ -23,272 +28,317 @@
namespace aapt {
/**
- * This is how PackageManager builds class names from AndroidManifest.xml entries.
+ * This is how PackageManager builds class names from AndroidManifest.xml
+ * entries.
*/
-static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
+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::string> fully_qualified_class_name =
+ util::GetFullyQualifiedClassName("a", attr->value);
+
+ StringPiece qualified_class_name = fully_qualified_class_name
+ ? fully_qualified_class_name.value()
+ : attr->value;
+
+ if (!util::IsJavaClassName(qualified_class_name)) {
+ diag->Error(DiagMessage(el->line_number)
+ << "attribute 'android:name' in <" << el->name
+ << "> tag must be a valid Java class name");
+ return false;
+ }
+ return true;
+}
- // 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, "name")) {
+ return NameIsJavaClassName(el, attr, diag);
+ }
+ 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, "name")) {
+ return NameIsJavaClassName(el, attr, diag);
+ }
+ diag->Error(DiagMessage(el->line_number)
+ << "<" << el->name << "> is missing attribute 'android:name'");
+ return false;
}
-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'");
+static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
+ xml::Attribute* attr = el->FindAttribute({}, "package");
+ if (!attr) {
+ diag->Error(DiagMessage(el->line_number)
+ << "<manifest> tag is missing 'package' attribute");
+ return false;
+ } else if (ResourceUtils::IsReference(attr->value)) {
+ diag->Error(
+ DiagMessage(el->line_number)
+ << "attribute 'package' in <manifest> tag must not be a reference");
return false;
+ } else if (!util::IsJavaPackageName(attr->value)) {
+ diag->Error(DiagMessage(el->line_number)
+ << "attribute 'package' in <manifest> tag is not a valid Java "
+ "package name: '"
+ << attr->value << "'");
+ return false;
+ }
+ return true;
}
-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;
+/**
+ * The coreApp attribute in <manifest> is not a regular AAPT attribute, so type
+ * checking on it is manual.
+ */
+static bool FixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) {
+ if (xml::Attribute* attr = el->FindAttribute("", "coreApp")) {
+ std::unique_ptr<BinaryPrimitive> result =
+ ResourceUtils::TryParseBool(attr->value);
+ if (!result) {
+ diag->Error(DiagMessage(el->line_number)
+ << "attribute coreApp must be a boolean");
+ return false;
}
- return true;
+ attr->compiled_value = std::move(result);
+ }
+ 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;
- }
+bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
+ IDiagnostics* diag) {
+ // First verify some options.
+ if (options_.rename_manifest_package) {
+ if (!util::IsJavaPackageName(options_.rename_manifest_package.value())) {
+ diag->Error(DiagMessage() << "invalid manifest package override '"
+ << options_.rename_manifest_package.value()
+ << "'");
+ return false;
+ }
+ }
+
+ if (options_.rename_instrumentation_target_package) {
+ if (!util::IsJavaPackageName(
+ options_.rename_instrumentation_target_package.value())) {
+ diag->Error(DiagMessage()
+ << "invalid instrumentation target package override '"
+ << options_.rename_instrumentation_target_package.value()
+ << "'");
+ return false;
+ }
+ }
+
+ // Common intent-filter actions.
+ xml::XmlNodeAction intent_filter_action;
+ intent_filter_action["action"];
+ intent_filter_action["category"];
+ intent_filter_action["data"];
+
+ // Common meta-data actions.
+ xml::XmlNodeAction meta_data_action;
+
+ // Manifest actions.
+ xml::XmlNodeAction& manifest_action = (*executor)["manifest"];
+ manifest_action.Action(VerifyManifest);
+ manifest_action.Action(FixCoreAppAttribute);
+ manifest_action.Action([&](xml::Element* el) -> bool {
+ if (options_.version_name_default) {
+ if (el->FindAttribute(xml::kSchemaAndroid, "versionName") == nullptr) {
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionName",
+ options_.version_name_default.value()});
+ }
}
- if (mOptions.renameInstrumentationTargetPackage) {
- if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) {
- diag->error(DiagMessage() << "invalid instrumentation target package override '"
- << mOptions.renameInstrumentationTargetPackage.value() << "'");
- return false;
- }
+ if (options_.version_code_default) {
+ if (el->FindAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) {
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionCode",
+ options_.version_code_default.value()});
+ }
+ }
+ return true;
+ });
+
+ // Meta tags.
+ manifest_action["eat-comment"];
+
+ // Uses-sdk actions.
+ manifest_action["uses-sdk"].Action([&](xml::Element* el) -> bool {
+ if (options_.min_sdk_version_default &&
+ el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) {
+ // There was no minSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "minSdkVersion",
+ options_.min_sdk_version_default.value()});
}
- // 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 (options_.target_sdk_version_default &&
+ el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion") == nullptr) {
+ // There was no targetSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "targetSdkVersion",
+ options_.target_sdk_version_default.value()});
+ }
+ return true;
+ });
- 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() });
- }
+ // Instrumentation actions.
+ manifest_action["instrumentation"].Action([&](xml::Element* el) -> bool {
+ if (!options_.rename_instrumentation_target_package) {
+ return true;
+ }
- 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;
- });
+ if (xml::Attribute* attr =
+ el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) {
+ attr->value = options_.rename_instrumentation_target_package.value();
+ }
+ return true;
+ });
- // Instrumentation actions.
- manifestAction[u"instrumentation"].action([&](xml::Element* el) -> bool {
- if (!mOptions.renameInstrumentationTargetPackage) {
- return true;
- }
+ manifest_action["original-package"];
+ manifest_action["protected-broadcast"];
+ manifest_action["uses-permission"];
+ manifest_action["permission"];
+ manifest_action["permission-tree"];
+ manifest_action["permission-group"];
- 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"supports-screens"];
- manifestAction[u"compatible-screens"];
- manifestAction[u"supports-gl-texture"];
-
- // Application actions.
- xml::XmlNodeAction& applicationAction = (*executor)[u"manifest"][u"application"];
- applicationAction.action(optionalNameIsJavaClassName);
-
- // Uses library actions.
- applicationAction[u"uses-library"];
-
- // 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;
-}
+ manifest_action["uses-configuration"];
+ manifest_action["uses-feature"];
+ manifest_action["supports-screens"];
-class FullyQualifiedClassNameVisitor : public xml::Visitor {
-public:
- using xml::Visitor::visit;
+ manifest_action["compatible-screens"];
+ manifest_action["compatible-screens"]["screen"];
- FullyQualifiedClassNameVisitor(const StringPiece16& package) : mPackage(package) {
- }
+ manifest_action["supports-gl-texture"];
- 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());
- }
- }
+ manifest_action["meta-data"] = meta_data_action;
- // Super implementation to iterate over the children.
- xml::Visitor::visit(el);
- }
+ // Application actions.
+ xml::XmlNodeAction& application_action = manifest_action["application"];
+ application_action.Action(OptionalNameIsJavaClassName);
-private:
- StringPiece16 mPackage;
-};
+ // Uses library actions.
+ application_action["uses-library"];
-static bool renameManifestPackage(const StringPiece16& packageOverride, xml::Element* manifestEl) {
- xml::Attribute* attr = manifestEl->findAttribute({}, u"package");
+ // Meta-data.
+ application_action["meta-data"] = meta_data_action;
- // We've already verified that the manifest element is present, with a package name specified.
- assert(attr);
+ // Activity actions.
+ application_action["activity"].Action(RequiredNameIsJavaClassName);
+ application_action["activity"]["intent-filter"] = intent_filter_action;
+ application_action["activity"]["meta-data"] = meta_data_action;
- std::u16string originalPackage = std::move(attr->value);
- attr->value = packageOverride.toString();
+ // Activity alias actions.
+ application_action["activity-alias"]["intent-filter"] = intent_filter_action;
+ application_action["activity-alias"]["meta-data"] = meta_data_action;
- FullyQualifiedClassNameVisitor visitor(originalPackage);
- manifestEl->accept(&visitor);
- return true;
+ // Service actions.
+ application_action["service"].Action(RequiredNameIsJavaClassName);
+ application_action["service"]["intent-filter"] = intent_filter_action;
+ application_action["service"]["meta-data"] = meta_data_action;
+
+ // Receiver actions.
+ application_action["receiver"].Action(RequiredNameIsJavaClassName);
+ application_action["receiver"]["intent-filter"] = intent_filter_action;
+ application_action["receiver"]["meta-data"] = meta_data_action;
+
+ // Provider actions.
+ application_action["provider"].Action(RequiredNameIsJavaClassName);
+ application_action["provider"]["intent-filter"] = intent_filter_action;
+ application_action["provider"]["meta-data"] = meta_data_action;
+ application_action["provider"]["grant-uri-permissions"];
+ application_action["provider"]["path-permissions"];
+
+ 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;
+class FullyQualifiedClassNameVisitor : public xml::Visitor {
+ public:
+ using xml::Visitor::Visit;
+
+ explicit FullyQualifiedClassNameVisitor(const StringPiece& package)
+ : package_(package) {}
+
+ void Visit(xml::Element* el) override {
+ for (xml::Attribute& attr : el->attributes) {
+ if (attr.namespace_uri == xml::kSchemaAndroid &&
+ class_attributes_.find(attr.name) != class_attributes_.end()) {
+ if (Maybe<std::string> new_value =
+ util::GetFullyQualifiedClassName(package_, attr.value)) {
+ attr.value = std::move(new_value.value());
+ }
+ }
}
- 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));
- }
+ // Super implementation to iterate over the children.
+ xml::Visitor::Visit(el);
+ }
- xml::XmlActionExecutor executor;
- if (!buildRules(&executor, context->getDiagnostics())) {
- return false;
- }
+ private:
+ StringPiece package_;
+ std::unordered_set<StringPiece> class_attributes_ = {"name"};
+};
- if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(),
- doc)) {
- return false;
- }
+static bool RenameManifestPackage(const StringPiece& package_override,
+ xml::Element* manifest_el) {
+ xml::Attribute* attr = manifest_el->FindAttribute({}, "package");
- 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;
- }
+ // We've already verified that the manifest element is present, with a package
+ // name specified.
+ CHECK(attr != nullptr);
+
+ std::string original_package = std::move(attr->value);
+ attr->value = package_override.ToString();
+
+ FullyQualifiedClassNameVisitor visitor(original_package);
+ manifest_el->Accept(&visitor);
+ return true;
+}
+
+bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) {
+ xml::Element* root = xml::FindRootElement(doc->root.get());
+ if (!root || !root->namespace_uri.empty() || root->name != "manifest") {
+ context->GetDiagnostics()->Error(DiagMessage(doc->file.source)
+ << "root tag must be <manifest>");
+ return false;
+ }
+
+ if ((options_.min_sdk_version_default ||
+ options_.target_sdk_version_default) &&
+ root->FindChild({}, "uses-sdk") == nullptr) {
+ // Auto insert a <uses-sdk> element. This must be inserted before the
+ // <application> tag. The device runtime PackageParser will make SDK version
+ // decisions while parsing <application>.
+ std::unique_ptr<xml::Element> uses_sdk = util::make_unique<xml::Element>();
+ uses_sdk->name = "uses-sdk";
+ root->InsertChild(0, std::move(uses_sdk));
+ }
+
+ xml::XmlActionExecutor executor;
+ if (!BuildRules(&executor, context->GetDiagnostics())) {
+ return false;
+ }
+
+ if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist,
+ context->GetDiagnostics(), doc)) {
+ return false;
+ }
+
+ if (options_.rename_manifest_package) {
+ // Rename manifest package outside of the XmlActionExecutor.
+ // We need to extract the old package name and FullyQualify all class
+ // names.
+ if (!RenameManifestPackage(options_.rename_manifest_package.value(),
+ root)) {
+ return false;
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 4d9356a933c2..470f65eb01c4 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -17,22 +17,24 @@
#ifndef AAPT_LINK_MANIFESTFIXER_H
#define AAPT_LINK_MANIFESTFIXER_H
+#include <string>
+
+#include "android-base/macros.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;
+ Maybe<std::string> min_sdk_version_default;
+ Maybe<std::string> target_sdk_version_default;
+ Maybe<std::string> rename_manifest_package;
+ Maybe<std::string> rename_instrumentation_target_package;
+ Maybe<std::string> version_name_default;
+ Maybe<std::string> version_code_default;
};
/**
@@ -40,18 +42,20 @@ struct ManifestFixerOptions {
* where specified with ManifestFixerOptions.
*/
class ManifestFixer : public IXmlResourceConsumer {
-public:
- ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
- }
+ public:
+ explicit ManifestFixer(const ManifestFixerOptions& options)
+ : options_(options) {}
+
+ bool Consume(IAaptContext* context, xml::XmlResource* doc) override;
- bool consume(IAaptContext* context, xml::XmlResource* doc) override;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ManifestFixer);
-private:
- bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag);
+ bool BuildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag);
- ManifestFixerOptions mOptions;
+ ManifestFixerOptions options_;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_LINK_MANIFESTFIXER_H */
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index f993720b9566..fc6970c8c5bd 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -15,242 +15,341 @@
*/
#include "link/ManifestFixer.h"
-#include "test/Builders.h"
-#include "test/Context.h"
-#include <gtest/gtest.h>
+#include "test/Test.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 {};
+ std::unique_ptr<IAaptContext> mContext;
+
+ void SetUp() override {
+ mContext =
+ test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .AddSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .AddSymbol(
+ "android:attr/package", ResourceId(0x01010000),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING)
+ .Build())
+ .AddSymbol(
+ "android:attr/minSdkVersion", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .Build())
+ .AddSymbol(
+ "android:attr/targetSdkVersion", ResourceId(0x01010002),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .Build())
+ .AddSymbol("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>"));
+ 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\" />"));
+ 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, AllowMetaData) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <meta-data />
+ <application>
+ <meta-data />
+ <activity android:name=".Hi"><meta-data /></activity>
+ <activity-alias android:name=".Ho"><meta-data /></activity-alias>
+ <receiver android:name=".OffToWork"><meta-data /></receiver>
+ <provider android:name=".We"><meta-data /></provider>
+ <service android:name=".Go"><meta-data /></service>
+ </application>
+ </manifest>)EOF");
+ ASSERT_NE(nullptr, doc);
}
TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
- ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
+ ManifestFixerOptions options = {std::string("8"), std::string("22")};
- std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ 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>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* el;
+ xml::Attribute* attr;
+
+ el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->FindChild({}, "uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("7", attr->value);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("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>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->FindChild({}, "uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("8", attr->value);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("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>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->FindChild({}, "uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("8", attr->value);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("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);
+ package="android" />)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->FindChild({}, "uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("8", attr->value);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("22", attr->value);
+}
- 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, UsesSdkMustComeBeforeApplication) {
+ ManifestFixerOptions options = {std::string("8"), std::string("22")};
+ 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" />
+ </manifest>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* manifest_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, manifest_el);
+ ASSERT_EQ("manifest", manifest_el->name);
+
+ xml::Element* application_el = manifest_el->FindChild("", "application");
+ ASSERT_NE(nullptr, application_el);
+
+ xml::Element* uses_sdk_el = manifest_el->FindChild("", "uses-sdk");
+ ASSERT_NE(nullptr, uses_sdk_el);
+
+ // Check that the uses_sdk_el comes before application_el in the children
+ // vector.
+ // Since there are no namespaces here, these children are direct descendants
+ // of manifest.
+ auto uses_sdk_iter =
+ std::find_if(manifest_el->children.begin(), manifest_el->children.end(),
+ [&](const std::unique_ptr<xml::Node>& child) {
+ return child.get() == uses_sdk_el;
+ });
+
+ auto application_iter =
+ std::find_if(manifest_el->children.begin(), manifest_el->children.end(),
+ [&](const std::unique_ptr<xml::Node>& child) {
+ return child.get() == application_el;
+ });
+
+ ASSERT_NE(manifest_el->children.end(), uses_sdk_iter);
+ ASSERT_NE(manifest_el->children.end(), application_iter);
+
+ // The distance should be positive, meaning uses_sdk_iter comes before
+ // application_iter.
+ EXPECT_GT(std::distance(uses_sdk_iter, application_iter), 0);
}
TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) {
- ManifestFixerOptions options;
- options.renameManifestPackage = std::u16string(u"com.android");
+ ManifestFixerOptions options;
+ options.rename_manifest_package = std::string("com.android");
- std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ 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);
+ </manifest>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
- xml::Element* manifestEl = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, manifestEl);
+ xml::Element* manifestEl = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
- xml::Attribute* attr = nullptr;
+ xml::Attribute* attr = nullptr;
- attr = manifestEl->findAttribute({}, u"package");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::u16string(u"com.android"), attr->value);
+ attr = manifestEl->FindAttribute({}, "package");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("com.android"), attr->value);
- xml::Element* applicationEl = manifestEl->findChild({}, u"application");
- ASSERT_NE(nullptr, applicationEl);
+ xml::Element* applicationEl = manifestEl->FindChild({}, "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(xml::kSchemaAndroid, "name");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("android.MainApplication"), attr->value);
- attr = applicationEl->findAttribute({}, u"text");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::u16string(u"hello"), attr->value);
+ attr = applicationEl->FindAttribute({}, "text");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("hello"), attr->value);
- xml::Element* el;
- el = applicationEl->findChild({}, u"activity");
- ASSERT_NE(nullptr, el);
+ xml::Element* el;
+ el = applicationEl->FindChild({}, "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);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ(std::string("android.activity.Start"), attr->value);
- el = applicationEl->findChild({}, u"receiver");
- ASSERT_NE(nullptr, el);
+ el = applicationEl->FindChild({}, "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);
+ attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value);
}
-TEST_F(ManifestFixerTest, RenameManifestInstrumentationPackageAndFullyQualifyTarget) {
- ManifestFixerOptions options;
- options.renameInstrumentationTargetPackage = std::u16string(u"com.android");
+TEST_F(ManifestFixerTest,
+ RenameManifestInstrumentationPackageAndFullyQualifyTarget) {
+ ManifestFixerOptions options;
+ options.rename_instrumentation_target_package = std::string("com.android");
- std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ 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);
+ </manifest>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
- xml::Element* manifestEl = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, manifestEl);
+ xml::Element* manifest_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, manifest_el);
- xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation");
- ASSERT_NE(nullptr, instrumentationEl);
+ xml::Element* instrumentation_el =
+ manifest_el->FindChild({}, "instrumentation");
+ ASSERT_NE(nullptr, instrumentation_el);
- xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::u16string(u"com.android"), attr->value);
+ xml::Attribute* attr =
+ instrumentation_el->FindAttribute(xml::kSchemaAndroid, "targetPackage");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("com.android"), attr->value);
}
TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
- ManifestFixerOptions options;
- options.versionNameDefault = std::u16string(u"Beta");
- options.versionCodeDefault = std::u16string(u"0x10000000");
+ ManifestFixerOptions options;
+ options.version_name_default = std::string("Beta");
+ options.version_code_default = std::string("0x10000000");
- std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ 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);
+ package="android" />)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* manifest_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, manifest_el);
+
+ xml::Attribute* attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("Beta"), attr->value);
+
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("0x10000000"), attr->value);
+}
+
+TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) {
+ EXPECT_EQ(nullptr,
+ Verify("<manifest package=\"android\" coreApp=\"hello\" />"));
+ EXPECT_EQ(nullptr,
+ Verify("<manifest package=\"android\" coreApp=\"1dp\" />"));
+
+ std::unique_ptr<xml::XmlResource> doc =
+ Verify("<manifest package=\"android\" coreApp=\"true\" />");
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
- xml::Element* manifestEl = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, manifestEl);
+ EXPECT_EQ("manifest", el->name);
- xml::Attribute* attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::u16string(u"Beta"), attr->value);
+ xml::Attribute* attr = el->FindAttribute("", "coreApp");
+ ASSERT_NE(nullptr, attr);
- attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::u16string(u"0x10000000"), attr->value);
+ EXPECT_NE(nullptr, attr->compiled_value);
+ EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(attr->compiled_value.get()));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index 3c8af4f81ffe..cc07a6e1925b 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -14,67 +14,74 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
#include "link/Linkers.h"
#include <algorithm>
#include <iterator>
+#include "android-base/logging.h"
+
+#include "ResourceTable.h"
+
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;
- }
+OutputIterator move_if(InputContainer& input_container, OutputIterator result,
+ Predicate pred) {
+ const auto last = input_container.end();
+ auto new_end =
+ std::find_if(input_container.begin(), input_container.end(), pred);
+ if (new_end == last) {
+ return result;
+ }
- *result = std::move(*newEnd);
+ *result = std::move(*new_end);
- auto first = newEnd;
- ++first;
+ auto first = new_end;
+ ++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;
- }
+ 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.
+ *new_end = std::move(*first);
+ ++new_end;
}
+ }
- inputContainer.erase(newEnd, last);
- return result;
+ input_container.erase(new_end, last);
+ return result;
}
-bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) {
- for (auto& package : table->packages) {
- ResourceTableType* type = package->findType(ResourceType::kAttr);
- if (!type) {
- continue;
- }
+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;
- }
+ if (type->symbol_status.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());
+ ResourceTableType* priv_attr_type =
+ package->FindOrCreateType(ResourceType::kAttrPrivate);
+ CHECK(priv_attr_type->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;
+ move_if(type->entries, std::back_inserter(priv_attr_type->entries),
+ [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+ return entry->symbol_status.state != SymbolState::kPublic;
+ });
+ break;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index dbe0c92253c1..90c4922625be 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -15,64 +15,66 @@
*/
#include "link/Linkers.h"
-#include "test/Builders.h"
-#include "test/Context.h"
-#include <gtest/gtest.h>
+#include "test/Test.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);
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddSimple("android:attr/publicA")
+ .AddSimple("android:attr/privateA")
+ .AddSimple("android:attr/publicB")
+ .AddSimple("android:attr/privateB")
+ .SetSymbolState("android:attr/publicA", ResourceId(0x01010000),
+ SymbolState::kPublic)
+ .SetSymbolState("android:attr/publicB", ResourceId(0x01010000),
+ SymbolState::kPublic)
+ .Build();
+
+ PrivateAttributeMover mover;
+ ASSERT_TRUE(mover.Consume(context.get(), table.get()));
+
+ ResourceTablePackage* package = table->FindPackage("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("publicA"), nullptr);
+ EXPECT_NE(type->FindEntry("publicB"), nullptr);
+
+ type = package->FindType(ResourceType::kAttrPrivate);
+ ASSERT_NE(type, nullptr);
+ ASSERT_EQ(type->entries.size(), 2u);
+ EXPECT_NE(type->FindEntry("privateA"), nullptr);
+ EXPECT_NE(type->FindEntry("privateB"), nullptr);
}
-TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+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();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .AddSimple("android:attr/privateA")
+ .AddSimple("android:attr/privateB")
+ .Build();
- PrivateAttributeMover mover;
- ASSERT_TRUE(mover.consume(context.get(), table.get()));
+ PrivateAttributeMover mover;
+ ASSERT_TRUE(mover.Consume(context.get(), table.get()));
- ResourceTablePackage* package = table->findPackage(u"android");
- ASSERT_NE(package, nullptr);
+ ResourceTablePackage* package = table->FindPackage("android");
+ ASSERT_NE(package, nullptr);
- ResourceTableType* type = package->findType(ResourceType::kAttr);
- ASSERT_NE(type, nullptr);
- ASSERT_EQ(type->entries.size(), 2u);
+ 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);
+ type = package->FindType(ResourceType::kAttrPrivate);
+ ASSERT_EQ(type, nullptr);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/link/ProductFilter.cpp
index 8784e891b293..c1a95ee1bcec 100644
--- a/tools/aapt2/link/ProductFilter.cpp
+++ b/tools/aapt2/link/ProductFilter.cpp
@@ -14,105 +14,110 @@
* limitations under the License.
*/
-#include "link/ProductFilter.h"
+#include "link/Linkers.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;
- }
+#include "ResourceTable.h"
- // 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);
+namespace aapt {
- ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get();
- diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource())
- << "default product also defined here");
- return end;
- }
+ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep(
+ const ResourceNameRef& name, const ResourceConfigValueIter begin,
+ const ResourceConfigValueIter end, IDiagnostics* diag) {
+ ResourceConfigValueIter default_product_iter = end;
+ ResourceConfigValueIter selected_product_iter = end;
+
+ for (ResourceConfigValueIter iter = begin; iter != end; ++iter) {
+ ResourceConfigValue* config_value = iter->get();
+ if (products_.find(config_value->product) != products_.end()) {
+ if (selected_product_iter != end) {
+ // We have two possible values for this product!
+ diag->Error(DiagMessage(config_value->value->GetSource())
+ << "selection of product '" << config_value->product
+ << "' for resource " << name << " is ambiguous");
+
+ ResourceConfigValue* previously_selected_config_value =
+ selected_product_iter->get();
+ diag->Note(
+ DiagMessage(previously_selected_config_value->value->GetSource())
+ << "product '" << previously_selected_config_value->product
+ << "' is also a candidate");
+ return end;
+ }
- // Mark the default.
- defaultProductIter = iter;
- }
+ // Select this product.
+ selected_product_iter = iter;
}
- if (defaultProductIter == end) {
- diag->error(DiagMessage() << "no default product defined for resource " << name);
+ if (config_value->product.empty() || config_value->product == "default") {
+ if (default_product_iter != end) {
+ // We have two possible default values.
+ diag->Error(DiagMessage(config_value->value->GetSource())
+ << "multiple default products defined for resource "
+ << name);
+
+ ResourceConfigValue* previously_default_config_value =
+ default_product_iter->get();
+ diag->Note(
+ DiagMessage(previously_default_config_value->value->GetSource())
+ << "default product also defined here");
return end;
- }
+ }
- if (selectedProductIter == end) {
- selectedProductIter = defaultProductIter;
+ // Mark the default.
+ default_product_iter = iter;
}
- return selectedProductIter;
+ }
+
+ if (default_product_iter == end) {
+ diag->Error(DiagMessage() << "no default product defined for resource "
+ << name);
+ return end;
+ }
+
+ if (selected_product_iter == end) {
+ selected_product_iter = default_product_iter;
+ }
+ return selected_product_iter;
}
-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);
+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>> new_values;
+
+ ResourceConfigValueIter iter = entry->values.begin();
+ ResourceConfigValueIter start_range_iter = iter;
+ while (iter != entry->values.end()) {
+ ++iter;
+ if (iter == entry->values.end() ||
+ (*iter)->config != (*start_range_iter)->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 value_to_keep = SelectProductToKeep(
+ name, start_range_iter, iter, context->GetDiagnostics());
+ if (value_to_keep == 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.
+ new_values.push_back(std::move(*value_to_keep));
}
+
+ // Start the next range of products.
+ start_range_iter = iter;
+ }
}
+
+ // Now move the new values in to place.
+ entry->values = std::move(new_values);
+ }
}
- return !error;
+ }
+ return !error;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h
deleted file mode 100644
index d2edd38289dc..000000000000
--- a/tools/aapt2/link/ProductFilter.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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
index f4f756ae4519..379ad26836e8 100644
--- a/tools/aapt2/link/ProductFilter_test.cpp
+++ b/tools/aapt2/link/ProductFilter_test.cpp
@@ -14,123 +14,117 @@
* limitations under the License.
*/
-#include "link/ProductFilter.h"
-#include "test/Builders.h"
-#include "test/Context.h"
+#include "link/Linkers.h"
-#include <gtest/gtest.h>
+#include "test/Test.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"));
+ 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("android:string/one"), land, "",
+ test::ValueBuilder<Id>().SetSource(Source("land/default.xml")).Build(),
+ context->GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:string/one"), land, "tablet",
+ test::ValueBuilder<Id>().SetSource(Source("land/tablet.xml")).Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:string/one"), port, "",
+ test::ValueBuilder<Id>().SetSource(Source("port/default.xml")).Build(),
+ context->GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("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, "android:string/one", land, ""));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
+ &table, "android:string/one", land, "tablet"));
+ EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(
+ &table, "android:string/one", port, ""));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
+ &table, "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"));
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ ResourceTable table;
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:string/one"),
+ ConfigDescription::DefaultConfig(), "",
+ test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(),
+ context->GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("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, "android:string/one",
+ ConfigDescription::DefaultConfig(), ""));
+ EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(
+ &table, "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));
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ ResourceTable table;
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:string/one"),
+ ConfigDescription::DefaultConfig(), "",
+ test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(),
+ context->GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:string/one"),
+ ConfigDescription::DefaultConfig(), "tablet",
+ test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(),
+ context->GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("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));
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ ResourceTable table;
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("android:string/one"),
+ ConfigDescription::DefaultConfig(), "",
+ test::ValueBuilder<Id>().SetSource(Source(".xml")).Build(),
+ context->GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ test::ParseNameOrDie("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
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 66eb0df048db..be787b2727ad 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -14,8 +14,12 @@
* limitations under the License.
*/
+#include "link/ReferenceLinker.h"
+
+#include "android-base/logging.h"
+#include "androidfw/ResourceTypes.h"
+
#include "Diagnostics.h"
-#include "ReferenceLinker.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
@@ -26,309 +30,344 @@
#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
+ * 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) {
+ public:
+ using ValueVisitor::Visit;
+
+ ReferenceLinkerVisitor(IAaptContext* context, SymbolTable* symbols,
+ StringPool* string_pool, xml::IPackageDeclStack* decl,
+ CallSite* callsite)
+ : context_(context),
+ symbols_(symbols),
+ package_decls_(decl),
+ string_pool_(string_pool),
+ callsite_(callsite) {}
+
+ void Visit(Reference* ref) override {
+ if (!ReferenceLinker::LinkReference(ref, context_, symbols_, package_decls_,
+ callsite_)) {
+ error_ = true;
}
-
- 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());
}
- /**
- * 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 err_str;
+
+ // 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 transformed_reference = entry.key;
+ TransformReferenceFromNamespace(package_decls_,
+ context_->GetCompilationPackage(),
+ &transformed_reference);
+
+ // Find the attribute in the symbol table and check if it is visible from
+ // this callsite.
+ const SymbolTable::Symbol* symbol =
+ ReferenceLinker::ResolveAttributeCheckVisibility(
+ transformed_reference, context_->GetNameMangler(), symbols_,
+ callsite_, &err_str);
+ 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);
+ context_->GetDiagnostics()->Error(msg);
+ error_ = true;
}
- 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;
- }
- }
+ } else {
+ DiagMessage msg(entry.key.GetSource());
+ msg << "style attribute '";
+ ReferenceLinker::WriteResourceName(&msg, entry.key,
+ transformed_reference);
+ msg << "' " << err_str;
+ context_->GetDiagnostics()->Error(msg);
+ error_ = true;
+ }
}
+ }
+
+ bool HasError() { return error_; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReferenceLinkerVisitor);
+
+ /**
+ * 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* raw_string = ValueCast<RawString>(value.get())) {
+ std::unique_ptr<Item> transformed =
+ ResourceUtils::TryParseItemForAttribute(*raw_string->value, attr);
+
+ // If we could not parse as any specific type, try a basic STRING.
+ if (!transformed &&
+ (attr->type_mask & android::ResTable_map::TYPE_STRING)) {
+ util::StringBuilder string_builder;
+ string_builder.Append(*raw_string->value);
+ if (string_builder) {
+ transformed = util::make_unique<String>(
+ string_pool_->MakeRef(string_builder.ToString()));
+ }
+ }
+
+ if (transformed) {
+ return transformed;
+ }
+ };
+ return value;
+ }
+
+ IAaptContext* context_;
+ SymbolTable* symbols_;
+ xml::IPackageDeclStack* package_decls_;
+ StringPool* string_pool_;
+ CallSite* callsite_;
+ bool error_ = false;
+};
- bool hasError() {
- return mError;
- }
+class EmptyDeclStack : public xml::IPackageDeclStack {
+ public:
+ EmptyDeclStack() = default;
-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;
+ Maybe<xml::ExtractedPackage> TransformPackageAlias(
+ const StringPiece& alias,
+ const StringPiece& local_package) const override {
+ if (alias.empty()) {
+ return xml::ExtractedPackage{local_package.ToString(),
+ true /* private */};
}
+ return {};
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EmptyDeclStack);
};
-} // namespace
+} // namespace
/**
- * The symbol is visible if it is public, or if the reference to it is requesting private access
+ * 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());
+bool ReferenceLinker::IsSymbolVisible(const SymbolTable::Symbol& symbol,
+ const Reference& ref,
+ const CallSite& callsite) {
+ if (!symbol.is_public && !ref.private_reference) {
+ if (ref.name) {
+ return callsite.resource.package == ref.name.value().package;
+ } else if (ref.id && symbol.id) {
+ return ref.id.value().package_id() == symbol.id.value().package_id();
} else {
- return nullptr;
+ return false;
}
+ }
+ return true;
}
-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::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::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;
+const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(
+ const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols,
+ CallSite* callsite, std::string* out_error) {
+ const SymbolTable::Symbol* symbol =
+ ResolveSymbol(reference, name_mangler, symbols);
+ if (!symbol) {
+ if (out_error) *out_error = "not found";
+ return nullptr;
+ }
+
+ if (!IsSymbolVisible(*symbol, reference, *callsite)) {
+ if (out_error) *out_error = "is private";
+ 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 {};
- }
+const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility(
+ const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols,
+ CallSite* callsite, std::string* out_error) {
+ const SymbolTable::Symbol* symbol = ResolveSymbolCheckVisibility(
+ reference, name_mangler, symbols, callsite, out_error);
+ if (!symbol) {
+ return nullptr;
+ }
+
+ if (!symbol->attribute) {
+ if (out_error) *out_error = "is not an attribute";
+ return nullptr;
+ }
+ return symbol;
+}
- if (!symbol->attribute) {
- if (outError) *outError = "is not an attribute";
- return {};
- }
- return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(
+ const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols,
+ CallSite* callsite, std::string* out_error) {
+ const SymbolTable::Symbol* symbol =
+ ResolveSymbol(reference, name_mangler, symbols);
+ if (!symbol) {
+ if (out_error) *out_error = "not found";
+ return {};
+ }
+
+ if (!symbol->attribute) {
+ if (out_error) *out_error = "is not an attribute";
+ return {};
+ }
+ return xml::AaptAttribute{symbol->id, *symbol->attribute};
}
-void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig,
+void ReferenceLinker::WriteResourceName(DiagMessage* out_msg,
+ const Reference& orig,
const Reference& transformed) {
- assert(outMsg);
+ CHECK(out_msg != nullptr);
- if (orig.name) {
- *outMsg << orig.name.value();
- if (transformed.name.value() != orig.name.value()) {
- *outMsg << " (aka " << transformed.name.value() << ")";
- }
- } else {
- *outMsg << orig.id.value();
+ if (orig.name) {
+ *out_msg << orig.name.value();
+ if (transformed.name.value() != orig.name.value()) {
+ *out_msg << " (aka " << transformed.name.value() << ")";
}
+ } else {
+ *out_msg << 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;
+bool ReferenceLinker::LinkReference(Reference* reference, IAaptContext* context,
+ SymbolTable* symbols,
+ xml::IPackageDeclStack* decls,
+ CallSite* callsite) {
+ CHECK(reference != nullptr);
+ CHECK(reference->name || reference->id);
+
+ Reference transformed_reference = *reference;
+ TransformReferenceFromNamespace(decls, context->GetCompilationPackage(),
+ &transformed_reference);
+
+ std::string err_str;
+ const SymbolTable::Symbol* s = ResolveSymbolCheckVisibility(
+ transformed_reference, context->GetNameMangler(), symbols, callsite,
+ &err_str);
+ 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 error_msg(reference->GetSource());
+ error_msg << "resource ";
+ WriteResourceName(&error_msg, *reference, transformed_reference);
+ error_msg << " " << err_str;
+ context->GetDiagnostics()->Error(error_msg);
+ return false;
}
-namespace {
+bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) {
+ EmptyDeclStack decl_stack;
+ 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->symbol_status.state != SymbolState::kUndefined &&
+ entry->values.empty()) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(entry->symbol_status.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->string_pool, &decl_stack,
+ &callsite);
-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 */ };
+ for (auto& config_value : entry->values) {
+ config_value->value->Accept(&visitor);
}
- 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;
- }
- }
+ if (visitor.HasError()) {
+ error = true;
}
+ }
}
- return !error;
+ }
+ return !error;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index 7993aaf39e47..bdabf249709d 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -17,6 +17,8 @@
#ifndef AAPT_LINKER_REFERENCELINKER_H
#define AAPT_LINKER_REFERENCELINKER_H
+#include "android-base/macros.h"
+
#include "Resource.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
@@ -25,82 +27,91 @@
#include "process/SymbolTable.h"
#include "xml/XmlDom.h"
-#include <cassert>
-
namespace aapt {
/**
- * Resolves all references to resources in the ResourceTable and assigns them IDs.
+ * 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.
+ * 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;
+class ReferenceLinker : public IResourceTableConsumer {
+ public:
+ ReferenceLinker() = default;
+
+ /**
+ * 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. out_error holds the error message.
+ */
+ static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(
+ const Reference& reference, NameMangler* name_mangler,
+ SymbolTable* symbols, CallSite* callsite, std::string* out_error);
+
+ /**
+ * 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* name_mangler,
+ SymbolTable* symbols, CallSite* callsite, std::string* out_error);
+
+ /**
+ * 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* name_mangler,
+ SymbolTable* symbols, CallSite* callsite, std::string* out_error);
+
+ /**
+ * Writes the resource name to the DiagMessage, using the
+ * "orig_name (aka <transformed_name>)" syntax.
+ */
+ static void WriteResourceName(DiagMessage* out_msg, 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;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReferenceLinker);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_LINKER_REFERENCELINKER_H */
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
index 76b23098a35c..4ca36a9e2b22 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -15,6 +15,7 @@
*/
#include "link/ReferenceLinker.h"
+
#include "test/Test.h"
using android::ResTable_map;
@@ -22,206 +23,238 @@ 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));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddReference("com.app.test:string/foo", ResourceId(0x7f020000),
+ "com.app.test:string/bar")
+
+ // Test use of local reference (w/o package name).
+ .AddReference("com.app.test:string/bar", ResourceId(0x7f020001),
+ "string/baz")
+
+ .AddReference("com.app.test:string/baz", ResourceId(0x7f020002),
+ "android:string/ok")
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .AddSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .AddPublicSymbol("android:string/ok", ResourceId(0x01040034))
+ .Build())
+ .Build();
+
+ ReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context.get(), table.get()));
+
+ Reference* ref =
+ test::GetValue<Reference>(table.get(), "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(), "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(), "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");
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddValue("com.app.test:style/Theme",
+ test::StyleBuilder()
+ .SetParent("android:style/Theme.Material")
+ .AddItem("android:attr/foo",
+ ResourceUtils::TryParseColor("#ff00ff"))
+ .AddItem("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(), "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);
+ style->entries.back().value =
+ util::make_unique<RawString>(table->string_pool.MakeRef("one|two"));
+ }
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .AddSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .AddPublicSymbol("android:style/Theme.Material",
+ ResourceId(0x01060000))
+ .AddPublicSymbol("android:attr/foo", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .SetTypeMask(ResTable_map::TYPE_COLOR)
+ .Build())
+ .AddPublicSymbol("android:attr/bar", ResourceId(0x01010002),
+ test::AttributeBuilder()
+ .SetTypeMask(ResTable_map::TYPE_FLAGS)
+ .AddItem("one", 0x01)
+ .AddItem("two", 0x02)
+ .Build())
+ .Build())
+ .Build();
+
+ ReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context.get(), table.get()));
+
+ Style* style = test::GetValue<Style>(table.get(), "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));
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .SetNameManglerPolicy(
+ NameManglerPolicy{"com.app.test", {"com.android.support"}})
+ .AddSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .AddPublicSymbol("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("com.app.test", 0x7f)
+ .AddValue("com.app.test:style/Theme", ResourceId(0x7f020000),
+ test::StyleBuilder()
+ .AddItem("com.android.support:attr/foo",
+ ResourceUtils::TryParseColor("#ff0000"))
+ .Build())
+ .Build();
+
+ ReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context.get(), table.get()));
+
+ Style* style = test::GetValue<Style>(table.get(), "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()));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddReference("com.app.test:string/foo", ResourceId(0x7f020000),
+ "android:string/hidden")
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .AddSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .AddSymbol("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()));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddReference("com.app.test:string/foo", ResourceId(0x7f020000),
+ "com.app.lib:string/hidden")
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .SetNameManglerPolicy(
+ NameManglerPolicy{"com.app.test", {"com.app.lib"}})
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .AddSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .AddSymbol("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()));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddValue("com.app.test:style/Theme",
+ test::StyleBuilder()
+ .AddItem("android:attr/hidden",
+ ResourceUtils::TryParseColor("#ff00ff"))
+ .Build())
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .AddSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .AddSymbol("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
+} // namespace aapt
diff --git a/tools/aapt2/link/ResourceDeduper.cpp b/tools/aapt2/link/ResourceDeduper.cpp
new file mode 100644
index 000000000000..9431dcee9714
--- /dev/null
+++ b/tools/aapt2/link/ResourceDeduper.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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/Linkers.h"
+
+#include <algorithm>
+
+#include "DominatorTree.h"
+#include "ResourceTable.h"
+
+namespace aapt {
+
+namespace {
+
+/**
+ * Remove duplicated key-value entries from dominated resources.
+ *
+ * Based on the dominator tree, we can remove a value of an entry if:
+ *
+ * 1. The configuration for the entry's value is dominated by a configuration
+ * with an equivalent entry value.
+ * 2. All compatible configurations for the entry (those not in conflict and
+ * unrelated by domination with the configuration for the entry's value) have
+ * an equivalent entry value.
+ */
+class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor {
+ public:
+ using Node = DominatorTree::Node;
+
+ explicit DominatedKeyValueRemover(IAaptContext* context, ResourceEntry* entry)
+ : context_(context), entry_(entry) {}
+
+ void VisitConfig(Node* node) {
+ Node* parent = node->parent();
+ if (!parent) {
+ return;
+ }
+ ResourceConfigValue* node_value = node->value();
+ ResourceConfigValue* parent_value = parent->value();
+ if (!node_value || !parent_value) {
+ return;
+ }
+ if (!node_value->value->Equals(parent_value->value.get())) {
+ return;
+ }
+
+ // Compare compatible configs for this entry and ensure the values are
+ // equivalent.
+ const ConfigDescription& node_configuration = node_value->config;
+ for (const auto& sibling : entry_->values) {
+ if (!sibling->value) {
+ // Sibling was already removed.
+ continue;
+ }
+ if (node_configuration.IsCompatibleWith(sibling->config) &&
+ !node_value->value->Equals(sibling->value.get())) {
+ // The configurations are compatible, but the value is
+ // different, so we can't remove this value.
+ return;
+ }
+ }
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage(node_value->value->GetSource())
+ << "removing dominated duplicate resource with name \""
+ << entry_->name << "\"");
+ }
+ node_value->value = {};
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DominatedKeyValueRemover);
+
+ IAaptContext* context_;
+ ResourceEntry* entry_;
+};
+
+static void DedupeEntry(IAaptContext* context, ResourceEntry* entry) {
+ DominatorTree tree(entry->values);
+ DominatedKeyValueRemover remover(context, entry);
+ tree.Accept(&remover);
+
+ // Erase the values that were removed.
+ entry->values.erase(
+ std::remove_if(
+ entry->values.begin(), entry->values.end(),
+ [](const std::unique_ptr<ResourceConfigValue>& val) -> bool {
+ return val == nullptr || val->value == nullptr;
+ }),
+ entry->values.end());
+}
+
+} // namespace
+
+bool ResourceDeduper::Consume(IAaptContext* context, ResourceTable* table) {
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ DedupeEntry(context, entry.get());
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ResourceDeduper_test.cpp b/tools/aapt2/link/ResourceDeduper_test.cpp
new file mode 100644
index 000000000000..d38059ddd391
--- /dev/null
+++ b/tools/aapt2/link/ResourceDeduper_test.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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/Linkers.h"
+
+#include "ResourceTable.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+TEST(ResourceDeduperTest, SameValuesAreDeduped) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+ const ConfigDescription en_config = test::ParseConfigOrDie("en");
+ const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
+ // Chosen because this configuration is compatible with en.
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/dedupe", ResourceId{}, default_config,
+ "dedupe")
+ .AddString("android:string/dedupe", ResourceId{}, en_config, "dedupe")
+ .AddString("android:string/dedupe", ResourceId{}, land_config,
+ "dedupe")
+ .AddString("android:string/dedupe2", ResourceId{}, default_config,
+ "dedupe")
+ .AddString("android:string/dedupe2", ResourceId{}, en_config,
+ "dedupe")
+ .AddString("android:string/dedupe2", ResourceId{}, en_v21_config,
+ "keep")
+ .AddString("android:string/dedupe2", ResourceId{}, land_config,
+ "dedupe")
+ .Build();
+
+ ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/dedupe", en_config));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/dedupe", land_config));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/dedupe2", en_config));
+ EXPECT_NE(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/dedupe2", en_v21_config));
+}
+
+TEST(ResourceDeduperTest, DifferentValuesAreKept) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+ const ConfigDescription en_config = test::ParseConfigOrDie("en");
+ const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
+ // Chosen because this configuration is compatible with en.
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/keep", ResourceId{}, default_config,
+ "keep")
+ .AddString("android:string/keep", ResourceId{}, en_config, "keep")
+ .AddString("android:string/keep", ResourceId{}, en_v21_config,
+ "keep2")
+ .AddString("android:string/keep", ResourceId{}, land_config, "keep2")
+ .Build();
+
+ ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_NE(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/keep", en_config));
+ EXPECT_NE(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/keep", en_v21_config));
+ EXPECT_NE(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/keep", land_config));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 7471e15db41a..d808da31d3b3 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -14,308 +14,383 @@
* limitations under the License.
*/
+#include "link/TableMerger.h"
+
+#include "android-base/logging.h"
+
#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");
+TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table,
+ const TableMergerOptions& options)
+ : context_(context), master_table_(out_table), options_(options) {
+ // Create the desired package that all tables will be merged into.
+ master_package_ = master_table_->CreatePackage(
+ context_->GetCompilationPackage(), context_->GetPackageId());
+ CHECK(master_package_ != nullptr) << "package name or ID already taken";
}
-bool TableMerger::merge(const Source& src, ResourceTable* table,
+bool TableMerger::Merge(const Source& src, ResourceTable* table,
io::IFileCollection* collection) {
- return mergeImpl(src, table, collection, false /* overlay */, true /* allow new */);
+ return MergeImpl(src, table, collection, false /* overlay */,
+ true /* allow new */);
}
-bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table,
+bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table,
io::IFileCollection* collection) {
- return mergeImpl(src, table, collection, true /* overlay */, mOptions.autoAddOverlay);
+ return MergeImpl(src, table, collection, true /* overlay */,
+ options_.auto_add_overlay);
}
/**
* 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;
- };
- }
+bool TableMerger::MergeImpl(const Source& src, ResourceTable* table,
+ io::IFileCollection* collection, bool overlay,
+ bool allow_new) {
+ const uint8_t desired_package_id = context_->GetPackageId();
+
+ bool error = false;
+ for (auto& package : table->packages) {
+ // Warn of packages with an unrelated ID.
+ const Maybe<ResourceId>& id = package->id;
+ if (id && id.value() != 0x0 && id.value() != desired_package_id) {
+ context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package "
+ << package->name);
+ continue;
+ }
- // 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);
- }
+ // Only merge an empty package or the package we're building.
+ // Other packages may exist, which likely contain attribute definitions.
+ // This is because at compile time it is unknown if the attributes are
+ // simply
+ // uses of the attribute or definitions.
+ if (package->name.empty() ||
+ context_->GetCompilationPackage() == package->name) {
+ FileMergeCallback callback;
+ if (collection) {
+ callback = [&](const ResourceNameRef& name,
+ const ConfigDescription& config, FileReference* new_file,
+ FileReference* old_file) -> bool {
+ // The old file's path points inside the APK, so we can use it as is.
+ io::IFile* f = collection->FindFile(*old_file->path);
+ if (!f) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "file '" << *old_file->path
+ << "' not found");
+ return false;
+ }
+
+ new_file->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,
+ allow_new, callback);
}
- return !error;
+ }
+ 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 TableMerger::MergeAndMangle(const Source& src,
+ const StringPiece& package_name,
+ ResourceTable* table,
+ io::IFileCollection* collection) {
+ bool error = false;
+ for (auto& package : table->packages) {
+ // Warn of packages with an unrelated ID.
+ if (package_name != package->name) {
+ context_->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;
- }
+ bool mangle = package_name != context_->GetCompilationPackage();
+ merged_packages_.insert(package->name);
+
+ auto callback = [&](
+ const ResourceNameRef& name, const ConfigDescription& config,
+ FileReference* new_file, FileReference* old_file) -> bool {
+ // The old file's path points inside the APK, so we can use it as is.
+ io::IFile* f = collection->FindFile(*old_file->path);
+ if (!f) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(src) << "file '" << *old_file->path << "' not found");
+ return false;
+ }
+
+ new_file->file = f;
+ return true;
+ };
+
+ error |= !DoMerge(src, table, package.get(), mangle, false /* overlay */,
+ true /* allow new */, callback);
+ }
+ return !error;
+}
- newFile->file = f;
- return true;
- };
+static bool MergeType(IAaptContext* context, const Source& src,
+ ResourceTableType* dst_type,
+ ResourceTableType* src_type) {
+ if (dst_type->symbol_status.state < src_type->symbol_status.state) {
+ // The incoming type's visibility is stronger, so we should override
+ // the visibility.
+ if (src_type->symbol_status.state == SymbolState::kPublic) {
+ // Only copy the ID if the source is public, or else the ID is
+ // meaningless.
+ dst_type->id = src_type->id;
+ }
+ dst_type->symbol_status = std::move(src_type->symbol_status);
+ } else if (dst_type->symbol_status.state == SymbolState::kPublic &&
+ src_type->symbol_status.state == SymbolState::kPublic &&
+ dst_type->id && src_type->id &&
+ dst_type->id.value() != src_type->id.value()) {
+ // Both types are public and have different IDs.
+ context->GetDiagnostics()->Error(DiagMessage(src)
+ << "cannot merge type '" << src_type->type
+ << "': conflicting public IDs");
+ return false;
+ }
+ return true;
+}
- error |= !doMerge(src, table, package.get(),
- mangle, false /* overlay */, true /* allow new */, callback);
+static bool MergeEntry(IAaptContext* context, const Source& src,
+ ResourceEntry* dst_entry, ResourceEntry* src_entry) {
+ if (dst_entry->symbol_status.state < src_entry->symbol_status.state) {
+ // The incoming type's visibility is stronger, so we should override
+ // the visibility.
+ if (src_entry->symbol_status.state == SymbolState::kPublic) {
+ // Only copy the ID if the source is public, or else the ID is
+ // meaningless.
+ dst_entry->id = src_entry->id;
}
- return !error;
+ dst_entry->symbol_status = std::move(src_entry->symbol_status);
+ } else if (src_entry->symbol_status.state == SymbolState::kPublic &&
+ dst_entry->symbol_status.state == SymbolState::kPublic &&
+ dst_entry->id && src_entry->id &&
+ dst_entry->id.value() != src_entry->id.value()) {
+ // Both entries are public and have different IDs.
+ context->GetDiagnostics()->Error(
+ DiagMessage(src) << "cannot merge entry '" << src_entry->name
+ << "': conflicting public IDs");
+ return false;
+ }
+ return true;
}
-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;
- }
+/**
+ * Modified CollisionResolver which will merge Styleables. Used with overlays.
+ *
+ * Styleables are not actual resources, but they are treated as such during the
+ * compilation phase. Styleables don't simply overlay each other, their
+ * definitions merge
+ * and accumulate. If both values are Styleables, we just merge them into the
+ * existing value.
+ */
+static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing,
+ Value* incoming) {
+ if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) {
+ if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) {
+ // Styleables get merged.
+ existing_styleable->MergeWith(incoming_styleable);
+ return ResourceTable::CollisionResult::kKeepOriginal;
+ }
+ }
+ // Delegate to the default handler.
+ return ResourceTable::ResolveValueCollision(existing, incoming);
+}
- dstType->symbolStatus = std::move(srcType->symbolStatus);
- dstType->id = srcType->id;
- }
+static ResourceTable::CollisionResult MergeConfigValue(
+ IAaptContext* context, const ResourceNameRef& res_name, const bool overlay,
+ ResourceConfigValue* dst_config_value,
+ ResourceConfigValue* src_config_value) {
+ using CollisionResult = ResourceTable::CollisionResult;
+
+ Value* dst_value = dst_config_value->value.get();
+ Value* src_value = src_config_value->value.get();
+
+ CollisionResult collision_result;
+ if (overlay) {
+ collision_result = ResolveMergeCollision(dst_value, src_value);
+ } else {
+ collision_result =
+ ResourceTable::ResolveValueCollision(dst_value, src_value);
+ }
+
+ if (collision_result == CollisionResult::kConflict) {
+ if (overlay) {
+ return CollisionResult::kTakeNew;
+ }
- 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);
- }
- }
+ // Error!
+ context->GetDiagnostics()->Error(
+ DiagMessage(src_value->GetSource())
+ << "resource '" << res_name << "' has a conflicting value for "
+ << "configuration (" << src_config_value->config << ")");
+ context->GetDiagnostics()->Note(DiagMessage(dst_value->GetSource())
+ << "originally defined here");
+ return CollisionResult::kConflict;
+ }
+ return collision_result;
+}
- 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;
- }
+bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
+ ResourceTablePackage* src_package,
+ const bool mangle_package, const bool overlay,
+ const bool allow_new_resources,
+ const FileMergeCallback& callback) {
+ bool error = false;
+
+ for (auto& src_type : src_package->types) {
+ ResourceTableType* dst_type =
+ master_package_->FindOrCreateType(src_type->type);
+ if (!MergeType(context_, src, dst_type, src_type.get())) {
+ 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);
- }
- }
+ for (auto& src_entry : src_type->entries) {
+ std::string entry_name = src_entry->name;
+ if (mangle_package) {
+ entry_name =
+ NameMangler::MangleEntry(src_package->name, src_entry->name);
+ }
+
+ ResourceEntry* dst_entry;
+ if (allow_new_resources) {
+ dst_entry = dst_type->FindOrCreateEntry(entry_name);
+ } else {
+ dst_entry = dst_type->FindEntry(entry_name);
+ }
+
+ const ResourceNameRef res_name(src_package->name, src_type->type,
+ src_entry->name);
+
+ if (!dst_entry) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(src) << "resource " << res_name
+ << " does not override an existing resource");
+ context_->GetDiagnostics()->Note(
+ DiagMessage(src) << "define an <add-resource> tag or use "
+ << "--auto-add-overlay");
+ error = true;
+ continue;
+ }
+
+ if (!MergeEntry(context_, src, dst_entry, src_entry.get())) {
+ error = true;
+ continue;
+ }
+
+ for (auto& src_config_value : src_entry->values) {
+ using CollisionResult = ResourceTable::CollisionResult;
+
+ ResourceConfigValue* dst_config_value = dst_entry->FindValue(
+ src_config_value->config, src_config_value->product);
+ if (dst_config_value) {
+ CollisionResult collision_result =
+ MergeConfigValue(context_, res_name, overlay, dst_config_value,
+ src_config_value.get());
+ if (collision_result == CollisionResult::kConflict) {
+ error = true;
+ continue;
+ } else if (collision_result == CollisionResult::kKeepOriginal) {
+ continue;
+ }
+ } else {
+ dst_config_value = dst_entry->FindOrCreateValue(
+ src_config_value->config, src_config_value->product);
+ }
- 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));
- }
+ // Continue if we're taking the new resource.
+
+ if (FileReference* f =
+ ValueCast<FileReference>(src_config_value->value.get())) {
+ std::unique_ptr<FileReference> new_file_ref;
+ if (mangle_package) {
+ new_file_ref = CloneAndMangleFile(src_package->name, *f);
+ } else {
+ new_file_ref = std::unique_ptr<FileReference>(
+ f->Clone(&master_table_->string_pool));
+ }
+
+ if (callback) {
+ if (!callback(res_name, src_config_value->config,
+ new_file_ref.get(), f)) {
+ error = true;
+ continue;
}
+ }
+ dst_config_value->value = std::move(new_file_ref);
+
+ } else {
+ dst_config_value->value = std::unique_ptr<Value>(
+ src_config_value->value->Clone(&master_table_->string_pool));
}
+ }
}
- return !error;
+ }
+ 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));
+std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile(
+ const std::string& package, const FileReference& file_ref) {
+ StringPiece prefix, entry, suffix;
+ if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
+ std::string mangled_entry =
+ NameMangler::MangleEntry(package, entry.ToString());
+ std::string newPath = prefix.ToString() + mangled_entry + suffix.ToString();
+ std::unique_ptr<FileReference> new_file_ref =
+ util::make_unique<FileReference>(
+ master_table_->string_pool.MakeRef(newPath));
+ new_file_ref->SetComment(file_ref.GetComment());
+ new_file_ref->SetSource(file_ref.GetSource());
+ return new_file_ref;
+ }
+ return std::unique_ptr<FileReference>(
+ file_ref.Clone(&master_table_->string_pool));
}
-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::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file,
+ bool overlay) {
+ ResourceTable table;
+ std::string path = ResourceUtils::BuildResourceFileName(file_desc);
+ std::unique_ptr<FileReference> file_ref =
+ util::make_unique<FileReference>(table.string_pool.MakeRef(path));
+ file_ref->SetSource(file_desc.source);
+ file_ref->file = file;
+
+ ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0);
+ pkg->FindOrCreateType(file_desc.name.type)
+ ->FindOrCreateEntry(file_desc.name.entry)
+ ->FindOrCreateValue(file_desc.config, {})
+ ->value = std::move(file_ref);
+
+ 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::MergeFile(const ResourceFile& file_desc, io::IFile* file) {
+ return MergeFileImpl(file_desc, file, false /* overlay */);
}
-bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) {
- return mergeFileImpl(fileDesc, file, true /* overlay */);
+bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc,
+ io::IFile* file) {
+ return MergeFileImpl(file_desc, file, true /* overlay */);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 80c2a5e69b66..4ab83c3f2de3 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -17,6 +17,11 @@
#ifndef AAPT_TABLEMERGER_H
#define AAPT_TABLEMERGER_H
+#include <functional>
+#include <map>
+
+#include "android-base/macros.h"
+
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
@@ -25,102 +30,114 @@
#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;
+ /**
+ * If true, resources in overlays can be added without previously having
+ * existed.
+ */
+ bool auto_add_overlay = false;
};
/**
- * TableMerger takes resource tables and merges all packages within the tables that have the same
+ * 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
+ * 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
+ * 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);
+ public:
+ /**
+ * Note: The out_table ResourceTable must live longer than this TableMerger.
+ * References are made to this ResourceTable for efficiency reasons.
+ */
+ TableMerger(IAaptContext* context, ResourceTable* out_table,
+ const TableMergerOptions& options);
+
+ const std::set<std::string>& merged_packages() const {
+ return merged_packages_;
+ }
+
+ /**
+ * 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 StringPiece& 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:
+ DISALLOW_COPY_AND_ASSIGN(TableMerger);
+
+ using FileMergeCallback = std::function<bool(const ResourceNameRef&,
+ const ConfigDescription& config,
+ FileReference*, FileReference*)>;
+
+ IAaptContext* context_;
+ ResourceTable* master_table_;
+ TableMergerOptions options_;
+ ResourceTablePackage* master_package_;
+ std::set<std::string> merged_packages_;
+
+ bool MergeFileImpl(const ResourceFile& file_desc, io::IFile* file,
+ bool overlay);
+
+ bool MergeImpl(const Source& src, ResourceTable* src_table,
+ io::IFileCollection* collection, bool overlay, bool allow_new);
+
+ bool DoMerge(const Source& src, ResourceTable* src_table,
+ ResourceTablePackage* src_package, const bool mangle_package,
+ const bool overlay, const bool allow_new_resources,
+ const FileMergeCallback& callback);
+
+ std::unique_ptr<FileReference> CloneAndMangleFile(const std::string& package,
+ const FileReference& value);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_TABLEMERGER_H */
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 4a80d3f48777..742f5a7a570a 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -14,207 +14,334 @@
* 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>
+#include "filter/ConfigFilter.h"
+#include "io/FileSystem.h"
+#include "test/Test.h"
namespace aapt {
struct TableMergerTest : public ::testing::Test {
- std::unique_ptr<IAaptContext> mContext;
+ std::unique_ptr<IAaptContext> context_;
- void SetUp() override {
- mContext = test::ContextBuilder()
- // We are compiling this package.
- .setCompilationPackage(u"com.app.a")
+ void SetUp() override {
+ context_ =
+ test::ContextBuilder()
+ // We are compiling this package.
+ .SetCompilationPackage("com.app.a")
- // Merge all packages that have this package ID.
- .setPackageId(0x7f)
+ // 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" } })
+ // Mangle all packages that do not have this package name.
+ .SetNameManglerPolicy(NameManglerPolicy{"com.app.a", {"com.app.b"}})
- .build();
- }
+ .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")));
+ std::unique_ptr<ResourceTable> table_a =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddReference("com.app.a:id/foo", "com.app.a:id/bar")
+ .AddReference("com.app.a:id/bar", "com.app.b:id/foo")
+ .AddValue(
+ "com.app.a:styleable/view",
+ test::StyleableBuilder().AddItem("com.app.b:id/foo").Build())
+ .Build();
+
+ std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder()
+ .SetPackageId("com.app.b", 0x7f)
+ .AddSimple("com.app.b:id/foo")
+ .Build();
+
+ ResourceTable final_table;
+ TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
+ io::FileCollection collection;
+
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(
+ merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+
+ EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0);
+
+ // Entries from com.app.a should not be mangled.
+ AAPT_EXPECT_TRUE(
+ final_table.FindResource(test::ParseNameOrDie("com.app.a:id/foo")));
+ AAPT_EXPECT_TRUE(
+ final_table.FindResource(test::ParseNameOrDie("com.app.a:id/bar")));
+ AAPT_EXPECT_TRUE(final_table.FindResource(
+ test::ParseNameOrDie("com.app.a:styleable/view")));
+
+ // The unmangled name should not be present.
+ AAPT_EXPECT_FALSE(
+ final_table.FindResource(test::ParseNameOrDie("com.app.b:id/foo")));
+
+ // Look for the mangled name.
+ AAPT_EXPECT_TRUE(final_table.FindResource(
+ test::ParseNameOrDie("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);
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ResourceFile file_desc;
+ file_desc.config = test::ParseConfigOrDie("hdpi-v4");
+ file_desc.name = test::ParseNameOrDie("layout/main");
+ file_desc.source = Source("res/layout-hdpi/main.xml");
+ test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat");
+
+ ASSERT_TRUE(merger.MergeFile(file_desc, &test_file));
+
+ FileReference* file = test::GetValueForConfig<FileReference>(
+ &final_table, "com.app.a:layout/main", test::ParseConfigOrDie("hdpi-v4"));
+ ASSERT_NE(nullptr, file);
+ EXPECT_EQ(std::string("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));
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ResourceFile file_desc;
+ file_desc.name = test::ParseNameOrDie("xml/foo");
+ test::TestFile file_a("path/to/fileA.xml.flat");
+ test::TestFile file_b("path/to/fileB.xml.flat");
+
+ ASSERT_TRUE(merger.MergeFile(file_desc, &file_a));
+ ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b));
}
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);
+ std::unique_ptr<ResourceTable> table_a =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddFileReference("com.app.a:xml/file", "res/xml/file.xml")
+ .Build();
+ std::unique_ptr<ResourceTable> table_b =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.b", 0x7f)
+ .AddFileReference("com.app.b:xml/file", "res/xml/file.xml")
+ .Build();
+
+ ResourceTable final_table;
+ TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
+ io::FileCollection collection;
+ collection.InsertFile("res/xml/file.xml");
+
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(
+ merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+
+ FileReference* f =
+ test::GetValue<FileReference>(&final_table, "com.app.a:xml/file");
+ ASSERT_NE(f, nullptr);
+ EXPECT_EQ(std::string("res/xml/file.xml"), *f->path);
+
+ f = test::GetValue<FileReference>(&final_table,
+ "com.app.a:xml/com.app.b$file");
+ ASSERT_NE(f, nullptr);
+ EXPECT_EQ(std::string("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);
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x00)
+ .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
+ .Build();
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x00)
+ .AddValue("bool/foo", ResourceUtils::TryParseBool("false"))
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, base.get()));
+ ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
+
+ BinaryPrimitive* foo =
+ test::GetValue<BinaryPrimitive>(&final_table, "com.app.a:bool/foo");
+ ASSERT_NE(nullptr, foo);
+ EXPECT_EQ(0x0u, foo->value.data);
+}
+
+TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) {
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
+ SymbolState::kPublic)
+ .Build();
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
+ SymbolState::kPublic)
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, base.get()));
+ ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
+}
+
+TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) {
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
+ SymbolState::kPublic)
+ .Build();
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001),
+ SymbolState::kPublic)
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, base.get()));
+ ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
+}
+
+TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) {
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
+ SymbolState::kPublic)
+ .Build();
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002),
+ SymbolState::kPublic)
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, base.get()));
+ ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
}
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()));
+ std::unique_ptr<ResourceTable> table_a =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", {}, SymbolState::kUndefined)
+ .Build();
+ std::unique_ptr<ResourceTable> table_b =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
+ .Build();
+
+ ResourceTable final_table;
+ TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
+
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(merger.MergeOverlay({}, table_b.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()));
+ std::unique_ptr<ResourceTable> table_a =
+ test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
+ std::unique_ptr<ResourceTable> table_b =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = true;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(merger.MergeOverlay({}, table_b.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()));
+ std::unique_ptr<ResourceTable> table_a =
+ test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
+ std::unique_ptr<ResourceTable> table_b =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_FALSE(merger.MergeOverlay({}, table_b.get()));
+}
+
+TEST_F(TableMergerTest, OverlaidStyleablesShouldBeMerged) {
+ std::unique_ptr<ResourceTable> table_a =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddValue("com.app.a:styleable/Foo",
+ test::StyleableBuilder()
+ .AddItem("com.app.a:attr/bar")
+ .AddItem("com.app.a:attr/foo", ResourceId(0x01010000))
+ .Build())
+ .Build();
+
+ std::unique_ptr<ResourceTable> table_b =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddValue("com.app.a:styleable/Foo",
+ test::StyleableBuilder()
+ .AddItem("com.app.a:attr/bat")
+ .AddItem("com.app.a:attr/foo")
+ .Build())
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = true;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+
+ Styleable* styleable =
+ test::GetValue<Styleable>(&final_table, "com.app.a:styleable/Foo");
+ ASSERT_NE(nullptr, styleable);
+
+ std::vector<Reference> expected_refs = {
+ Reference(test::ParseNameOrDie("com.app.a:attr/bar")),
+ Reference(test::ParseNameOrDie("com.app.a:attr/bat")),
+ Reference(test::ParseNameOrDie("com.app.a:attr/foo"),
+ ResourceId(0x01010000)),
+ };
+
+ EXPECT_EQ(expected_refs, styleable->entries);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/VersionCollapser.cpp b/tools/aapt2/link/VersionCollapser.cpp
new file mode 100644
index 000000000000..3df58994333f
--- /dev/null
+++ b/tools/aapt2/link/VersionCollapser.cpp
@@ -0,0 +1,162 @@
+/*
+ * 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/Linkers.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "ResourceTable.h"
+
+namespace aapt {
+
+template <typename Iterator, typename Pred>
+class FilterIterator {
+ public:
+ FilterIterator(Iterator begin, Iterator end, Pred pred = Pred())
+ : current_(begin), end_(end), pred_(pred) {
+ Advance();
+ }
+
+ bool HasNext() { return current_ != end_; }
+
+ Iterator NextIter() {
+ Iterator iter = current_;
+ ++current_;
+ Advance();
+ return iter;
+ }
+
+ typename Iterator::reference Next() { return *NextIter(); }
+
+ private:
+ void Advance() {
+ for (; current_ != end_; ++current_) {
+ if (pred_(*current_)) {
+ return;
+ }
+ }
+ }
+
+ Iterator current_, end_;
+ Pred pred_;
+};
+
+template <typename Iterator, typename Pred>
+FilterIterator<Iterator, Pred> make_filter_iterator(Iterator begin,
+ Iterator end = Iterator(),
+ Pred pred = Pred()) {
+ return FilterIterator<Iterator, Pred>(begin, end, pred);
+}
+
+/**
+ * Every Configuration with an SDK version specified that is less than minSdk
+ * will be removed.
+ * The exception is when there is no exact matching resource for the minSdk. The
+ * next smallest
+ * one will be kept.
+ */
+static void CollapseVersions(int min_sdk, ResourceEntry* entry) {
+ // First look for all sdks less than minSdk.
+ for (auto iter = entry->values.rbegin(); iter != entry->values.rend();
+ ++iter) {
+ // Check if the item was already marked for removal.
+ if (!(*iter)) {
+ continue;
+ }
+
+ const ConfigDescription& config = (*iter)->config;
+ if (config.sdkVersion <= min_sdk) {
+ // This is the first configuration we've found with a smaller or equal SDK
+ // level
+ // to the minimum. We MUST keep this one, but remove all others we find,
+ // which get
+ // overridden by this one.
+
+ ConfigDescription config_without_sdk = config.CopyWithoutSdkVersion();
+ auto pred = [&](const std::unique_ptr<ResourceConfigValue>& val) -> bool {
+ // Check that the value hasn't already been marked for removal.
+ if (!val) {
+ return false;
+ }
+
+ // Only return Configs that differ in SDK version.
+ config_without_sdk.sdkVersion = val->config.sdkVersion;
+ return config_without_sdk == val->config &&
+ val->config.sdkVersion <= min_sdk;
+ };
+
+ // Remove the rest that match.
+ auto filter_iter =
+ make_filter_iterator(iter + 1, entry->values.rend(), pred);
+ while (filter_iter.HasNext()) {
+ filter_iter.Next() = {};
+ }
+ }
+ }
+
+ // Now erase the nullptr values.
+ entry->values.erase(
+ std::remove_if(entry->values.begin(), entry->values.end(),
+ [](const std::unique_ptr<ResourceConfigValue>& val)
+ -> bool { return val == nullptr; }),
+ entry->values.end());
+
+ // Strip the version qualifiers for every resource with version <= minSdk.
+ // This will ensure
+ // that the resource entries are all packed together in the same ResTable_type
+ // struct
+ // and take up less space in the resources.arsc table.
+ bool modified = false;
+ for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
+ if (config_value->config.sdkVersion != 0 &&
+ config_value->config.sdkVersion <= min_sdk) {
+ // Override the resource with a Configuration without an SDK.
+ std::unique_ptr<ResourceConfigValue> new_value =
+ util::make_unique<ResourceConfigValue>(
+ config_value->config.CopyWithoutSdkVersion(),
+ config_value->product);
+ new_value->value = std::move(config_value->value);
+ config_value = std::move(new_value);
+
+ modified = true;
+ }
+ }
+
+ if (modified) {
+ // We've modified the keys (ConfigDescription) by changing the sdkVersion to
+ // 0. We MUST re-sort to ensure ordering guarantees hold.
+ std::sort(entry->values.begin(), entry->values.end(),
+ [](const std::unique_ptr<ResourceConfigValue>& a,
+ const std::unique_ptr<ResourceConfigValue>& b) -> bool {
+ return a->config.compare(b->config) < 0;
+ });
+ }
+}
+
+bool VersionCollapser::Consume(IAaptContext* context, ResourceTable* table) {
+ const int min_sdk = context->GetMinSdkVersion();
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ CollapseVersions(min_sdk, entry.get());
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/VersionCollapser_test.cpp b/tools/aapt2/link/VersionCollapser_test.cpp
new file mode 100644
index 000000000000..1b5592f717d9
--- /dev/null
+++ b/tools/aapt2/link/VersionCollapser_test.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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/Linkers.h"
+
+#include "test/Test.h"
+
+namespace aapt {
+
+static std::unique_ptr<ResourceTable> BuildTableWithConfigs(
+ const StringPiece& name, std::initializer_list<std::string> list) {
+ test::ResourceTableBuilder builder;
+ for (const std::string& item : list) {
+ builder.AddSimple(name, test::ParseConfigOrDie(item));
+ }
+ return builder.Build();
+}
+
+TEST(VersionCollapserTest, CollapseVersions) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetMinSdkVersion(7).Build();
+
+ const StringPiece res_name = "@android:string/foo";
+
+ std::unique_ptr<ResourceTable> table = BuildTableWithConfigs(
+ res_name,
+ {"land-v4", "land-v5", "sw600dp", "land-v6", "land-v14", "land-v21"});
+
+ VersionCollapser collapser;
+ ASSERT_TRUE(collapser.Consume(context.get(), table.get()));
+
+ // These should be removed.
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v4")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v5")));
+ // This one should be removed because it was renamed to 'land', with the
+ // version dropped.
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v6")));
+
+ // These should remain.
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("sw600dp")));
+
+ // 'land' should be present because it was renamed from 'land-v6'.
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land")));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v14")));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v21")));
+}
+
+TEST(VersionCollapserTest, CollapseVersionsWhenMinSdkIsHighest) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetMinSdkVersion(21).Build();
+
+ const StringPiece res_name = "@android:string/foo";
+
+ std::unique_ptr<ResourceTable> table = BuildTableWithConfigs(
+ res_name, {"land-v4", "land-v5", "sw600dp", "land-v6", "land-v14",
+ "land-v21", "land-v22"});
+ VersionCollapser collapser;
+ ASSERT_TRUE(collapser.Consume(context.get(), table.get()));
+
+ // These should all be removed.
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v4")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v5")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v6")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v14")));
+
+ // These should remain.
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(
+ table.get(), res_name,
+ test::ParseConfigOrDie("sw600dp").CopyWithoutSdkVersion()));
+
+ // land-v21 should have been converted to land.
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land")));
+ // land-v22 should remain as-is.
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v22")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlNamespaceRemover.cpp b/tools/aapt2/link/XmlNamespaceRemover.cpp
new file mode 100644
index 000000000000..24aa5660ae30
--- /dev/null
+++ b/tools/aapt2/link/XmlNamespaceRemover.cpp
@@ -0,0 +1,88 @@
+/*
+ * 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/Linkers.h"
+
+#include <algorithm>
+
+#include "ResourceTable.h"
+
+namespace aapt {
+
+namespace {
+
+/**
+ * Visits each xml Node, removing URI references and nested namespaces.
+ */
+class XmlVisitor : public xml::Visitor {
+ public:
+ explicit XmlVisitor(bool keep_uris) : keep_uris_(keep_uris) {}
+
+ void Visit(xml::Element* el) override {
+ // Strip namespaces
+ for (auto& child : el->children) {
+ while (child && xml::NodeCast<xml::Namespace>(child.get())) {
+ if (child->children.empty()) {
+ child = {};
+ } else {
+ child = std::move(child->children.front());
+ child->parent = el;
+ }
+ }
+ }
+ el->children.erase(
+ std::remove_if(el->children.begin(), el->children.end(),
+ [](const std::unique_ptr<xml::Node>& child) -> bool {
+ return child == nullptr;
+ }),
+ el->children.end());
+
+ if (!keep_uris_) {
+ for (xml::Attribute& attr : el->attributes) {
+ attr.namespace_uri = std::string();
+ }
+ el->namespace_uri = std::string();
+ }
+ xml::Visitor::Visit(el);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlVisitor);
+
+ bool keep_uris_;
+};
+
+} // namespace
+
+bool XmlNamespaceRemover::Consume(IAaptContext* context,
+ xml::XmlResource* resource) {
+ if (!resource->root) {
+ return false;
+ }
+ // Replace any root namespaces until the root is a non-namespace node
+ while (xml::NodeCast<xml::Namespace>(resource->root.get())) {
+ if (resource->root->children.empty()) {
+ break;
+ }
+ resource->root = std::move(resource->root->children.front());
+ resource->root->parent = nullptr;
+ }
+ XmlVisitor visitor(keep_uris_);
+ resource->root->Accept(&visitor);
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlNamespaceRemover_test.cpp b/tools/aapt2/link/XmlNamespaceRemover_test.cpp
new file mode 100644
index 000000000000..a176c03a6432
--- /dev/null
+++ b/tools/aapt2/link/XmlNamespaceRemover_test.cpp
@@ -0,0 +1,122 @@
+/*
+ * 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/Linkers.h"
+
+#include "test/Test.h"
+
+namespace aapt {
+
+class XmlUriTestVisitor : public xml::Visitor {
+ public:
+ XmlUriTestVisitor() = default;
+
+ void Visit(xml::Element* el) override {
+ for (const auto& attr : el->attributes) {
+ EXPECT_EQ(std::string(), attr.namespace_uri);
+ }
+ EXPECT_EQ(std::string(), el->namespace_uri);
+ xml::Visitor::Visit(el);
+ }
+
+ void Visit(xml::Namespace* ns) override {
+ EXPECT_EQ(std::string(), ns->namespace_uri);
+ xml::Visitor::Visit(ns);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlUriTestVisitor);
+};
+
+class XmlNamespaceTestVisitor : public xml::Visitor {
+ public:
+ XmlNamespaceTestVisitor() = default;
+
+ void Visit(xml::Namespace* ns) override {
+ ADD_FAILURE() << "Detected namespace: " << ns->namespace_prefix << "=\""
+ << ns->namespace_uri << "\"";
+ xml::Visitor::Visit(ns);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlNamespaceTestVisitor);
+};
+
+class XmlNamespaceRemoverTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ context_ =
+ test::ContextBuilder().SetCompilationPackage("com.app.test").Build();
+ }
+
+ protected:
+ std::unique_ptr<IAaptContext> context_;
+};
+
+TEST_F(XmlNamespaceRemoverTest, RemoveUris) {
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:text="hello" />)EOF");
+
+ XmlNamespaceRemover remover;
+ ASSERT_TRUE(remover.Consume(context_.get(), doc.get()));
+
+ xml::Node* root = doc.get()->root.get();
+ ASSERT_NE(root, nullptr);
+
+ XmlUriTestVisitor visitor;
+ root->Accept(&visitor);
+}
+
+TEST_F(XmlNamespaceRemoverTest, RemoveNamespaces) {
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:foo="http://schemas.android.com/apk/res/foo"
+ foo:bar="foobar"
+ android:text="hello" />)EOF");
+
+ XmlNamespaceRemover remover;
+ ASSERT_TRUE(remover.Consume(context_.get(), doc.get()));
+
+ xml::Node* root = doc.get()->root.get();
+ ASSERT_NE(root, nullptr);
+
+ XmlNamespaceTestVisitor visitor;
+ root->Accept(&visitor);
+}
+
+TEST_F(XmlNamespaceRemoverTest, RemoveNestedNamespaces) {
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:text="hello">
+ <View xmlns:foo="http://schemas.example.com/foo"
+ android:text="foo"/>
+ </View>)EOF");
+
+ XmlNamespaceRemover remover;
+ ASSERT_TRUE(remover.Consume(context_.get(), doc.get()));
+
+ xml::Node* root = doc.get()->root.get();
+ ASSERT_NE(root, nullptr);
+
+ XmlNamespaceTestVisitor visitor;
+ root->Accept(&visitor);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 568bc74895c7..a8198318b69e 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -14,10 +14,11 @@
* limitations under the License.
*/
+#include "link/Linkers.h"
+
#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"
@@ -29,145 +30,161 @@ 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
+ * 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) {
+ public:
+ using ValueVisitor::Visit;
+
+ ReferenceVisitor(IAaptContext* context, SymbolTable* symbols,
+ xml::IPackageDeclStack* decls, CallSite* callsite)
+ : context_(context),
+ symbols_(symbols),
+ decls_(decls),
+ callsite_(callsite),
+ error_(false) {}
+
+ void Visit(Reference* ref) override {
+ if (!ReferenceLinker::LinkReference(ref, context_, symbols_, decls_,
+ callsite_)) {
+ error_ = true;
}
+ }
- void visit(Reference* ref) override {
- if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) {
- mError = true;
- }
- }
+ bool HasError() const { return error_; }
- bool hasError() const {
- return mError;
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReferenceVisitor);
-private:
- IAaptContext* mContext;
- SymbolTable* mSymbols;
- xml::IPackageDeclStack* mDecls;
- CallSite* mCallSite;
- bool mError;
+ IAaptContext* context_;
+ SymbolTable* symbols_;
+ xml::IPackageDeclStack* decls_;
+ CallSite* callsite_;
+ bool error_;
};
/**
* 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);
- }
+ public:
+ using xml::PackageAwareVisitor::Visit;
+
+ XmlVisitor(IAaptContext* context, SymbolTable* symbols, const Source& source,
+ std::set<int>* sdk_levels_found, CallSite* callsite)
+ : context_(context),
+ symbols_(symbols),
+ source_(source),
+ sdk_levels_found_(sdk_levels_found),
+ callsite_(callsite),
+ reference_visitor_(context, symbols, this, callsite) {}
+
+ void Visit(xml::Element* el) override {
+ const Source source = source_.WithLine(el->line_number);
+ for (xml::Attribute& attr : el->attributes) {
+ Maybe<xml::ExtractedPackage> maybe_package =
+ xml::ExtractPackageFromNamespace(attr.namespace_uri);
+ if (maybe_package) {
+ // There is a valid package name for this attribute. We will look this
+ // up.
+ StringPiece package = maybe_package.value().package;
+ if (package.empty()) {
+ // Empty package means the 'current' or 'local' package.
+ package = context_->GetCompilationPackage();
+ }
- if (attr.compiledValue) {
- // With a compiledValue, we must resolve the reference and assign it an ID.
- attr.compiledValue->setSource(source);
- attr.compiledValue->accept(&mReferenceVisitor);
+ Reference attr_ref(
+ ResourceNameRef(package, ResourceType::kAttr, attr.name));
+ attr_ref.private_reference = maybe_package.value().private_namespace;
+
+ std::string err_str;
+ attr.compiled_attribute = ReferenceLinker::CompileXmlAttribute(
+ attr_ref, context_->GetNameMangler(), symbols_, callsite_,
+ &err_str);
+
+ // Convert the string value into a compiled Value if this is a valid
+ // attribute.
+ if (attr.compiled_attribute) {
+ if (attr.compiled_attribute.value().id) {
+ // Record all SDK levels from which the attributes were defined.
+ const size_t sdk_level = FindAttributeSdkLevel(
+ attr.compiled_attribute.value().id.value());
+ if (sdk_level > 1) {
+ sdk_levels_found_->insert(sdk_level);
}
+ }
+
+ const Attribute* attribute =
+ &attr.compiled_attribute.value().attribute;
+ attr.compiled_value =
+ ResourceUtils::TryParseItemForAttribute(attr.value, attribute);
+ if (!attr.compiled_value &&
+ !(attribute->type_mask & android::ResTable_map::TYPE_STRING)) {
+ // We won't be able to encode this as a string.
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source) << "'" << attr.value << "' "
+ << "is incompatible with attribute "
+ << package << ":" << attr.name << " "
+ << *attribute);
+ error_ = true;
+ }
+
+ } else {
+ context_->GetDiagnostics()->Error(DiagMessage(source)
+ << "attribute '" << package << ":"
+ << attr.name << "' " << err_str);
+ error_ = true;
}
-
- // Call the super implementation.
- xml::PackageAwareVisitor::visit(el);
+ } else if (!attr.compiled_value) {
+ // We still encode references, but only if we haven't manually set this
+ // to
+ // another compiled value.
+ attr.compiled_value = ResourceUtils::TryParseReference(attr.value);
+ }
+
+ if (attr.compiled_value) {
+ // With a compiledValue, we must resolve the reference and assign it an
+ // ID.
+ attr.compiled_value->SetSource(source);
+ attr.compiled_value->Accept(&reference_visitor_);
+ }
}
- bool hasError() {
- return mError || mReferenceVisitor.hasError();
- }
+ // Call the super implementation.
+ xml::PackageAwareVisitor::Visit(el);
+ }
-private:
- IAaptContext* mContext;
- SymbolTable* mSymbols;
- Source mSource;
- std::set<int>* mSdkLevelsFound;
- CallSite* mCallSite;
- ReferenceVisitor mReferenceVisitor;
- bool mError = false;
-};
+ bool HasError() { return error_ || reference_visitor_.HasError(); }
-} // namespace
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlVisitor);
-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;
+ IAaptContext* context_;
+ SymbolTable* symbols_;
+ Source source_;
+ std::set<int>* sdk_levels_found_;
+ CallSite* callsite_;
+ ReferenceVisitor reference_visitor_;
+ bool error_ = false;
+};
+
+} // namespace
+
+bool XmlReferenceLinker::Consume(IAaptContext* context,
+ xml::XmlResource* resource) {
+ sdk_levels_found_.clear();
+ CallSite callsite = {resource->file.name};
+ XmlVisitor visitor(context, context->GetExternalSymbols(),
+ resource->file.source, &sdk_levels_found_, &callsite);
+ if (resource->root) {
+ resource->root->Accept(&visitor);
+ return !visitor.HasError();
+ }
+ return false;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index af9098b9e483..810f63c0410e 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -14,247 +14,286 @@
* 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;
+ public:
+ void SetUp() override {
+ context_ =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetNameManglerPolicy(
+ NameManglerPolicy{"com.app.test", {"com.android.support"}})
+ .AddSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .AddPublicSymbol(
+ "android:attr/layout_width", ResourceId(0x01010000),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_ENUM |
+ android::ResTable_map::TYPE_DIMENSION)
+ .AddItem("match_parent", 0xffffffff)
+ .Build())
+ .AddPublicSymbol(
+ "android:attr/background", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_COLOR)
+ .Build())
+ .AddPublicSymbol("android:attr/attr",
+ ResourceId(0x01010002),
+ test::AttributeBuilder().Build())
+ .AddPublicSymbol(
+ "android:attr/text", ResourceId(0x01010003),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING)
+ .Build())
+
+ // Add one real symbol that was introduces in v21
+ .AddPublicSymbol("android:attr/colorAccent",
+ ResourceId(0x01010435),
+ test::AttributeBuilder().Build())
+
+ // Private symbol.
+ .AddSymbol("android:color/hidden", ResourceId(0x01020001))
+
+ .AddPublicSymbol("android:id/id", ResourceId(0x01030000))
+ .AddSymbol("com.app.test:id/id", ResourceId(0x7f030000))
+ .AddSymbol("com.app.test:color/green",
+ ResourceId(0x7f020000))
+ .AddSymbol("com.app.test:color/red", ResourceId(0x7f020001))
+ .AddSymbol(
+ "com.app.test:attr/colorAccent", ResourceId(0x7f010000),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_COLOR)
+ .Build())
+ .AddPublicSymbol(
+ "com.app.test:attr/com.android.support$colorAccent",
+ ResourceId(0x7f010001),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_COLOR)
+ .Build())
+ .AddPublicSymbol("com.app.test:attr/attr",
+ ResourceId(0x7f010002),
+ test::AttributeBuilder().Build())
+ .Build())
+ .Build();
+ }
+
+ protected:
+ std::unique_ptr<IAaptContext> context_;
};
TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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);
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, nullptr);
+
+ xml::Attribute* xml_attr =
+ view_el->FindAttribute(xml::kSchemaAndroid, "layout_width");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x01010000));
+ ASSERT_NE(xml_attr->compiled_value, nullptr);
+ ASSERT_NE(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()),
+ nullptr);
+
+ xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "background");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x01010001));
+ ASSERT_NE(xml_attr->compiled_value, nullptr);
+ Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->name);
+ EXPECT_EQ(ref->name.value(),
+ test::ParseNameOrDie("color/green")); // Make sure the name
+ // didn't change.
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000));
+
+ xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ ASSERT_FALSE(
+ xml_attr->compiled_value); // Strings don't get compiled for memory sake.
+
+ xml_attr = view_el->FindAttribute("", "class");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_FALSE(xml_attr->compiled_attribute);
+ ASSERT_EQ(xml_attr->compiled_value, nullptr);
}
TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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()));
+ XmlReferenceLinker linker;
+ ASSERT_FALSE(linker.Consume(context_.get(), doc.get()));
}
-TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+TEST_F(XmlReferenceLinkerTest,
+ PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) {
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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()));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
}
TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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);
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+ EXPECT_TRUE(linker.sdk_levels().count(21) == 1);
}
TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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);
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, nullptr);
+
+ xml::Attribute* xml_attr = view_el->FindAttribute(
+ xml::BuildPackageNamespace("com.android.support"), "colorAccent");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x7f010001));
+ ASSERT_NE(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()),
+ nullptr);
}
TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, nullptr);
+
+ xml::Attribute* xml_attr =
+ view_el->FindAttribute(xml::kSchemaAuto, "colorAccent");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x7f010000));
+ Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.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(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, nullptr);
+
+ // All attributes and references in this element should be referring to
+ // "android" (0x01).
+ xml::Attribute* xml_attr =
+ view_el->FindAttribute(xml::kSchemaAndroid, "attr");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x01010002));
+ Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x01030000));
+
+ ASSERT_FALSE(view_el->GetChildElements().empty());
+ view_el = view_el->GetChildElements().front();
+ ASSERT_NE(view_el, nullptr);
+
+ // All attributes and references in this element should be referring to
+ // "com.app.test" (0x7f).
+ xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"),
+ "attr");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x7f010002));
+ ref = ValueCast<Reference>(xml_attr->compiled_value.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(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, nullptr);
+
+ // All attributes and references in this element should be referring to
+ // "com.app.test" (0x7f).
+ xml::Attribute* xml_attr = view_el->FindAttribute(
+ xml::BuildPackageNamespace("com.app.test"), "attr");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x7f010002));
+ Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index 9affb836340c..4526a79577d9 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -17,48 +17,49 @@
#ifndef AAPT_PROCESS_IRESOURCETABLECONSUMER_H
#define AAPT_PROCESS_IRESOURCETABLECONSUMER_H
+#include <iostream>
+#include <list>
+#include <sstream>
+
#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 ~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;
+ virtual SymbolTable* GetExternalSymbols() = 0;
+ virtual IDiagnostics* GetDiagnostics() = 0;
+ virtual const std::string& GetCompilationPackage() = 0;
+ virtual uint8_t GetPackageId() = 0;
+ virtual NameMangler* GetNameMangler() = 0;
+ virtual bool IsVerbose() = 0;
+ virtual int GetMinSdkVersion() = 0;
};
struct IResourceTableConsumer {
- virtual ~IResourceTableConsumer() = default;
+ virtual ~IResourceTableConsumer() = default;
- virtual bool consume(IAaptContext* context, ResourceTable* table) = 0;
+ virtual bool Consume(IAaptContext* context, ResourceTable* table) = 0;
};
namespace xml {
-struct XmlResource;
+class XmlResource;
}
struct IXmlResourceConsumer {
- virtual ~IXmlResourceConsumer() = default;
+ virtual ~IXmlResourceConsumer() = default;
- virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0;
+ virtual bool Consume(IAaptContext* context, xml::XmlResource* resource) = 0;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_PROCESS_IRESOURCETABLECONSUMER_H */
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index eaaf06f7e530..767384d8d956 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -14,297 +14,288 @@
* limitations under the License.
*/
+#include "process/SymbolTable.h"
+
+#include "androidfw/AssetManager.h"
+#include "androidfw/ResourceTypes.h"
+
#include "ConfigDescription.h"
#include "Resource.h"
+#include "ResourceUtils.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));
+void SymbolTable::AppendSource(std::unique_ptr<ISymbolSource> source) {
+ sources_.push_back(std::move(source));
- // We do not clear the cache, because sources earlier in the list take precedent.
+ // 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));
+void SymbolTable::PrependSource(std::unique_ptr<ISymbolSource> source) {
+ sources_.insert(sources_.begin(), std::move(source));
- // We must clear the cache in case we did a lookup before adding this resource.
- mCache.clear();
+ // We must clear the cache in case we did a lookup before adding this
+ // resource.
+ cache_.clear();
}
-const SymbolTable::Symbol* SymbolTable::findByName(const ResourceName& name) {
- if (const std::shared_ptr<Symbol>& s = mCache.get(name)) {
- return s.get();
+const SymbolTable::Symbol* SymbolTable::FindByName(const ResourceName& name) {
+ if (const std::shared_ptr<Symbol>& s = cache_.get(name)) {
+ return s.get();
+ }
+
+ // We did not find it in the cache, so look through the sources.
+ for (auto& symbolSource : sources_) {
+ 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> shared_symbol =
+ std::shared_ptr<Symbol>(symbol.release());
+ cache_.put(name, shared_symbol);
+
+ if (shared_symbol->id) {
+ // The symbol has an ID, so we can also cache this!
+ id_cache_.put(shared_symbol->id.value(), shared_symbol);
+ }
+ return shared_symbol.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;
+ }
+ return nullptr;
}
-const SymbolTable::Symbol* SymbolTable::findById(ResourceId id) {
- if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
- return s.get();
+const SymbolTable::Symbol* SymbolTable::FindById(const ResourceId& id) {
+ if (const std::shared_ptr<Symbol>& s = id_cache_.get(id)) {
+ return s.get();
+ }
+
+ // We did not find it in the cache, so look through the sources.
+ for (auto& symbolSource : sources_) {
+ 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> shared_symbol =
+ std::shared_ptr<Symbol>(symbol.release());
+ id_cache_.put(id, shared_symbol);
+ return shared_symbol.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;
+ }
+ 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;
+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 successful 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));
- }
+std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName(
+ const ResourceName& name) {
+ Maybe<ResourceTable::SearchResult> result = table_->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->is_public = (sr.entry->symbol_status.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* config_value = sr.entry->FindValue(kDefaultConfig);
+ if (config_value) {
+ // This resource has an Attribute.
+ if (Attribute* attr = ValueCast<Attribute>(config_value->value.get())) {
+ symbol->attribute = std::make_shared<Attribute>(*attr);
+ } else {
return {};
+ }
}
+ }
+ return symbol;
+}
- ResourceTable::SearchResult sr = result.value();
-
- std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>();
- symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic);
+bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) {
+ int32_t cookie = 0;
+ return assets_.addAssetPath(android::String8(path.data(), path.size()),
+ &cookie);
+}
- 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());
+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->type_mask = entry[i].map.value.data;
+ break;
}
-
- 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 {};
- }
+ }
+
+ if (s->attribute) {
+ for (size_t i = 0; i < (size_t)count; i++) {
+ const android::ResTable_map& map_entry = entry[i].map;
+ if (Res_INTERNALID(map_entry.name.ident)) {
+ switch (map_entry.name.ident) {
+ case android::ResTable_map::ATTR_MIN:
+ s->attribute->min_int = static_cast<int32_t>(map_entry.value.data);
+ break;
+ case android::ResTable_map::ATTR_MAX:
+ s->attribute->max_int = static_cast<int32_t>(map_entry.value.data);
+ break;
}
- }
- return symbol;
-}
-
-bool AssetManagerSymbolSource::addAssetPath(const StringPiece& path) {
- int32_t cookie = 0;
- return mAssets.addAssetPath(android::String8(path.data(), path.size()), &cookie);
-}
+ continue;
+ }
-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) {
+ android::ResTable::resource_name entry_name;
+ if (!table.getResourceName(map_entry.name.ident, false, &entry_name)) {
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;
- }
- }
+ Maybe<ResourceName> parsed_name =
+ ResourceUtils::ToResourceName(entry_name);
+ if (!parsed_name) {
+ return nullptr;
+ }
- 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));
- }
+ Attribute::Symbol symbol;
+ symbol.symbol.name = parsed_name.value();
+ symbol.symbol.id = ResourceId(map_entry.name.ident);
+ symbol.value = map_entry.value.data;
+ s->attribute->symbols.push_back(std::move(symbol));
}
- table.unlockBag(entry);
- return s;
+ }
+ 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> AssetManagerSymbolSource::FindByName(
+ const ResourceName& name) {
+ const android::ResTable& table = assets_.getResources(false);
- 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;
- }
+ const std::u16string package16 = util::Utf8ToUtf16(name.package);
+ const std::u16string type16 = util::Utf8ToUtf16(ToString(name.type));
+ const std::u16string entry16 = util::Utf8ToUtf16(name.entry);
- if (s) {
- s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
- return s;
- }
+ uint32_t type_spec_flags = 0;
+ ResourceId res_id = table.identifierForName(
+ entry16.data(), entry16.size(), type16.data(), type16.size(),
+ package16.data(), package16.size(), &type_spec_flags);
+ if (!res_id.is_valid()) {
return {};
+ }
+
+ std::unique_ptr<SymbolTable::Symbol> s;
+ if (name.type == ResourceType::kAttr) {
+ s = LookupAttributeInTable(table, res_id);
+ } else {
+ s = util::make_unique<SymbolTable::Symbol>();
+ s->id = res_id;
+ }
+
+ if (s) {
+ s->is_public =
+ (type_spec_flags & 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;
+static Maybe<ResourceName> GetResourceName(const android::ResTable& table,
+ ResourceId id) {
+ android::ResTable::resource_name res_name = {};
+ if (!table.getResourceName(id.id, true, &res_name)) {
+ return {};
+ }
+ return ResourceUtils::ToResourceName(res_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 {};
- }
+std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById(
+ ResourceId id) {
+ const android::ResTable& table = assets_.getResources(false);
+ Maybe<ResourceName> maybe_name = GetResourceName(table, id);
+ if (!maybe_name) {
+ return {};
+ }
- uint32_t typeSpecFlags = 0;
- table.getResourceFlags(id.id, &typeSpecFlags);
+ uint32_t type_spec_flags = 0;
+ table.getResourceFlags(id.id, &type_spec_flags);
- 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;
- }
+ std::unique_ptr<SymbolTable::Symbol> s;
+ if (maybe_name.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 {};
+ if (s) {
+ s->is_public =
+ (type_spec_flags & 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 {};
+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
+} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index e684bb06f1f5..25f756522580 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -17,116 +17,115 @@
#ifndef AAPT_PROCESS_SYMBOLTABLE_H
#define AAPT_PROCESS_SYMBOLTABLE_H
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+#include "android-base/macros.h"
+#include "androidfw/AssetManager.h"
+#include "utils/JenkinsHash.h"
+#include "utils/LruCache.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;
+ std::hash<std::string> str_hash;
+ android::hash_t hash = 0;
+ hash = android::JenkinsHashMix(hash, (uint32_t)str_hash(name.package));
+ hash = android::JenkinsHashMix(hash, (uint32_t)name.type);
+ hash = android::JenkinsHashMix(hash, (uint32_t)str_hash(name.entry));
+ return hash;
}
inline android::hash_t hash_type(const ResourceId& id) {
- return android::hash_type(id.id);
+ return android::hash_type(id.id);
}
class ISymbolSource;
class SymbolTable {
-public:
- struct Symbol {
- Symbol() : Symbol(Maybe<ResourceId>{}) {
- }
+ public:
+ struct Symbol {
+ Symbol() : Symbol(Maybe<ResourceId>{}) {}
- Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) {
- }
+ explicit 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)
+ : 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 Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr,
+ bool pub)
+ : id(i), attribute(attr), is_public(pub) {}
- Symbol(const Symbol&) = default;
- Symbol(Symbol&&) = default;
- Symbol& operator=(const Symbol&) = default;
- Symbol& operator=(Symbol&&) = default;
+ 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;
- };
+ Maybe<ResourceId> id;
+ std::shared_ptr<Attribute> attribute;
+ bool is_public = false;
+ };
- SymbolTable() : mCache(200), mIdCache(200) {
- }
+ SymbolTable() : cache_(200), id_cache_(200) {}
- void appendSource(std::unique_ptr<ISymbolSource> source);
- void prependSource(std::unique_ptr<ISymbolSource> source);
+ 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);
+ /**
+ * Never hold on to the result between calls to FindByName or FindById. The
+ * results stored in a cache which may evict entries.
+ */
+ const Symbol* FindByName(const ResourceName& name);
+ const Symbol* FindById(const 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);
+ /**
+ * 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;
+ private:
+ std::vector<std::unique_ptr<ISymbolSource>> sources_;
- // 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;
+ // We use shared_ptr because unique_ptr is not supported and
+ // we need automatic deletion.
+ android::LruCache<ResourceName, std::shared_ptr<Symbol>> cache_;
+ android::LruCache<ResourceId, std::shared_ptr<Symbol>> id_cache_;
- DISALLOW_COPY_AND_ASSIGN(SymbolTable);
+ DISALLOW_COPY_AND_ASSIGN(SymbolTable);
};
/**
- * An interface that a symbol source implements in order to surface symbol information
+ * 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 {};
+ 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 {};
+ }
};
/**
@@ -135,38 +134,40 @@ public:
* Lookups by ID are ignored.
*/
class ResourceTableSymbolSource : public ISymbolSource {
-public:
- explicit ResourceTableSymbolSource(ResourceTable* table) : mTable(table) {
- }
+ public:
+ explicit ResourceTableSymbolSource(ResourceTable* table) : table_(table) {}
- std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override;
+ std::unique_ptr<SymbolTable::Symbol> FindByName(
+ const ResourceName& name) override;
- std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override {
- return {};
- }
+ std::unique_ptr<SymbolTable::Symbol> FindById(ResourceId id) override {
+ return {};
+ }
-private:
- ResourceTable* mTable;
+ private:
+ ResourceTable* table_;
- DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource);
+ DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource);
};
class AssetManagerSymbolSource : public ISymbolSource {
-public:
- AssetManagerSymbolSource() = default;
+ public:
+ AssetManagerSymbolSource() = default;
- bool addAssetPath(const StringPiece& path);
+ 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;
+ 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;
+ private:
+ android::AssetManager assets_;
- DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource);
+ DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_PROCESS_SYMBOLTABLE_H */
diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp
index 34f31be3d0db..9ea0786cbc8e 100644
--- a/tools/aapt2/process/SymbolTable_test.cpp
+++ b/tools/aapt2/process/SymbolTable_test.cpp
@@ -15,39 +15,44 @@
*/
#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);
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddSimple("android:id/foo", ResourceId(0x01020000))
+ .AddSimple("android:id/bar")
+ .AddValue("android:attr/foo", ResourceId(0x01010000),
+ test::AttributeBuilder().Build())
+ .Build();
+
+ ResourceTableSymbolSource symbol_source(table.get());
+ EXPECT_NE(nullptr,
+ symbol_source.FindByName(test::ParseNameOrDie("android:id/foo")));
+ EXPECT_NE(nullptr,
+ symbol_source.FindByName(test::ParseNameOrDie("android:id/bar")));
+
+ std::unique_ptr<SymbolTable::Symbol> s =
+ symbol_source.FindByName(test::ParseNameOrDie("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);
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddValue("android:^attr-private/foo", ResourceId(0x01010000),
+ test::AttributeBuilder().Build())
+ .Build();
+
+ ResourceTableSymbolSource symbol_source(table.get());
+ std::unique_ptr<SymbolTable::Symbol> s =
+ symbol_source.FindByName(test::ParseNameOrDie("android:attr/foo"));
+ ASSERT_NE(nullptr, s);
+ EXPECT_NE(nullptr, s->attribute);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp
index 99981c52e26f..38bf4e3bd8eb 100644
--- a/tools/aapt2/proto/ProtoHelpers.cpp
+++ b/tools/aapt2/proto/ProtoHelpers.cpp
@@ -18,120 +18,151 @@
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 SerializeStringPoolToPb(const StringPool& pool,
+ pb::StringPool* out_pb_pool) {
+ BigBuffer buffer(1024);
+ StringPool::FlattenUtf8(&buffer, pool);
+
+ std::string* data = out_pb_pool->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 SerializeSourceToPb(const Source& source, StringPool* src_pool,
+ pb::Source* out_pb_source) {
+ StringPool::Ref ref = src_pool->MakeRef(source.path);
+ out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index()));
+ if (source.line) {
+ out_pb_source->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();
- }
+void DeserializeSourceFromPb(const pb::Source& pb_source,
+ const android::ResStringPool& src_pool,
+ Source* out_source) {
+ if (pb_source.has_path_idx()) {
+ out_source->path = util::GetString(src_pool, pb_source.path_idx());
+ }
- if (pbSource.has_line_no()) {
- outSource->line = static_cast<size_t>(pbSource.line_no());
- }
+ if (pb_source.has_line_no()) {
+ out_source->line = static_cast<size_t>(pb_source.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;
+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;
+SymbolState DeserializeVisibilityFromPb(
+ pb::SymbolStatus_Visibility pb_visibility) {
+ switch (pb_visibility) {
+ 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));
+void SerializeConfig(const ConfigDescription& config,
+ pb::ConfigDescription* out_pb_config) {
+ android::ResTable_config flat_config = config;
+ flat_config.size = sizeof(flat_config);
+ flat_config.swapHtoD();
+ out_pb_config->set_data(&flat_config, sizeof(flat_config));
}
-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;
+bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config,
+ ConfigDescription* out_config) {
+ if (!pb_config.has_data()) {
+ return false;
+ }
+
+ const android::ResTable_config* config;
+ if (pb_config.data().size() > sizeof(*config)) {
+ return false;
+ }
+
+ config = reinterpret_cast<const android::ResTable_config*>(
+ pb_config.data().data());
+ out_config->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;
+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;
+Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) {
+ switch (pb_type) {
+ 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;
+pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) {
+ switch (plural_idx) {
+ 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;
+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
+} // namespace aapt
diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h
index 02e67f17c80c..735cda0bde78 100644
--- a/tools/aapt2/proto/ProtoHelpers.h
+++ b/tools/aapt2/proto/ProtoHelpers.h
@@ -17,36 +17,45 @@
#ifndef AAPT_PROTO_PROTOHELPERS_H
#define AAPT_PROTO_PROTOHELPERS_H
+#include "androidfw/ResourceTypes.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 SerializeStringPoolToPb(const StringPool& pool,
+ pb::StringPool* out_pb_pool);
+
+void SerializeSourceToPb(const Source& source, StringPool* src_pool,
+ pb::Source* out_pb_source);
+
+void DeserializeSourceFromPb(const pb::Source& pb_source,
+ const android::ResStringPool& src_pool,
+ Source* out_source);
+
+pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state);
+
+SymbolState DeserializeVisibilityFromPb(
+ pb::SymbolStatus_Visibility pb_visibility);
+
+void SerializeConfig(const ConfigDescription& config,
+ pb::ConfigDescription* out_pb_config);
-void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource);
-void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
- Source* outSource);
+bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config,
+ ConfigDescription* out_config);
-pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state);
-SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility);
+pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type);
-void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig);
-bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
- ConfigDescription* outConfig);
+Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type);
-pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type);
-Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType);
+pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx);
-pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx);
-size_t deserializePluralEnumFromPb(pb::Plural_Arity arity);
+size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity);
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_PROTO_PROTOHELPERS_H */
diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h
index 6e224ab00af4..39c50038d599 100644
--- a/tools/aapt2/proto/ProtoSerialize.h
+++ b/tools/aapt2/proto/ProtoSerialize.h
@@ -17,61 +17,61 @@
#ifndef AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
#define AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
+#include "android-base/macros.h"
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/io/zero_copy_stream_impl_lite.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:
+ explicit CompiledFileOutputStream(
+ google::protobuf::io::ZeroCopyOutputStream* out);
-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();
+ void WriteLittleEndian32(uint32_t value);
+ void WriteCompiledFile(const pb::CompiledFile* compiledFile);
+ void WriteData(const BigBuffer* buffer);
+ void WriteData(const void* data, size_t len);
+ bool HadError();
-private:
- bool ensureFileWritten();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
- google::protobuf::io::CodedOutputStream mOut;
- pb::CompiledFile* mPbFile;
+ void EnsureAlignedWrite();
- DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
+ google::protobuf::io::CodedOutputStream out_;
};
class CompiledFileInputStream {
-public:
- CompiledFileInputStream(const void* data, size_t size);
+ public:
+ explicit CompiledFileInputStream(const void* data, size_t size);
- const pb::CompiledFile* CompiledFile();
+ bool ReadLittleEndian32(uint32_t* outVal);
+ bool ReadCompiledFile(pb::CompiledFile* outVal);
+ bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen);
- const void* data();
- size_t size();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
-private:
- google::protobuf::io::CodedInputStream mIn;
- std::unique_ptr<pb::CompiledFile> mPbFile;
- const uint8_t* mData;
- size_t mSize;
+ void EnsureAlignedRead();
- DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
+ google::protobuf::io::CodedInputStream in_;
};
-} // 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);
+
+} // namespace aapt
#endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 1ec48f09f228..d93d495b106f 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -14,509 +14,476 @@
* limitations under the License.
*/
+#include "proto/ProtoSerialize.h"
+
+#include "android-base/logging.h"
+#include "androidfw/ResourceTypes.h"
+
#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);
+ public:
+ using ValueVisitor::Visit;
+
+ explicit ReferenceIdToNameVisitor(
+ const std::map<ResourceId, ResourceNameRef>* mapping)
+ : mapping_(mapping) {
+ CHECK(mapping_ != nullptr);
+ }
+
+ void Visit(Reference* reference) override {
+ if (!reference->id || !reference->id.value().is_valid()) {
+ return;
}
- 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();
- }
+ ResourceId id = reference->id.value();
+ auto cache_iter = mapping_->find(id);
+ if (cache_iter != mapping_->end()) {
+ reference->name = cache_iter->second.ToResourceName();
}
+ }
-private:
- const std::map<ResourceId, ResourceNameRef>* mMapping;
+ private:
+ const std::map<ResourceId, ResourceNameRef>* mapping_;
};
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:
+ PackagePbDeserializer(const android::ResStringPool* valuePool,
+ const android::ResStringPool* sourcePool,
+ const android::ResStringPool* symbolPool,
+ const Source& source, IDiagnostics* diag)
+ : value_pool_(valuePool),
+ source_pool_(sourcePool),
+ symbol_pool_(symbolPool),
+ source_(source),
+ diag_(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());
}
-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;
+ 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 {};
+ ResourceTablePackage* pkg =
+ table->CreatePackage(pbPackage.package_name(), id);
+ for (const pb::Type& pbType : pbPackage.types()) {
+ const ResourceType* resType = ParseResourceType(pbType.name());
+ if (!resType) {
+ diag_->Error(DiagMessage(source_) << "unknown type '" << pbType.name()
+ << "'");
+ return {};
+ }
+
+ ResourceTableType* type = pkg->FindOrCreateType(*resType);
+
+ for (const pb::Entry& pbEntry : pbType.entries()) {
+ ResourceEntry* entry = type->FindOrCreateEntry(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(), *source_pool_,
+ &entry->symbol_status.source);
+ }
+
+ if (pbStatus.has_comment()) {
+ entry->symbol_status.comment = pbStatus.comment();
+ }
+
+ SymbolState visibility =
+ DeserializeVisibilityFromPb(pbStatus.visibility());
+ entry->symbol_status.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());
}
- 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 {};
- }
- }
+ if (type->symbol_status.state != SymbolState::kPublic) {
+ // If the type has not been made public, do so now.
+ type->symbol_status.state = SymbolState::kPublic;
+ if (pbType.has_id()) {
+ type->id = static_cast<uint8_t>(pbType.id());
+ }
+ }
+ } else if (visibility == SymbolState::kPrivate) {
+ if (type->symbol_status.state == SymbolState::kUndefined) {
+ type->symbol_status.state = SymbolState::kPrivate;
}
+ }
}
- 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");
+ ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id());
+ if (resId.is_valid()) {
+ idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name);
}
- return {};
- }
- std::unique_ptr<Value> deserializeValueFromPb(const pb::Value& pbValue,
- const ConfigDescription& config,
- StringPool* pool) {
- const bool isWeak = pbValue.has_weak() ? pbValue.weak() : false;
+ for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) {
+ const pb::ConfigDescription& pbConfig = pbConfigValue.config();
- std::unique_ptr<Value> value;
- if (pbValue.has_item()) {
- value = deserializeItemFromPb(pbValue.item(), config, pool);
- if (!value) {
- return {};
- }
+ ConfigDescription config;
+ if (!DeserializeConfigDescriptionFromPb(pbConfig, &config)) {
+ diag_->Error(DiagMessage(source_) << "invalid configuration");
+ 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();
- attr->minInt = pbAttr.min_int();
- attr->maxInt = pbAttr.max_int();
- 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");
+ ResourceConfigValue* configValue =
+ entry->FindOrCreateValue(config, pbConfig.product());
+ if (configValue->value) {
+ // Duplicate config.
+ diag_->Error(DiagMessage(source_) << "duplicate configuration");
return {};
- }
+ }
- assert(value && "forgot to set value");
+ configValue->value = DeserializeValueFromPb(
+ pbConfigValue.value(), config, &table->string_pool);
+ if (!configValue->value) {
+ return {};
+ }
+ }
+ }
+ }
- value->setWeak(isWeak);
- deserializeItemCommon(pbValue, value.get());
- return value;
+ ReferenceIdToNameVisitor visitor(&idIndex);
+ VisitAllValuesInPackage(pkg, &visitor);
+ return true;
+ }
+
+ private:
+ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
+ const ConfigDescription& config,
+ StringPool* pool) {
+ if (pb_item.has_ref()) {
+ const pb::Reference& pb_ref = pb_item.ref();
+ std::unique_ptr<Reference> ref = util::make_unique<Reference>();
+ if (!DeserializeReferenceFromPb(pb_ref, ref.get())) {
+ return {};
+ }
+ return std::move(ref);
+
+ } else if (pb_item.has_prim()) {
+ const pb::Primitive& pb_prim = pb_item.prim();
+ android::Res_value prim = {};
+ prim.dataType = static_cast<uint8_t>(pb_prim.type());
+ prim.data = pb_prim.data();
+ return util::make_unique<BinaryPrimitive>(prim);
+
+ } else if (pb_item.has_id()) {
+ return util::make_unique<Id>();
+
+ } else if (pb_item.has_str()) {
+ const uint32_t idx = pb_item.str().idx();
+ const std::string str = util::GetString(*value_pool_, idx);
+
+ const android::ResStringPool_span* spans = value_pool_->styleAt(idx);
+ if (spans && spans->name.index != android::ResStringPool_span::END) {
+ StyleString style_str = {str};
+ while (spans->name.index != android::ResStringPool_span::END) {
+ style_str.spans.push_back(
+ Span{util::GetString(*value_pool_, spans->name.index),
+ spans->firstChar, spans->lastChar});
+ spans++;
+ }
+ return util::make_unique<StyledString>(pool->MakeRef(
+ style_str,
+ StringPool::Context(StringPool::Context::kStylePriority, config)));
+ }
+ return util::make_unique<String>(
+ pool->MakeRef(str, StringPool::Context(config)));
+
+ } else if (pb_item.has_raw_str()) {
+ const uint32_t idx = pb_item.raw_str().idx();
+ const std::string str = util::GetString(*value_pool_, idx);
+ return util::make_unique<RawString>(
+ pool->MakeRef(str, StringPool::Context(config)));
+
+ } else if (pb_item.has_file()) {
+ const uint32_t idx = pb_item.file().path_idx();
+ const std::string str = util::GetString(*value_pool_, idx);
+ return util::make_unique<FileReference>(pool->MakeRef(
+ str,
+ StringPool::Context(StringPool::Context::kHighPriority, config)));
+
+ } else {
+ diag_->Error(DiagMessage(source_) << "unknown item");
}
+ return {};
+ }
- bool deserializeReferenceFromPb(const pb::Reference& pbRef, Reference* outRef) {
- outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type());
- outRef->privateReference = pbRef.private_();
+ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
+ const ConfigDescription& config,
+ StringPool* pool) {
+ const bool is_weak = pb_value.has_weak() ? pb_value.weak() : false;
- if (!pbRef.has_id() && !pbRef.has_symbol_idx()) {
- return false;
+ std::unique_ptr<Value> value;
+ if (pb_value.has_item()) {
+ value = DeserializeItemFromPb(pb_value.item(), config, pool);
+ if (!value) {
+ return {};
+ }
+
+ } else if (pb_value.has_compound_value()) {
+ const pb::CompoundValue& pb_compound_value = pb_value.compound_value();
+ if (pb_compound_value.has_attr()) {
+ const pb::Attribute& pb_attr = pb_compound_value.attr();
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak);
+ attr->type_mask = pb_attr.format_flags();
+ attr->min_int = pb_attr.min_int();
+ attr->max_int = pb_attr.max_int();
+ for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbols()) {
+ Attribute::Symbol symbol;
+ DeserializeItemCommon(pb_symbol, &symbol.symbol);
+ if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol)) {
+ return {};
+ }
+ symbol.value = pb_symbol.value();
+ attr->symbols.push_back(std::move(symbol));
}
-
- if (pbRef.has_id()) {
- outRef->id = ResourceId(pbRef.id());
+ value = std::move(attr);
+
+ } else if (pb_compound_value.has_style()) {
+ const pb::Style& pb_style = pb_compound_value.style();
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ if (pb_style.has_parent()) {
+ style->parent = Reference();
+ if (!DeserializeReferenceFromPb(pb_style.parent(),
+ &style->parent.value())) {
+ return {};
+ }
+
+ if (pb_style.has_parent_source()) {
+ Source parent_source;
+ DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_,
+ &parent_source);
+ style->parent.value().SetSource(std::move(parent_source));
+ }
}
- 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;
- }
+ for (const pb::Style_Entry& pb_entry : pb_style.entries()) {
+ Style::Entry entry;
+ DeserializeItemCommon(pb_entry, &entry.key);
+ if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key)) {
+ return {};
+ }
+
+ entry.value = DeserializeItemFromPb(pb_entry.item(), config, pool);
+ if (!entry.value) {
+ return {};
+ }
- outRef->name = nameRef.toResourceName();
+ DeserializeItemCommon(pb_entry, entry.value.get());
+ style->entries.push_back(std::move(entry));
}
- return true;
- }
+ value = std::move(style);
+
+ } else if (pb_compound_value.has_styleable()) {
+ const pb::Styleable& pb_styleable = pb_compound_value.styleable();
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ for (const pb::Styleable_Entry& pb_entry : pb_styleable.entries()) {
+ Reference attr_ref;
+ DeserializeItemCommon(pb_entry, &attr_ref);
+ DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref);
+ styleable->entries.push_back(std::move(attr_ref));
+ }
+ value = std::move(styleable);
+
+ } else if (pb_compound_value.has_array()) {
+ const pb::Array& pb_array = pb_compound_value.array();
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+ for (const pb::Array_Entry& pb_entry : pb_array.entries()) {
+ std::unique_ptr<Item> item =
+ DeserializeItemFromPb(pb_entry.item(), config, pool);
+ if (!item) {
+ return {};
+ }
- 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));
+ DeserializeItemCommon(pb_entry, item.get());
+ array->items.push_back(std::move(item));
}
+ value = std::move(array);
+
+ } else if (pb_compound_value.has_plural()) {
+ const pb::Plural& pb_plural = pb_compound_value.plural();
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ for (const pb::Plural_Entry& pb_entry : pb_plural.entries()) {
+ size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity());
+ plural->values[pluralIdx] =
+ DeserializeItemFromPb(pb_entry.item(), config, pool);
+ if (!plural->values[pluralIdx]) {
+ return {};
+ }
- if (pbItem.has_comment()) {
- outValue->setComment(util::utf8ToUtf16(pbItem.comment()));
+ DeserializeItemCommon(pb_entry, plural->values[pluralIdx].get());
}
- }
+ value = std::move(plural);
-private:
- const android::ResStringPool* mValuePool;
- const android::ResStringPool* mSourcePool;
- const android::ResStringPool* mSymbolPool;
- const Source mSource;
- IDiagnostics* mDiag;
-};
+ } else {
+ diag_->Error(DiagMessage(source_) << "unknown compound value");
+ return {};
+ }
+ } else {
+ diag_->Error(DiagMessage(source_) << "unknown value");
+ return {};
+ }
-} // namespace
+ CHECK(value) << "forgot to set value";
-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;
+ value->SetWeak(is_weak);
+ DeserializeItemCommon(pb_value, value.get());
+ return value;
+ }
- std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+ bool DeserializeReferenceFromPb(const pb::Reference& pb_ref,
+ Reference* out_ref) {
+ out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type());
+ out_ref->private_reference = pb_ref.private_();
- if (!pbTable.has_string_pool()) {
- diag->error(DiagMessage(source) << "no string pool found");
- return {};
+ if (!pb_ref.has_id() && !pb_ref.has_symbol_idx()) {
+ return false;
}
- 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 {};
+ if (pb_ref.has_id()) {
+ out_ref->id = ResourceId(pb_ref.id());
}
- 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 {};
- }
+ if (pb_ref.has_symbol_idx()) {
+ const std::string str_symbol =
+ util::GetString(*symbol_pool_, pb_ref.symbol_idx());
+ ResourceNameRef name_ref;
+ if (!ResourceUtils::ParseResourceName(str_symbol, &name_ref, nullptr)) {
+ diag_->Error(DiagMessage(source_) << "invalid reference name '"
+ << str_symbol << "'");
+ return false;
+ }
+
+ out_ref->name = name_ref.ToResourceName();
}
-
- 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 {};
- }
+ return true;
+ }
+
+ template <typename T>
+ void DeserializeItemCommon(const T& pb_item, Value* out_value) {
+ if (pb_item.has_source()) {
+ Source source;
+ DeserializeSourceFromPb(pb_item.source(), *source_pool_, &source);
+ out_value->SetSource(std::move(source));
}
- PackagePbDeserializer packagePbDeserializer(&valuePool, &sourcePool, &symbolPool, source, diag);
- for (const pb::Package& pbPackage : pbTable.packages()) {
- if (!packagePbDeserializer.deserializeFromPb(pbPackage, table.get())) {
- return {};
- }
+ if (pb_item.has_comment()) {
+ out_value->SetComment(pb_item.comment());
}
- 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;
+ }
+
+ private:
+ const android::ResStringPool* value_pool_;
+ const android::ResStringPool* source_pool_;
+ const android::ResStringPool* symbol_pool_;
+ const Source source_;
+ IDiagnostics* diag_;
+};
- // 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() });
+} // namespace
+
+std::unique_ptr<ResourceTable> DeserializeTableFromPb(
+ const pb::ResourceTable& pb_table, 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 (!pb_table.has_string_pool()) {
+ diag->Error(DiagMessage(source) << "no string pool found");
+ return {};
+ }
+
+ ResStringPool value_pool;
+ status_t result = value_pool.setTo(pb_table.string_pool().data().data(),
+ pb_table.string_pool().data().size());
+ if (result != NO_ERROR) {
+ diag->Error(DiagMessage(source) << "invalid string pool");
+ return {};
+ }
+
+ ResStringPool source_pool;
+ if (pb_table.has_source_pool()) {
+ result = source_pool.setTo(pb_table.source_pool().data().data(),
+ pb_table.source_pool().data().size());
+ if (result != NO_ERROR) {
+ diag->Error(DiagMessage(source) << "invalid source pool");
+ return {};
}
- 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);
+ ResStringPool symbol_pool;
+ if (pb_table.has_symbol_pool()) {
+ result = symbol_pool.setTo(pb_table.symbol_pool().data().data(),
+ pb_table.symbol_pool().data().size());
+ if (result != NO_ERROR) {
+ diag->Error(DiagMessage(source) << "invalid symbol pool");
+ return {};
}
- return mPbFile.get();
-}
+ }
-const void* CompiledFileInputStream::data() {
- if (!mPbFile) {
- if (!CompiledFile()) {
- return nullptr;
- }
+ PackagePbDeserializer package_pb_deserializer(&value_pool, &source_pool,
+ &symbol_pool, source, diag);
+ for (const pb::Package& pb_package : pb_table.packages()) {
+ if (!package_pb_deserializer.DeserializeFromPb(pb_package, table.get())) {
+ return {};
}
- return mData;
+ }
+ return table;
}
-size_t CompiledFileInputStream::size() {
- if (!mPbFile) {
- if (!CompiledFile()) {
- return 0;
- }
+std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb(
+ const pb::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) {
+ std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>();
+
+ ResourceNameRef name_ref;
+
+ // Need to create an lvalue here so that nameRef can point to something real.
+ if (!ResourceUtils::ParseResourceName(pb_file.resource_name(), &name_ref)) {
+ diag->Error(DiagMessage(source)
+ << "invalid resource name in compiled file header: "
+ << pb_file.resource_name());
+ return {};
+ }
+ file->name = name_ref.ToResourceName();
+ file->source.path = pb_file.source_path();
+ DeserializeConfigDescriptionFromPb(pb_file.config(), &file->config);
+
+ for (const pb::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbols()) {
+ // Need to create an lvalue here so that nameRef can point to something
+ // real.
+ if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(),
+ &name_ref)) {
+ diag->Error(DiagMessage(source)
+ << "invalid resource name for exported symbol in "
+ "compiled file header: "
+ << pb_file.resource_name());
+ return {};
}
- return mSize;
+ file->exported_symbols.push_back(
+ SourcedResourceName{name_ref.ToResourceName(), pb_symbol.line_no()});
+ }
+ return file;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
index 5d1b72b0ebbd..0d0e46da4ec8 100644
--- a/tools/aapt2/proto/TableProtoSerializer.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -22,184 +22,196 @@
#include "proto/ProtoSerialize.h"
#include "util/BigBuffer.h"
+#include <android-base/logging.h>
+
+using google::protobuf::io::CodedOutputStream;
+using google::protobuf::io::CodedInputStream;
+using google::protobuf::io::ZeroCopyOutputStream;
+
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) {
+ public:
+ using RawValueVisitor::Visit;
+
+ /**
+ * Constructor to use when expecting to serialize any value.
+ */
+ PbSerializerVisitor(StringPool* source_pool, StringPool* symbol_pool,
+ pb::Value* out_pb_value)
+ : source_pool_(source_pool),
+ symbol_pool_(symbol_pool),
+ out_pb_value_(out_pb_value),
+ out_pb_item_(nullptr) {}
+
+ /**
+ * Constructor to use when expecting to serialize an Item.
+ */
+ PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool,
+ pb::Item* outPbItem)
+ : source_pool_(sourcePool),
+ symbol_pool_(symbolPool),
+ out_pb_value_(nullptr),
+ out_pb_item_(outPbItem) {}
+
+ void Visit(Reference* ref) override {
+ SerializeReferenceToPb(*ref, pb_item()->mutable_ref());
+ }
+
+ void Visit(String* str) override {
+ pb_item()->mutable_str()->set_idx(str->value.index());
+ }
+
+ void Visit(StyledString* str) override {
+ pb_item()->mutable_str()->set_idx(str->value.index());
+ }
+
+ void Visit(FileReference* file) override {
+ pb_item()->mutable_file()->set_path_idx(file->path.index());
+ }
+
+ void Visit(Id* id) override { pb_item()->mutable_id(); }
+
+ void Visit(RawString* raw_str) override {
+ pb_item()->mutable_raw_str()->set_idx(raw_str->value.index());
+ }
+
+ void Visit(BinaryPrimitive* prim) override {
+ android::Res_value val = {};
+ prim->Flatten(&val);
+
+ pb::Primitive* pb_prim = pb_item()->mutable_prim();
+ pb_prim->set_type(val.dataType);
+ pb_prim->set_data(val.data);
+ }
+
+ void VisitItem(Item* item) override { LOG(FATAL) << "unimplemented item"; }
+
+ void Visit(Attribute* attr) override {
+ pb::Attribute* pb_attr = pb_compound_value()->mutable_attr();
+ pb_attr->set_format_flags(attr->type_mask);
+ pb_attr->set_min_int(attr->min_int);
+ pb_attr->set_max_int(attr->max_int);
+
+ for (auto& symbol : attr->symbols) {
+ pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbols();
+ SerializeItemCommonToPb(symbol.symbol, pb_symbol);
+ SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name());
+ pb_symbol->set_value(symbol.value);
}
-
- void visit(Reference* ref) override {
- serializeReferenceToPb(*ref, getPbItem()->mutable_ref());
+ }
+
+ void Visit(Style* style) override {
+ pb::Style* pb_style = pb_compound_value()->mutable_style();
+ if (style->parent) {
+ SerializeReferenceToPb(style->parent.value(), pb_style->mutable_parent());
+ SerializeSourceToPb(style->parent.value().GetSource(), source_pool_,
+ pb_style->mutable_parent_source());
}
- void visit(String* str) override {
- getPbItem()->mutable_str()->set_idx(str->value.getIndex());
- }
+ for (Style::Entry& entry : style->entries) {
+ pb::Style_Entry* pb_entry = pb_style->add_entries();
+ SerializeReferenceToPb(entry.key, pb_entry->mutable_key());
- void visit(StyledString* str) override {
- getPbItem()->mutable_str()->set_idx(str->value.getIndex());
+ pb::Item* pb_item = pb_entry->mutable_item();
+ SerializeItemCommonToPb(entry.key, pb_entry);
+ PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_item);
+ entry.value->Accept(&sub_visitor);
}
-
- void visit(FileReference* file) override {
- getPbItem()->mutable_file()->set_path_idx(file->path.getIndex());
+ }
+
+ void Visit(Styleable* styleable) override {
+ pb::Styleable* pb_styleable = pb_compound_value()->mutable_styleable();
+ for (Reference& entry : styleable->entries) {
+ pb::Styleable_Entry* pb_entry = pb_styleable->add_entries();
+ SerializeItemCommonToPb(entry, pb_entry);
+ SerializeReferenceToPb(entry, pb_entry->mutable_attr());
}
-
- 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(Array* array) override {
+ pb::Array* pb_array = pb_compound_value()->mutable_array();
+ for (auto& value : array->items) {
+ pb::Array_Entry* pb_entry = pb_array->add_entries();
+ SerializeItemCommonToPb(*value, pb_entry);
+ PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_,
+ pb_entry->mutable_item());
+ value->Accept(&sub_visitor);
}
-
- 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(Plural* plural) override {
+ pb::Plural* pb_plural = pb_compound_value()->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* pb_entry = pb_plural->add_entries();
+ pb_entry->set_arity(SerializePluralEnumToPb(i));
+ pb::Item* pb_element = pb_entry->mutable_item();
+ SerializeItemCommonToPb(*plural->values[i], pb_entry);
+ PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_element);
+ plural->values[i]->Accept(&sub_visitor);
}
+ }
- 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());
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PbSerializerVisitor);
- pb::Item* pbItem = pbEntry->mutable_item();
- serializeItemCommonToPb(entry.key, pbEntry);
- PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbItem);
- entry.value->accept(&subVisitor);
- }
+ pb::Item* pb_item() {
+ if (out_pb_value_) {
+ return out_pb_value_->mutable_item();
}
-
- 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());
- }
+ return out_pb_item_;
+ }
+
+ pb::CompoundValue* pb_compound_value() {
+ CHECK(out_pb_value_ != nullptr);
+ return out_pb_value_->mutable_compound_value();
+ }
+
+ template <typename T>
+ void SerializeItemCommonToPb(const Item& item, T* pb_item) {
+ SerializeSourceToPb(item.GetSource(), source_pool_,
+ pb_item->mutable_source());
+ if (!item.GetComment().empty()) {
+ pb_item->set_comment(item.GetComment());
}
+ }
- 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 SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) {
+ if (ref.id) {
+ pb_ref->set_id(ref.id.value().id);
}
- 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);
- }
+ if (ref.name) {
+ StringPool::Ref symbol_ref =
+ symbol_pool_->MakeRef(ref.name.value().ToString());
+ pb_ref->set_symbol_idx(static_cast<uint32_t>(symbol_ref.index()));
}
-private:
- pb::Item* getPbItem() {
- if (mOutPbValue) {
- return mOutPbValue->mutable_item();
- }
- return mOutPbItem;
- }
+ pb_ref->set_private_(ref.private_reference);
+ pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type));
+ }
- 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;
+ StringPool* source_pool_;
+ StringPool* symbol_pool_;
+ pb::Value* out_pb_value_;
+ pb::Item* out_pb_item_;
};
-} // namespace
+} // 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 {
+std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {
+ // We must do this before writing the resources, since the string pool IDs may
+ // change.
+ table->string_pool.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;
@@ -207,116 +219,195 @@ std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table) {
if (diff < 0) return true;
if (diff > 0) return false;
return a.value < b.value;
- });
- table->stringPool.prune();
+ });
+ table->string_pool.Prune();
- std::unique_ptr<pb::ResourceTable> pbTable = util::make_unique<pb::ResourceTable>();
- serializeStringPoolToPb(table->stringPool, pbTable->mutable_string_pool());
+ auto pb_table = util::make_unique<pb::ResourceTable>();
+ SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool());
- StringPool sourcePool, symbolPool;
+ StringPool source_pool, symbol_pool;
- for (auto& package : table->packages) {
- pb::Package* pbPackage = pbTable->add_packages();
- if (package->id) {
- pbPackage->set_package_id(package->id.value());
+ for (auto& package : table->packages) {
+ pb::Package* pb_package = pb_table->add_packages();
+ if (package->id) {
+ pb_package->set_package_id(package->id.value());
+ }
+ pb_package->set_package_name(package->name);
+
+ for (auto& type : package->types) {
+ pb::Type* pb_type = pb_package->add_types();
+ if (type->id) {
+ pb_type->set_id(type->id.value());
+ }
+ pb_type->set_name(ToString(type->type).ToString());
+
+ for (auto& entry : type->entries) {
+ pb::Entry* pb_entry = pb_type->add_entries();
+ if (entry->id) {
+ pb_entry->set_id(entry->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);
- }
- }
+ pb_entry->set_name(entry->name);
+
+ // Write the SymbolStatus struct.
+ pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status();
+ pb_status->set_visibility(
+ SerializeVisibilityToPb(entry->symbol_status.state));
+ SerializeSourceToPb(entry->symbol_status.source, &source_pool,
+ pb_status->mutable_source());
+ pb_status->set_comment(entry->symbol_status.comment);
+
+ for (auto& config_value : entry->values) {
+ pb::ConfigValue* pb_config_value = pb_entry->add_config_values();
+ SerializeConfig(config_value->config,
+ pb_config_value->mutable_config());
+ if (!config_value->product.empty()) {
+ pb_config_value->mutable_config()->set_product(
+ config_value->product);
+ }
+
+ pb::Value* pb_value = pb_config_value->mutable_value();
+ SerializeSourceToPb(config_value->value->GetSource(), &source_pool,
+ pb_value->mutable_source());
+ if (!config_value->value->GetComment().empty()) {
+ pb_value->set_comment(config_value->value->GetComment());
+ }
+
+ if (config_value->value->IsWeak()) {
+ pb_value->set_weak(true);
+ }
+
+ PbSerializerVisitor visitor(&source_pool, &symbol_pool, pb_value);
+ config_value->value->Accept(&visitor);
}
+ }
}
+ }
- serializeStringPoolToPb(sourcePool, pbTable->mutable_source_pool());
- serializeStringPoolToPb(symbolPool, pbTable->mutable_symbol_pool());
- return pbTable;
+ SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool());
+ SerializeStringPoolToPb(symbol_pool, pb_table->mutable_symbol_pool());
+ return pb_table;
}
-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());
+std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb(
+ const ResourceFile& file) {
+ auto pb_file = util::make_unique<pb::CompiledFile>();
+ pb_file->set_resource_name(file.name.ToString());
+ pb_file->set_source_path(file.source.path);
+ SerializeConfig(file.config, pb_file->mutable_config());
+
+ for (const SourcedResourceName& exported : file.exported_symbols) {
+ pb::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbols();
+ pb_symbol->set_resource_name(exported.name.ToString());
+ pb_symbol->set_line_no(exported.line);
+ }
+ return pb_file;
+}
- 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(ZeroCopyOutputStream* out)
+ : out_(out) {}
+
+void CompiledFileOutputStream::EnsureAlignedWrite() {
+ const int padding = out_.ByteCount() % 4;
+ if (padding > 0) {
+ uint32_t zero = 0u;
+ out_.WriteRaw(&zero, padding);
+ }
}
-CompiledFileOutputStream::CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out,
- pb::CompiledFile* pbFile) :
- mOut(out), mPbFile(pbFile) {
+void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) {
+ EnsureAlignedWrite();
+ out_.WriteLittleEndian32(val);
}
-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();
+void CompiledFileOutputStream::WriteCompiledFile(
+ const pb::CompiledFile* compiled_file) {
+ EnsureAlignedWrite();
+ out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file->ByteSize()));
+ compiled_file->SerializeWithCachedSizes(&out_);
}
-bool CompiledFileOutputStream::Write(const void* data, int size) {
- if (!ensureFileWritten()) {
- return false;
- }
- mOut.WriteRaw(data, size);
- return !mOut.HadError();
+void CompiledFileOutputStream::WriteData(const BigBuffer* buffer) {
+ EnsureAlignedWrite();
+ out_.WriteLittleEndian64(static_cast<uint64_t>(buffer->size()));
+ for (const BigBuffer::Block& block : *buffer) {
+ out_.WriteRaw(block.buffer.get(), block.size);
+ }
+}
+
+void CompiledFileOutputStream::WriteData(const void* data, size_t len) {
+ EnsureAlignedWrite();
+ out_.WriteLittleEndian64(static_cast<uint64_t>(len));
+ out_.WriteRaw(data, len);
+}
+
+bool CompiledFileOutputStream::HadError() { return out_.HadError(); }
+
+CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size)
+ : in_(static_cast<const uint8_t*>(data), size) {}
+
+void CompiledFileInputStream::EnsureAlignedRead() {
+ const int padding = in_.CurrentPosition() % 4;
+ if (padding > 0) {
+ // Reads are always 4 byte aligned.
+ in_.Skip(padding);
+ }
+}
+
+bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) {
+ EnsureAlignedRead();
+ return in_.ReadLittleEndian32(out_val);
+}
+
+bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* out_val) {
+ EnsureAlignedRead();
+
+ google::protobuf::uint64 pb_size = 0u;
+ if (!in_.ReadLittleEndian64(&pb_size)) {
+ return false;
+ }
+
+ CodedInputStream::Limit l = in_.PushLimit(static_cast<int>(pb_size));
+
+ // Check that we haven't tried to read past the end.
+ if (static_cast<uint64_t>(in_.BytesUntilLimit()) != pb_size) {
+ in_.PopLimit(l);
+ in_.PushLimit(0);
+ return false;
+ }
+
+ if (!out_val->ParsePartialFromCodedStream(&in_)) {
+ in_.PopLimit(l);
+ in_.PushLimit(0);
+ return false;
+ }
+
+ in_.PopLimit(l);
+ return true;
}
-bool CompiledFileOutputStream::Finish() {
- return ensureFileWritten();
+bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset,
+ uint64_t* out_len) {
+ EnsureAlignedRead();
+
+ google::protobuf::uint64 pb_size = 0u;
+ if (!in_.ReadLittleEndian64(&pb_size)) {
+ return false;
+ }
+
+ // Check that we aren't trying to read past the end.
+ if (pb_size > static_cast<uint64_t>(in_.BytesUntilLimit())) {
+ in_.PushLimit(0);
+ return false;
+ }
+
+ uint64_t offset = static_cast<uint64_t>(in_.CurrentPosition());
+ if (!in_.Skip(pb_size)) {
+ return false;
+ }
+
+ *out_offset = offset;
+ *out_len = pb_size;
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
index dd995d858d77..fdd5197167ef 100644
--- a/tools/aapt2/proto/TableProtoSerializer_test.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp
@@ -14,161 +14,211 @@
* 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>
+#include "ResourceTable.h"
+#include "test/Test.h"
+
+using ::google::protobuf::io::StringOutputStream;
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());
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000),
+ "res/layout/main.xml")
+ .AddReference("com.app.a:layout/other", ResourceId(0x7f020001),
+ "com.app.a:layout/main")
+ .AddString("com.app.a:string/text", {}, "hi")
+ .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>())
+ .Build();
+
+ Symbol public_symbol;
+ public_symbol.state = SymbolState::kPublic;
+ ASSERT_TRUE(table->SetSymbolState(
+ test::ParseNameOrDie("com.app.a:layout/main"), ResourceId(0x7f020000),
+ public_symbol, context->GetDiagnostics()));
+
+ Id* id = test::GetValue<Id>(table.get(), "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->string_pool.MakeRef("one"));
+ ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"),
+ ConfigDescription{}, {}, std::move(plural),
+ context->GetDiagnostics()));
+
+ // Make a resource with different products.
+ ASSERT_TRUE(table->AddResource(
+ test::ParseNameOrDie("com.app.a:integer/one"),
+ test::ParseConfigOrDie("land"), {},
+ test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u),
+ context->GetDiagnostics()));
+ ASSERT_TRUE(table->AddResource(
+ test::ParseNameOrDie("com.app.a:integer/one"),
+ test::ParseConfigOrDie("land"), "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 expected_ref;
+ expected_ref.name = test::ParseNameOrDie("android:layout/main");
+ expected_ref.id = ResourceId(0x01020000);
+ ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:layout/abc"),
+ ConfigDescription::DefaultConfig(), {},
+ util::make_unique<Reference>(expected_ref),
+ context->GetDiagnostics()));
+
+ std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table.get());
+ ASSERT_NE(nullptr, pb_table);
+
+ std::unique_ptr<ResourceTable> new_table = DeserializeTableFromPb(
+ *pb_table, Source{"test"}, context->GetDiagnostics());
+ ASSERT_NE(nullptr, new_table);
+
+ Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo");
+ ASSERT_NE(nullptr, new_id);
+ EXPECT_EQ(id->IsWeak(), new_id->IsWeak());
+
+ Maybe<ResourceTable::SearchResult> result =
+ new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
+ AAPT_ASSERT_TRUE(result);
+ EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state);
+ EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state);
+
+ // Find the product-dependent values
+ BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"),
+ "");
+ ASSERT_NE(nullptr, prim);
+ EXPECT_EQ(123u, prim->value.data);
+
+ prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"),
+ "tablet");
+ ASSERT_NE(nullptr, prim);
+ EXPECT_EQ(321u, prim->value.data);
+
+ Reference* actual_ref =
+ test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc");
+ ASSERT_NE(nullptr, actual_ref);
+ AAPT_ASSERT_TRUE(actual_ref->name);
+ AAPT_ASSERT_TRUE(actual_ref->id);
+ EXPECT_EQ(expected_ref.name.value(), actual_ref->name.value());
+ EXPECT_EQ(expected_ref.id.value(), actual_ref->id.value());
}
TEST(TableProtoSerializer, SerializeFileHeader) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ ResourceFile f;
+ f.config = test::ParseConfigOrDie("hdpi-v9");
+ f.name = test::ParseNameOrDie("com.app.a:layout/main");
+ f.source.path = "res/layout-hdpi-v9/main.xml";
+ f.exported_symbols.push_back(
+ SourcedResourceName{test::ParseNameOrDie("id/unchecked"), 23u});
+
+ const std::string expected_data1 = "123";
+ const std::string expected_data2 = "1234";
+
+ std::string output_str;
+ {
+ std::unique_ptr<pb::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f);
+
+ f.name.entry = "__" + f.name.entry + "$0";
+ std::unique_ptr<pb::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f);
+
+ StringOutputStream out_stream(&output_str);
+ CompiledFileOutputStream out_file_stream(&out_stream);
+ out_file_stream.WriteLittleEndian32(2);
+ out_file_stream.WriteCompiledFile(pb_file1.get());
+ out_file_stream.WriteData(expected_data1.data(), expected_data1.size());
+ out_file_stream.WriteCompiledFile(pb_file2.get());
+ out_file_stream.WriteData(expected_data2.data(), expected_data2.size());
+ ASSERT_FALSE(out_file_stream.HadError());
+ }
- 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 });
+ CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
+ uint32_t num_files = 0;
+ ASSERT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
+ ASSERT_EQ(2u, num_files);
- const std::string expectedData = "1234";
+ // Read the first compiled file.
- std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f);
+ pb::CompiledFile new_pb_file;
+ ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file));
- std::string outputStr;
- {
- google::protobuf::io::StringOutputStream outStream(&outputStr);
- CompiledFileOutputStream outFileStream(&outStream, pbFile.get());
+ std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb(
+ new_pb_file, Source("test"), context->GetDiagnostics());
+ ASSERT_NE(nullptr, file);
- ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size()));
- ASSERT_TRUE(outFileStream.Finish());
- }
+ uint64_t offset, len;
+ ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
- CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
- const pb::CompiledFile* newPbFile = inFileStream.CompiledFile();
- ASSERT_NE(nullptr, newPbFile);
+ std::string actual_data(output_str.data() + offset, len);
+ EXPECT_EQ(expected_data1, actual_data);
- std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(*newPbFile, Source{ "test" },
- context->getDiagnostics());
- ASSERT_NE(nullptr, file);
+ // Expect the data to be aligned.
+ EXPECT_EQ(0u, offset & 0x03);
- 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->exported_symbols.size());
+ EXPECT_EQ(test::ParseNameOrDie("id/unchecked"),
+ file->exported_symbols[0].name);
- ASSERT_EQ(1u, file->exportedSymbols.size());
- EXPECT_EQ(test::parseNameOrDie(u"@+id/unchecked"), file->exportedSymbols[0].name);
+ // Read the second compiled file.
+
+ ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file));
+
+ file = DeserializeCompiledFileFromPb(new_pb_file, Source("test"),
+ context->GetDiagnostics());
+ ASSERT_NE(nullptr, file);
+
+ ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
+
+ actual_data = std::string(output_str.data() + offset, len);
+ EXPECT_EQ(expected_data2, actual_data);
+
+ // Expect the data to be aligned.
+ EXPECT_EQ(0u, offset & 0x03);
}
TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) {
- ResourceFile f;
- std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f);
+ ResourceFile f;
+ std::unique_ptr<pb::CompiledFile> pb_file = SerializeCompiledFileToPb(f);
+
+ const std::string expected_data = "1234";
+
+ std::string output_str;
+ {
+ StringOutputStream out_stream(&output_str);
+ CompiledFileOutputStream out_file_stream(&out_stream);
+ out_file_stream.WriteLittleEndian32(1);
+ out_file_stream.WriteCompiledFile(pb_file.get());
+ out_file_stream.WriteData(expected_data.data(), expected_data.size());
+ ASSERT_FALSE(out_file_stream.HadError());
+ }
- const std::string expectedData = "1234";
+ output_str[4] = 0xff;
- std::string outputStr;
- {
- google::protobuf::io::StringOutputStream outStream(&outputStr);
- CompiledFileOutputStream outFileStream(&outStream, pbFile.get());
+ CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
- ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size()));
- ASSERT_TRUE(outFileStream.Finish());
- }
+ uint32_t num_files = 0;
+ EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
+ EXPECT_EQ(1u, num_files);
- outputStr[0] = 0xff;
+ pb::CompiledFile new_pb_file;
+ EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file));
- CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
- EXPECT_EQ(nullptr, inFileStream.CompiledFile());
- EXPECT_EQ(nullptr, inFileStream.data());
- EXPECT_EQ(0u, inFileStream.size());
+ uint64_t offset, len;
+ EXPECT_FALSE(in_file_stream.ReadDataMetaData(&offset, &len));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
new file mode 100644
index 000000000000..800103307e2b
--- /dev/null
+++ b/tools/aapt2/readme.md
@@ -0,0 +1,44 @@
+# Android Asset Packaging Tool 2.0 (AAPT2) release notes
+
+## Version 2.4
+### `aapt2 link ...`
+- Supports `<meta-data>` tags in `<manifest>`.
+
+## Version 2.3
+### `aapt2`
+- Support new `font` resource type.
+
+## Version 2.2
+### `aapt2 compile ...`
+- Added support for inline complex XML resources. See
+ https://developer.android.com/guide/topics/resources/complex-xml-resources.html
+### `aapt link ...`
+- Duplicate resource filtering: removes duplicate resources in dominated configurations
+ that are always identical when selected at runtime. This can be disabled with
+ `--no-resource-deduping`.
+
+## Version 2.1
+### `aapt2 link ...`
+- Configuration Split APK support: supports splitting resources that match a set of
+ configurations to a separate APK which can be loaded alongside the base APK on
+ API 21+ devices. This is done using the flag
+ `--split path/to/split.apk:<config1>[,<config2>,...]`.
+- SDK version resource filtering: Resources with an SDK version qualifier that is unreachable
+ at runtime due to the minimum SDK level declared by the AndroidManifest.xml are stripped.
+
+## Version 2.0
+### `aapt2 compile ...`
+- Pseudo-localization: generates pseudolocalized versions of default strings when the
+ `--pseudo-localize` option is specified.
+- Legacy mode: treats some class of errors as warnings in order to be more compatible
+ with AAPT when `--legacy` is specified.
+- Compile directory: treats the input file as a directory when `--dir` is
+ specified. This will emit a zip of compiled files, one for each file in the directory.
+ The directory must follow the Android resource directory structure
+ (res/values-[qualifiers]/file.ext).
+
+### `aapt2 link ...`
+- Automatic attribute versioning: adds version qualifiers to resources that use attributes
+ introduced in a later SDK level. This can be disabled with `--no-auto-version`.
+- Min SDK resource filtering: removes resources that can't possibly be selected at runtime due
+ to the application's minimum supported SDK level.
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 4bfdb1205e19..7aad86fa7257 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#include "ConfigDescription.h"
-#include "ResourceTable.h"
#include "split/TableSplitter.h"
#include <algorithm>
@@ -23,243 +21,271 @@
#include <set>
#include <unordered_map>
#include <vector>
+#include "android-base/logging.h"
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "util/Util.h"
namespace aapt {
using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
-using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
+using ConfigDensityGroups =
+ std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
-static ConfigDescription copyWithoutDensity(const ConfigDescription& config) {
- ConfigDescription withoutDensity = config;
- withoutDensity.density = 0;
- return withoutDensity;
+static ConfigDescription CopyWithoutDensity(const ConfigDescription& config) {
+ ConfigDescription without_density = config;
+ without_density.density = 0;
+ return without_density;
}
/**
* 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;
- }
- }
+ public:
+ explicit SplitValueSelector(const SplitConstraints& constraints) {
+ for (const ConfigDescription& config : constraints.configs) {
+ if (config.density == 0) {
+ density_independent_configs_.insert(config);
+ } else {
+ density_dependent_config_to_density_map_[CopyWithoutDensity(config)] =
+ config.density;
+ }
}
+ }
- std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups,
- ConfigClaimedMap* claimedValues) {
- std::vector<ResourceConfigValue*> selected;
+ std::vector<ResourceConfigValue*> SelectValues(
+ const ConfigDensityGroups& density_groups,
+ ConfigClaimedMap* claimed_values) {
+ 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);
+ // Select the regular values.
+ for (auto& entry : *claimed_values) {
+ // Check if the entry has a density.
+ ResourceConfigValue* config_value = entry.first;
+ if (config_value->config.density == 0 && !entry.second) {
+ // This is still available.
+ if (density_independent_configs_.find(config_value->config) !=
+ density_independent_configs_.end()) {
+ selected.push_back(config_value);
- // Mark the entry as taken.
- entry.second = true;
- }
- }
+ // 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;
- }
+ // Now examine the densities
+ for (auto& entry : density_groups) {
+ // 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*>& related_values = entry.second;
+ auto density_value_iter =
+ density_dependent_config_to_density_map_.find(config);
+ if (density_value_iter !=
+ density_dependent_config_to_density_map_.end()) {
+ // Select the best one!
+ ConfigDescription target_density = config;
+ target_density.density = density_value_iter->second;
- // 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);
- }
+ ResourceConfigValue* best_value = nullptr;
+ for (ResourceConfigValue* this_value : related_values) {
+ if (!best_value ||
+ this_value->config.isBetterThan(best_value->config,
+ &target_density)) {
+ best_value = this_value;
+ }
}
- return selected;
+ CHECK(best_value != nullptr);
+
+ // When we select one of these, they are all claimed such that the base
+ // doesn't include any anymore.
+ (*claimed_values)[best_value] = true;
+ selected.push_back(best_value);
+ }
}
+ return selected;
+ }
-private:
- std::set<ConfigDescription> mDensityIndependentConfigs;
- std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SplitValueSelector);
+
+ std::set<ConfigDescription> density_independent_configs_;
+ std::map<ConfigDescription, uint16_t>
+ density_dependent_config_to_density_map_;
};
/**
- * Marking non-preferred densities as claimed will make sure the base doesn't include them,
+ * 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;
+static void MarkNonPreferredDensitiesAsClaimed(
+ uint16_t preferred_density, const ConfigDensityGroups& density_groups,
+ ConfigClaimedMap* config_claimed_map) {
+ for (auto& entry : density_groups) {
+ const ConfigDescription& config = entry.first;
+ const std::vector<ResourceConfigValue*>& related_values = 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);
+ ConfigDescription target_density = config;
+ target_density.density = preferred_density;
+ ResourceConfigValue* best_value = nullptr;
+ for (ResourceConfigValue* this_value : related_values) {
+ if (!best_value) {
+ best_value = this_value;
+ } else if (this_value->config.isBetterThan(best_value->config,
+ &target_density)) {
+ // Claim the previous value so that it is not included in the base.
+ (*config_claimed_map)[best_value] = true;
+ best_value = this_value;
+ } else {
+ // Claim this value so that it is not included in the base.
+ (*config_claimed_map)[this_value] = true;
+ }
}
+ CHECK(best_value != nullptr);
+ }
}
-
-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;
- }
- }
+bool TableSplitter::VerifySplitConstraints(IAaptContext* context) {
+ bool error = false;
+ for (size_t i = 0; i < split_constraints_.size(); i++) {
+ for (size_t j = i + 1; j < split_constraints_.size(); j++) {
+ for (const ConfigDescription& config : split_constraints_[i].configs) {
+ if (split_constraints_[j].configs.find(config) !=
+ split_constraints_[j].configs.end()) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "config '" << config
+ << "' appears in multiple splits, "
+ << "target split ambiguous");
+ error = true;
}
+ }
}
- return !error;
+ }
+ 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;
- }
+void TableSplitter::SplitTable(ResourceTable* original_table) {
+ const size_t split_count = split_constraints_.size();
+ for (auto& pkg : original_table->packages) {
+ // Initialize all packages for splits.
+ for (size_t idx = 0; idx < split_count; idx++) {
+ ResourceTable* split_table = splits_[idx].get();
+ split_table->CreatePackage(pkg->name, pkg->id);
+ }
- 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();
- }
- }
- }
+ for (auto& type : pkg->types) {
+ if (type->type == ResourceType::kMipmap) {
+ // Always keep mipmaps.
+ continue;
+ }
- // 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;
+ for (auto& entry : type->entries) {
+ if (options_.config_filter) {
+ // First eliminate any resource that we definitely don't want.
+ for (std::unique_ptr<ResourceConfigValue>& config_value :
+ entry->values) {
+ if (!options_.config_filter->Match(config_value->config)) {
+ // null out the entry. We will clean up and remove nulls at the
+ // end for performance reasons.
+ config_value.reset();
+ }
+ }
+ }
- if (configValue->config.density != 0) {
- // Create a bucket for this density-dependent config.
- densityGroups[copyWithoutDensity(configValue->config)]
- .push_back(configValue.get());
- }
- }
- }
+ // 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 density_groups;
+ ConfigClaimedMap config_claimed_map;
+ for (const std::unique_ptr<ResourceConfigValue>& config_value :
+ entry->values) {
+ if (config_value) {
+ config_claimed_map[config_value.get()] = false;
- // 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();
+ if (config_value->config.density != 0) {
+ // Create a bucket for this density-dependent config.
+ density_groups[CopyWithoutDensity(config_value->config)]
+ .push_back(config_value.get());
+ }
+ }
+ }
- // Select the values we want from this entry for this split.
- SplitValueSelector selector(splitConstraint);
- std::vector<ResourceConfigValue*> selectedValues =
- selector.selectValues(densityGroups, &configClaimedMap);
+ // 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 < split_count; idx++) {
+ const SplitConstraints& split_constraint = split_constraints_[idx];
+ ResourceTable* split_table = splits_[idx].get();
- // 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;
- }
+ // Select the values we want from this entry for this split.
+ SplitValueSelector selector(split_constraint);
+ std::vector<ResourceConfigValue*> selected_values =
+ selector.SelectValues(density_groups, &config_claimed_map);
- ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name);
- if (!splitEntry->id) {
- splitEntry->id = entry->id;
- splitEntry->symbolStatus = entry->symbolStatus;
- }
+ // No need to do any work if we selected nothing.
+ if (!selected_values.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* split_pkg =
+ split_table->FindPackage(pkg->name);
+ ResourceTableType* split_type =
+ split_pkg->FindOrCreateType(type->type);
+ if (!split_type->id) {
+ split_type->id = type->id;
+ split_type->symbol_status = type->symbol_status;
+ }
- // 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));
- }
- }
- }
+ ResourceEntry* split_entry =
+ split_type->FindOrCreateEntry(entry->name);
+ if (!split_entry->id) {
+ split_entry->id = entry->id;
+ split_entry->symbol_status = entry->symbol_status;
+ }
- if (mPreferredDensity) {
- markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(),
- densityGroups,
- &configClaimedMap);
- }
+ // Copy the selected values into the new Split Entry.
+ for (ResourceConfigValue* config_value : selected_values) {
+ ResourceConfigValue* new_config_value =
+ split_entry->FindOrCreateValue(config_value->config,
+ config_value->product);
+ new_config_value->value = std::unique_ptr<Value>(
+ config_value->value->Clone(&split_table->string_pool));
+ }
+ }
+ }
- // 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();
- }
- }
+ if (options_.preferred_density) {
+ MarkNonPreferredDensitiesAsClaimed(options_.preferred_density.value(),
+ density_groups,
+ &config_claimed_map);
+ }
- // Now erase all nullptrs.
- entry->values.erase(
- std::remove(entry->values.begin(), entry->values.end(), nullptr),
- entry->values.end());
- }
+ // All splits are handled, now check to see what wasn't claimed and
+ // remove
+ // whatever exists in other splits.
+ for (std::unique_ptr<ResourceConfigValue>& config_value :
+ entry->values) {
+ if (config_value && config_claimed_map[config_value.get()]) {
+ // Claimed, remove from base.
+ config_value.reset();
+ }
}
+
+ // Now erase all nullptrs.
+ entry->values.erase(
+ std::remove(entry->values.begin(), entry->values.end(), nullptr),
+ entry->values.end());
+ }
}
+ }
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h
index 15e0764c4259..1ae327120796 100644
--- a/tools/aapt2/split/TableSplitter.h
+++ b/tools/aapt2/split/TableSplitter.h
@@ -17,62 +17,58 @@
#ifndef AAPT_SPLIT_TABLESPLITTER_H
#define AAPT_SPLIT_TABLESPLITTER_H
+#include <set>
+#include <vector>
+#include "android-base/macros.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;
+ std::set<ConfigDescription> configs;
};
struct TableSplitterOptions {
- /**
- * The preferred density to keep in the table, stripping out all others.
- */
- Maybe<uint16_t> preferredDensity;
+ /**
+ * The preferred density to keep in the table, stripping out all others.
+ */
+ Maybe<uint16_t> preferred_density;
- /**
- * Configuration filter that determines which resource configuration values end up in
- * the final table.
- */
- IConfigFilter* configFilter = nullptr;
+ /**
+ * Configuration filter that determines which resource configuration values
+ * end up in
+ * the final table.
+ */
+ IConfigFilter* config_filter = 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>());
- }
+ public:
+ TableSplitter(const std::vector<SplitConstraints>& splits,
+ const TableSplitterOptions& options)
+ : split_constraints_(splits), options_(options) {
+ for (size_t i = 0; i < split_constraints_.size(); i++) {
+ splits_.push_back(util::make_unique<ResourceTable>());
}
+ }
- bool verifySplitConstraints(IAaptContext* context);
+ bool VerifySplitConstraints(IAaptContext* context);
- void splitTable(ResourceTable* originalTable);
+ void SplitTable(ResourceTable* original_table);
- const std::vector<std::unique_ptr<ResourceTable>>& getSplits() {
- return mSplits;
- }
+ std::vector<std::unique_ptr<ResourceTable>>& splits() { return splits_; }
-private:
- std::vector<SplitConstraints> mSplitConstraints;
- std::vector<std::unique_ptr<ResourceTable>> mSplits;
- Maybe<uint16_t> mPreferredDensity;
- IConfigFilter* mConfigFilter;
+ private:
+ std::vector<SplitConstraints> split_constraints_;
+ std::vector<std::unique_ptr<ResourceTable>> splits_;
+ TableSplitterOptions options_;
- DISALLOW_COPY_AND_ASSIGN(TableSplitter);
+ 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
index 74ca32e04a30..088dac374458 100644
--- a/tools/aapt2/split/TableSplitter_test.cpp
+++ b/tools/aapt2/split/TableSplitter_test.cpp
@@ -15,93 +15,192 @@
*/
#include "split/TableSplitter.h"
-#include "test/Builders.h"
-#include "test/Common.h"
-#include <gtest/gtest.h>
+#include "test/Test.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"));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:drawable/icon",
+ "res/drawable-mdpi/icon.png",
+ test::ParseConfigOrDie("mdpi"))
+ .AddFileReference("android:drawable/icon",
+ "res/drawable-hdpi/icon.png",
+ test::ParseConfigOrDie("hdpi"))
+ .AddFileReference("android:drawable/icon",
+ "res/drawable-xhdpi/icon.png",
+ test::ParseConfigOrDie("xhdpi"))
+ .AddFileReference("android:drawable/icon",
+ "res/drawable-xxhdpi/icon.png",
+ test::ParseConfigOrDie("xxhdpi"))
+ .AddSimple("android:string/one")
+ .Build();
+
+ TableSplitterOptions options;
+ options.preferred_density = ConfigDescription::DENSITY_XHIGH;
+ TableSplitter splitter({}, options);
+ splitter.SplitTable(table.get());
+
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/icon",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/icon",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/icon",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/icon",
+ test::ParseConfigOrDie("xxhdpi")));
+ EXPECT_NE(nullptr, test::GetValue<Id>(table.get(), "android:string/one"));
+}
+
+TEST(TableSplitterTest, SplitTableByDensity) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:drawable/foo", "res/drawable-mdpi/foo.png",
+ test::ParseConfigOrDie("mdpi"))
+ .AddFileReference("android:drawable/foo", "res/drawable-hdpi/foo.png",
+ test::ParseConfigOrDie("hdpi"))
+ .AddFileReference("android:drawable/foo",
+ "res/drawable-xhdpi/foo.png",
+ test::ParseConfigOrDie("xhdpi"))
+ .AddFileReference("android:drawable/foo",
+ "res/drawable-xxhdpi/foo.png",
+ test::ParseConfigOrDie("xxhdpi"))
+ .Build();
+
+ std::vector<SplitConstraints> constraints;
+ constraints.push_back(SplitConstraints{{test::ParseConfigOrDie("mdpi")}});
+ constraints.push_back(SplitConstraints{{test::ParseConfigOrDie("hdpi")}});
+ constraints.push_back(SplitConstraints{{test::ParseConfigOrDie("xhdpi")}});
+
+ TableSplitter splitter(constraints, TableSplitterOptions{});
+ splitter.SplitTable(table.get());
+
+ ASSERT_EQ(3u, splitter.splits().size());
+
+ ResourceTable* split_one = splitter.splits()[0].get();
+ ResourceTable* split_two = splitter.splits()[1].get();
+ ResourceTable* split_three = splitter.splits()[2].get();
+
+ // Just xxhdpi should be in the base.
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/foo",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/foo",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/foo",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/foo",
+ test::ParseConfigOrDie("xxhdpi")));
+
+ // Each split should have one and only one drawable.
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ split_one, "android:drawable/foo",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_one, "android:drawable/foo",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_one, "android:drawable/foo",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_one, "android:drawable/foo",
+ test::ParseConfigOrDie("xxhdpi")));
+
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_two, "android:drawable/foo",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ split_two, "android:drawable/foo",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_two, "android:drawable/foo",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_two, "android:drawable/foo",
+ test::ParseConfigOrDie("xxhdpi")));
+
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_three, "android:drawable/foo",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_three, "android:drawable/foo",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ split_three, "android:drawable/foo",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_three, "android:drawable/foo",
+ test::ParseConfigOrDie("xxhdpi")));
}
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")));
+ ResourceTable table;
+
+ const ResourceName foo = test::ParseNameOrDie("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.splits().size());
+
+ ResourceTable* split_one = splitter.splits()[0].get();
+ ResourceTable* split_two = splitter.splits()[1].get();
+
+ // All but the xxhdpi resource should be gone, since there were closer matches
+ // in land-xhdpi.
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(&table, "android:string/foo",
+ test::ParseConfigOrDie("land-hdpi")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(&table, "android:string/foo",
+ test::ParseConfigOrDie("land-xhdpi")));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(&table, "android:string/foo",
+ test::ParseConfigOrDie("land-xxhdpi")));
+
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(split_one, "android:string/foo",
+ test::ParseConfigOrDie("land-hdpi")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(split_one, "android:string/foo",
+ test::ParseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(split_one, "android:string/foo",
+ test::ParseConfigOrDie("land-xxhdpi")));
+
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(split_two, "android:string/foo",
+ test::ParseConfigOrDie("land-hdpi")));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(split_two, "android:string/foo",
+ test::ParseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(split_two, "android:string/foo",
+ test::ParseConfigOrDie("land-xxhdpi")));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 8eb4bc88168d..9377306dc1b6 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -17,240 +17,269 @@
#ifndef AAPT_TEST_BUILDERS_H
#define AAPT_TEST_BUILDERS_H
+#include <memory>
+
+#include "android-base/logging.h"
+#include "android-base/macros.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);
- }
+ public:
+ ResourceTableBuilder() = default;
+
+ StringPool* string_pool() { return &table_->string_pool; }
+
+ ResourceTableBuilder& SetPackageId(const StringPiece& package_name,
+ uint8_t id) {
+ ResourceTablePackage* package = table_->CreatePackage(package_name, id);
+ CHECK(package != nullptr);
+ return *this;
+ }
+
+ ResourceTableBuilder& AddSimple(const StringPiece& name,
+ const ResourceId& id = {}) {
+ return AddValue(name, id, util::make_unique<Id>());
+ }
+
+ ResourceTableBuilder& AddSimple(const StringPiece& name,
+ const ConfigDescription& config,
+ const ResourceId& id = {}) {
+ return AddValue(name, config, id, util::make_unique<Id>());
+ }
+
+ ResourceTableBuilder& AddReference(const StringPiece& name,
+ const StringPiece& ref) {
+ return AddReference(name, {}, ref);
+ }
+
+ ResourceTableBuilder& AddReference(const StringPiece& name,
+ const ResourceId& id,
+ const StringPiece& ref) {
+ return AddValue(name, id,
+ util::make_unique<Reference>(ParseNameOrDie(ref)));
+ }
+
+ ResourceTableBuilder& AddString(const StringPiece& name,
+ const StringPiece& str) {
+ return AddString(name, {}, str);
+ }
+
+ ResourceTableBuilder& AddString(const StringPiece& name, const ResourceId& id,
+ const StringPiece& str) {
+ return AddValue(
+ name, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
+ }
+
+ ResourceTableBuilder& AddString(const StringPiece& name, const ResourceId& id,
+ const ConfigDescription& config,
+ const StringPiece& str) {
+ return AddValue(name, config, id, util::make_unique<String>(
+ table_->string_pool.MakeRef(str)));
+ }
+
+ ResourceTableBuilder& AddFileReference(const StringPiece& name,
+ const StringPiece& path) {
+ return AddFileReference(name, {}, path);
+ }
+
+ ResourceTableBuilder& AddFileReference(const StringPiece& name,
+ const ResourceId& id,
+ const StringPiece& path) {
+ return AddValue(name, id, util::make_unique<FileReference>(
+ table_->string_pool.MakeRef(path)));
+ }
+
+ ResourceTableBuilder& AddFileReference(const StringPiece& name,
+ const StringPiece& path,
+ const ConfigDescription& config) {
+ return AddValue(name, config, {}, util::make_unique<FileReference>(
+ table_->string_pool.MakeRef(path)));
+ }
+
+ ResourceTableBuilder& AddValue(const StringPiece& name,
+ std::unique_ptr<Value> value) {
+ return AddValue(name, {}, std::move(value));
+ }
+
+ ResourceTableBuilder& AddValue(const StringPiece& name, const ResourceId& id,
+ std::unique_ptr<Value> value) {
+ return AddValue(name, {}, id, std::move(value));
+ }
+
+ ResourceTableBuilder& AddValue(const StringPiece& name,
+ const ConfigDescription& config,
+ const ResourceId& id,
+ std::unique_ptr<Value> value) {
+ ResourceName res_name = ParseNameOrDie(name);
+ CHECK(table_->AddResourceAllowMangled(res_name, id, config, {},
+ std::move(value), &diagnostics_));
+ return *this;
+ }
+
+ ResourceTableBuilder& SetSymbolState(const StringPiece& name,
+ const ResourceId& id,
+ SymbolState state) {
+ ResourceName res_name = ParseNameOrDie(name);
+ Symbol symbol;
+ symbol.state = state;
+ CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol,
+ &diagnostics_));
+ return *this;
+ }
+
+ std::unique_ptr<ResourceTable> Build() { return std::move(table_); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceTableBuilder);
+
+ DummyDiagnosticsImpl diagnostics_;
+ std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();
};
-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<Reference> BuildReference(
+ const StringPiece& ref, const 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);
+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);
- }
+ public:
+ template <typename... Args>
+ explicit ValueBuilder(Args&&... args)
+ : value_(new T{std::forward<Args>(args)...}) {}
+
+ template <typename... Args>
+ ValueBuilder& SetSource(Args&&... args) {
+ value_->SetSource(Source{std::forward<Args>(args)...});
+ return *this;
+ }
+
+ ValueBuilder& SetComment(const StringPiece& str) {
+ value_->SetComment(str);
+ return *this;
+ }
+
+ std::unique_ptr<Value> Build() { return std::move(value_); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ValueBuilder);
+
+ std::unique_ptr<Value> value_;
};
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);
- }
+ public:
+ explicit AttributeBuilder(bool weak = false)
+ : attr_(util::make_unique<Attribute>(weak)) {
+ attr_->type_mask = android::ResTable_map::TYPE_ANY;
+ }
+
+ AttributeBuilder& SetTypeMask(uint32_t typeMask) {
+ attr_->type_mask = typeMask;
+ return *this;
+ }
+
+ AttributeBuilder& AddItem(const StringPiece& name, uint32_t value) {
+ attr_->symbols.push_back(Attribute::Symbol{
+ Reference(ResourceName({}, ResourceType::kId, name)), value});
+ return *this;
+ }
+
+ std::unique_ptr<Attribute> Build() { return std::move(attr_); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AttributeBuilder);
+
+ std::unique_ptr<Attribute> attr_;
};
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);
- }
+ public:
+ StyleBuilder() = default;
+
+ StyleBuilder& SetParent(const StringPiece& str) {
+ style_->parent = Reference(ParseNameOrDie(str));
+ return *this;
+ }
+
+ StyleBuilder& AddItem(const StringPiece& str, std::unique_ptr<Item> value) {
+ style_->entries.push_back(
+ Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)});
+ return *this;
+ }
+
+ StyleBuilder& AddItem(const StringPiece& str, const ResourceId& id,
+ std::unique_ptr<Item> value) {
+ AddItem(str, std::move(value));
+ style_->entries.back().key.id = id;
+ return *this;
+ }
+
+ std::unique_ptr<Style> Build() { return std::move(style_); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StyleBuilder);
+
+ std::unique_ptr<Style> style_ = util::make_unique<Style>();
};
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);
- }
+ public:
+ StyleableBuilder() = default;
+
+ StyleableBuilder& AddItem(const StringPiece& str,
+ const Maybe<ResourceId>& id = {}) {
+ styleable_->entries.push_back(Reference(ParseNameOrDie(str)));
+ styleable_->entries.back().id = id;
+ return *this;
+ }
+
+ std::unique_ptr<Styleable> Build() { return std::move(styleable_); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StyleableBuilder);
+
+ std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>();
};
-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> 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"));
+ CHECK(doc != nullptr) << "failed to parse inline XML string";
+ 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;
+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
+} // namespace test
+} // namespace aapt
#endif /* AAPT_TEST_BUILDERS_H */
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index faccd47783ff..36892017f2d3 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -17,6 +17,12 @@
#ifndef AAPT_TEST_COMMON_H
#define AAPT_TEST_COMMON_H
+#include <iostream>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "gtest/gtest.h"
+
#include "ConfigDescription.h"
#include "Debug.h"
#include "ResourceTable.h"
@@ -26,11 +32,9 @@
#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.
+// 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))
@@ -41,81 +45,84 @@ 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;
- }
+ void Log(Level level, DiagMessageActual& actual_msg) override {
+ switch (level) {
+ case Level::Note:
+ return;
+
+ case Level::Warn:
+ std::cerr << actual_msg.source << ": warn: " << actual_msg.message
+ << "." << std::endl;
+ break;
+
+ case Level::Error:
+ std::cerr << actual_msg.source << ": error: " << actual_msg.message
+ << "." << std::endl;
+ break;
}
+ }
};
-inline IDiagnostics* getDiagnostics() {
- static DummyDiagnosticsImpl diag;
- return &diag;
+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 ResourceName ParseNameOrDie(const StringPiece& str) {
+ ResourceNameRef ref;
+ CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "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;
+inline ConfigDescription ParseConfigOrDie(const StringPiece& str) {
+ ConfigDescription config;
+ CHECK(ConfigDescription::Parse(str, &config)) << "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());
- }
+template <typename T>
+T* GetValueForConfigAndProduct(ResourceTable* table,
+ const StringPiece& res_name,
+ const ConfigDescription& config,
+ const StringPiece& product) {
+ Maybe<ResourceTable::SearchResult> result =
+ table->FindResource(ParseNameOrDie(res_name));
+ if (result) {
+ ResourceConfigValue* config_value =
+ result.value().entry->FindValue(config, product);
+ if (config_value) {
+ return ValueCast<T>(config_value->value.get());
}
- return nullptr;
+ }
+ 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* GetValueForConfig(ResourceTable* table, const StringPiece& res_name,
+ const ConfigDescription& config) {
+ return GetValueForConfigAndProduct<T>(table, res_name, config, {});
}
-template <typename T> T* getValue(ResourceTable* table, const StringPiece16& resName) {
- return getValueForConfig<T>(table, resName, {});
+template <typename T>
+T* GetValue(ResourceTable* table, const StringPiece& res_name) {
+ return GetValueForConfig<T>(table, res_name, {});
}
class TestFile : public io::IFile {
-private:
- Source mSource;
+ public:
+ explicit TestFile(const StringPiece& path) : source_(path) {}
-public:
- TestFile(const StringPiece& path) : mSource(path) {}
+ std::unique_ptr<io::IData> OpenAsData() override { return {}; }
- std::unique_ptr<io::IData> openAsData() override {
- return {};
- }
+ const Source& GetSource() const override { return source_; }
- const Source& getSource() const override {
- return mSource;
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestFile);
+
+ Source source_;
};
-} // namespace test
-} // namespace aapt
+} // namespace test
+} // namespace aapt
#endif /* AAPT_TEST_COMMON_H */
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 96752d33dd9a..7986329ab640 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -17,159 +17,162 @@
#ifndef AAPT_TEST_CONTEXT_H
#define AAPT_TEST_CONTEXT_H
-#include "NameMangler.h"
-#include "util/Util.h"
+#include <list>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "NameMangler.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "test/Common.h"
-
-#include <cassert>
-#include <list>
+#include "util/Util.h"
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();
- }
+ public:
+ Context() = default;
- NameMangler* getNameMangler() override {
- return &mNameMangler;
- }
+ SymbolTable* GetExternalSymbols() override { return &symbols_; }
- bool verbose() override {
- return false;
- }
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
-private:
- friend class ContextBuilder;
+ const std::string& GetCompilationPackage() override {
+ CHECK(bool(compilation_package_)) << "package name not set";
+ return compilation_package_.value();
+ }
- Context() : mNameMangler({}) {
- }
+ uint8_t GetPackageId() override {
+ CHECK(bool(package_id_)) << "package ID not set";
+ return package_id_.value();
+ }
- Maybe<std::u16string> mCompilationPackage;
- Maybe<uint8_t> mPackageId;
- StdErrDiagnostics mDiagnostics;
- SymbolTable mSymbols;
- NameMangler mNameMangler;
-};
+ NameMangler* GetNameMangler() override { return &name_mangler_; }
-class ContextBuilder {
-private:
- std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context());
+ bool IsVerbose() override { return false; }
-public:
- ContextBuilder& setCompilationPackage(const StringPiece16& package) {
- mContext->mCompilationPackage = package.toString();
- return *this;
- }
+ int GetMinSdkVersion() override { return min_sdk_version_; }
- ContextBuilder& setPackageId(uint8_t id) {
- mContext->mPackageId = id;
- return *this;
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Context);
- ContextBuilder& setNameManglerPolicy(NameManglerPolicy policy) {
- mContext->mNameMangler = NameMangler(policy);
- return *this;
- }
+ friend class ContextBuilder;
- ContextBuilder& addSymbolSource(std::unique_ptr<ISymbolSource> src) {
- mContext->getExternalSymbols()->appendSource(std::move(src));
- return *this;
- }
+ Maybe<std::string> compilation_package_;
+ Maybe<uint8_t> package_id_;
+ StdErrDiagnostics diagnostics_;
+ SymbolTable symbols_;
+ NameMangler name_mangler_ = NameMangler({});
+ int min_sdk_version_ = 0;
+};
- std::unique_ptr<Context> build() {
- return std::move(mContext);
- }
+class ContextBuilder {
+ public:
+ ContextBuilder& SetCompilationPackage(const StringPiece& package) {
+ context_->compilation_package_ = package.ToString();
+ return *this;
+ }
+
+ ContextBuilder& SetPackageId(uint8_t id) {
+ context_->package_id_ = id;
+ return *this;
+ }
+
+ ContextBuilder& SetNameManglerPolicy(const NameManglerPolicy& policy) {
+ context_->name_mangler_ = NameMangler(policy);
+ return *this;
+ }
+
+ ContextBuilder& AddSymbolSource(std::unique_ptr<ISymbolSource> src) {
+ context_->GetExternalSymbols()->AppendSource(std::move(src));
+ return *this;
+ }
+
+ ContextBuilder& SetMinSdkVersion(int min_sdk) {
+ context_->min_sdk_version_ = min_sdk;
+ return *this;
+ }
+
+ std::unique_ptr<Context> Build() { return std::move(context_); }
+
+ private:
+ std::unique_ptr<Context> context_ = std::unique_ptr<Context>(new Context());
};
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;
+ public:
+ StaticSymbolSourceBuilder& AddPublicSymbol(
+ const StringPiece& name, ResourceId id,
+ std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<SymbolTable::Symbol> symbol =
+ util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true);
+ symbol_source_->name_map_[ParseNameOrDie(name)] = symbol.get();
+ symbol_source_->id_map_[id] = symbol.get();
+ symbol_source_->symbols_.push_back(std::move(symbol));
+ return *this;
+ }
+
+ StaticSymbolSourceBuilder& AddSymbol(const StringPiece& name, ResourceId id,
+ std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<SymbolTable::Symbol> symbol =
+ util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false);
+ symbol_source_->name_map_[ParseNameOrDie(name)] = symbol.get();
+ symbol_source_->id_map_[id] = symbol.get();
+ symbol_source_->symbols_.push_back(std::move(symbol));
+ return *this;
+ }
+
+ std::unique_ptr<ISymbolSource> Build() { return std::move(symbol_source_); }
+
+ private:
+ class StaticSymbolSource : public ISymbolSource {
+ public:
+ StaticSymbolSource() = default;
+
+ std::unique_ptr<SymbolTable::Symbol> FindByName(
+ const ResourceName& name) override {
+ auto iter = name_map_.find(name);
+ if (iter != name_map_.end()) {
+ return CloneSymbol(iter->second);
+ }
+ return nullptr;
}
- 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<SymbolTable::Symbol> FindById(ResourceId id) override {
+ auto iter = id_map_.find(id);
+ if (iter != id_map_.end()) {
+ return CloneSymbol(iter->second);
+ }
+ return nullptr;
}
- std::unique_ptr<ISymbolSource> build() {
- return std::move(mSymbolSource);
+ std::list<std::unique_ptr<SymbolTable::Symbol>> symbols_;
+ std::map<ResourceName, SymbolTable::Symbol*> name_map_;
+ std::map<ResourceId, SymbolTable::Symbol*> id_map_;
+
+ 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->is_public = sym->is_public;
+ return clone;
}
-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>();
+ DISALLOW_COPY_AND_ASSIGN(StaticSymbolSource);
+ };
+
+ std::unique_ptr<StaticSymbolSource> symbol_source_ =
+ util::make_unique<StaticSymbolSource>();
};
-} // namespace test
-} // namespace aapt
+} // namespace test
+} // namespace aapt
#endif /* AAPT_TEST_CONTEXT_H */
diff --git a/tools/aapt2/test/Test.h b/tools/aapt2/test/Test.h
index d4845cfc19b7..ec07432fa51e 100644
--- a/tools/aapt2/test/Test.h
+++ b/tools/aapt2/test/Test.h
@@ -17,16 +17,10 @@
#ifndef AAPT_TEST_TEST_H
#define AAPT_TEST_TEST_H
+#include "gtest/gtest.h"
+
#include "test/Builders.h"
#include "test/Common.h"
#include "test/Context.h"
-#include <gtest/gtest.h>
-
-namespace aapt {
-namespace test {
-
-} // namespace test
-} // namespace aapt
-
-#endif // AAPT_TEST_TEST_H
+#endif // AAPT_TEST_TEST_H
diff --git a/tools/aapt2/tools/consumers/__init__.py b/tools/aapt2/tools/consumers/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/tools/aapt2/tools/consumers/__init__.py
diff --git a/tools/aapt2/tools/consumers/duplicates.py b/tools/aapt2/tools/consumers/duplicates.py
new file mode 100644
index 000000000000..4f70b6cae057
--- /dev/null
+++ b/tools/aapt2/tools/consumers/duplicates.py
@@ -0,0 +1,132 @@
+"""
+Looks for duplicate resource definitions and removes all but the last one.
+"""
+
+import os.path
+import xml.parsers.expat
+
+class DuplicateRemover:
+ def matches(self, file_path):
+ dirname, basename = os.path.split(file_path)
+ dirname = os.path.split(dirname)[1]
+ return dirname.startswith("values") and basename.endswith(".xml")
+
+ def consume(self, xml_path, input):
+ parser = xml.parsers.expat.ParserCreate("utf-8")
+ parser.returns_unicode = True
+ tracker = ResourceDefinitionLocator(parser)
+ parser.StartElementHandler = tracker.start_element
+ parser.EndElementHandler = tracker.end_element
+ parser.Parse(input)
+
+ # Treat the input as UTF-8 or else column numbers will be wrong.
+ input_lines = input.decode('utf-8').splitlines(True)
+
+ # Extract the duplicate resource definitions, ignoring the last definition
+ # which will take precedence and be left intact.
+ duplicates = []
+ for res_name, entries in tracker.resource_definitions.iteritems():
+ if len(entries) > 1:
+ duplicates += entries[:-1]
+
+ # Sort the duplicates so that they are in order. That way we only do one pass.
+ duplicates = sorted(duplicates, key=lambda x: x.start)
+
+ last_line_no = 0
+ last_col_no = 0
+ output_lines = []
+ current_line = ""
+ for definition in duplicates:
+ print "{0}: removing duplicate resource '{1}'".format(xml_path, definition.name)
+
+ if last_line_no < definition.start[0]:
+ # The next definition is on a new line, so write what we have
+ # to the output.
+ new_line = current_line + input_lines[last_line_no][last_col_no:]
+ if not new_line.isspace():
+ output_lines.append(new_line)
+ current_line = ""
+ last_col_no = 0
+ last_line_no += 1
+
+ # Copy all the lines up until this one.
+ for line_to_copy in xrange(last_line_no, definition.start[0]):
+ output_lines.append(input_lines[line_to_copy])
+
+ # Add to the existing line we're building, by including the prefix of this line
+ # and skipping the lines and characters until the end of this duplicate
+ # definition.
+ last_line_no = definition.start[0]
+ current_line += input_lines[last_line_no][last_col_no:definition.start[1]]
+ last_line_no = definition.end[0]
+ last_col_no = definition.end[1]
+
+ new_line = current_line + input_lines[last_line_no][last_col_no:]
+ if not new_line.isspace():
+ output_lines.append(new_line)
+ current_line = ""
+ last_line_no += 1
+ last_col_no = 0
+
+ for line_to_copy in xrange(last_line_no, len(input_lines)):
+ output_lines.append(input_lines[line_to_copy])
+
+ if len(duplicates) > 0:
+ print "deduped {0}".format(xml_path)
+ return "".join(output_lines).encode("utf-8")
+ return input
+
+class Duplicate:
+ """A small struct to maintain the positions of a Duplicate resource definition."""
+ def __init__(self, name, product, depth, start, end):
+ self.name = name
+ self.product = product
+ self.depth = depth
+ self.start = start
+ self.end = end
+
+class ResourceDefinitionLocator:
+ """Callback class for xml.parsers.expat which records resource definitions and their
+ locations.
+ """
+ def __init__(self, parser):
+ self.resource_definitions = {}
+ self._parser = parser
+ self._depth = 0
+ self._current_resource = None
+
+ def start_element(self, tag_name, attrs):
+ self._depth += 1
+ if self._depth == 2 and tag_name not in ["public", "java-symbol", "eat-comment", "skip"]:
+ resource_name = None
+ product = ""
+ try:
+ product = attrs["product"]
+ except KeyError:
+ pass
+
+ if tag_name == "item":
+ resource_name = "{0}/{1}".format(attrs["type"], attrs["name"])
+ else:
+ resource_name = "{0}/{1}".format(tag_name, attrs["name"])
+ self._current_resource = Duplicate(
+ resource_name,
+ product,
+ self._depth,
+ (self._parser.CurrentLineNumber - 1, self._parser.CurrentColumnNumber),
+ None)
+
+ def end_element(self, tag_name):
+ if self._current_resource and self._depth == self._current_resource.depth:
+ # Record the end position of the element, which is the length of the name
+ # plus the </> symbols (len("</>") == 3).
+ self._current_resource.end = (self._parser.CurrentLineNumber - 1,
+ self._parser.CurrentColumnNumber + 3 + len(tag_name))
+ key_name = "{0}:{1}".format(self._current_resource.name,
+ self._current_resource.product)
+ try:
+ self.resource_definitions[key_name] += [self._current_resource]
+ except KeyError:
+ self.resource_definitions[key_name] = [self._current_resource]
+ self._current_resource = None
+ self._depth -= 1
diff --git a/tools/aapt2/tools/consumers/positional_arguments.py b/tools/aapt2/tools/consumers/positional_arguments.py
new file mode 100644
index 000000000000..176e4c9ded60
--- /dev/null
+++ b/tools/aapt2/tools/consumers/positional_arguments.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+
+"""
+Looks for strings with multiple substitution arguments (%d, &s, etc)
+and replaces them with positional arguments (%1$d, %2$s).
+"""
+
+import os.path
+import re
+import xml.parsers.expat
+
+class PositionalArgumentFixer:
+ def matches(self, file_path):
+ dirname, basename = os.path.split(file_path)
+ dirname = os.path.split(dirname)[1]
+ return dirname.startswith("values") and basename.endswith(".xml")
+
+ def consume(self, xml_path, input):
+ parser = xml.parsers.expat.ParserCreate("utf-8")
+ locator = SubstitutionArgumentLocator(parser)
+ parser.returns_unicode = True
+ parser.StartElementHandler = locator.start_element
+ parser.EndElementHandler = locator.end_element
+ parser.CharacterDataHandler = locator.character_data
+ parser.Parse(input)
+
+ if len(locator.arguments) > 0:
+ output = ""
+ last_index = 0
+ for arg in locator.arguments:
+ output += input[last_index:arg.start]
+ output += "%{0}$".format(arg.number)
+ last_index = arg.start + 1
+ output += input[last_index:]
+ print "fixed {0}".format(xml_path)
+ return output
+ return input
+
+class Argument:
+ def __init__(self, start, number):
+ self.start = start
+ self.number = number
+
+class SubstitutionArgumentLocator:
+ """Callback class for xml.parsers.expat which records locations of
+ substitution arguments in strings when there are more than 1 of
+ them in a single <string> tag (and they are not positional).
+ """
+ def __init__(self, parser):
+ self.arguments = []
+ self._parser = parser
+ self._depth = 0
+ self._within_string = False
+ self._current_arguments = []
+ self._next_number = 1
+
+ def start_element(self, tag_name, attrs):
+ self._depth += 1
+ if self._depth == 2 and tag_name == "string" and "translateable" not in attrs:
+ self._within_string = True
+
+ def character_data(self, data):
+ if self._within_string:
+ for m in re.finditer("%[-#+ 0,(]?\d*[bBhHsScCdoxXeEfgGaAtTn]", data):
+ start, end = m.span()
+ self._current_arguments.append(\
+ Argument(self._parser.CurrentByteIndex + start, self._next_number))
+ self._next_number += 1
+
+ def end_element(self, tag_name):
+ if self._within_string and self._depth == 2:
+ if len(self._current_arguments) > 1:
+ self.arguments += self._current_arguments
+ self._current_arguments = []
+ self._within_string = False
+ self._next_number = 1
+ self._depth -= 1
diff --git a/tools/aapt2/tools/fix_resources.py b/tools/aapt2/tools/fix_resources.py
new file mode 100644
index 000000000000..b6fcd915d9eb
--- /dev/null
+++ b/tools/aapt2/tools/fix_resources.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+
+"""
+Scans each resource file in res/ applying various transformations
+to fix invalid resource files.
+"""
+
+import os
+import os.path
+import sys
+import tempfile
+
+from consumers.duplicates import DuplicateRemover
+from consumers.positional_arguments import PositionalArgumentFixer
+
+def do_it(res_path, consumers):
+ for file_path in enumerate_files(res_path):
+ eligible_consumers = filter(lambda c: c.matches(file_path), consumers)
+ if len(eligible_consumers) > 0:
+ print "checking {0} ...".format(file_path)
+
+ original_contents = read_contents(file_path)
+ contents = original_contents
+ for c in eligible_consumers:
+ contents = c.consume(file_path, contents)
+ if original_contents != contents:
+ write_contents(file_path, contents)
+
+def enumerate_files(res_path):
+ """Enumerates all files in the resource directory."""
+ values_directories = os.listdir(res_path)
+ values_directories = map(lambda f: os.path.join(res_path, f), values_directories)
+ all_files = []
+ for dir in values_directories:
+ files = os.listdir(dir)
+ files = map(lambda f: os.path.join(dir, f), files)
+ for f in files:
+ yield f
+
+def read_contents(file_path):
+ """Reads the contents of file_path without decoding."""
+ with open(file_path) as fin:
+ return fin.read()
+
+def write_contents(file_path, contents):
+ """Writes the bytes in contents to file_path by first writing to a temporary, then
+ renaming the temporary to file_path, ensuring a consistent write.
+ """
+ dirname, basename = os.path.split(file_path)
+ temp_name = ""
+ with tempfile.NamedTemporaryFile(prefix=basename, dir=dirname, delete=False) as temp:
+ temp_name = temp.name
+ temp.write(contents)
+ os.rename(temp.name, file_path)
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print >> sys.stderr, "please specify a path to a resource directory"
+ sys.exit(1)
+
+ res_path = os.path.abspath(sys.argv[1])
+ print "looking in {0} ...".format(res_path)
+ do_it(res_path, [DuplicateRemover(), PositionalArgumentFixer()])
diff --git a/tools/aapt2/public_attr_map.py b/tools/aapt2/tools/public_attr_map.py
index 92136a8d9acd..92136a8d9acd 100644
--- a/tools/aapt2/public_attr_map.py
+++ b/tools/aapt2/tools/public_attr_map.py
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index ec4675167676..aeabcff011ed 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -14,22 +14,25 @@
* limitations under the License.
*/
+#include "unflatten/BinaryResourceParser.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/TypeWrappers.h"
+
#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;
@@ -41,533 +44,552 @@ namespace {
* given a mapping from resource ID to resource name.
*/
class ReferenceIdToNameVisitor : public ValueVisitor {
-private:
- const std::map<ResourceId, ResourceName>* mMapping;
+ public:
+ using ValueVisitor::Visit;
-public:
- using ValueVisitor::visit;
+ explicit ReferenceIdToNameVisitor(
+ const std::map<ResourceId, ResourceName>* mapping)
+ : mapping_(mapping) {
+ CHECK(mapping_ != nullptr);
+ }
- ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) :
- mMapping(mapping) {
- assert(mMapping);
+ void Visit(Reference* reference) override {
+ if (!reference->id || !reference->id.value().is_valid()) {
+ return;
}
- 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 = {};
- }
+ ResourceId id = reference->id.value();
+ auto cache_iter = mapping_->find(id);
+ if (cache_iter != mapping_->end()) {
+ reference->name = cache_iter->second;
}
-};
+ }
-} // namespace
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor);
-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;
- }
- }
+ const std::map<ResourceId, ResourceName>* mapping_;
+};
- if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt resource table: "
- << parser.getLastError());
- return false;
- }
- return !error;
+} // namespace
+
+BinaryResourceParser::BinaryResourceParser(IAaptContext* context,
+ ResourceTable* table,
+ const Source& source,
+ const void* data, size_t len)
+ : context_(context),
+ table_(table),
+ source_(source),
+ data_(data),
+ data_len_(len) {}
+
+bool BinaryResourceParser::Parse() {
+ ResChunkPullParser parser(data_, data_len_);
+
+ bool error = false;
+ while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
+ if (parser.chunk()->type != android::RES_TABLE_TYPE) {
+ context_->GetDiagnostics()->Warn(DiagMessage(source_)
+ << "unknown chunk of type '"
+ << (int)parser.chunk()->type << "'");
+ continue;
+ }
+
+ if (!ParseTable(parser.chunk())) {
+ error = true;
+ }
+ }
+
+ if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "corrupt resource table: " << parser.error());
+ return false;
+ }
+ return !error;
}
/**
- * Parses the resource table, which contains all the packages, types, and entries.
+ * 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;
- }
+bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
+ const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk);
+ if (!table_header) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt ResTable_header chunk");
+ return false;
+ }
+
+ ResChunkPullParser parser(GetChunkData(&table_header->header),
+ GetChunkDataLen(&table_header->header));
+ while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
+ switch (util::DeviceToHost16(parser.chunk()->type)) {
+ case android::RES_STRING_POOL_TYPE:
+ if (value_pool_.getError() == NO_INIT) {
+ status_t err = value_pool_.setTo(
+ parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
+ if (err != NO_ERROR) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "corrupt string pool in ResTable: "
+ << value_pool_.getError());
+ 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;
+ // Reserve some space for the strings we are going to add.
+ table_->string_pool.HintWillAdd(value_pool_.size(),
+ value_pool_.styleCount());
+ } else {
+ context_->GetDiagnostics()->Warn(
+ DiagMessage(source_) << "unexpected string pool in ResTable");
}
- }
+ 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;
+ case android::RES_TABLE_PACKAGE_TYPE:
+ if (!ParsePackage(parser.chunk())) {
+ return false;
}
- }
-
- 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;
+ break;
+
+ default:
+ context_->GetDiagnostics()->Warn(
+ DiagMessage(source_)
+ << "unexpected chunk type "
+ << (int)util::DeviceToHost16(parser.chunk()->type));
+ break;
+ }
+ }
+
+ if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "corrupt resource table: " << parser.error());
+ 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 << "'");
+bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
+ const ResTable_package* package_header = ConvertTo<ResTable_package>(chunk);
+ if (!package_header) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt ResTable_package chunk");
+ return false;
+ }
+
+ uint32_t package_id = util::DeviceToHost32(package_header->id);
+ if (package_id > std::numeric_limits<uint8_t>::max()) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "package ID is too big (" << package_id << ")");
+ return false;
+ }
+
+ // Extract the package name.
+ size_t len = strnlen16((const char16_t*)package_header->name,
+ arraysize(package_header->name));
+ std::u16string package_name;
+ package_name.resize(len);
+ for (size_t i = 0; i < len; i++) {
+ package_name[i] = util::DeviceToHost16(package_header->name[i]);
+ }
+
+ ResourceTablePackage* package = table_->CreatePackage(
+ util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id));
+ if (!package) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "incompatible package '" << package_name
+ << "' with ID " << package_id);
+ 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.
+ type_pool_.uninit();
+ key_pool_.uninit();
+
+ ResChunkPullParser parser(GetChunkData(&package_header->header),
+ GetChunkDataLen(&package_header->header));
+ while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
+ switch (util::DeviceToHost16(parser.chunk()->type)) {
+ case android::RES_STRING_POOL_TYPE:
+ if (type_pool_.getError() == NO_INIT) {
+ status_t err = type_pool_.setTo(
+ parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
+ if (err != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt type string pool in "
+ << "ResTable_package: "
+ << type_pool_.getError());
return false;
- }
-
- if (!mTable->addResourceAllowMangled(name, config, {}, std::move(resourceValue),
- mContext->getDiagnostics())) {
+ }
+ } else if (key_pool_.getError() == NO_INIT) {
+ status_t err = key_pool_.setTo(
+ parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
+ if (err != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt key string pool in "
+ << "ResTable_package: "
+ << key_pool_.getError());
return false;
+ }
+ } else {
+ context_->GetDiagnostics()->Warn(DiagMessage(source_)
+ << "unexpected string pool");
}
+ break;
- 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;
- }
+ case android::RES_TABLE_TYPE_SPEC_TYPE:
+ if (!ParseTypeSpec(parser.chunk())) {
+ return false;
}
+ break;
- // 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 });
+ case android::RES_TABLE_TYPE_TYPE:
+ if (!ParseType(package, parser.chunk())) {
+ return false;
}
- }
- return true;
+ break;
+
+ default:
+ context_->GetDiagnostics()->Warn(
+ DiagMessage(source_)
+ << "unexpected chunk type "
+ << (int)util::DeviceToHost16(parser.chunk()->type));
+ break;
+ }
+ }
+
+ if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "corrupt ResTable_package: " << parser.error());
+ return false;
+ }
+
+ // Now go through the table and change local resource ID references to
+ // symbolic references.
+ ReferenceIdToNameVisitor visitor(&id_index_);
+ VisitAllValuesInTable(table_, &visitor);
+ 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>();
- }
+bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
+ if (type_pool_.getError() != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "missing type string pool");
+ return false;
+ }
+
+ const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk);
+ if (!type_spec) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt ResTable_typeSpec chunk");
+ return false;
+ }
+
+ if (type_spec->id == 0) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "ResTable_typeSpec has invalid id: "
+ << type_spec->id);
+ return false;
+ }
+ return true;
+}
- 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}));
- }
+bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
+ const ResChunk_header* chunk) {
+ if (type_pool_.getError() != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "missing type string pool");
+ return false;
+ }
+
+ if (key_pool_.getError() != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "missing key string pool");
+ return false;
+ }
+
+ const ResTable_type* type = ConvertTo<ResTable_type>(chunk);
+ if (!type) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt ResTable_type chunk");
+ return false;
+ }
+
+ if (type->id == 0) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "ResTable_type has invalid id: "
+ << (int)type->id);
+ return false;
+ }
+
+ ConfigDescription config;
+ config.copyFromDtoH(type->config);
+
+ const std::string type_str = util::GetString(type_pool_, type->id - 1);
+
+ const ResourceType* parsed_type = ParseResourceType(type_str);
+ if (!parsed_type) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "invalid type name '" << type_str
+ << "' 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, *parsed_type,
+ util::GetString(key_pool_, util::DeviceToHost32(entry->key.index)));
+
+ const ResourceId res_id(package->id.value(), type->id,
+ static_cast<uint16_t>(it.index()));
+
+ std::unique_ptr<Value> resource_value;
+ 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.
+ resource_value = ParseMapEntry(name, config, mapEntry);
+ } else {
+ const Res_value* value =
+ (const Res_value*)((const uint8_t*)entry +
+ util::DeviceToHost32(entry->size));
+ resource_value = ParseValue(name, config, value, entry->flags);
+ }
+
+ if (!resource_value) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "failed to parse value for resource " << name
+ << " (" << res_id << ") with configuration '"
+ << config << "'");
+ return false;
+ }
+
+ if (!table_->AddResourceAllowMangled(name, config, {},
+ std::move(resource_value),
+ context_->GetDiagnostics())) {
+ return false;
+ }
+
+ if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
+ Symbol symbol;
+ symbol.state = SymbolState::kPublic;
+ symbol.source = source_.WithLine(0);
+ if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol,
+ context_->GetDiagnostics())) {
+ return false;
+ }
}
- 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);
+ // Add this resource name->id mapping to the index so
+ // that we can resolve all ID references to name references.
+ auto cache_iter = id_index_.find(res_id);
+ if (cache_iter == id_index_.end()) {
+ id_index_.insert({res_id, name});
}
-
- // Treat this as a raw binary primitive.
- return util::make_unique<BinaryPrimitive>(*value);
+ }
+ return true;
}
-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<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) {
+ const std::string str = util::GetString(value_pool_, data);
+
+ const ResStringPool_span* spans = value_pool_.styleAt(data);
+
+ // Check if the string has a valid style associated with it.
+ if (spans != nullptr && spans->name.index != ResStringPool_span::END) {
+ StyleString style_str = {str};
+ while (spans->name.index != ResStringPool_span::END) {
+ style_str.spans.push_back(
+ Span{util::GetString(value_pool_, spans->name.index),
+ spans->firstChar, spans->lastChar});
+ spans++;
+ }
+ return util::make_unique<StyledString>(table_->string_pool.MakeRef(
+ style_str,
+ StringPool::Context(StringPool::Context::kStylePriority, config)));
+ } else {
+ if (name.type != ResourceType::kString && util::StartsWith(str, "res/")) {
+ // This must be a FileReference.
+ return util::make_unique<FileReference>(table_->string_pool.MakeRef(
+ str,
+ StringPool::Context(StringPool::Context::kHighPriority, config)));
+ }
+
+ // There are no styles associated with this string, so treat it as
+ // a simple string.
+ return util::make_unique<String>(
+ table_->string_pool.MakeRef(str, StringPool::Context(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 null_type = {};
+ null_type.dataType = Res_value::TYPE_REFERENCE;
+ return util::make_unique<BinaryPrimitive>(null_type);
+ }
+
+ // 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<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<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:
+ LOG(FATAL) << "unknown map type";
+ break;
+ }
+ return {};
}
-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<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& map_entry : map) {
+ if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) {
+ continue;
+ }
+
+ Style::Entry style_entry;
+ style_entry.key = Reference(util::DeviceToHost32(map_entry.name.ident));
+ style_entry.value = ParseValue(name, config, &map_entry.value, 0);
+ if (!style_entry.value) {
+ return {};
+ }
+ style->entries.push_back(std::move(style_entry));
+ }
+ return style;
}
-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<Attribute> BinaryResourceParser::ParseAttr(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ const bool is_weak =
+ (util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak);
+
+ // First we must discover what type of attribute this is. Find the type mask.
+ auto type_mask_iter =
+ std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
+ return util::DeviceToHost32(entry.name.ident) ==
+ ResTable_map::ATTR_TYPE;
+ });
+
+ if (type_mask_iter != end(map)) {
+ attr->type_mask = util::DeviceToHost32(type_mask_iter->value.data);
+ }
+
+ for (const ResTable_map& map_entry : map) {
+ if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) {
+ switch (util::DeviceToHost32(map_entry.name.ident)) {
+ case ResTable_map::ATTR_MIN:
+ attr->min_int = static_cast<int32_t>(map_entry.value.data);
+ break;
+ case ResTable_map::ATTR_MAX:
+ attr->max_int = static_cast<int32_t>(map_entry.value.data);
+ break;
+ }
+ continue;
+ }
+
+ if (attr->type_mask &
+ (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
+ Attribute::Symbol symbol;
+ symbol.value = util::DeviceToHost32(map_entry.value.data);
+ symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident));
+ attr->symbols.push_back(std::move(symbol));
+ }
+ }
+
+ // TODO(adamlesinski): Find i80n, attributes.
+ return attr;
}
-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 {};
- }
+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& map_entry : map) {
+ array->items.push_back(ParseValue(name, config, &map_entry.value, 0));
+ }
+ return array;
+}
- 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;
+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& map_entry : map) {
+ std::unique_ptr<Item> item = ParseValue(name, config, &map_entry.value, 0);
+ if (!item) {
+ return {};
+ }
+
+ switch (util::DeviceToHost32(map_entry.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
+} // namespace aapt
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 12bc13db38f2..dc668fdab7b6 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -17,16 +17,17 @@
#ifndef AAPT_BINARY_RESOURCE_PARSER_H
#define AAPT_BINARY_RESOURCE_PARSER_H
+#include <string>
+
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "Source.h"
-
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-#include <string>
-
namespace aapt {
struct SymbolTable_entry;
@@ -39,80 +40,86 @@ struct SymbolTable_entry;
* chunks and types.
*/
class BinaryResourceParser {
-public:
- /*
- * Creates a parser, which will read `len` bytes from `data`, and
- * add any resources parsed to `table`. `source` is for logging purposes.
- */
- BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
- const void* data, size_t dataLen);
-
- BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
-
- /*
- * Parses the binary resource table and returns true if successful.
- */
- bool parse();
-
-private:
- bool parseTable(const android::ResChunk_header* chunk);
- bool parsePackage(const android::ResChunk_header* chunk);
- bool parseTypeSpec(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<Value> parseMapEntry(const ResourceNameRef& name,
- const ConfigDescription& config,
- const android::ResTable_map_entry* map);
-
- std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, const ConfigDescription& config,
+ public:
+ /*
+ * Creates a parser, which will read `len` bytes from `data`, and
+ * add any resources parsed to `table`. `source` is for logging purposes.
+ */
+ BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+ const Source& source, const void* data, size_t data_len);
+
+ /*
+ * Parses the binary resource table and returns true if successful.
+ */
+ bool Parse();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BinaryResourceParser);
+
+ bool ParseTable(const android::ResChunk_header* chunk);
+ bool ParsePackage(const android::ResChunk_header* chunk);
+ bool ParseTypeSpec(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<Value> ParseMapEntry(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);
+
+ 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<Attribute> parseAttr(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);
-
- /**
- * 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);
+ /**
+ * 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& map_entry, Value* value);
- IAaptContext* mContext;
- ResourceTable* mTable;
+ IAaptContext* context_;
+ ResourceTable* table_;
- const Source mSource;
+ const Source source_;
- const void* mData;
- const size_t mDataLen;
+ const void* data_;
+ const size_t data_len_;
- // The standard value string pool for resource values.
- android::ResStringPool mValuePool;
+ // The standard value string pool for resource values.
+ android::ResStringPool value_pool_;
- // The string pool that holds the names of the types defined
- // in this table.
- android::ResStringPool mTypePool;
+ // The string pool that holds the names of the types defined
+ // in this table.
+ android::ResStringPool type_pool_;
- // The string pool that holds the names of the entries defined
- // in this table.
- android::ResStringPool mKeyPool;
+ // The string pool that holds the names of the entries defined
+ // in this table.
+ android::ResStringPool key_pool_;
- // A mapping of resource ID to resource name. When we finish parsing
- // we use this to convert all resource IDs to symbolic references.
- std::map<ResourceId, ResourceName> mIdIndex;
+ // A mapping of resource ID to resource name. When we finish parsing
+ // we use this to convert all resource IDs to symbolic references.
+ std::map<ResourceId, ResourceName> id_index_;
};
-} // namespace aapt
+} // namespace aapt
namespace android {
@@ -121,13 +128,14 @@ namespace android {
*/
inline const ResTable_map* begin(const ResTable_map_entry* map) {
- return (const ResTable_map*)((const uint8_t*) map + aapt::util::deviceToHost32(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 begin(map) + aapt::util::deviceToHost32(map->count);
+ return begin(map) + aapt::util::DeviceToHost32(map->count);
}
-} // namespace android
+} // namespace android
-#endif // AAPT_BINARY_RESOURCE_PARSER_H
+#endif // AAPT_BINARY_RESOURCE_PARSER_H
diff --git a/tools/aapt2/unflatten/ResChunkPullParser.cpp b/tools/aapt2/unflatten/ResChunkPullParser.cpp
index 6f8bb1b29b62..5d71ff315874 100644
--- a/tools/aapt2/unflatten/ResChunkPullParser.cpp
+++ b/tools/aapt2/unflatten/ResChunkPullParser.cpp
@@ -15,55 +15,60 @@
*/
#include "unflatten/ResChunkPullParser.h"
-#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
#include <cstddef>
+#include "android-base/logging.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "util/Util.h"
+
namespace aapt {
using android::ResChunk_header;
-ResChunkPullParser::Event ResChunkPullParser::next() {
- if (!isGoodEvent(mEvent)) {
- return mEvent;
- }
+ResChunkPullParser::Event ResChunkPullParser::Next() {
+ if (!IsGoodEvent(event_)) {
+ return event_;
+ }
- if (mEvent == Event::StartDocument) {
- mCurrentChunk = mData;
- } else {
- mCurrentChunk = (const ResChunk_header*)
- (((const char*) mCurrentChunk) + util::deviceToHost32(mCurrentChunk->size));
- }
+ if (event_ == Event::kStartDocument) {
+ current_chunk_ = data_;
+ } else {
+ current_chunk_ =
+ (const ResChunk_header*)(((const char*)current_chunk_) +
+ util::DeviceToHost32(current_chunk_->size));
+ }
- 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);
+ const std::ptrdiff_t diff = (const char*)current_chunk_ - (const char*)data_;
+ CHECK(diff >= 0) << "diff is negative";
+ const size_t offset = static_cast<const size_t>(diff);
- if (offset == mLen) {
- mCurrentChunk = nullptr;
- return (mEvent = Event::EndDocument);
- } else if (offset + sizeof(ResChunk_header) > mLen) {
- mLastError = "chunk is past the end of the document";
- mCurrentChunk = nullptr;
- return (mEvent = Event::BadDocument);
- }
+ if (offset == len_) {
+ current_chunk_ = nullptr;
+ return (event_ = Event::kEndDocument);
+ } else if (offset + sizeof(ResChunk_header) > len_) {
+ error_ = "chunk is past the end of the document";
+ current_chunk_ = nullptr;
+ return (event_ = Event::kBadDocument);
+ }
- if (util::deviceToHost16(mCurrentChunk->headerSize) < sizeof(ResChunk_header)) {
- mLastError = "chunk has too small header";
- mCurrentChunk = nullptr;
- return (mEvent = Event::BadDocument);
- } 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 + util::deviceToHost32(mCurrentChunk->size) > mLen) {
- mLastError = "chunk's data extends past the end of the document";
- mCurrentChunk = nullptr;
- return (mEvent = Event::BadDocument);
- }
- return (mEvent = Event::Chunk);
+ if (util::DeviceToHost16(current_chunk_->headerSize) <
+ sizeof(ResChunk_header)) {
+ error_ = "chunk has too small header";
+ current_chunk_ = nullptr;
+ return (event_ = Event::kBadDocument);
+ } else if (util::DeviceToHost32(current_chunk_->size) <
+ util::DeviceToHost16(current_chunk_->headerSize)) {
+ error_ = "chunk's total size is smaller than header";
+ current_chunk_ = nullptr;
+ return (event_ = Event::kBadDocument);
+ } else if (offset + util::DeviceToHost32(current_chunk_->size) > len_) {
+ error_ = "chunk's data extends past the end of the document";
+ current_chunk_ = nullptr;
+ return (event_ = Event::kBadDocument);
+ }
+ return (event_ = Event::kChunk);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/unflatten/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h
index a51d5bfdc9b3..437fc5c8a9b6 100644
--- a/tools/aapt2/unflatten/ResChunkPullParser.h
+++ b/tools/aapt2/unflatten/ResChunkPullParser.h
@@ -17,11 +17,13 @@
#ifndef AAPT_RES_CHUNK_PULL_PARSER_H
#define AAPT_RES_CHUNK_PULL_PARSER_H
-#include "util/Util.h"
-
-#include <androidfw/ResourceTypes.h>
#include <string>
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "util/Util.h"
+
namespace aapt {
/**
@@ -37,88 +39,88 @@ namespace aapt {
* pointing to the data portion of a chunk.
*/
class ResChunkPullParser {
-public:
- enum class Event {
- StartDocument,
- EndDocument,
- BadDocument,
-
- Chunk,
- };
-
- /**
- * Returns false if the event is EndDocument or BadDocument.
- */
- static bool isGoodEvent(Event event);
-
- /**
- * Create a ResChunkPullParser to read android::ResChunk_headers
- * from the memory pointed to by data, of len bytes.
- */
- ResChunkPullParser(const void* data, size_t len);
-
- ResChunkPullParser(const ResChunkPullParser&) = delete;
-
- Event getEvent() const;
- const std::string& getLastError() const;
- const android::ResChunk_header* getChunk() const;
-
- /**
- * Move to the next android::ResChunk_header.
- */
- Event next();
-
-private:
- Event mEvent;
- const android::ResChunk_header* mData;
- size_t mLen;
- const android::ResChunk_header* mCurrentChunk;
- std::string mLastError;
+ public:
+ enum class Event {
+ kStartDocument,
+ kEndDocument,
+ kBadDocument,
+
+ kChunk,
+ };
+
+ /**
+ * Returns false if the event is EndDocument or BadDocument.
+ */
+ static bool IsGoodEvent(Event event);
+
+ /**
+ * Create a ResChunkPullParser to read android::ResChunk_headers
+ * from the memory pointed to by data, of len bytes.
+ */
+ ResChunkPullParser(const void* data, size_t len);
+
+ Event event() const;
+ const std::string& error() const;
+ const android::ResChunk_header* chunk() const;
+
+ /**
+ * Move to the next android::ResChunk_header.
+ */
+ Event Next();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResChunkPullParser);
+
+ Event event_;
+ const android::ResChunk_header* data_;
+ size_t len_;
+ const android::ResChunk_header* current_chunk_;
+ std::string error_;
};
template <typename T>
-inline static const T* convertTo(const android::ResChunk_header* chunk) {
- if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) {
- return nullptr;
- }
- return reinterpret_cast<const T*>(chunk);
+inline static const T* ConvertTo(const android::ResChunk_header* chunk) {
+ 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) + util::deviceToHost16(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 uint32_t getChunkDataLen(const android::ResChunk_header* chunk) {
- return util::deviceToHost32(chunk->size) - util::deviceToHost16(chunk->headerSize);
+inline static uint32_t GetChunkDataLen(const android::ResChunk_header* chunk) {
+ return util::DeviceToHost32(chunk->size) -
+ util::DeviceToHost16(chunk->headerSize);
}
//
// Implementation
//
-inline bool ResChunkPullParser::isGoodEvent(ResChunkPullParser::Event event) {
- return event != Event::EndDocument && event != Event::BadDocument;
+inline bool ResChunkPullParser::IsGoodEvent(ResChunkPullParser::Event event) {
+ return event != Event::kEndDocument && event != Event::kBadDocument;
}
-inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len) :
- mEvent(Event::StartDocument),
- mData(reinterpret_cast<const android::ResChunk_header*>(data)),
- mLen(len),
- mCurrentChunk(nullptr) {
-}
+inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len)
+ : event_(Event::kStartDocument),
+ data_(reinterpret_cast<const android::ResChunk_header*>(data)),
+ len_(len),
+ current_chunk_(nullptr) {}
-inline ResChunkPullParser::Event ResChunkPullParser::getEvent() const {
- return mEvent;
+inline ResChunkPullParser::Event ResChunkPullParser::event() const {
+ return event_;
}
-inline const std::string& ResChunkPullParser::getLastError() const {
- return mLastError;
-}
+inline const std::string& ResChunkPullParser::error() const { return error_; }
-inline const android::ResChunk_header* ResChunkPullParser::getChunk() const {
- return mCurrentChunk;
+inline const android::ResChunk_header* ResChunkPullParser::chunk() const {
+ return current_chunk_;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RES_CHUNK_PULL_PARSER_H
+#endif // AAPT_RES_CHUNK_PULL_PARSER_H
diff --git a/tools/aapt2/util/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp
index c88e3c102415..ef99dca286a4 100644
--- a/tools/aapt2/util/BigBuffer.cpp
+++ b/tools/aapt2/util/BigBuffer.cpp
@@ -20,33 +20,60 @@
#include <memory>
#include <vector>
+#include "android-base/logging.h"
+
namespace aapt {
-void* BigBuffer::nextBlockImpl(size_t size) {
- if (!mBlocks.empty()) {
- Block& block = mBlocks.back();
- if (block.mBlockSize - block.size >= size) {
- void* outBuffer = block.buffer.get() + block.size;
- block.size += size;
- mSize += size;
- return outBuffer;
- }
+void* BigBuffer::NextBlockImpl(size_t size) {
+ if (!blocks_.empty()) {
+ Block& block = blocks_.back();
+ if (block.block_size_ - block.size >= size) {
+ void* out_buffer = block.buffer.get() + block.size;
+ block.size += size;
+ size_ += size;
+ return out_buffer;
}
+ }
+
+ const size_t actual_size = std::max(block_size_, size);
- const size_t actualSize = std::max(mBlockSize, size);
+ Block block = {};
- Block block = {};
+ // Zero-allocate the block's buffer.
+ block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actual_size]());
+ CHECK(block.buffer);
- // Zero-allocate the block's buffer.
- block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actualSize]());
- assert(block.buffer);
+ block.size = size;
+ block.block_size_ = actual_size;
- block.size = size;
- block.mBlockSize = actualSize;
+ blocks_.push_back(std::move(block));
+ size_ += size;
+ return blocks_.back().buffer.get();
+}
+
+void* BigBuffer::NextBlock(size_t* out_size) {
+ if (!blocks_.empty()) {
+ Block& block = blocks_.back();
+ if (block.size != block.block_size_) {
+ void* out_buffer = block.buffer.get() + block.size;
+ size_t size = block.block_size_ - block.size;
+ block.size = block.block_size_;
+ size_ += size;
+ *out_size = size;
+ return out_buffer;
+ }
+ }
- mBlocks.push_back(std::move(block));
- mSize += size;
- return mBlocks.back().buffer.get();
+ // Zero-allocate the block's buffer.
+ Block block = {};
+ block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[block_size_]());
+ CHECK(block.buffer);
+ block.size = block_size_;
+ block.block_size_ = block_size_;
+ blocks_.push_back(std::move(block));
+ size_ += block_size_;
+ *out_size = block_size_;
+ return blocks_.back().buffer.get();
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/util/BigBuffer.h b/tools/aapt2/util/BigBuffer.h
index cad2a2e63be1..d23c41d4d6f0 100644
--- a/tools/aapt2/util/BigBuffer.h
+++ b/tools/aapt2/util/BigBuffer.h
@@ -17,12 +17,14 @@
#ifndef AAPT_BIG_BUFFER_H
#define AAPT_BIG_BUFFER_H
-#include <cassert>
#include <cstring>
#include <memory>
#include <type_traits>
#include <vector>
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
namespace aapt {
/**
@@ -32,130 +34,153 @@ namespace aapt {
* block is allocated and appended to the end of the list.
*/
class BigBuffer {
-public:
- /**
- * A contiguous block of allocated memory.
- */
- struct Block {
- /**
- * Pointer to the memory.
- */
- std::unique_ptr<uint8_t[]> buffer;
-
- /**
- * Size of memory that is currently occupied. The actual
- * allocation may be larger.
- */
- size_t size;
-
- private:
- friend class BigBuffer;
-
- /**
- * The size of the memory block allocation.
- */
- size_t mBlockSize;
- };
-
- typedef std::vector<Block>::const_iterator const_iterator;
-
- /**
- * Create a BigBuffer with block allocation sizes
- * of blockSize.
- */
- BigBuffer(size_t blockSize);
-
- BigBuffer(const BigBuffer&) = delete; // No copying.
-
- BigBuffer(BigBuffer&& rhs);
-
- /**
- * Number of occupied bytes in all the allocated blocks.
- */
- size_t size() const;
-
- /**
- * Returns a pointer to an array of T, where T is
- * a POD type. The elements are zero-initialized.
- */
- template <typename T>
- T* nextBlock(size_t count = 1);
-
- /**
- * Moves the specified BigBuffer into this one. When this method
- * returns, buffer is empty.
- */
- void appendBuffer(BigBuffer&& buffer);
-
+ public:
+ /**
+ * A contiguous block of allocated memory.
+ */
+ struct Block {
/**
- * Pads the block with 'bytes' bytes of zero values.
+ * Pointer to the memory.
*/
- void pad(size_t bytes);
+ std::unique_ptr<uint8_t[]> buffer;
/**
- * Pads the block so that it aligns on a 4 byte boundary.
+ * Size of memory that is currently occupied. The actual
+ * allocation may be larger.
*/
- void align4();
+ size_t size;
- const_iterator begin() const;
- const_iterator end() const;
+ private:
+ friend class BigBuffer;
-private:
/**
- * Returns a pointer to a buffer of the requested size.
- * The buffer is zero-initialized.
+ * The size of the memory block allocation.
*/
- void* nextBlockImpl(size_t size);
-
- size_t mBlockSize;
- size_t mSize;
- std::vector<Block> mBlocks;
+ size_t block_size_;
+ };
+
+ typedef std::vector<Block>::const_iterator const_iterator;
+
+ /**
+ * Create a BigBuffer with block allocation sizes
+ * of block_size.
+ */
+ explicit BigBuffer(size_t block_size);
+
+ BigBuffer(BigBuffer&& rhs);
+
+ /**
+ * Number of occupied bytes in all the allocated blocks.
+ */
+ size_t size() const;
+
+ /**
+ * Returns a pointer to an array of T, where T is
+ * a POD type. The elements are zero-initialized.
+ */
+ template <typename T>
+ T* NextBlock(size_t count = 1);
+
+ /**
+ * Returns the next block available and puts the size in out_count.
+ * This is useful for grabbing blocks where the size doesn't matter.
+ * Use BackUp() to give back any bytes that were not used.
+ */
+ void* NextBlock(size_t* out_count);
+
+ /**
+ * Backs up count bytes. This must only be called after NextBlock()
+ * and can not be larger than sizeof(T) * count of the last NextBlock()
+ * call.
+ */
+ void BackUp(size_t count);
+
+ /**
+ * Moves the specified BigBuffer into this one. When this method
+ * returns, buffer is empty.
+ */
+ void AppendBuffer(BigBuffer&& buffer);
+
+ /**
+ * Pads the block with 'bytes' bytes of zero values.
+ */
+ void Pad(size_t bytes);
+
+ /**
+ * Pads the block so that it aligns on a 4 byte boundary.
+ */
+ void Align4();
+
+ size_t block_size() const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBuffer);
+
+ /**
+ * Returns a pointer to a buffer of the requested size.
+ * The buffer is zero-initialized.
+ */
+ void* NextBlockImpl(size_t size);
+
+ size_t block_size_;
+ size_t size_;
+ std::vector<Block> blocks_;
};
-inline BigBuffer::BigBuffer(size_t blockSize) : mBlockSize(blockSize), mSize(0) {
-}
+inline BigBuffer::BigBuffer(size_t block_size)
+ : block_size_(block_size), size_(0) {}
-inline BigBuffer::BigBuffer(BigBuffer&& rhs) :
- mBlockSize(rhs.mBlockSize), mSize(rhs.mSize), mBlocks(std::move(rhs.mBlocks)) {
-}
+inline BigBuffer::BigBuffer(BigBuffer&& rhs)
+ : block_size_(rhs.block_size_),
+ size_(rhs.size_),
+ blocks_(std::move(rhs.blocks_)) {}
-inline size_t BigBuffer::size() const {
- return mSize;
-}
+inline size_t BigBuffer::size() const { return size_; }
+
+inline size_t BigBuffer::block_size() const { return block_size_; }
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));
+inline T* BigBuffer::NextBlock(size_t count) {
+ static_assert(std::is_standard_layout<T>::value,
+ "T must be standard_layout type");
+ CHECK(count != 0);
+ return reinterpret_cast<T*>(NextBlockImpl(sizeof(T) * count));
}
-inline void BigBuffer::appendBuffer(BigBuffer&& buffer) {
- std::move(buffer.mBlocks.begin(), buffer.mBlocks.end(), std::back_inserter(mBlocks));
- mSize += buffer.mSize;
- buffer.mBlocks.clear();
- buffer.mSize = 0;
+inline void BigBuffer::BackUp(size_t count) {
+ Block& block = blocks_.back();
+ block.size -= count;
+ size_ -= count;
}
-inline void BigBuffer::pad(size_t bytes) {
- nextBlock<char>(bytes);
+inline void BigBuffer::AppendBuffer(BigBuffer&& buffer) {
+ std::move(buffer.blocks_.begin(), buffer.blocks_.end(),
+ std::back_inserter(blocks_));
+ size_ += buffer.size_;
+ buffer.blocks_.clear();
+ buffer.size_ = 0;
}
-inline void BigBuffer::align4() {
- const size_t unaligned = mSize % 4;
- if (unaligned != 0) {
- pad(4 - unaligned);
- }
+inline void BigBuffer::Pad(size_t bytes) { NextBlock<char>(bytes); }
+
+inline void BigBuffer::Align4() {
+ const size_t unaligned = size_ % 4;
+ if (unaligned != 0) {
+ Pad(4 - unaligned);
+ }
}
inline BigBuffer::const_iterator BigBuffer::begin() const {
- return mBlocks.begin();
+ return blocks_.begin();
}
inline BigBuffer::const_iterator BigBuffer::end() const {
- return mBlocks.end();
+ return blocks_.end();
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_BIG_BUFFER_H
+#endif // AAPT_BIG_BUFFER_H
diff --git a/tools/aapt2/util/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp
index 2a24f123e18e..12c0b3ee3214 100644
--- a/tools/aapt2/util/BigBuffer_test.cpp
+++ b/tools/aapt2/util/BigBuffer_test.cpp
@@ -16,83 +16,83 @@
#include "util/BigBuffer.h"
-#include <gtest/gtest.h>
+#include "test/Test.h"
namespace aapt {
TEST(BigBufferTest, AllocateSingleBlock) {
- BigBuffer buffer(4);
+ BigBuffer buffer(4);
- EXPECT_NE(nullptr, buffer.nextBlock<char>(2));
- EXPECT_EQ(2u, buffer.size());
+ EXPECT_NE(nullptr, buffer.NextBlock<char>(2));
+ EXPECT_EQ(2u, buffer.size());
}
TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) {
- BigBuffer buffer(16);
+ BigBuffer buffer(16);
- char* b1 = buffer.nextBlock<char>(8);
- EXPECT_NE(nullptr, b1);
+ char* b1 = buffer.NextBlock<char>(8);
+ EXPECT_NE(nullptr, b1);
- char* b2 = buffer.nextBlock<char>(4);
- EXPECT_NE(nullptr, b2);
+ char* b2 = buffer.NextBlock<char>(4);
+ EXPECT_NE(nullptr, b2);
- EXPECT_EQ(b1 + 8, b2);
+ EXPECT_EQ(b1 + 8, b2);
}
TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) {
- BigBuffer buffer(16);
+ BigBuffer buffer(16);
- EXPECT_NE(nullptr, buffer.nextBlock<char>(32));
- EXPECT_EQ(32u, buffer.size());
+ EXPECT_NE(nullptr, buffer.NextBlock<char>(32));
+ EXPECT_EQ(32u, buffer.size());
}
TEST(BigBufferTest, AppendAndMoveBlock) {
- BigBuffer buffer(16);
+ BigBuffer buffer(16);
- uint32_t* b1 = buffer.nextBlock<uint32_t>();
- ASSERT_NE(nullptr, b1);
- *b1 = 33;
+ uint32_t* b1 = buffer.NextBlock<uint32_t>();
+ ASSERT_NE(nullptr, b1);
+ *b1 = 33;
- {
- BigBuffer buffer2(16);
- b1 = buffer2.nextBlock<uint32_t>();
- ASSERT_NE(nullptr, b1);
- *b1 = 44;
+ {
+ BigBuffer buffer2(16);
+ b1 = buffer2.NextBlock<uint32_t>();
+ ASSERT_NE(nullptr, b1);
+ *b1 = 44;
- buffer.appendBuffer(std::move(buffer2));
- EXPECT_EQ(0u, buffer2.size());
- EXPECT_EQ(buffer2.begin(), buffer2.end());
- }
+ buffer.AppendBuffer(std::move(buffer2));
+ EXPECT_EQ(0u, buffer2.size());
+ EXPECT_EQ(buffer2.begin(), buffer2.end());
+ }
- EXPECT_EQ(2 * sizeof(uint32_t), buffer.size());
+ EXPECT_EQ(2 * sizeof(uint32_t), buffer.size());
- auto b = buffer.begin();
- ASSERT_NE(b, buffer.end());
- ASSERT_EQ(sizeof(uint32_t), b->size);
- ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
- ++b;
+ auto b = buffer.begin();
+ ASSERT_NE(b, buffer.end());
+ ASSERT_EQ(sizeof(uint32_t), b->size);
+ ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
+ ++b;
- ASSERT_NE(b, buffer.end());
- ASSERT_EQ(sizeof(uint32_t), b->size);
- ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
- ++b;
+ ASSERT_NE(b, buffer.end());
+ ASSERT_EQ(sizeof(uint32_t), b->size);
+ ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
+ ++b;
- ASSERT_EQ(b, buffer.end());
+ ASSERT_EQ(b, buffer.end());
}
TEST(BigBufferTest, PadAndAlignProperly) {
- BigBuffer buffer(16);
-
- ASSERT_NE(buffer.nextBlock<char>(2), nullptr);
- ASSERT_EQ(2u, buffer.size());
- buffer.pad(2);
- ASSERT_EQ(4u, buffer.size());
- buffer.align4();
- ASSERT_EQ(4u, buffer.size());
- buffer.pad(2);
- ASSERT_EQ(6u, buffer.size());
- buffer.align4();
- ASSERT_EQ(8u, buffer.size());
+ BigBuffer buffer(16);
+
+ ASSERT_NE(buffer.NextBlock<char>(2), nullptr);
+ ASSERT_EQ(2u, buffer.size());
+ buffer.Pad(2);
+ ASSERT_EQ(4u, buffer.size());
+ buffer.Align4();
+ ASSERT_EQ(4u, buffer.size());
+ buffer.Pad(2);
+ ASSERT_EQ(6u, buffer.size());
+ buffer.Align4();
+ ASSERT_EQ(8u, buffer.size());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 042ff0e6c1b9..f034607ea8f5 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -15,15 +15,20 @@
*/
#include "util/Files.h"
-#include "util/Util.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
#include <algorithm>
-#include <android-base/file.h>
#include <cerrno>
#include <cstdio>
-#include <dirent.h>
#include <string>
-#include <sys/stat.h>
+
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "android-base/logging.h"
+
+#include "util/Util.h"
#ifdef _WIN32
// Windows includes.
@@ -33,244 +38,230 @@
namespace aapt {
namespace file {
-FileType getFileType(const StringPiece& path) {
- struct stat sb;
- if (stat(path.data(), &sb) < 0) {
- if (errno == ENOENT || errno == ENOTDIR) {
- return FileType::kNonexistant;
- }
- return FileType::kUnknown;
+FileType GetFileType(const StringPiece& path) {
+ struct stat sb;
+ if (stat(path.data(), &sb) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR) {
+ return FileType::kNonexistant;
}
+ return FileType::kUnknown;
+ }
- if (S_ISREG(sb.st_mode)) {
- return FileType::kRegular;
- } else if (S_ISDIR(sb.st_mode)) {
- return FileType::kDirectory;
- } else if (S_ISCHR(sb.st_mode)) {
- return FileType::kCharDev;
- } else if (S_ISBLK(sb.st_mode)) {
- return FileType::kBlockDev;
- } else if (S_ISFIFO(sb.st_mode)) {
- return FileType::kFifo;
+ if (S_ISREG(sb.st_mode)) {
+ return FileType::kRegular;
+ } else if (S_ISDIR(sb.st_mode)) {
+ return FileType::kDirectory;
+ } else if (S_ISCHR(sb.st_mode)) {
+ return FileType::kCharDev;
+ } else if (S_ISBLK(sb.st_mode)) {
+ return FileType::kBlockDev;
+ } else if (S_ISFIFO(sb.st_mode)) {
+ return FileType::kFifo;
#if defined(S_ISLNK)
- } else if (S_ISLNK(sb.st_mode)) {
- return FileType::kSymlink;
+ } else if (S_ISLNK(sb.st_mode)) {
+ return FileType::kSymlink;
#endif
#if defined(S_ISSOCK)
- } else if (S_ISSOCK(sb.st_mode)) {
- return FileType::kSocket;
+ } else if (S_ISSOCK(sb.st_mode)) {
+ return FileType::kSocket;
#endif
- } else {
- return FileType::kUnknown;
- }
-}
-
-std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) {
- DIR* dir = opendir(root.data());
- if (dir == nullptr) {
- if (outError) {
- std::stringstream errorStr;
- errorStr << "unable to open file: " << strerror(errno);
- *outError = errorStr.str();
- }
- return {};
- }
-
- std::vector<std::string> files;
- dirent* entry;
- while ((entry = readdir(dir))) {
- files.emplace_back(entry->d_name);
- }
-
- closedir(dir);
- return files;
+ } else {
+ return FileType::kUnknown;
+ }
}
-inline static int mkdirImpl(const StringPiece& path) {
+inline static int MkdirImpl(const StringPiece& path) {
#ifdef _WIN32
- return _mkdir(path.toString().c_str());
+ return _mkdir(path.ToString().c_str());
#else
- return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+ return mkdir(path.ToString().c_str(),
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP);
#endif
}
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 && current != start) {
- StringPiece parentPath(start, current - start);
- int result = mkdirImpl(parentPath);
- if (result < 0 && errno != EEXIST) {
- return false;
- }
- }
+ const char* start = path.begin();
+ const char* end = path.end();
+ for (const char* current = start; current != end; ++current) {
+ if (*current == sDirSep && current != start) {
+ StringPiece parent_path(start, current - start);
+ int result = MkdirImpl(parent_path);
+ if (result < 0 && errno != EEXIST) {
+ return false;
+ }
}
- return mkdirImpl(path) == 0 || errno == EEXIST;
+ }
+ return MkdirImpl(path) == 0 || errno == EEXIST;
}
-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 StringPiece(start, current - start);
- }
+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 StringPiece(start, current - start);
}
- return {};
+ }
+ 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;
- }
+StringPiece GetFilename(const StringPiece& path) {
+ const char* end = path.end();
+ const char* last_dir_sep = path.begin();
+ for (const char* c = path.begin(); c != end; ++c) {
+ if (*c == sDirSep) {
+ last_dir_sep = c + 1;
}
- return StringPiece(lastDirSep, end - lastDirSep);
+ }
+ return StringPiece(last_dir_sep, end - last_dir_sep);
}
-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 {};
+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());
+void AppendPath(std::string* base, StringPiece part) {
+ CHECK(base != nullptr);
+ const bool base_has_trailing_sep =
+ (!base->empty() && *(base->end() - 1) == sDirSep);
+ const bool part_has_leading_sep =
+ (!part.empty() && *(part.begin()) == sDirSep);
+ if (base_has_trailing_sep && part_has_leading_sep) {
+ // Remove the part's leading sep
+ part = part.substr(1, part.size() - 1);
+ } else if (!base_has_trailing_sep && !part_has_leading_sep) {
+ // 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;
+std::string PackageToPath(const StringPiece& package) {
+ std::string out_path;
+ for (StringPiece part : util::Tokenize(package, '.')) {
+ AppendPath(&out_path, part);
+ }
+ return out_path;
}
-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 {};
- }
+Maybe<android::FileMap> MmapPath(const StringPiece& path,
+ std::string* out_error) {
+ std::unique_ptr<FILE, decltype(fclose)*> f = {fopen(path.data(), "rb"),
+ fclose};
+ if (!f) {
+ if (out_error) *out_error = android::base::SystemErrorCodeToString(errno);
+ return {};
+ }
- int fd = fileno(f.get());
+ int fd = fileno(f.get());
- struct stat fileStats = {};
- if (fstat(fd, &fileStats) != 0) {
- if (outError) *outError = strerror(errno);
- return {};
- }
+ struct stat filestats = {};
+ if (fstat(fd, &filestats) != 0) {
+ if (out_error) *out_error = android::base::SystemErrorCodeToString(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);
- }
+ 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);
+ if (!filemap.create(path.data(), fd, 0, filestats.st_size, true)) {
+ if (out_error) *out_error = android::base::SystemErrorCodeToString(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;
- }
+bool AppendArgsFromFile(const StringPiece& path,
+ std::vector<std::string>* out_arglist,
+ std::string* out_error) {
+ std::string contents;
+ if (!android::base::ReadFileToString(path.ToString(), &contents)) {
+ if (out_error) *out_error = "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());
- }
+ for (StringPiece line : util::Tokenize(contents, ' ')) {
+ line = util::TrimWhitespace(line);
+ if (!line.empty()) {
+ out_arglist->push_back(line.ToString());
}
- return true;
+ }
+ return true;
}
-bool FileFilter::setPattern(const StringPiece& pattern) {
- mPatternTokens = util::splitAndLowercase(pattern, ':');
- return true;
+bool FileFilter::SetPattern(const StringPiece& pattern) {
+ pattern_tokens_ = util::SplitAndLowercase(pattern, ':');
+ return true;
}
bool FileFilter::operator()(const std::string& filename, FileType type) const {
- if (filename == "." || filename == "..") {
- return false;
- }
+ if (filename == "." || filename == "..") {
+ return false;
+ }
- const char kDir[] = "dir";
- const char kFile[] = "file";
- const size_t filenameLen = filename.length();
- bool chatty = true;
- for (const std::string& token : mPatternTokens) {
- const char* tokenStr = token.c_str();
- if (*tokenStr == '!') {
- chatty = false;
- tokenStr++;
- }
+ const char kDir[] = "dir";
+ const char kFile[] = "file";
+ const size_t filename_len = filename.length();
+ bool chatty = true;
+ for (const std::string& token : pattern_tokens_) {
+ const char* token_str = token.c_str();
+ if (*token_str == '!') {
+ chatty = false;
+ token_str++;
+ }
- if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) {
- if (type != FileType::kDirectory) {
- continue;
- }
- tokenStr += sizeof(kDir);
- }
+ if (strncasecmp(token_str, kDir, sizeof(kDir)) == 0) {
+ if (type != FileType::kDirectory) {
+ continue;
+ }
+ token_str += sizeof(kDir);
+ }
- if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) {
- if (type != FileType::kRegular) {
- continue;
- }
- tokenStr += sizeof(kFile);
- }
+ if (strncasecmp(token_str, kFile, sizeof(kFile)) == 0) {
+ if (type != FileType::kRegular) {
+ continue;
+ }
+ token_str += sizeof(kFile);
+ }
- bool ignore = false;
- size_t n = strlen(tokenStr);
- if (*tokenStr == '*') {
- // Math suffix.
- tokenStr++;
- n--;
- if (n <= filenameLen) {
- ignore = strncasecmp(tokenStr, filename.c_str() + filenameLen - n, n) == 0;
- }
- } else if (n > 1 && tokenStr[n - 1] == '*') {
- // Match prefix.
- ignore = strncasecmp(tokenStr, filename.c_str(), n - 1) == 0;
- } else {
- ignore = strcasecmp(tokenStr, filename.c_str()) == 0;
- }
+ bool ignore = false;
+ size_t n = strlen(token_str);
+ if (*token_str == '*') {
+ // Math suffix.
+ token_str++;
+ n--;
+ if (n <= filename_len) {
+ ignore =
+ strncasecmp(token_str, filename.c_str() + filename_len - n, n) == 0;
+ }
+ } else if (n > 1 && token_str[n - 1] == '*') {
+ // Match prefix.
+ ignore = strncasecmp(token_str, filename.c_str(), n - 1) == 0;
+ } else {
+ ignore = strcasecmp(token_str, filename.c_str()) == 0;
+ }
- if (ignore) {
- if (chatty) {
- mDiag->warn(DiagMessage() << "skipping "
- << (type == FileType::kDirectory ? "dir '" : "file '")
- << filename << "' due to ignore pattern '"
- << token << "'");
- }
- return false;
- }
+ if (ignore) {
+ if (chatty) {
+ diag_->Warn(DiagMessage()
+ << "skipping "
+ << (type == FileType::kDirectory ? "dir '" : "file '")
+ << filename << "' due to ignore pattern '" << token << "'");
+ }
+ return false;
}
- return true;
+ }
+ return true;
}
-} // namespace file
-} // namespace aapt
+} // namespace file
+} // namespace aapt
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index 4d8a1feb63b1..a157dbd80893 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -17,18 +17,18 @@
#ifndef AAPT_FILES_H
#define AAPT_FILES_H
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/macros.h"
+#include "utils/FileMap.h"
+
#include "Diagnostics.h"
#include "Maybe.h"
#include "Source.h"
-
#include "util/StringPiece.h"
-#include <utils/FileMap.h>
-#include <cassert>
-#include <memory>
-#include <string>
-#include <vector>
-
namespace aapt {
namespace file {
@@ -39,29 +39,23 @@ constexpr const char sDirSep = '/';
#endif
enum class FileType {
- kUnknown = 0,
- kNonexistant,
- kRegular,
- kDirectory,
- kCharDev,
- kBlockDev,
- kFifo,
- kSymlink,
- kSocket,
+ kUnknown = 0,
+ kNonexistant,
+ kRegular,
+ kDirectory,
+ kCharDev,
+ kBlockDev,
+ kFifo,
+ kSymlink,
+ kSocket,
};
-FileType getFileType(const StringPiece& path);
-
-/*
- * Lists files under the directory `root`. Files are listed
- * with just their leaf (filename) names.
- */
-std::vector<std::string> listFiles(const StringPiece& root);
+FileType GetFileType(const StringPiece& path);
/*
* Appends a path to `base`, separated by the directory separator.
*/
-void appendPath(std::string* base, StringPiece part);
+void AppendPath(std::string* base, StringPiece part);
/*
* Makes all the directories in `path`. The last element in the path
@@ -72,73 +66,75 @@ bool mkdirs(const StringPiece& path);
/**
* Returns all but the last part of the path.
*/
-StringPiece getStem(const StringPiece& path);
+StringPiece GetStem(const StringPiece& path);
/**
* Returns the last part of the path with extension.
*/
-StringPiece getFilename(const StringPiece& path);
+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);
+StringPiece GetExtension(const StringPiece& path);
/**
* Converts a package name (com.android.app) to a path: com/android/app
*/
-std::string packageToPath(const StringPiece& package);
+std::string PackageToPath(const StringPiece& package);
/**
* Creates a FileMap for the file at path.
*/
-Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError);
+Maybe<android::FileMap> MmapPath(const StringPiece& path,
+ std::string* out_error);
/**
* 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);
+bool AppendArgsFromFile(const StringPiece& path,
+ std::vector<std::string>* out_arglist,
+ std::string* out_error);
/*
* Filter that determines which resource files/directories are
* processed by AAPT. Takes a pattern string supplied by the user.
- * Pattern format is specified in the
- * FileFilter::setPattern(const std::string&) method.
+ * Pattern format is specified in the FileFilter::SetPattern() method.
*/
class FileFilter {
-public:
- FileFilter(IDiagnostics* diag) : mDiag(diag) {
- }
-
- /*
- * Patterns syntax:
- * - Delimiter is :
- * - Entry can start with the flag ! to avoid printing a warning
- * about the file being ignored.
- * - Entry can have the flag "<dir>" to match only directories
- * or <file> to match only files. Default is to match both.
- * - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
- * where prefix/suffix must have at least 1 character (so that
- * we don't match a '*' catch-all pattern.)
- * - The special filenames "." and ".." are always ignored.
- * - Otherwise the full string is matched.
- * - match is not case-sensitive.
- */
- bool setPattern(const StringPiece& pattern);
-
- /**
- * Applies the filter, returning true for pass, false for fail.
- */
- bool operator()(const std::string& filename, FileType type) const;
-
-private:
- IDiagnostics* mDiag;
- std::vector<std::string> mPatternTokens;
+ public:
+ explicit FileFilter(IDiagnostics* diag) : diag_(diag) {}
+
+ /*
+ * Patterns syntax:
+ * - Delimiter is :
+ * - Entry can start with the flag ! to avoid printing a warning
+ * about the file being ignored.
+ * - Entry can have the flag "<dir>" to match only directories
+ * or <file> to match only files. Default is to match both.
+ * - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
+ * where prefix/suffix must have at least 1 character (so that
+ * we don't match a '*' catch-all pattern.)
+ * - The special filenames "." and ".." are always ignored.
+ * - Otherwise the full string is matched.
+ * - match is not case-sensitive.
+ */
+ bool SetPattern(const StringPiece& pattern);
+
+ /**
+ * Applies the filter, returning true for pass, false for fail.
+ */
+ bool operator()(const std::string& filename, FileType type) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileFilter);
+
+ IDiagnostics* diag_;
+ std::vector<std::string> pattern_tokens_;
};
-} // namespace file
-} // namespace aapt
+} // namespace file
+} // namespace aapt
-#endif // AAPT_FILES_H
+#endif // AAPT_FILES_H
diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp
index efb04593ff82..219c18397358 100644
--- a/tools/aapt2/util/Files_test.cpp
+++ b/tools/aapt2/util/Files_test.cpp
@@ -14,45 +14,46 @@
* limitations under the License.
*/
-#include "test/Test.h"
#include "util/Files.h"
#include <sstream>
+#include "test/Test.h"
+
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;
+ public:
+ void SetUp() override {
+ std::stringstream builder;
+ builder << "hello" << sDirSep << "there";
+ expected_path_ = builder.str();
+ }
+
+ protected:
+ std::string expected_path_;
};
-TEST_F(FilesTest, appendPath) {
- std::string base = "hello";
- appendPath(&base, "there");
- EXPECT_EQ(mExpectedPath, base);
+TEST_F(FilesTest, AppendPath) {
+ std::string base = "hello";
+ AppendPath(&base, "there");
+ EXPECT_EQ(expected_path_, base);
}
-TEST_F(FilesTest, appendPathWithLeadingOrTrailingSeparators) {
- std::string base = "hello/";
- appendPath(&base, "there");
- EXPECT_EQ(mExpectedPath, base);
+TEST_F(FilesTest, AppendPathWithLeadingOrTrailingSeparators) {
+ std::string base = "hello/";
+ AppendPath(&base, "there");
+ EXPECT_EQ(expected_path_, base);
- base = "hello";
- appendPath(&base, "/there");
- EXPECT_EQ(mExpectedPath, base);
+ base = "hello";
+ AppendPath(&base, "/there");
+ EXPECT_EQ(expected_path_, base);
- base = "hello/";
- appendPath(&base, "/there");
- EXPECT_EQ(mExpectedPath, base);
+ base = "hello/";
+ AppendPath(&base, "/there");
+ EXPECT_EQ(expected_path_, base);
}
-} // namespace files
-} // namespace aapt
+} // namespace files
+} // namespace aapt
diff --git a/tools/aapt2/util/ImmutableMap.h b/tools/aapt2/util/ImmutableMap.h
index b1f9e9d2fb57..59858e492c4c 100644
--- a/tools/aapt2/util/ImmutableMap.h
+++ b/tools/aapt2/util/ImmutableMap.h
@@ -17,68 +17,66 @@
#ifndef AAPT_UTIL_IMMUTABLEMAP_H
#define AAPT_UTIL_IMMUTABLEMAP_H
-#include "util/TypeTraits.h"
-
#include <utility>
#include <vector>
+#include "util/TypeTraits.h"
+
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)) {
+ static_assert(is_comparable<TKey, TKey>::value, "key is not comparable");
+
+ public:
+ using const_iterator =
+ typename std::vector<std::pair<TKey, TValue>>::const_iterator;
+
+ ImmutableMap(ImmutableMap&&) = default;
+ ImmutableMap& operator=(ImmutableMap&&) = default;
+
+ 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 end_iter = end();
+ auto iter = std::lower_bound(data_.begin(), end_iter, key, cmp);
+ if (iter == end_iter || iter->first == key) {
+ return iter;
}
+ return end_iter;
+ }
-public:
- using const_iterator = typename decltype(mData)::const_iterator;
-
- ImmutableMap(ImmutableMap&&) = default;
- ImmutableMap& operator=(ImmutableMap&&) = default;
+ const_iterator begin() const { return data_.begin(); }
- ImmutableMap(const ImmutableMap&) = delete;
- ImmutableMap& operator=(const ImmutableMap&) = delete;
+ const_iterator end() const { return data_.end(); }
- 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));
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ImmutableMap);
- 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;
- }
+ explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data)
+ : data_(std::move(data)) {}
- const_iterator begin() const {
- return mData.begin();
- }
-
- const_iterator end() const {
- return mData.end();
- }
+ std::vector<std::pair<TKey, TValue>> data_;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_UTIL_IMMUTABLEMAP_H */
diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h
index 595db960d5e5..b43f8e87fd68 100644
--- a/tools/aapt2/util/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -17,12 +17,13 @@
#ifndef AAPT_MAYBE_H
#define AAPT_MAYBE_H
-#include "util/TypeTraits.h"
-
-#include <cassert>
#include <type_traits>
#include <utility>
+#include "android-base/logging.h"
+
+#include "util/TypeTraits.h"
+
namespace aapt {
/**
@@ -32,280 +33,292 @@ namespace aapt {
*/
template <typename T>
class Maybe {
-public:
- /**
- * Construct Nothing.
- */
- Maybe();
+ public:
+ /**
+ * Construct Nothing.
+ */
+ Maybe();
+
+ ~Maybe();
- ~Maybe();
+ Maybe(const Maybe& rhs);
- Maybe(const Maybe& rhs);
+ template <typename U>
+ Maybe(const Maybe<U>& rhs); // NOLINT(implicit)
- template <typename U>
- Maybe(const Maybe<U>& rhs);
+ Maybe(Maybe&& rhs);
- Maybe(Maybe&& rhs);
+ template <typename U>
+ Maybe(Maybe<U>&& rhs); // NOLINT(implicit)
- template <typename U>
- Maybe(Maybe<U>&& rhs);
+ Maybe& operator=(const Maybe& rhs);
- Maybe& operator=(const Maybe& rhs);
+ template <typename U>
+ Maybe& operator=(const Maybe<U>& rhs);
- template <typename U>
- Maybe& operator=(const Maybe<U>& rhs);
+ Maybe& operator=(Maybe&& rhs);
- Maybe& operator=(Maybe&& rhs);
+ template <typename U>
+ Maybe& operator=(Maybe<U>&& rhs);
- template <typename U>
- Maybe& operator=(Maybe<U>&& rhs);
+ /**
+ * Construct a Maybe holding a value.
+ */
+ Maybe(const T& value); // NOLINT(implicit)
- /**
- * Construct a Maybe holding a value.
- */
- Maybe(const T& value);
+ /**
+ * Construct a Maybe holding a value.
+ */
+ Maybe(T&& value); // NOLINT(implicit)
- /**
- * Construct a Maybe holding a value.
- */
- Maybe(T&& value);
+ /**
+ * True if this holds a value, false if
+ * it holds Nothing.
+ */
+ explicit operator bool() const;
- /**
- * True if this holds a value, false if
- * it holds Nothing.
- */
- explicit operator bool() const;
+ /**
+ * Gets the value if one exists, or else
+ * panics.
+ */
+ T& value();
- /**
- * Gets the value if one exists, or else
- * panics.
- */
- T& value();
+ /**
+ * Gets the value if one exists, or else
+ * panics.
+ */
+ const T& value() const;
- /**
- * Gets the value if one exists, or else
- * panics.
- */
- const T& value() const;
+ T value_or_default(const T& def) const;
-private:
- template <typename U>
- friend class Maybe;
+ private:
+ template <typename U>
+ friend class Maybe;
- template <typename U>
- Maybe& copy(const Maybe<U>& rhs);
+ template <typename U>
+ Maybe& copy(const Maybe<U>& rhs);
- template <typename U>
- Maybe& move(Maybe<U>&& rhs);
+ template <typename U>
+ Maybe& move(Maybe<U>&& rhs);
- void destroy();
+ void destroy();
- bool mNothing;
+ bool nothing_;
- typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage;
+ typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_;
};
template <typename T>
-Maybe<T>::Maybe()
-: mNothing(true) {
-}
+Maybe<T>::Maybe() : nothing_(true) {}
template <typename T>
Maybe<T>::~Maybe() {
- if (!mNothing) {
- destroy();
- }
+ if (!nothing_) {
+ destroy();
+ }
}
template <typename T>
-Maybe<T>::Maybe(const Maybe& rhs)
-: mNothing(rhs.mNothing) {
- if (!rhs.mNothing) {
- new (&mStorage) T(reinterpret_cast<const T&>(rhs.mStorage));
- }
+Maybe<T>::Maybe(const Maybe& rhs) : nothing_(rhs.nothing_) {
+ if (!rhs.nothing_) {
+ new (&storage_) T(reinterpret_cast<const T&>(rhs.storage_));
+ }
}
template <typename T>
template <typename U>
-Maybe<T>::Maybe(const Maybe<U>& rhs)
-: mNothing(rhs.mNothing) {
- if (!rhs.mNothing) {
- new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
- }
+Maybe<T>::Maybe(const Maybe<U>& rhs) : nothing_(rhs.nothing_) {
+ if (!rhs.nothing_) {
+ new (&storage_) T(reinterpret_cast<const U&>(rhs.storage_));
+ }
}
template <typename T>
-Maybe<T>::Maybe(Maybe&& rhs)
-: mNothing(rhs.mNothing) {
- if (!rhs.mNothing) {
- rhs.mNothing = true;
-
- // Move the value from rhs.
- new (&mStorage) T(std::move(reinterpret_cast<T&>(rhs.mStorage)));
- rhs.destroy();
- }
+Maybe<T>::Maybe(Maybe&& rhs) : nothing_(rhs.nothing_) {
+ if (!rhs.nothing_) {
+ rhs.nothing_ = true;
+
+ // Move the value from rhs.
+ new (&storage_) T(std::move(reinterpret_cast<T&>(rhs.storage_)));
+ rhs.destroy();
+ }
}
template <typename T>
template <typename U>
-Maybe<T>::Maybe(Maybe<U>&& rhs)
-: mNothing(rhs.mNothing) {
- if (!rhs.mNothing) {
- rhs.mNothing = true;
-
- // Move the value from rhs.
- new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
- rhs.destroy();
- }
+Maybe<T>::Maybe(Maybe<U>&& rhs) : nothing_(rhs.nothing_) {
+ if (!rhs.nothing_) {
+ rhs.nothing_ = true;
+
+ // Move the value from rhs.
+ new (&storage_) T(std::move(reinterpret_cast<U&>(rhs.storage_)));
+ rhs.destroy();
+ }
}
template <typename T>
inline Maybe<T>& Maybe<T>::operator=(const Maybe& rhs) {
- // Delegate to the actual assignment.
- return copy(rhs);
+ // Delegate to the actual assignment.
+ return copy(rhs);
}
template <typename T>
template <typename U>
inline Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) {
- return copy(rhs);
+ return copy(rhs);
}
template <typename T>
template <typename U>
Maybe<T>& Maybe<T>::copy(const Maybe<U>& rhs) {
- if (mNothing && rhs.mNothing) {
- // Both are nothing, nothing to do.
- return *this;
- } else if (!mNothing && !rhs.mNothing) {
- // We both are something, so assign rhs to us.
- reinterpret_cast<T&>(mStorage) = reinterpret_cast<const U&>(rhs.mStorage);
- } else if (mNothing) {
- // We are nothing but rhs is something.
- mNothing = rhs.mNothing;
-
- // Copy the value from rhs.
- new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
- } else {
- // We are something but rhs is nothing, so destroy our value.
- mNothing = rhs.mNothing;
- destroy();
- }
+ if (nothing_ && rhs.nothing_) {
+ // Both are nothing, nothing to do.
return *this;
+ } else if (!nothing_ && !rhs.nothing_) {
+ // We both are something, so assign rhs to us.
+ reinterpret_cast<T&>(storage_) = reinterpret_cast<const U&>(rhs.storage_);
+ } else if (nothing_) {
+ // We are nothing but rhs is something.
+ nothing_ = rhs.nothing_;
+
+ // Copy the value from rhs.
+ new (&storage_) T(reinterpret_cast<const U&>(rhs.storage_));
+ } else {
+ // We are something but rhs is nothing, so destroy our value.
+ nothing_ = rhs.nothing_;
+ destroy();
+ }
+ return *this;
}
template <typename T>
inline Maybe<T>& Maybe<T>::operator=(Maybe&& rhs) {
- // Delegate to the actual assignment.
- return move(std::forward<Maybe<T>>(rhs));
+ // Delegate to the actual assignment.
+ return move(std::forward<Maybe<T>>(rhs));
}
template <typename T>
template <typename U>
inline Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) {
- return move(std::forward<Maybe<U>>(rhs));
+ return move(std::forward<Maybe<U>>(rhs));
}
template <typename T>
template <typename U>
Maybe<T>& Maybe<T>::move(Maybe<U>&& rhs) {
- if (mNothing && rhs.mNothing) {
- // Both are nothing, nothing to do.
- return *this;
- } else if (!mNothing && !rhs.mNothing) {
- // We both are something, so move assign rhs to us.
- rhs.mNothing = true;
- reinterpret_cast<T&>(mStorage) = std::move(reinterpret_cast<U&>(rhs.mStorage));
- rhs.destroy();
- } else if (mNothing) {
- // We are nothing but rhs is something.
- mNothing = false;
- rhs.mNothing = true;
-
- // Move the value from rhs.
- new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
- rhs.destroy();
- } else {
- // We are something but rhs is nothing, so destroy our value.
- mNothing = true;
- destroy();
- }
+ if (nothing_ && rhs.nothing_) {
+ // Both are nothing, nothing to do.
return *this;
+ } else if (!nothing_ && !rhs.nothing_) {
+ // We both are something, so move assign rhs to us.
+ rhs.nothing_ = true;
+ reinterpret_cast<T&>(storage_) =
+ std::move(reinterpret_cast<U&>(rhs.storage_));
+ rhs.destroy();
+ } else if (nothing_) {
+ // We are nothing but rhs is something.
+ nothing_ = false;
+ rhs.nothing_ = true;
+
+ // Move the value from rhs.
+ new (&storage_) T(std::move(reinterpret_cast<U&>(rhs.storage_)));
+ rhs.destroy();
+ } else {
+ // We are something but rhs is nothing, so destroy our value.
+ nothing_ = true;
+ destroy();
+ }
+ return *this;
}
template <typename T>
-Maybe<T>::Maybe(const T& value)
-: mNothing(false) {
- new (&mStorage) T(value);
+Maybe<T>::Maybe(const T& value) : nothing_(false) {
+ new (&storage_) T(value);
}
template <typename T>
-Maybe<T>::Maybe(T&& value)
-: mNothing(false) {
- new (&mStorage) T(std::forward<T>(value));
+Maybe<T>::Maybe(T&& value) : nothing_(false) {
+ new (&storage_) T(std::forward<T>(value));
}
template <typename T>
Maybe<T>::operator bool() const {
- return !mNothing;
+ return !nothing_;
}
template <typename T>
T& Maybe<T>::value() {
- assert(!mNothing && "Maybe<T>::value() called on Nothing");
- return reinterpret_cast<T&>(mStorage);
+ CHECK(!nothing_) << "Maybe<T>::value() called on Nothing";
+ return reinterpret_cast<T&>(storage_);
}
template <typename T>
const T& Maybe<T>::value() const {
- assert(!mNothing && "Maybe<T>::value() called on Nothing");
- return reinterpret_cast<const T&>(mStorage);
+ CHECK(!nothing_) << "Maybe<T>::value() called on Nothing";
+ return reinterpret_cast<const T&>(storage_);
+}
+
+template <typename T>
+T Maybe<T>::value_or_default(const T& def) const {
+ if (nothing_) {
+ return def;
+ }
+ return reinterpret_cast<const T&>(storage_);
}
template <typename T>
void Maybe<T>::destroy() {
- reinterpret_cast<T&>(mStorage).~T();
+ reinterpret_cast<T&>(storage_).~T();
}
template <typename T>
inline Maybe<typename std::remove_reference<T>::type> make_value(T&& value) {
- return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value));
+ return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value));
}
template <typename T>
inline Maybe<T> make_nothing() {
- return Maybe<T>();
+ 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
+ * 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;
+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);
+typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=(
+ const Maybe<T>& a, const Maybe<U>& b) {
+ return !(a == b);
+}
+
+template <typename T, typename U>
+typename std::enable_if<has_lt_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 false;
+ }
+ return !a;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_MAYBE_H
+#endif // AAPT_MAYBE_H
diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
index 5d42dc3ac3ab..ca14793dd5c3 100644
--- a/tools/aapt2/util/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -14,122 +14,116 @@
* limitations under the License.
*/
-#include "test/Common.h"
#include "util/Maybe.h"
-#include <gtest/gtest.h>
#include <string>
+#include "test/Test.h"
+
namespace aapt {
struct Dummy {
- Dummy() {
- data = new int;
- *data = 1;
- std::cerr << "Construct Dummy{0x" << (void *) this
- << "} with data=0x" << (void*) data
- << std::endl;
- }
-
- Dummy(const Dummy& rhs) {
- data = nullptr;
- if (rhs.data) {
- data = new int;
- *data = *rhs.data;
- }
- std::cerr << "CopyConstruct Dummy{0x" << (void *) this
- << "} from Dummy{0x" << (const void*) &rhs
- << "}" << std::endl;
- }
-
- Dummy(Dummy&& rhs) {
- data = rhs.data;
- rhs.data = nullptr;
- std::cerr << "MoveConstruct Dummy{0x" << (void *) this
- << "} from Dummy{0x" << (const void*) &rhs
- << "}" << std::endl;
- }
-
- Dummy& operator=(const Dummy& rhs) {
- delete data;
- data = nullptr;
-
- if (rhs.data) {
- data = new int;
- *data = *rhs.data;
- }
- std::cerr << "CopyAssign Dummy{0x" << (void *) this
- << "} from Dummy{0x" << (const void*) &rhs
- << "}" << std::endl;
- return *this;
- }
-
- Dummy& operator=(Dummy&& rhs) {
- delete data;
- data = rhs.data;
- rhs.data = nullptr;
- std::cerr << "MoveAssign Dummy{0x" << (void *) this
- << "} from Dummy{0x" << (const void*) &rhs
- << "}" << std::endl;
- return *this;
+ Dummy() {
+ data = new int;
+ *data = 1;
+ std::cerr << "Construct Dummy{0x" << (void*)this << "} with data=0x"
+ << (void*)data << std::endl;
+ }
+
+ Dummy(const Dummy& rhs) {
+ data = nullptr;
+ if (rhs.data) {
+ data = new int;
+ *data = *rhs.data;
}
-
- ~Dummy() {
- std::cerr << "Destruct Dummy{0x" << (void *) this
- << "} with data=0x" << (void*) data
- << std::endl;
- delete data;
+ std::cerr << "CopyConstruct Dummy{0x" << (void*)this << "} from Dummy{0x"
+ << (const void*)&rhs << "}" << std::endl;
+ }
+
+ Dummy(Dummy&& rhs) {
+ data = rhs.data;
+ rhs.data = nullptr;
+ std::cerr << "MoveConstruct Dummy{0x" << (void*)this << "} from Dummy{0x"
+ << (const void*)&rhs << "}" << std::endl;
+ }
+
+ Dummy& operator=(const Dummy& rhs) {
+ delete data;
+ data = nullptr;
+
+ if (rhs.data) {
+ data = new int;
+ *data = *rhs.data;
}
-
- int* data;
+ std::cerr << "CopyAssign Dummy{0x" << (void*)this << "} from Dummy{0x"
+ << (const void*)&rhs << "}" << std::endl;
+ return *this;
+ }
+
+ Dummy& operator=(Dummy&& rhs) {
+ delete data;
+ data = rhs.data;
+ rhs.data = nullptr;
+ std::cerr << "MoveAssign Dummy{0x" << (void*)this << "} from Dummy{0x"
+ << (const void*)&rhs << "}" << std::endl;
+ return *this;
+ }
+
+ ~Dummy() {
+ std::cerr << "Destruct Dummy{0x" << (void*)this << "} with data=0x"
+ << (void*)data << std::endl;
+ delete data;
+ }
+
+ int* data;
};
TEST(MaybeTest, MakeNothing) {
- Maybe<int> val = make_nothing<int>();
- AAPT_EXPECT_FALSE(val);
+ Maybe<int> val = make_nothing<int>();
+ AAPT_EXPECT_FALSE(val);
- Maybe<std::string> val2 = make_nothing<std::string>();
- AAPT_EXPECT_FALSE(val2);
+ Maybe<std::string> val2 = make_nothing<std::string>();
+ AAPT_EXPECT_FALSE(val2);
- val2 = make_nothing<std::string>();
- AAPT_EXPECT_FALSE(val2);
+ val2 = make_nothing<std::string>();
+ AAPT_EXPECT_FALSE(val2);
}
TEST(MaybeTest, MakeSomething) {
- Maybe<int> val = make_value(23);
- AAPT_ASSERT_TRUE(val);
- EXPECT_EQ(23, val.value());
+ Maybe<int> val = make_value(23);
+ AAPT_ASSERT_TRUE(val);
+ EXPECT_EQ(23, val.value());
- Maybe<std::string> val2 = make_value(std::string("hey"));
- AAPT_ASSERT_TRUE(val2);
- EXPECT_EQ(std::string("hey"), val2.value());
+ Maybe<std::string> val2 = make_value(std::string("hey"));
+ AAPT_ASSERT_TRUE(val2);
+ EXPECT_EQ(std::string("hey"), val2.value());
}
TEST(MaybeTest, Lifecycle) {
- Maybe<Dummy> val = make_nothing<Dummy>();
+ Maybe<Dummy> val = make_nothing<Dummy>();
- Maybe<Dummy> val2 = make_value(Dummy());
+ Maybe<Dummy> val2 = make_value(Dummy());
}
TEST(MaybeTest, MoveAssign) {
- Maybe<Dummy> val;
- {
- Maybe<Dummy> val2 = Dummy();
- val = std::move(val2);
- }
+ Maybe<Dummy> val;
+ {
+ Maybe<Dummy> val2 = Dummy();
+ val = std::move(val2);
+ }
}
TEST(MaybeTest, Equality) {
- Maybe<int> a = 1;
- Maybe<int> b = 1;
- Maybe<int> c;
+ Maybe<int> a = 1;
+ Maybe<int> b = 1;
+ Maybe<int> c;
- Maybe<int> emptyA, emptyB;
+ Maybe<int> emptyA, emptyB;
- EXPECT_EQ(a, b);
- EXPECT_EQ(b, a);
- EXPECT_NE(a, c);
- EXPECT_EQ(emptyA, emptyB);
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(b, a);
+ EXPECT_NE(a, c);
+ EXPECT_EQ(emptyA, emptyB);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h
index f91bccc93019..5144b1f1fd6a 100644
--- a/tools/aapt2/util/StringPiece.h
+++ b/tools/aapt2/util/StringPiece.h
@@ -19,8 +19,10 @@
#include <ostream>
#include <string>
-#include <utils/String8.h>
-#include <utils/Unicode.h>
+
+#include "utils/JenkinsHash.h"
+#include "utils/String8.h"
+#include "utils/Unicode.h"
namespace aapt {
@@ -34,42 +36,46 @@ namespace aapt {
*/
template <typename TChar>
class BasicStringPiece {
-public:
- using const_iterator = const TChar*;
- using difference_type = size_t;
-
- BasicStringPiece();
- BasicStringPiece(const BasicStringPiece<TChar>& str);
- BasicStringPiece(const std::basic_string<TChar>& str);
- BasicStringPiece(const TChar* str);
- BasicStringPiece(const TChar* str, size_t len);
-
- BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
- BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
-
- BasicStringPiece<TChar> substr(size_t start, size_t len) const;
- BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const;
-
- const TChar* data() const;
- size_t length() const;
- size_t size() const;
- 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;
- bool operator==(const BasicStringPiece<TChar>& rhs) const;
- bool operator!=(const BasicStringPiece<TChar>& rhs) const;
-
- const_iterator begin() const;
- const_iterator end() const;
-
-private:
- const TChar* mData;
- size_t mLength;
+ public:
+ using const_iterator = const TChar*;
+ using difference_type = size_t;
+
+ // End of string marker.
+ constexpr static const size_t npos = static_cast<size_t>(-1);
+
+ BasicStringPiece();
+ BasicStringPiece(const BasicStringPiece<TChar>& str);
+ BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(implicit)
+ BasicStringPiece(const TChar* str); // NOLINT(implicit)
+ BasicStringPiece(const TChar* str, size_t len);
+
+ BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
+ BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
+
+ BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const;
+ BasicStringPiece<TChar> substr(
+ BasicStringPiece<TChar>::const_iterator begin,
+ BasicStringPiece<TChar>::const_iterator end) const;
+
+ const TChar* data() const;
+ size_t length() const;
+ size_t size() const;
+ 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;
+ bool operator==(const BasicStringPiece<TChar>& rhs) const;
+ bool operator!=(const BasicStringPiece<TChar>& rhs) const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ private:
+ const TChar* data_;
+ size_t length_;
};
using StringPiece = BasicStringPiece<char>;
@@ -80,181 +86,213 @@ using StringPiece16 = BasicStringPiece<char16_t>;
//
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece() : mData(nullptr) , mLength(0) {
-}
+constexpr const size_t BasicStringPiece<TChar>::npos;
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) :
- mData(str.mData), mLength(str.mLength) {
-}
+inline BasicStringPiece<TChar>::BasicStringPiece()
+ : data_(nullptr), length_(0) {}
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) :
- mData(str.data()), mLength(str.length()) {
-}
+inline BasicStringPiece<TChar>::BasicStringPiece(
+ const BasicStringPiece<TChar>& str)
+ : data_(str.data_), length_(str.length_) {}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece(
+ const std::basic_string<TChar>& str)
+ : data_(str.data()), length_(str.length()) {}
template <>
-inline BasicStringPiece<char>::BasicStringPiece(const char* str) :
- mData(str), mLength(str != nullptr ? strlen(str) : 0) {
-}
+inline BasicStringPiece<char>::BasicStringPiece(const char* str)
+ : data_(str), length_(str != nullptr ? strlen(str) : 0) {}
template <>
-inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) :
- mData(str), mLength(str != nullptr ? strlen16(str) : 0) {
-}
+inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str)
+ : data_(str), length_(str != nullptr ? strlen16(str) : 0) {}
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) :
- mData(str), mLength(len) {
-}
+inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len)
+ : data_(str), length_(len) {}
template <typename TChar>
inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
- const BasicStringPiece<TChar>& rhs) {
- mData = rhs.mData;
- mLength = rhs.mLength;
- return *this;
+ const BasicStringPiece<TChar>& rhs) {
+ data_ = rhs.data_;
+ length_ = rhs.length_;
+ return *this;
}
template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
- mData = str;
- mLength = len;
- return *this;
+inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(
+ const TChar* str, size_t len) {
+ data_ = str;
+ length_ = len;
+ return *this;
}
-
template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
- if (start + len > mLength) {
- return BasicStringPiece<TChar>();
- }
- return BasicStringPiece<TChar>(mData + start, len);
+inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
+ size_t start, size_t len) const {
+ if (len == npos) {
+ len = length_ - start;
+ }
+
+ if (start > length_ || start + len > length_) {
+ return BasicStringPiece<TChar>();
+ }
+ return BasicStringPiece<TChar>(data_ + start, len);
}
template <typename TChar>
inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
- BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const {
- return BasicStringPiece<TChar>(begin, end - begin);
+ BasicStringPiece<TChar>::const_iterator begin,
+ BasicStringPiece<TChar>::const_iterator end) const {
+ return BasicStringPiece<TChar>(begin, end - begin);
}
template <typename TChar>
inline const TChar* BasicStringPiece<TChar>::data() const {
- return mData;
+ return data_;
}
template <typename TChar>
inline size_t BasicStringPiece<TChar>::length() const {
- return mLength;
+ return length_;
}
template <typename TChar>
inline size_t BasicStringPiece<TChar>::size() const {
- return mLength;
+ return length_;
}
template <typename TChar>
inline bool BasicStringPiece<TChar>::empty() const {
- return mLength == 0;
+ return length_ == 0;
}
template <typename TChar>
-inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const {
- return std::basic_string<TChar>(mData, mLength);
+inline std::basic_string<TChar> BasicStringPiece<TChar>::ToString() const {
+ return std::basic_string<TChar>(data_, length_);
}
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;
+inline bool BasicStringPiece<char>::contains(
+ const BasicStringPiece<char>& rhs) const {
+ if (!data_ || !rhs.data_) {
+ return false;
+ }
+ if (rhs.length_ > length_) {
+ return false;
+ }
+ return strstr(data_, rhs.data_) != nullptr;
}
template <>
-inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
- const char nullStr = '\0';
- const char* b1 = mData != nullptr ? mData : &nullStr;
- const char* e1 = b1 + mLength;
- const char* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
- const char* e2 = b2 + rhs.mLength;
-
- while (b1 < e1 && b2 < e2) {
- const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
- if (d) {
- return d;
- }
+inline int BasicStringPiece<char>::compare(
+ const BasicStringPiece<char>& rhs) const {
+ const char nullStr = '\0';
+ const char* b1 = data_ != nullptr ? data_ : &nullStr;
+ const char* e1 = b1 + length_;
+ const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
+ const char* e2 = b2 + rhs.length_;
+
+ while (b1 < e1 && b2 < e2) {
+ const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
+ if (d) {
+ return d;
}
- return static_cast<int>(mLength - rhs.mLength);
+ }
+ return static_cast<int>(length_ - rhs.length_);
}
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
- android::String8 utf8(str.data(), str.size());
- return out.write(utf8.string(), utf8.size());
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const BasicStringPiece<char16_t>& str) {
+ android::String8 utf8(str.data(), str.size());
+ 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;
+inline bool BasicStringPiece<char16_t>::contains(
+ const BasicStringPiece<char16_t>& rhs) const {
+ if (!data_ || !rhs.data_) {
+ return false;
+ }
+ if (rhs.length_ > length_) {
+ return false;
+ }
+ return strstr16(data_, rhs.data_) != nullptr;
}
template <>
-inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
- const char16_t nullStr = u'\0';
- const char16_t* b1 = mData != nullptr ? mData : &nullStr;
- const char16_t* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
- return strzcmp16(b1, mLength, b2, rhs.mLength);
+inline int BasicStringPiece<char16_t>::compare(
+ const BasicStringPiece<char16_t>& rhs) const {
+ const char16_t nullStr = u'\0';
+ const char16_t* b1 = data_ != nullptr ? data_ : &nullStr;
+ const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
+ return strzcmp16(b1, length_, b2, rhs.length_);
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) < 0;
+inline bool BasicStringPiece<TChar>::operator<(
+ const BasicStringPiece<TChar>& rhs) const {
+ return compare(rhs) < 0;
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) > 0;
+inline bool BasicStringPiece<TChar>::operator>(
+ const BasicStringPiece<TChar>& rhs) const {
+ return compare(rhs) > 0;
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) == 0;
+inline bool BasicStringPiece<TChar>::operator==(
+ const BasicStringPiece<TChar>& rhs) const {
+ return compare(rhs) == 0;
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) != 0;
+inline bool BasicStringPiece<TChar>::operator!=(
+ const BasicStringPiece<TChar>& rhs) const {
+ return compare(rhs) != 0;
}
template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
- return mData;
+inline typename BasicStringPiece<TChar>::const_iterator
+BasicStringPiece<TChar>::begin() const {
+ return data_;
}
template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
- return mData + mLength;
+inline typename BasicStringPiece<TChar>::const_iterator
+BasicStringPiece<TChar>::end() const {
+ return data_ + length_;
}
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
- return out.write(str.data(), str.size());
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const BasicStringPiece<char>& str) {
+ return out.write(str.data(), str.size());
}
-} // namespace aapt
+} // 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());
+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
+namespace std {
+
+template <typename TChar>
+struct hash<aapt::BasicStringPiece<TChar>> {
+ size_t operator()(const aapt::BasicStringPiece<TChar>& str) const {
+ uint32_t hashCode = android::JenkinsHashMixBytes(
+ 0, reinterpret_cast<const uint8_t*>(str.data()),
+ sizeof(TChar) * str.size());
+ return static_cast<size_t>(hashCode);
+ }
+};
+
+} // namespace std
+
+#endif // AAPT_STRING_PIECE_H
diff --git a/tools/aapt2/util/StringPiece_test.cpp b/tools/aapt2/util/StringPiece_test.cpp
index 853a9a46fde8..048961d49584 100644
--- a/tools/aapt2/util/StringPiece_test.cpp
+++ b/tools/aapt2/util/StringPiece_test.cpp
@@ -14,81 +14,82 @@
* limitations under the License.
*/
+#include "util/StringPiece.h"
+
#include <algorithm>
-#include <gtest/gtest.h>
#include <string>
#include <vector>
-#include "util/StringPiece.h"
+#include "test/Test.h"
namespace aapt {
TEST(StringPieceTest, CompareNonNullTerminatedPiece) {
- StringPiece a("hello world", 5);
- StringPiece b("hello moon", 5);
- EXPECT_EQ(a, b);
+ StringPiece a("hello world", 5);
+ StringPiece b("hello moon", 5);
+ EXPECT_EQ(a, b);
- StringPiece16 a16(u"hello world", 5);
- StringPiece16 b16(u"hello moon", 5);
- EXPECT_EQ(a16, b16);
+ StringPiece16 a16(u"hello world", 5);
+ StringPiece16 b16(u"hello moon", 5);
+ EXPECT_EQ(a16, b16);
}
TEST(StringPieceTest, PiecesHaveCorrectSortOrder) {
- std::u16string testing(u"testing");
- std::u16string banana(u"banana");
- std::u16string car(u"car");
+ std::string testing("testing");
+ std::string banana("banana");
+ std::string car("car");
- EXPECT_TRUE(StringPiece16(testing) > banana);
- EXPECT_TRUE(StringPiece16(testing) > car);
- EXPECT_TRUE(StringPiece16(banana) < testing);
- EXPECT_TRUE(StringPiece16(banana) < car);
- EXPECT_TRUE(StringPiece16(car) < testing);
- EXPECT_TRUE(StringPiece16(car) > banana);
+ EXPECT_TRUE(StringPiece(testing) > banana);
+ EXPECT_TRUE(StringPiece(testing) > car);
+ EXPECT_TRUE(StringPiece(banana) < testing);
+ EXPECT_TRUE(StringPiece(banana) < car);
+ EXPECT_TRUE(StringPiece(car) < testing);
+ EXPECT_TRUE(StringPiece(car) > banana);
}
TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) {
- std::string testing("testing");
- std::string banana("banana");
- std::string car("car");
+ std::string testing("testing");
+ std::string banana("banana");
+ std::string car("car");
- EXPECT_TRUE(StringPiece(testing) > banana);
- EXPECT_TRUE(StringPiece(testing) > car);
- EXPECT_TRUE(StringPiece(banana) < testing);
- EXPECT_TRUE(StringPiece(banana) < car);
- EXPECT_TRUE(StringPiece(car) < testing);
- EXPECT_TRUE(StringPiece(car) > banana);
+ EXPECT_TRUE(StringPiece(testing) > banana);
+ EXPECT_TRUE(StringPiece(testing) > car);
+ EXPECT_TRUE(StringPiece(banana) < testing);
+ EXPECT_TRUE(StringPiece(banana) < car);
+ EXPECT_TRUE(StringPiece(car) < testing);
+ 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.");
+ StringPiece text("I am a leaf on the wind.");
+ StringPiece start_needle("I am");
+ StringPiece end_needle("wind.");
+ StringPiece middle_needle("leaf");
+ StringPiece empty_needle("");
+ StringPiece missing_needle("soar");
+ StringPiece long_needle("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));
+ EXPECT_TRUE(text.contains(start_needle));
+ EXPECT_TRUE(text.contains(end_needle));
+ EXPECT_TRUE(text.contains(middle_needle));
+ EXPECT_TRUE(text.contains(empty_needle));
+ EXPECT_FALSE(text.contains(missing_needle));
+ EXPECT_FALSE(text.contains(long_needle));
- 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.");
+ StringPiece16 text16(u"I am a leaf on the wind.");
+ StringPiece16 start_needle16(u"I am");
+ StringPiece16 end_needle16(u"wind.");
+ StringPiece16 middle_needle16(u"leaf");
+ StringPiece16 empty_needle16(u"");
+ StringPiece16 missing_needle16(u"soar");
+ StringPiece16 long_needle16(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));
+ EXPECT_TRUE(text16.contains(start_needle16));
+ EXPECT_TRUE(text16.contains(end_needle16));
+ EXPECT_TRUE(text16.contains(middle_needle16));
+ EXPECT_TRUE(text16.contains(empty_needle16));
+ EXPECT_FALSE(text16.contains(missing_needle16));
+ EXPECT_FALSE(text16.contains(long_needle16));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/util/TypeTraits.h b/tools/aapt2/util/TypeTraits.h
index 76c13d615e41..b6539edce6d9 100644
--- a/tools/aapt2/util/TypeTraits.h
+++ b/tools/aapt2/util/TypeTraits.h
@@ -21,19 +21,20 @@
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 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, <);
@@ -43,9 +44,10 @@ DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <);
*/
template <typename T, typename U>
struct is_comparable {
- static constexpr bool value = has_eq_op<T, U>::value && has_lt_op<T, U>::value;
+ static constexpr bool value =
+ has_eq_op<T, U>::value && has_lt_op<T, U>::value;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_UTIL_TYPETRAITS_H */
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index e07c88ec9579..d5c0c8a7a5fe 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -14,486 +14,558 @@
* limitations under the License.
*/
+#include "util/Util.h"
#include "util/BigBuffer.h"
#include "util/Maybe.h"
#include "util/StringPiece.h"
-#include "util/Util.h"
+#include <utils/Unicode.h>
#include <algorithm>
#include <ostream>
#include <string>
-#include <utils/Unicode.h>
#include <vector>
namespace aapt {
namespace util {
-static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
- const std::function<char(char)>& f) {
- std::vector<std::string> parts;
- const StringPiece::const_iterator end = std::end(str);
- StringPiece::const_iterator start = std::begin(str);
- StringPiece::const_iterator current;
- do {
- current = std::find(start, end, sep);
- parts.emplace_back(str.substr(start, current).toString());
- if (f) {
- std::string& part = parts.back();
- std::transform(part.begin(), part.end(), part.begin(), f);
- }
- start = current + 1;
- } while (current != end);
- return parts;
+static std::vector<std::string> SplitAndTransform(
+ const StringPiece& str, char sep, const std::function<char(char)>& f) {
+ std::vector<std::string> parts;
+ const StringPiece::const_iterator end = std::end(str);
+ StringPiece::const_iterator start = std::begin(str);
+ StringPiece::const_iterator current;
+ do {
+ current = std::find(start, end, sep);
+ parts.emplace_back(str.substr(start, current).ToString());
+ if (f) {
+ std::string& part = parts.back();
+ std::transform(part.begin(), part.end(), part.begin(), f);
+ }
+ start = current + 1;
+ } while (current != end);
+ return parts;
}
-std::vector<std::string> split(const StringPiece& str, char sep) {
- return splitAndTransform(str, sep, nullptr);
+std::vector<std::string> Split(const StringPiece& str, char sep) {
+ return SplitAndTransform(str, sep, nullptr);
}
-std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep) {
- return splitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
+ return SplitAndTransform(str, sep, ::tolower);
}
-StringPiece16 trimWhitespace(const StringPiece16& str) {
- if (str.size() == 0 || str.data() == nullptr) {
- return str;
- }
-
- const char16_t* start = str.data();
- const char16_t* end = str.data() + str.length();
-
- while (start != end && util::isspace16(*start)) {
- start++;
- }
-
- while (end != start && util::isspace16(*(end - 1))) {
- end--;
- }
+bool StartsWith(const StringPiece& str, const StringPiece& prefix) {
+ if (str.size() < prefix.size()) {
+ return false;
+ }
+ return str.substr(0, prefix.size()) == prefix;
+}
- return StringPiece16(start, end - start);
+bool EndsWith(const StringPiece& str, const StringPiece& suffix) {
+ if (str.size() < suffix.size()) {
+ return false;
+ }
+ return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
}
-StringPiece trimWhitespace(const StringPiece& str) {
- if (str.size() == 0 || str.data() == nullptr) {
- return str;
- }
+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();
+ const char* start = str.data();
+ const char* end = str.data() + str.length();
- while (start != end && isspace(*start)) {
- start++;
- }
+ while (start != end && isspace(*start)) {
+ start++;
+ }
- while (end != start && isspace(*(end - 1))) {
- end--;
- }
+ while (end != start && isspace(*(end - 1))) {
+ end--;
+ }
- return StringPiece(start, end - start);
+ return StringPiece(start, end - start);
}
-StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
- const StringPiece16& allowedChars) {
- const auto endIter = str.end();
- for (auto iter = str.begin(); iter != endIter; ++iter) {
- char16_t c = *iter;
- if ((c >= u'a' && c <= u'z') ||
- (c >= u'A' && c <= u'Z') ||
- (c >= u'0' && c <= u'9')) {
- continue;
- }
-
- bool match = false;
- for (char16_t i : allowedChars) {
- if (c == i) {
- match = true;
- break;
- }
- }
-
- if (!match) {
- return iter;
- }
+StringPiece::const_iterator FindNonAlphaNumericAndNotInSet(
+ const StringPiece& str, const StringPiece& allowed_chars) {
+ const auto end_iter = str.end();
+ for (auto iter = str.begin(); iter != end_iter; ++iter) {
+ char c = *iter;
+ if ((c >= u'a' && c <= u'z') || (c >= u'A' && c <= u'Z') ||
+ (c >= u'0' && c <= u'9')) {
+ continue;
}
- return endIter;
-}
-
-bool isJavaClassName(const StringPiece16& str) {
- size_t pieces = 0;
- for (const StringPiece16& piece : tokenize(str, u'.')) {
- pieces++;
- if (piece.empty()) {
- return false;
- }
- // Can't have starting or trailing $ character.
- if (piece.data()[0] == u'$' || piece.data()[piece.size() - 1] == u'$') {
- return false;
- }
+ bool match = false;
+ for (char i : allowed_chars) {
+ if (c == i) {
+ match = true;
+ break;
+ }
+ }
- if (findNonAlphaNumericAndNotInSet(piece, u"$_") != piece.end()) {
- return false;
- }
+ if (!match) {
+ return iter;
}
- return pieces >= 2;
+ }
+ return end_iter;
}
-bool isJavaPackageName(const StringPiece16& str) {
- if (str.empty()) {
- return false;
+bool IsJavaClassName(const StringPiece& str) {
+ size_t pieces = 0;
+ for (const StringPiece& piece : Tokenize(str, '.')) {
+ pieces++;
+ if (piece.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;
- }
+ // Can't have starting or trailing $ character.
+ if (piece.data()[0] == '$' || piece.data()[piece.size() - 1] == '$') {
+ return false;
+ }
- if (findNonAlphaNumericAndNotInSet(piece, u"_") != piece.end()) {
- return false;
- }
+ if (FindNonAlphaNumericAndNotInSet(piece, "$_") != piece.end()) {
+ return false;
}
- return pieces >= 1;
+ }
+ return pieces >= 2;
}
-Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
- const StringPiece16& className) {
- if (className.empty()) {
- return {};
- }
+bool IsJavaPackageName(const StringPiece& str) {
+ if (str.empty()) {
+ return false;
+ }
- if (util::isJavaClassName(className)) {
- return className.toString();
+ size_t pieces = 0;
+ for (const StringPiece& piece : Tokenize(str, '.')) {
+ pieces++;
+ if (piece.empty()) {
+ return false;
}
- if (package.empty()) {
- return {};
+ if (piece.data()[0] == '_' || piece.data()[piece.size() - 1] == '_') {
+ return false;
}
- if (className.data()[0] != u'.') {
- return {};
+ if (FindNonAlphaNumericAndNotInSet(piece, "_") != piece.end()) {
+ return false;
}
+ }
+ return pieces >= 1;
+}
- std::u16string result(package.data(), package.size());
- result.append(className.data(), className.size());
- if (!isJavaClassName(result)) {
- return {};
- }
- return result;
+Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package,
+ const StringPiece& classname) {
+ if (classname.empty()) {
+ return {};
+ }
+
+ if (util::IsJavaClassName(classname)) {
+ return classname.ToString();
+ }
+
+ if (package.empty()) {
+ return {};
+ }
+
+ std::string result(package.data(), package.size());
+ if (classname.data()[0] != '.') {
+ result += '.';
+ }
+
+ result.append(classname.data(), classname.size());
+ if (!IsJavaClassName(result)) {
+ return {};
+ }
+ 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);
+static size_t ConsumeDigits(const char* start, const char* end) {
+ const char* c = start;
+ for (; c != end && *c >= '0' && *c <= '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;
- }
- }
+bool VerifyJavaStringFormat(const StringPiece& str) {
+ const char* c = str.begin();
+ const char* const end = str.end();
+
+ size_t arg_count = 0;
+ bool nonpositional = false;
+ while (c != end) {
+ if (*c == '%' && c + 1 < end) {
+ c++;
+
+ if (*c == '%') {
+ c++;
+ continue;
+ }
+
+ arg_count++;
+
+ size_t num_digits = ConsumeDigits(c, end);
+ if (num_digits > 0) {
+ c += num_digits;
+ if (c != end && *c != '$') {
+ // The digits were a size, but not a positional argument.
+ nonpositional = true;
}
+ } else if (*c == '<') {
+ // Reusing last argument, bad idea since positions can be moved around
+ // during translation.
+ nonpositional = true;
- if (c != end) {
- c++;
+ c++;
+
+ // Optionally we can have a $ after
+ if (c != end && *c == '$') {
+ c++;
+ }
+ } else {
+ nonpositional = true;
+ }
+
+ // Ignore size, width, flags, etc.
+ while (c != end && (*c == '-' || *c == '#' || *c == '+' || *c == ' ' ||
+ *c == ',' || *c == '(' || (*c >= '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 (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;
+ if (c != end) {
+ c++;
}
- return true;
+ }
+
+ if (arg_count > 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)++) {
- char16_t c = **start;
- int a;
- if (c >= '0' && c <= '9') {
- a = c - '0';
- } else if (c >= 'a' && c <= 'f') {
- a = c - 'a' + 10;
- } else if (c >= 'A' && c <= 'F') {
- a = c - 'A' + 10;
- } else {
- return make_nothing<char16_t>();
- }
- code = (code << 4) | a;
+static Maybe<std::string> ParseUnicodeCodepoint(const char** start,
+ const char* end) {
+ char32_t code = 0;
+ for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
+ char c = **start;
+ char32_t a;
+ if (c >= '0' && c <= '9') {
+ a = c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ a = c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ a = c - 'A' + 10;
+ } else {
+ return {};
}
- return make_value(code);
+ code = (code << 4) | a;
+ }
+
+ ssize_t len = utf32_to_utf8_length(&code, 1);
+ if (len < 0) {
+ return {};
+ }
+
+ std::string result_utf8;
+ result_utf8.resize(len);
+ utf32_to_utf8(&code, 1, &*result_utf8.begin(), len + 1);
+ return result_utf8;
}
-StringBuilder& StringBuilder::append(const StringPiece16& str) {
- if (!mError.empty()) {
- return *this;
- }
-
- const char16_t* const end = str.end();
- const char16_t* start = str.begin();
- const char16_t* current = start;
- while (current != end) {
- 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
- // space now.
- if (mTrailingSpace) {
- // We had trailing whitespace, so
- // replace with a single space.
- if (!mStr.empty()) {
- mStr += u' ';
- }
- mTrailingSpace = false;
- }
- }
- mQuote = !mQuote;
- mStr.append(start, current - start);
- start = current + 1;
- } else if (*current == u'\'' && !mQuote) {
- // This should be escaped.
- mError = "unescaped apostrophe";
+StringBuilder& StringBuilder::Append(const StringPiece& str) {
+ if (!error_.empty()) {
+ return *this;
+ }
+
+ // Where the new data will be appended to.
+ size_t new_data_index = str_.size();
+
+ const char* const end = str.end();
+ const char* start = str.begin();
+ const char* current = start;
+ while (current != end) {
+ if (last_char_was_escape_) {
+ switch (*current) {
+ case 't':
+ str_ += '\t';
+ break;
+ case 'n':
+ str_ += '\n';
+ break;
+ case '#':
+ str_ += '#';
+ break;
+ case '@':
+ str_ += '@';
+ break;
+ case '?':
+ str_ += '?';
+ break;
+ case '"':
+ str_ += '"';
+ break;
+ case '\'':
+ str_ += '\'';
+ break;
+ case '\\':
+ str_ += '\\';
+ break;
+ case 'u': {
+ current++;
+ Maybe<std::string> c = ParseUnicodeCodepoint(&current, end);
+ if (!c) {
+ error_ = "invalid unicode escape sequence";
return *this;
- } else if (*current == u'\\') {
- // This is an escape sequence, convert to the real value.
- if (!mQuote && mTrailingSpace) {
- // We had trailing whitespace, so
- // replace with a single space.
- if (!mStr.empty()) {
- mStr += u' ';
- }
- mTrailingSpace = false;
- }
- mStr.append(start, current - start);
- start = current + 1;
- mLastCharWasEscape = true;
- } else if (!mQuote) {
- // This is not quoted text, so look for whitespace.
- if (isspace16(*current)) {
- // We found whitespace, see if we have seen some
- // before.
- if (!mTrailingSpace) {
- // We didn't see a previous adjacent space,
- // so mark that we did.
- mTrailingSpace = true;
- mStr.append(start, current - start);
- }
-
- // Keep skipping whitespace.
- start = current + 1;
- } else if (mTrailingSpace) {
- // We saw trailing space before, so replace all
- // that trailing space with one space.
- if (!mStr.empty()) {
- mStr += u' ';
- }
- mTrailingSpace = false;
- }
+ }
+ str_ += c.value();
+ current -= 1;
+ break;
+ }
+
+ default:
+ // Ignore.
+ break;
+ }
+ last_char_was_escape_ = false;
+ start = current + 1;
+ } else if (*current == '"') {
+ if (!quote_ && trailing_space_) {
+ // We found an opening quote, and we have
+ // trailing space, so we should append that
+ // space now.
+ if (trailing_space_) {
+ // We had trailing whitespace, so
+ // replace with a single space.
+ if (!str_.empty()) {
+ str_ += ' ';
+ }
+ trailing_space_ = false;
+ }
+ }
+ quote_ = !quote_;
+ str_.append(start, current - start);
+ start = current + 1;
+ } else if (*current == '\'' && !quote_) {
+ // This should be escaped.
+ error_ = "unescaped apostrophe";
+ return *this;
+ } else if (*current == '\\') {
+ // This is an escape sequence, convert to the real value.
+ if (!quote_ && trailing_space_) {
+ // We had trailing whitespace, so
+ // replace with a single space.
+ if (!str_.empty()) {
+ str_ += ' ';
+ }
+ trailing_space_ = false;
+ }
+ str_.append(start, current - start);
+ start = current + 1;
+ last_char_was_escape_ = true;
+ } else if (!quote_) {
+ // This is not quoted text, so look for whitespace.
+ if (isspace(*current)) {
+ // We found whitespace, see if we have seen some
+ // before.
+ if (!trailing_space_) {
+ // We didn't see a previous adjacent space,
+ // so mark that we did.
+ trailing_space_ = true;
+ str_.append(start, current - start);
+ }
+
+ // Keep skipping whitespace.
+ start = current + 1;
+ } else if (trailing_space_) {
+ // We saw trailing space before, so replace all
+ // that trailing space with one space.
+ if (!str_.empty()) {
+ str_ += ' ';
}
- current++;
+ trailing_space_ = false;
+ }
}
- mStr.append(start, end - start);
+ current++;
+ }
+ str_.append(start, end - start);
+
+ // Accumulate the added string's UTF-16 length.
+ ssize_t len = utf8_to_utf16_length(
+ reinterpret_cast<const uint8_t*>(str_.data()) + new_data_index,
+ str_.size() - new_data_index);
+ if (len < 0) {
+ error_ = "invalid unicode code point";
return *this;
+ }
+ utf16_len_ += len;
+ return *this;
}
-std::u16string utf8ToUtf16(const StringPiece& utf8) {
- ssize_t utf16Length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()),
- utf8.length());
- if (utf16Length <= 0) {
- return {};
- }
+std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+ ssize_t utf16_length = utf8_to_utf16_length(
+ reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
+ if (utf16_length <= 0) {
+ return {};
+ }
+
+ std::u16string utf16;
+ utf16.resize(utf16_length);
+ utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(),
+ &*utf16.begin(), utf16_length + 1);
+ return utf16;
+}
+
+std::string Utf16ToUtf8(const StringPiece16& utf16) {
+ ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
+ if (utf8_length <= 0) {
+ return {};
+ }
- std::u16string utf16;
- utf16.resize(utf16Length);
- utf8_to_utf16(
- reinterpret_cast<const uint8_t*>(utf8.data()),
- utf8.length(),
- &*utf16.begin(),
- (size_t) utf16Length + 1);
- return utf16;
+ std::string utf8;
+ utf8.resize(utf8_length);
+ utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
+ return utf8;
}
-std::string utf16ToUtf8(const StringPiece16& utf16) {
- ssize_t utf8Length = utf16_to_utf8_length(utf16.data(), utf16.length());
- if (utf8Length <= 0) {
- return {};
+bool WriteAll(std::ostream& out, const BigBuffer& buffer) {
+ for (const auto& b : buffer) {
+ if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) {
+ return false;
}
+ }
+ return true;
+}
- 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);
- return utf8;
+std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) {
+ std::unique_ptr<uint8_t[]> data =
+ std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
+ uint8_t* p = data.get();
+ for (const auto& block : buffer) {
+ memcpy(p, block.buffer.get(), block.size);
+ p += block.size;
+ }
+ return data;
}
-bool writeAll(std::ostream& out, const BigBuffer& buffer) {
- for (const auto& b : buffer) {
- if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) {
- return false;
- }
+typename Tokenizer::iterator& Tokenizer::iterator::operator++() {
+ const char* start = token_.end();
+ const char* end = str_.end();
+ if (start == end) {
+ end_ = true;
+ token_.assign(token_.end(), 0);
+ return *this;
+ }
+
+ start += 1;
+ const char* current = start;
+ while (current != end) {
+ if (*current == separator_) {
+ token_.assign(start, current - start);
+ return *this;
}
- return true;
+ ++current;
+ }
+ token_.assign(start, end - start);
+ return *this;
}
-std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) {
- std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
- uint8_t* p = data.get();
- for (const auto& block : buffer) {
- memcpy(p, block.buffer.get(), block.size);
- p += block.size;
- }
- return data;
+bool Tokenizer::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() && end_ == rhs.end_;
}
-bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
- StringPiece16* outEntry, StringPiece16* outSuffix) {
- if (!stringStartsWith<char16_t>(path, u"res/")) {
- return false;
- }
+bool Tokenizer::iterator::operator!=(const iterator& rhs) const {
+ return !(*this == rhs);
+}
- StringPiece16::const_iterator lastOccurence = path.end();
- for (auto iter = path.begin() + StringPiece16(u"res/").size(); iter != path.end(); ++iter) {
- if (*iter == u'/') {
- lastOccurence = iter;
- }
+Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok,
+ bool end)
+ : str_(s), separator_(sep), token_(tok), end_(end) {}
+
+Tokenizer::Tokenizer(StringPiece str, char sep)
+ : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
+ end_(str, sep, StringPiece(str.end(), 0), true) {}
+
+bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
+ StringPiece* out_entry, StringPiece* out_suffix) {
+ const StringPiece res_prefix("res/");
+ if (!StartsWith(path, res_prefix)) {
+ return false;
+ }
+
+ StringPiece::const_iterator last_occurence = path.end();
+ for (auto iter = path.begin() + res_prefix.size(); iter != path.end();
+ ++iter) {
+ if (*iter == '/') {
+ last_occurence = iter;
}
+ }
- if (lastOccurence == path.end()) {
- return false;
- }
+ if (last_occurence == path.end()) {
+ return false;
+ }
+
+ auto iter = std::find(last_occurence, path.end(), '.');
+ *out_suffix = StringPiece(iter, path.end() - iter);
+ *out_entry = StringPiece(last_occurence + 1, iter - last_occurence - 1);
+ *out_prefix = StringPiece(path.begin(), last_occurence - path.begin() + 1);
+ return true;
+}
+
+StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) {
+ size_t len;
+ const char16_t* str = pool.stringAt(idx, &len);
+ if (str != nullptr) {
+ return StringPiece16(str, len);
+ }
+ return StringPiece16();
+}
- 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;
+std::string GetString(const android::ResStringPool& pool, size_t idx) {
+ size_t len;
+ const char* str = pool.string8At(idx, &len);
+ if (str != nullptr) {
+ return std::string(str, len);
+ }
+ return Utf16ToUtf8(GetString16(pool, idx));
}
-} // namespace util
-} // namespace aapt
+} // namespace util
+} // namespace aapt
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 0dacbd773488..05e9cc5d28ca 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -17,327 +17,246 @@
#ifndef AAPT_UTIL_H
#define AAPT_UTIL_H
-#include "util/BigBuffer.h"
-#include "util/Maybe.h"
-#include "util/StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
#include <functional>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
+#include "androidfw/ResourceTypes.h"
+#include "utils/ByteOrder.h"
+
+#include "util/BigBuffer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
+#ifdef _WIN32
+// TODO(adamlesinski): remove once http://b/32447322 is resolved.
+// utils/ByteOrder.h includes winsock2.h on WIN32,
+// which will pull in the ERROR definition. This conflicts
+// with android-base/logging.h, which takes care of undefining
+// ERROR, but it gets included too early (before winsock2.h).
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
namespace aapt {
namespace util {
-std::vector<std::string> split(const StringPiece& str, char sep);
-std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep);
+std::vector<std::string> Split(const StringPiece& str, char sep);
+std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep);
/**
* Returns true if the string starts with prefix.
*/
-template <typename T>
-bool stringStartsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& prefix) {
- if (str.size() < prefix.size()) {
- return false;
- }
- return str.substr(0, prefix.size()) == prefix;
-}
+bool StartsWith(const StringPiece& str, const StringPiece& prefix);
/**
* Returns true if the string ends with suffix.
*/
-template <typename T>
-bool stringEndsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& suffix) {
- if (str.size() < suffix.size()) {
- return false;
- }
- return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
-}
+bool EndsWith(const StringPiece& str, const StringPiece& suffix);
/**
* Creates a new StringPiece16 that points to a substring
* of the original string without leading or trailing whitespace.
*/
-StringPiece16 trimWhitespace(const StringPiece16& str);
+StringPiece TrimWhitespace(const StringPiece& str);
-StringPiece trimWhitespace(const StringPiece& str);
+StringPiece TrimWhitespace(const StringPiece& str);
/**
* UTF-16 isspace(). It basically checks for lower range characters that are
* whitespace.
*/
-inline bool isspace16(char16_t c) {
- return c < 0x0080 && isspace(c);
-}
+inline bool isspace16(char16_t c) { return c < 0x0080 && isspace(c); }
/**
* Returns an iterator to the first character that is not alpha-numeric and that
* is not in the allowedChars set.
*/
-StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
- const StringPiece16& allowedChars);
+StringPiece::const_iterator FindNonAlphaNumericAndNotInSet(
+ const StringPiece& str, const StringPiece& allowed_chars);
/**
* Tests that the string is a valid Java class name.
*/
-bool isJavaClassName(const StringPiece16& str);
+bool IsJavaClassName(const StringPiece& str);
/**
* Tests that the string is a valid Java package name.
*/
-bool isJavaPackageName(const StringPiece16& str);
+bool IsJavaPackageName(const StringPiece& str);
/**
- * Converts the class name to a fully qualified class name from the given `package`. Ex:
+ * Converts the class name to a fully qualified class name from the given
+ * `package`. Ex:
*
* asdf --> package.asdf
* .asdf --> package.asdf
* .a.b --> package.a.b
* asdf.adsf --> asdf.adsf
*/
-Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
- const StringPiece16& className);
-
+Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package,
+ const StringPiece& class_name);
/**
- * Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
+ * Makes a std::unique_ptr<> with the template parameter inferred by the
+ * compiler.
* This will be present in C++14 and can be removed then.
*/
template <typename T, class... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
- return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
+ return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
}
/**
- * Writes a set of items to the std::ostream, joining the times with the provided
+ * Writes a set of items to the std::ostream, joining the times with the
+ * provided
* separator.
*/
-template <typename Iterator>
-::std::function<::std::ostream&(::std::ostream&)> joiner(Iterator begin, Iterator end,
- const char* sep) {
- return [begin, end, sep](::std::ostream& out) -> ::std::ostream& {
- for (auto iter = begin; iter != end; ++iter) {
- if (iter != begin) {
- out << sep;
- }
- out << *iter;
- }
- return out;
- };
-}
-
-inline ::std::function<::std::ostream&(::std::ostream&)> formatSize(size_t size) {
- return [size](::std::ostream& out) -> ::std::ostream& {
- constexpr size_t K = 1024u;
- constexpr size_t M = K * K;
- constexpr size_t G = M * K;
- if (size < K) {
- out << size << "B";
- } else if (size < M) {
- out << (double(size) / K) << " KiB";
- } else if (size < G) {
- out << (double(size) / M) << " MiB";
- } else {
- out << (double(size) / G) << " GiB";
- }
- return out;
- };
+template <typename Container>
+::std::function<::std::ostream&(::std::ostream&)> Joiner(
+ const Container& container, const char* sep) {
+ using std::begin;
+ using std::end;
+ const auto begin_iter = begin(container);
+ const auto end_iter = end(container);
+ return [begin_iter, end_iter, sep](::std::ostream& out) -> ::std::ostream& {
+ for (auto iter = begin_iter; iter != end_iter; ++iter) {
+ if (iter != begin_iter) {
+ out << sep;
+ }
+ out << *iter;
+ }
+ return out;
+ };
}
/**
- * Helper method to extract a string from a StringPool.
+ * Helper method to extract a UTF-16 string from a StringPool. If the string is
+ * stored as UTF-8,
+ * the conversion to UTF-16 happens within ResStringPool.
*/
-inline StringPiece16 getString(const android::ResStringPool& pool, size_t idx) {
- size_t len;
- const char16_t* str = pool.stringAt(idx, &len);
- if (str != nullptr) {
- return StringPiece16(str, len);
- }
- return StringPiece16();
-}
+StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx);
-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();
-}
+/**
+ * Helper method to extract a UTF-8 string from a StringPool. If the string is
+ * stored as UTF-16,
+ * the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is
+ * done by this method,
+ * which maintains no state or cache. This means we must return an std::string
+ * copy.
+ */
+std::string GetString(const android::ResStringPool& pool, size_t idx);
/**
- * 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
+ * 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);
+bool VerifyJavaStringFormat(const StringPiece& str);
class StringBuilder {
-public:
- StringBuilder& append(const StringPiece16& str);
- const std::u16string& str() const;
- const std::string& error() const;
- operator bool() const;
-
-private:
- std::u16string mStr;
- bool mQuote = false;
- bool mTrailingSpace = false;
- bool mLastCharWasEscape = false;
- std::string mError;
+ public:
+ StringBuilder& Append(const StringPiece& str);
+ const std::string& ToString() const;
+ const std::string& Error() const;
+
+ // When building StyledStrings, we need UTF-16 indices into the string,
+ // which is what the Java layer expects when dealing with java
+ // String.charAt().
+ size_t Utf16Len() const;
+
+ explicit operator bool() const;
+
+ private:
+ std::string str_;
+ size_t utf16_len_ = 0;
+ bool quote_ = false;
+ bool trailing_space_ = false;
+ bool last_char_was_escape_ = false;
+ std::string error_;
};
-inline const std::u16string& StringBuilder::str() const {
- return mStr;
-}
+inline const std::string& StringBuilder::ToString() const { return str_; }
-inline const std::string& StringBuilder::error() const {
- return mError;
-}
+inline const std::string& StringBuilder::Error() const { return error_; }
-inline StringBuilder::operator bool() const {
- return mError.empty();
-}
+inline size_t StringBuilder::Utf16Len() const { return utf16_len_; }
+
+inline StringBuilder::operator bool() const { return error_.empty(); }
/**
* Converts a UTF8 string to a UTF16 string.
*/
-std::u16string utf8ToUtf16(const StringPiece& utf8);
-std::string utf16ToUtf8(const StringPiece16& utf8);
+std::u16string Utf8ToUtf16(const StringPiece& utf8);
+std::string Utf16ToUtf8(const StringPiece16& utf16);
/**
* Writes the entire BigBuffer to the output stream.
*/
-bool writeAll(std::ostream& out, const BigBuffer& buffer);
+bool WriteAll(std::ostream& out, const BigBuffer& buffer);
/*
* Copies the entire BigBuffer into a single buffer.
*/
-std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer);
+std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer);
/**
* A Tokenizer implemented as an iterable collection. It does not allocate
* any memory on the heap nor use standard containers.
*/
-template <typename Char>
class Tokenizer {
-public:
- class iterator {
- public:
- iterator(const iterator&) = default;
- iterator& operator=(const iterator&) = default;
-
- iterator& operator++();
- BasicStringPiece<Char> operator*();
- bool operator==(const iterator& rhs) const;
- bool operator!=(const iterator& rhs) const;
-
- private:
- friend class Tokenizer<Char>;
-
- iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end);
-
- BasicStringPiece<Char> mStr;
- Char mSeparator;
- BasicStringPiece<Char> mToken;
- bool mEnd;
- };
-
- Tokenizer(BasicStringPiece<Char> str, Char sep);
- iterator begin();
- iterator end();
-
-private:
- const iterator mBegin;
- const iterator mEnd;
-};
+ public:
+ class iterator {
+ public:
+ iterator(const iterator&) = default;
+ iterator& operator=(const iterator&) = default;
-template <typename Char>
-inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) {
- return Tokenizer<Char>(str, sep);
-}
+ iterator& operator++();
-template <typename Char>
-typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() {
- const Char* start = mToken.end();
- const Char* end = mStr.end();
- if (start == end) {
- mEnd = true;
- mToken.assign(mToken.end(), 0);
- return *this;
- }
+ StringPiece operator*() { return token_; }
+ bool operator==(const iterator& rhs) const;
+ bool operator!=(const iterator& rhs) const;
- start += 1;
- const Char* current = start;
- while (current != end) {
- if (*current == mSeparator) {
- mToken.assign(start, current - start);
- return *this;
- }
- ++current;
- }
- mToken.assign(start, end - start);
- return *this;
-}
+ private:
+ friend class Tokenizer;
-template <typename Char>
-inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() {
- return mToken;
-}
+ iterator(StringPiece s, char sep, StringPiece tok, bool end);
-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 mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() &&
- mEnd == rhs.mEnd;
-}
+ StringPiece str_;
+ char separator_;
+ StringPiece token_;
+ bool end_;
+ };
-template <typename Char>
-inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const {
- return !(*this == rhs);
-}
+ Tokenizer(StringPiece str, char sep);
-template <typename Char>
-inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep,
- BasicStringPiece<Char> tok, bool end) :
- mStr(s), mSeparator(sep), mToken(tok), mEnd(end) {
-}
+ iterator begin() { return begin_; }
-template <typename Char>
-inline typename Tokenizer<Char>::iterator Tokenizer<Char>::begin() {
- return mBegin;
-}
+ iterator end() { return end_; }
-template <typename Char>
-inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() {
- return mEnd;
-}
+ private:
+ const iterator begin_;
+ const iterator end_;
+};
-template <typename Char>
-inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) :
- mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)),
- mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) {
+inline Tokenizer Tokenize(const StringPiece& str, char sep) {
+ return Tokenizer(str, sep);
}
-inline uint16_t hostToDevice16(uint16_t value) {
- return htods(value);
-}
+inline uint16_t HostToDevice16(uint16_t value) { return htods(value); }
-inline uint32_t hostToDevice32(uint32_t value) {
- return htodl(value);
-}
+inline uint32_t HostToDevice32(uint32_t value) { return htodl(value); }
-inline uint16_t deviceToHost16(uint16_t value) {
- return dtohs(value);
-}
+inline uint16_t DeviceToHost16(uint16_t value) { return dtohs(value); }
-inline uint32_t deviceToHost32(uint32_t value) {
- return dtohl(value);
-}
+inline uint32_t DeviceToHost32(uint32_t value) { return dtohl(value); }
/**
* Given a path like: res/xml-sw600dp/foo.xml
@@ -348,20 +267,22 @@ inline uint32_t deviceToHost32(uint32_t value) {
*
* Returns true if successful.
*/
-bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
- StringPiece16* outEntry, StringPiece16* outSuffix);
+bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
+ StringPiece* out_entry, StringPiece* out_suffix);
-} // namespace util
+} // namespace util
/**
- * Stream operator for functions. Calls the function with the stream as an argument.
+ * Stream operator for functions. Calls the function with the stream as an
+ * argument.
* In the aapt namespace for lookup.
*/
-inline ::std::ostream& operator<<(::std::ostream& out,
- ::std::function<::std::ostream&(::std::ostream&)> f) {
- return f(out);
+inline ::std::ostream& operator<<(
+ ::std::ostream& out,
+ const ::std::function<::std::ostream&(::std::ostream&)>& f) {
+ return f(out);
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_UTIL_H
+#endif // AAPT_UTIL_H
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 1e0c7fa9152d..cac3de4696ab 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -14,191 +14,196 @@
* limitations under the License.
*/
-#include "test/Common.h"
-#include "util/StringPiece.h"
#include "util/Util.h"
-#include <gtest/gtest.h>
#include <string>
+#include "test/Test.h"
+
namespace aapt {
TEST(UtilTest, TrimOnlyWhitespace) {
- const std::u16string full = u"\n ";
+ const std::string full = "\n ";
- StringPiece16 trimmed = util::trimWhitespace(full);
- EXPECT_TRUE(trimmed.empty());
- EXPECT_EQ(0u, trimmed.size());
+ StringPiece trimmed = util::TrimWhitespace(full);
+ EXPECT_TRUE(trimmed.empty());
+ EXPECT_EQ(0u, trimmed.size());
}
TEST(UtilTest, StringEndsWith) {
- EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml"));
+ EXPECT_TRUE(util::EndsWith("hello.xml", ".xml"));
}
TEST(UtilTest, StringStartsWith) {
- EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he"));
+ EXPECT_TRUE(util::StartsWith("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());
+ EXPECT_EQ(StringPiece("this is a new\nline."), util::StringBuilder()
+ .Append("this is a new\\")
+ .Append("nline.")
+ .ToString());
}
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(StringPiece("hey guys this is so cool"),
+ util::StringBuilder()
+ .Append(" hey guys ")
+ .Append(" this is so cool ")
+ .ToString());
- 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(StringPiece(" wow, so many \t spaces. what?"),
+ util::StringBuilder()
+ .Append(" \" wow, so many \t ")
+ .Append("spaces. \"what? ")
+ .ToString());
- EXPECT_EQ(StringPiece16(u"where is the pie?"),
- util::StringBuilder().append(u" where \t ")
- .append(u" \nis the "" pie?")
- .str());
+ EXPECT_EQ(StringPiece("where is the pie?"), util::StringBuilder()
+ .Append(" where \t ")
+ .Append(" \nis the "
+ " pie?")
+ .ToString());
}
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(StringPiece("hey guys\n this \t is so\\ cool"),
+ util::StringBuilder()
+ .Append(" hey guys\\n ")
+ .Append(" this \\t is so\\\\ cool ")
+ .ToString());
- EXPECT_EQ(StringPiece16(u"@?#\\\'"),
- util::StringBuilder().append(u"\\@\\?\\#\\\\\\'")
- .str());
+ EXPECT_EQ(StringPiece("@?#\\\'"),
+ util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString());
}
TEST(UtilTest, StringBuilderMisplacedQuote) {
- util::StringBuilder builder{};
- EXPECT_FALSE(builder.append(u"they're coming!"));
+ util::StringBuilder builder{};
+ EXPECT_FALSE(builder.Append("they're coming!"));
}
TEST(UtilTest, StringBuilderUnicodeCodes) {
- EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"),
- util::StringBuilder().append(u"\\u00AF\\u0AF0 woah")
- .str());
+ EXPECT_EQ(std::string("\u00AF\u0AF0 woah"),
+ util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString());
- EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo"));
+ EXPECT_FALSE(util::StringBuilder().Append("\\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);
+ auto tokenizer = util::Tokenize(StringPiece("this| is|the|end"), '|');
+ auto iter = tokenizer.begin();
+ ASSERT_EQ(*iter, StringPiece("this"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece(" is"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece("the"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece("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);
+ auto tokenizer = util::Tokenize(StringPiece(""), '|');
+ auto iter = tokenizer.begin();
+ ASSERT_NE(tokenizer.end(), iter);
+ ASSERT_EQ(StringPiece(), *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());
+ auto tokenizer = util::Tokenize(StringPiece("one."), '.');
+ auto iter = tokenizer.begin();
+ ASSERT_EQ(*iter, StringPiece("one"));
+ ++iter;
+ ASSERT_NE(iter, tokenizer.end());
+ ASSERT_EQ(*iter, StringPiece());
}
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"));
+ EXPECT_TRUE(util::IsJavaClassName("android.test.Class"));
+ EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner"));
+ EXPECT_TRUE(util::IsJavaClassName("android_test.test.Class"));
+ EXPECT_TRUE(util::IsJavaClassName("_android_.test._Class_"));
+ EXPECT_FALSE(util::IsJavaClassName("android.test.$Inner"));
+ EXPECT_FALSE(util::IsJavaClassName("android.test.Inner$"));
+ EXPECT_FALSE(util::IsJavaClassName(".test.Class"));
+ EXPECT_FALSE(util::IsJavaClassName("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".."));
+ EXPECT_TRUE(util::IsJavaPackageName("android"));
+ EXPECT_TRUE(util::IsJavaPackageName("android.test"));
+ EXPECT_TRUE(util::IsJavaPackageName("android.test_thing"));
+ EXPECT_FALSE(util::IsJavaPackageName("_android"));
+ EXPECT_FALSE(util::IsJavaPackageName("android_"));
+ EXPECT_FALSE(util::IsJavaPackageName("android."));
+ EXPECT_FALSE(util::IsJavaPackageName(".android"));
+ EXPECT_FALSE(util::IsJavaPackageName("android._test"));
+ EXPECT_FALSE(util::IsJavaPackageName(".."));
}
TEST(UtilTest, FullyQualifiedClassName) {
- Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
- AAPT_ASSERT_FALSE(res);
+ Maybe<std::string> res = util::GetFullyQualifiedClassName("android", ".asdf");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "android.asdf");
- res = util::getFullyQualifiedClassName(u"android", u".asdf");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"android.asdf");
+ res = util::GetFullyQualifiedClassName("android", ".a.b");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "android.a.b");
- res = util::getFullyQualifiedClassName(u"android", u".a.b");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"android.a.b");
+ res = util::GetFullyQualifiedClassName("android", "a.b");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "a.b");
- res = util::getFullyQualifiedClassName(u"android", u"a.b");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"a.b");
+ res = util::GetFullyQualifiedClassName("", "a.b");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "a.b");
- res = util::getFullyQualifiedClassName(u"", u"a.b");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"a.b");
+ res = util::GetFullyQualifiedClassName("android", "Class");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "android.Class");
- res = util::getFullyQualifiedClassName(u"", u"");
- AAPT_ASSERT_FALSE(res);
+ res = util::GetFullyQualifiedClassName("", "");
+ AAPT_ASSERT_FALSE(res);
- res = util::getFullyQualifiedClassName(u"android", u"./Apple");
- AAPT_ASSERT_FALSE(res);
+ res = util::GetFullyQualifiedClassName("android", "./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");
+ StringPiece prefix, entry, suffix;
+ ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.xml",
+ &prefix, &entry, &suffix));
+ EXPECT_EQ(prefix, "res/xml-sw600dp/");
+ EXPECT_EQ(entry, "entry");
+ EXPECT_EQ(suffix, ".xml");
- ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.9.png", &prefix, &entry,
- &suffix));
+ ASSERT_TRUE(util::ExtractResFilePathParts("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_EQ(prefix, "res/xml-sw600dp/");
+ EXPECT_EQ(entry, "entry");
+ EXPECT_EQ(suffix, ".9.png");
- EXPECT_FALSE(util::extractResFilePathParts(u"AndroidManifest.xml", &prefix, &entry, &suffix));
- EXPECT_FALSE(util::extractResFilePathParts(u"res/.xml", &prefix, &entry, &suffix));
+ EXPECT_FALSE(util::ExtractResFilePathParts("AndroidManifest.xml", &prefix,
+ &entry, &suffix));
+ EXPECT_FALSE(
+ util::ExtractResFilePathParts("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".");
+ ASSERT_TRUE(
+ util::ExtractResFilePathParts("res//.", &prefix, &entry, &suffix));
+ EXPECT_EQ(prefix, "res//");
+ EXPECT_EQ(entry, "");
+ EXPECT_EQ(suffix, ".");
}
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"));
+ ASSERT_TRUE(util::VerifyJavaStringFormat("%09.34f"));
+ ASSERT_TRUE(util::VerifyJavaStringFormat("%9$.34f %8$"));
+ ASSERT_TRUE(util::VerifyJavaStringFormat("%% %%"));
+ ASSERT_FALSE(util::VerifyJavaStringFormat("%09$f %f"));
+ ASSERT_FALSE(util::VerifyJavaStringFormat("%09f %08s"));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 0ef67eaf3dc5..7580b469a98e 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -19,94 +19,94 @@
namespace aapt {
namespace xml {
-static bool wrapperOne(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) {
- return f(el);
+static bool wrapper_one(XmlNodeAction::ActionFunc& f, Element* el,
+ SourcePathDiagnostics*) {
+ return f(el);
}
-static bool wrapperTwo(XmlNodeAction::ActionFuncWithDiag& f, Element* el,
- SourcePathDiagnostics* diag) {
- return f(el, diag);
+static bool wrapper_two(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::ActionFunc f) {
+ actions_.emplace_back(std::bind(
+ wrapper_one, 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));
+void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithDiag f) {
+ actions_.emplace_back(std::bind(
+ wrapper_two, 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 << ">";
+static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) {
+ *msg << "<";
+ if (!el->namespace_uri.empty()) {
+ *msg << el->namespace_uri << ":";
+ }
+ *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);
- }
+bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy,
+ SourcePathDiagnostics* diag, Element* el) const {
+ bool error = false;
+ for (const ActionFuncWithDiag& action : actions_) {
+ 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;
- }
- }
+ for (Element* child_el : el->GetChildElements()) {
+ if (child_el->namespace_uri.empty()) {
+ std::map<std::string, XmlNodeAction>::const_iterator iter =
+ map_.find(child_el->name);
+ if (iter != map_.end()) {
+ error |= !iter->second.Execute(policy, diag, child_el);
+ continue;
+ }
+ }
- if (policy == XmlActionExecutorPolicy::Whitelist) {
- DiagMessage errorMsg(childEl->lineNumber);
- errorMsg << "unknown element ";
- printElementToDiagMessage(childEl, &errorMsg);
- errorMsg << " found";
- diag->error(errorMsg);
- error = true;
- }
+ if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ DiagMessage error_msg(child_el->line_number);
+ error_msg << "unknown element ";
+ PrintElementToDiagMessage(child_el, &error_msg);
+ error_msg << " found";
+ diag->Error(error_msg);
+ error = true;
}
- return !error;
+ }
+ return !error;
}
-bool XmlActionExecutor::execute(XmlActionExecutorPolicy policy, IDiagnostics* diag,
- XmlResource* doc) const {
- SourcePathDiagnostics sourceDiag(doc->file.source, diag);
+bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy,
+ IDiagnostics* diag, XmlResource* doc) const {
+ SourcePathDiagnostics source_diag(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;
+ Element* el = FindRootElement(doc);
+ if (!el) {
+ if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ source_diag.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 (el->namespace_uri.empty()) {
+ std::map<std::string, XmlNodeAction>::const_iterator iter =
+ map_.find(el->name);
+ if (iter != map_.end()) {
+ return iter->second.Execute(policy, &source_diag, el);
}
+ }
- if (policy == XmlActionExecutorPolicy::Whitelist) {
- DiagMessage errorMsg(el->lineNumber);
- errorMsg << "unknown element ";
- printElementToDiagMessage(el, &errorMsg);
- errorMsg << " found";
- sourceDiag.error(errorMsg);
- return false;
- }
- return true;
+ if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ DiagMessage error_msg(el->line_number);
+ error_msg << "unknown element ";
+ PrintElementToDiagMessage(el, &error_msg);
+ error_msg << " found";
+ source_diag.Error(error_msg);
+ return false;
+ }
+ return true;
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index 36b94dbfde05..68e35631988e 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -17,92 +17,97 @@
#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>
+#include "android-base/macros.h"
+
+#include "Diagnostics.h"
+#include "xml/XmlDom.h"
+
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,
+ /**
+ * Actions on run if elements are matched, errors occur only when actions
+ * return false.
+ */
+ kNone,
+
+ /**
+ * The actions defined must match and run. If an element is found that does
+ * not match
+ * an action, an error occurs.
+ */
+ kWhitelist,
};
/**
- * Contains the actions to perform at this XML node. This is a recursive data structure that
+ * 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;
+ 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::string& name) { return map_[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::string, XmlNodeAction> map_;
+ std::vector<ActionFuncWithDiag> actions_;
};
/**
- * Allows the definition of actions to execute at specific XML elements defined by their
+ * 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);
+ 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::string& name) { return map_[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::string, XmlNodeAction> map_;
+
+ DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor);
};
-} // namespace xml
-} // namespace aapt
+} // 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
index ebf287a251f2..7110c90fa3a9 100644
--- a/tools/aapt2/xml/XmlActionExecutor_test.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp
@@ -14,49 +14,53 @@
* limitations under the License.
*/
-#include "test/Test.h"
#include "xml/XmlActionExecutor.h"
+#include "test/Test.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);
+ XmlActionExecutor executor;
+ XmlNodeAction& manifest_action = executor["manifest"];
+ XmlNodeAction& application_action = manifest_action["application"];
+
+ Element* manifest_el = nullptr;
+ manifest_action.Action([&](Element* manifest) -> bool {
+ manifest_el = manifest;
+ return true;
+ });
+
+ Element* application_el = nullptr;
+ application_action.Action([&](Element* application) -> bool {
+ application_el = application;
+ return true;
+ });
+
+ std::unique_ptr<XmlResource> doc =
+ test::BuildXmlDom("<manifest><application /></manifest>");
+
+ StdErrDiagnostics diag;
+ ASSERT_TRUE(
+ executor.Execute(XmlActionExecutorPolicy::kNone, &diag, doc.get()));
+ ASSERT_NE(nullptr, manifest_el);
+ EXPECT_EQ(std::string("manifest"), manifest_el->name);
+
+ ASSERT_NE(nullptr, application_el);
+ EXPECT_EQ(std::string("application"), application_el->name);
}
TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) {
- XmlActionExecutor executor;
- executor[u"manifest"][u"application"];
+ XmlActionExecutor executor;
+ executor["manifest"]["application"];
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(
- "<manifest><application /><activity /></manifest>");
- StdErrDiagnostics diag;
- ASSERT_FALSE(executor.execute(XmlActionExecutorPolicy::Whitelist, &diag, doc.get()));
+ std::unique_ptr<XmlResource> doc =
+ test::BuildXmlDom("<manifest><application /><activity /></manifest>");
+ StdErrDiagnostics diag;
+ ASSERT_FALSE(
+ executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get()));
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 0ce333af3115..960d3614305e 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -15,430 +15,501 @@
*/
#include "XmlDom.h"
-#include "XmlPullParser.h"
-#include "util/Util.h"
-#include <cassert>
#include <expat.h>
+
+#include <cassert>
#include <memory>
#include <stack>
#include <string>
#include <tuple>
+#include "android-base/logging.h"
+
+#include "XmlPullParser.h"
+#include "util/Util.h"
+
namespace aapt {
namespace xml {
constexpr char kXmlNamespaceSep = 1;
struct Stack {
- std::unique_ptr<xml::Node> root;
- std::stack<xml::Node*> nodeStack;
- std::u16string pendingComment;
+ std::unique_ptr<xml::Node> root;
+ std::stack<xml::Node*> node_stack;
+ std::string pending_comment;
};
/**
* Extracts the namespace and name of an expanded element or attribute name.
*/
-static void splitName(const char* name, std::u16string* outNs, std::u16string* outName) {
- const char* p = name;
- while (*p != 0 && *p != kXmlNamespaceSep) {
- p++;
- }
-
- if (*p == 0) {
- outNs->clear();
- *outName = util::utf8ToUtf16(name);
- } else {
- *outNs = util::utf8ToUtf16(StringPiece(name, (p - name)));
- *outName = util::utf8ToUtf16(p + 1);
- }
+static void SplitName(const char* name, std::string* out_ns,
+ std::string* out_name) {
+ const char* p = name;
+ while (*p != 0 && *p != kXmlNamespaceSep) {
+ p++;
+ }
+
+ if (*p == 0) {
+ out_ns->clear();
+ *out_name = StringPiece(name).ToString();
+ } else {
+ *out_ns = StringPiece(name, (p - name)).ToString();
+ *out_name = StringPiece(p + 1).ToString();
+ }
}
-static void addToStack(Stack* stack, XML_Parser parser, std::unique_ptr<Node> node) {
- node->lineNumber = XML_GetCurrentLineNumber(parser);
- node->columnNumber = XML_GetCurrentColumnNumber(parser);
-
- Node* thisNode = node.get();
- if (!stack->nodeStack.empty()) {
- stack->nodeStack.top()->addChild(std::move(node));
- } else {
- stack->root = std::move(node);
- }
-
- if (!nodeCast<Text>(thisNode)) {
- stack->nodeStack.push(thisNode);
- }
+static void AddToStack(Stack* stack, XML_Parser parser,
+ std::unique_ptr<Node> node) {
+ node->line_number = XML_GetCurrentLineNumber(parser);
+ node->column_number = XML_GetCurrentColumnNumber(parser);
+
+ Node* this_node = node.get();
+ if (!stack->node_stack.empty()) {
+ stack->node_stack.top()->AppendChild(std::move(node));
+ } else {
+ stack->root = std::move(node);
+ }
+
+ if (!NodeCast<Text>(this_node)) {
+ stack->node_stack.push(this_node);
+ }
}
-static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix,
+ const char* uri) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- std::unique_ptr<Namespace> ns = util::make_unique<Namespace>();
- if (prefix) {
- ns->namespacePrefix = util::utf8ToUtf16(prefix);
- }
+ std::unique_ptr<Namespace> ns = util::make_unique<Namespace>();
+ if (prefix) {
+ ns->namespace_prefix = StringPiece(prefix).ToString();
+ }
- if (uri) {
- ns->namespaceUri = util::utf8ToUtf16(uri);
- }
+ if (uri) {
+ ns->namespace_uri = StringPiece(uri).ToString();
+ }
- addToStack(stack, parser, std::move(ns));
+ AddToStack(stack, parser, std::move(ns));
}
-static void XMLCALL endNamespaceHandler(void* userData, const char* prefix) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- assert(!stack->nodeStack.empty());
- stack->nodeStack.pop();
+ CHECK(!stack->node_stack.empty());
+ stack->node_stack.pop();
}
-static bool lessAttribute(const Attribute& lhs, const Attribute& rhs) {
- return std::tie(lhs.namespaceUri, lhs.name, lhs.value) <
- std::tie(rhs.namespaceUri, rhs.name, rhs.value);
+static bool less_attribute(const Attribute& lhs, const Attribute& rhs) {
+ return std::tie(lhs.namespace_uri, lhs.name, lhs.value) <
+ std::tie(rhs.namespace_uri, rhs.name, rhs.value);
}
-static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL StartElementHandler(void* user_data, const char* name,
+ const char** attrs) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- std::unique_ptr<Element> el = util::make_unique<Element>();
- splitName(name, &el->namespaceUri, &el->name);
+ std::unique_ptr<Element> el = util::make_unique<Element>();
+ SplitName(name, &el->namespace_uri, &el->name);
- while (*attrs) {
- Attribute attribute;
- splitName(*attrs++, &attribute.namespaceUri, &attribute.name);
- attribute.value = util::utf8ToUtf16(*attrs++);
+ while (*attrs) {
+ Attribute attribute;
+ SplitName(*attrs++, &attribute.namespace_uri, &attribute.name);
+ attribute.value = StringPiece(*attrs++).ToString();
- // Insert in sorted order.
- auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(), attribute,
- lessAttribute);
- el->attributes.insert(iter, std::move(attribute));
- }
+ // Insert in sorted order.
+ auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(),
+ attribute, less_attribute);
+ el->attributes.insert(iter, std::move(attribute));
+ }
- el->comment = std::move(stack->pendingComment);
- addToStack(stack, parser, std::move(el));
+ el->comment = std::move(stack->pending_comment);
+ AddToStack(stack, parser, std::move(el));
}
-static void XMLCALL endElementHandler(void* userData, const char* name) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL EndElementHandler(void* user_data, const char* name) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- assert(!stack->nodeStack.empty());
- //stack->nodeStack.top()->comment = std::move(stack->pendingComment);
- stack->nodeStack.pop();
+ CHECK(!stack->node_stack.empty());
+ // stack->nodeStack.top()->comment = std::move(stack->pendingComment);
+ stack->node_stack.pop();
}
-static void XMLCALL characterDataHandler(void* userData, const char* s, int len) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
-
- if (!s || len <= 0) {
+static void XMLCALL CharacterDataHandler(void* user_data, const char* s,
+ int len) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+
+ if (!s || len <= 0) {
+ return;
+ }
+
+ // See if we can just append the text to a previous text node.
+ if (!stack->node_stack.empty()) {
+ Node* currentParent = stack->node_stack.top();
+ if (!currentParent->children.empty()) {
+ Node* last_child = currentParent->children.back().get();
+ if (Text* text = NodeCast<Text>(last_child)) {
+ text->text += StringPiece(s, len).ToString();
return;
+ }
}
+ }
- // See if we can just append the text to a previous text node.
- if (!stack->nodeStack.empty()) {
- Node* currentParent = stack->nodeStack.top();
- if (!currentParent->children.empty()) {
- Node* lastChild = currentParent->children.back().get();
- if (Text* text = nodeCast<Text>(lastChild)) {
- text->text += util::utf8ToUtf16(StringPiece(s, len));
- return;
- }
- }
- }
-
- std::unique_ptr<Text> text = util::make_unique<Text>();
- text->text = util::utf8ToUtf16(StringPiece(s, len));
- addToStack(stack, parser, std::move(text));
+ std::unique_ptr<Text> text = util::make_unique<Text>();
+ text->text = StringPiece(s, len).ToString();
+ AddToStack(stack, parser, std::move(text));
}
-static void XMLCALL commentDataHandler(void* userData, const char* comment) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- if (!stack->pendingComment.empty()) {
- stack->pendingComment += '\n';
- }
- stack->pendingComment += util::utf8ToUtf16(comment);
+ if (!stack->pending_comment.empty()) {
+ stack->pending_comment += '\n';
+ }
+ stack->pending_comment += comment;
}
-std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) {
- Stack stack;
-
- XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
- XML_SetUserData(parser, &stack);
- XML_UseParserAsHandlerArg(parser);
- XML_SetElementHandler(parser, startElementHandler, endElementHandler);
- XML_SetNamespaceDeclHandler(parser, startNamespaceHandler, endNamespaceHandler);
- XML_SetCharacterDataHandler(parser, characterDataHandler);
- XML_SetCommentHandler(parser, commentDataHandler);
-
- char buffer[1024];
- while (!in->eof()) {
- in->read(buffer, sizeof(buffer) / sizeof(buffer[0]));
- if (in->bad() && !in->eof()) {
- stack.root = {};
- diag->error(DiagMessage(source) << strerror(errno));
- break;
- }
-
- if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) {
- stack.root = {};
- diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser)))
- << XML_ErrorString(XML_GetErrorCode(parser)));
- break;
- }
+std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag,
+ const Source& source) {
+ Stack stack;
+
+ XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
+ XML_SetUserData(parser, &stack);
+ XML_UseParserAsHandlerArg(parser);
+ XML_SetElementHandler(parser, StartElementHandler, EndElementHandler);
+ XML_SetNamespaceDeclHandler(parser, StartNamespaceHandler,
+ EndNamespaceHandler);
+ XML_SetCharacterDataHandler(parser, CharacterDataHandler);
+ XML_SetCommentHandler(parser, CommentDataHandler);
+
+ char buffer[1024];
+ while (!in->eof()) {
+ in->read(buffer, sizeof(buffer) / sizeof(buffer[0]));
+ if (in->bad() && !in->eof()) {
+ stack.root = {};
+ diag->Error(DiagMessage(source) << strerror(errno));
+ break;
}
- XML_ParserFree(parser);
- if (stack.root) {
- return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root));
+ if (XML_Parse(parser, buffer, in->gcount(), in->eof()) ==
+ XML_STATUS_ERROR) {
+ stack.root = {};
+ diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser)))
+ << XML_ErrorString(XML_GetErrorCode(parser)));
+ break;
}
- return {};
+ }
+
+ XML_ParserFree(parser);
+ if (stack.root) {
+ return util::make_unique<XmlResource>(ResourceFile{{}, {}, source},
+ std::move(stack.root));
+ }
+ return {};
}
-static void copyAttributes(Element* el, android::ResXMLParser* parser) {
- const size_t attrCount = parser->getAttributeCount();
- if (attrCount > 0) {
- el->attributes.reserve(attrCount);
- for (size_t i = 0; i < attrCount; i++) {
- Attribute attr;
- size_t len;
- const char16_t* str16 = parser->getAttributeNamespace(i, &len);
- if (str16) {
- attr.namespaceUri.assign(str16, len);
- }
-
- str16 = parser->getAttributeName(i, &len);
- if (str16) {
- attr.name.assign(str16, len);
- }
-
- str16 = parser->getAttributeStringValue(i, &len);
- if (str16) {
- attr.value.assign(str16, len);
- }
- el->attributes.push_back(std::move(attr));
- }
+static void CopyAttributes(Element* el, android::ResXMLParser* parser) {
+ const size_t attr_count = parser->getAttributeCount();
+ if (attr_count > 0) {
+ el->attributes.reserve(attr_count);
+ for (size_t i = 0; i < attr_count; i++) {
+ Attribute attr;
+ size_t len;
+ const char16_t* str16 = parser->getAttributeNamespace(i, &len);
+ if (str16) {
+ attr.namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
+
+ str16 = parser->getAttributeName(i, &len);
+ if (str16) {
+ attr.name = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
+
+ str16 = parser->getAttributeStringValue(i, &len);
+ if (str16) {
+ attr.value = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
+ el->attributes.push_back(std::move(attr));
}
+ }
}
-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<XmlResource> Inflate(const void* data, size_t data_len,
+ 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;
+ std::unique_ptr<Node> root;
+ std::stack<Node*> node_stack;
- ResXMLTree tree;
- if (tree.setTo(data, dataLen) != NO_ERROR) {
- return {};
- }
+ ResXMLTree tree;
+ if (tree.setTo(data, data_len) != NO_ERROR) {
+ return {};
+ }
+
+ ResXMLParser::event_code_t code;
+ while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT &&
+ code != ResXMLParser::END_DOCUMENT) {
+ std::unique_ptr<Node> new_node;
+ switch (code) {
+ case ResXMLParser::START_NAMESPACE: {
+ std::unique_ptr<Namespace> node = util::make_unique<Namespace>();
+ size_t len;
+ const char16_t* str16 = tree.getNamespacePrefix(&len);
+ if (str16) {
+ node->namespace_prefix = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
- ResXMLParser::event_code_t code;
- while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT &&
- code != ResXMLParser::END_DOCUMENT) {
- std::unique_ptr<Node> newNode;
- switch (code) {
- case ResXMLParser::START_NAMESPACE: {
- std::unique_ptr<Namespace> node = util::make_unique<Namespace>();
- size_t len;
- const char16_t* str16 = tree.getNamespacePrefix(&len);
- if (str16) {
- node->namespacePrefix.assign(str16, len);
- }
-
- str16 = tree.getNamespaceUri(&len);
- if (str16) {
- node->namespaceUri.assign(str16, len);
- }
- newNode = std::move(node);
- break;
- }
-
- case ResXMLParser::START_TAG: {
- std::unique_ptr<Element> node = util::make_unique<Element>();
- size_t len;
- const char16_t* str16 = tree.getElementNamespace(&len);
- if (str16) {
- node->namespaceUri.assign(str16, len);
- }
-
- str16 = tree.getElementName(&len);
- if (str16) {
- node->name.assign(str16, len);
- }
-
- copyAttributes(node.get(), &tree);
-
- newNode = std::move(node);
- break;
- }
-
- case ResXMLParser::TEXT: {
- std::unique_ptr<Text> node = util::make_unique<Text>();
- size_t len;
- const char16_t* str16 = tree.getText(&len);
- if (str16) {
- node->text.assign(str16, len);
- }
- newNode = std::move(node);
- break;
- }
-
- case ResXMLParser::END_NAMESPACE:
- case ResXMLParser::END_TAG:
- assert(!nodeStack.empty());
- nodeStack.pop();
- break;
-
- default:
- assert(false);
- break;
+ str16 = tree.getNamespaceUri(&len);
+ if (str16) {
+ node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
+ new_node = std::move(node);
+ break;
+ }
+
+ case ResXMLParser::START_TAG: {
+ std::unique_ptr<Element> node = util::make_unique<Element>();
+ size_t len;
+ const char16_t* str16 = tree.getElementNamespace(&len);
+ if (str16) {
+ node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len));
}
- if (newNode) {
- newNode->lineNumber = tree.getLineNumber();
-
- Node* thisNode = newNode.get();
- if (!root) {
- assert(nodeStack.empty());
- root = std::move(newNode);
- } else {
- assert(!nodeStack.empty());
- nodeStack.top()->addChild(std::move(newNode));
- }
-
- if (!nodeCast<Text>(thisNode)) {
- nodeStack.push(thisNode);
- }
+ str16 = tree.getElementName(&len);
+ if (str16) {
+ node->name = util::Utf16ToUtf8(StringPiece16(str16, len));
}
+
+ CopyAttributes(node.get(), &tree);
+
+ new_node = std::move(node);
+ break;
+ }
+
+ case ResXMLParser::TEXT: {
+ std::unique_ptr<Text> node = util::make_unique<Text>();
+ size_t len;
+ const char16_t* str16 = tree.getText(&len);
+ if (str16) {
+ node->text = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
+ new_node = std::move(node);
+ break;
+ }
+
+ case ResXMLParser::END_NAMESPACE:
+ case ResXMLParser::END_TAG:
+ CHECK(!node_stack.empty());
+ node_stack.pop();
+ break;
+
+ default:
+ LOG(FATAL) << "unhandled XML chunk type";
+ break;
+ }
+
+ if (new_node) {
+ new_node->line_number = tree.getLineNumber();
+
+ Node* this_node = new_node.get();
+ if (!root) {
+ CHECK(node_stack.empty()) << "node stack should be empty";
+ root = std::move(new_node);
+ } else {
+ CHECK(!node_stack.empty()) << "node stack should not be empty";
+ node_stack.top()->AppendChild(std::move(new_node));
+ }
+
+ if (!NodeCast<Text>(this_node)) {
+ node_stack.push(this_node);
+ }
}
- return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
+ }
+ return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
}
-Element* findRootElement(XmlResource* doc) {
- return findRootElement(doc->root.get());
+std::unique_ptr<Node> Namespace::Clone() {
+ auto ns = util::make_unique<Namespace>();
+ ns->comment = comment;
+ ns->line_number = line_number;
+ ns->column_number = column_number;
+ ns->namespace_prefix = namespace_prefix;
+ ns->namespace_uri = namespace_uri;
+
+ ns->children.reserve(children.size());
+ for (const std::unique_ptr<xml::Node>& child : children) {
+ ns->AppendChild(child->Clone());
+ }
+ return std::move(ns);
}
-Element* findRootElement(Node* node) {
- if (!node) {
- return nullptr;
- }
+Element* FindRootElement(XmlResource* doc) {
+ return FindRootElement(doc->root.get());
+}
- 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();
+Element* FindRootElement(Node* node) {
+ if (!node) {
+ return nullptr;
+ }
+
+ Element* el = nullptr;
+ while ((el = NodeCast<Element>(node)) == nullptr) {
+ if (node->children.empty()) {
+ return nullptr;
}
- return el;
+ // We are looking for the first element, and namespaces can only have one
+ // child.
+ node = node->children.front().get();
+ }
+ return el;
}
-void Node::addChild(std::unique_ptr<Node> child) {
- child->parent = this;
- children.push_back(std::move(child));
+void Node::AppendChild(std::unique_ptr<Node> child) {
+ child->parent = this;
+ children.push_back(std::move(child));
}
-Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) {
- for (auto& attr : attributes) {
- if (ns == attr.namespaceUri && name == attr.name) {
- return &attr;
- }
+void Node::InsertChild(size_t index, std::unique_ptr<Node> child) {
+ child->parent = this;
+ children.insert(children.begin() + index, std::move(child));
+}
+
+Attribute* Element::FindAttribute(const StringPiece& ns,
+ const StringPiece& name) {
+ for (auto& attr : attributes) {
+ if (ns == attr.namespace_uri && name == attr.name) {
+ return &attr;
}
- return nullptr;
+ }
+ return nullptr;
}
-Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) {
- return findChildWithAttribute(ns, name, {}, {}, {});
+Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
+ return FindChildWithAttribute(ns, name, {}, {}, {});
}
-Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
- const StringPiece16& attrNs, const StringPiece16& attrName,
- const StringPiece16& attrValue) {
- for (auto& childNode : children) {
- Node* child = childNode.get();
- while (nodeCast<Namespace>(child)) {
- if (child->children.empty()) {
- break;
- }
- child = child->children[0].get();
+Element* Element::FindChildWithAttribute(const StringPiece& ns,
+ const StringPiece& name,
+ const StringPiece& attr_ns,
+ const StringPiece& attr_name,
+ const StringPiece& attr_value) {
+ for (auto& child_node : children) {
+ Node* child = child_node.get();
+ while (NodeCast<Namespace>(child)) {
+ if (child->children.empty()) {
+ break;
+ }
+ child = child->children[0].get();
+ }
+
+ if (Element* el = NodeCast<Element>(child)) {
+ if (ns == el->namespace_uri && name == el->name) {
+ if (attr_ns.empty() && attr_name.empty()) {
+ return el;
}
- if (Element* el = nodeCast<Element>(child)) {
- if (ns == el->namespaceUri && name == el->name) {
- if (attrNs.empty() && attrName.empty()) {
- return el;
- }
-
- Attribute* attr = el->findAttribute(attrNs, attrName);
- if (attr && attrValue == attr->value) {
- return el;
- }
- }
+ Attribute* attr = el->FindAttribute(attr_ns, attr_name);
+ if (attr && attr_value == attr->value) {
+ return el;
}
+ }
}
- return nullptr;
+ }
+ return nullptr;
}
-std::vector<Element*> Element::getChildElements() {
- std::vector<Element*> elements;
- for (auto& childNode : children) {
- Node* child = childNode.get();
- while (nodeCast<Namespace>(child)) {
- if (child->children.empty()) {
- break;
- }
- child = child->children[0].get();
- }
+std::vector<Element*> Element::GetChildElements() {
+ std::vector<Element*> elements;
+ for (auto& child_node : children) {
+ Node* child = child_node.get();
+ while (NodeCast<Namespace>(child)) {
+ if (child->children.empty()) {
+ break;
+ }
+ child = child->children[0].get();
+ }
- if (Element* el = nodeCast<Element>(child)) {
- elements.push_back(el);
- }
+ if (Element* el = NodeCast<Element>(child)) {
+ elements.push_back(el);
}
- return elements;
+ }
+ return elements;
}
-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;
- }
+std::unique_ptr<Node> Element::Clone() {
+ auto el = util::make_unique<Element>();
+ el->comment = comment;
+ el->line_number = line_number;
+ el->column_number = column_number;
+ el->name = name;
+ el->namespace_uri = namespace_uri;
+
+ el->attributes.reserve(attributes.size());
+ for (xml::Attribute& attr : attributes) {
+ // Don't copy compiled values or attributes.
+ el->attributes.push_back(
+ xml::Attribute{attr.namespace_uri, attr.name, attr.value});
+ }
+
+ el->children.reserve(children.size());
+ for (const std::unique_ptr<xml::Node>& child : children) {
+ el->AppendChild(child->Clone());
+ }
+ return std::move(el);
+}
- Visitor::visit(ns);
+std::unique_ptr<Node> Text::Clone() {
+ auto t = util::make_unique<Text>();
+ t->comment = comment;
+ t->line_number = line_number;
+ t->column_number = column_number;
+ t->text = text;
+ return std::move(t);
+}
- if (added) {
- mPackageDecls.pop_back();
- }
+void PackageAwareVisitor::Visit(Namespace* ns) {
+ bool added = false;
+ if (Maybe<ExtractedPackage> maybe_package =
+ ExtractPackageFromNamespace(ns->namespace_uri)) {
+ ExtractedPackage& package = maybe_package.value();
+ package_decls_.push_back(
+ PackageDecl{ns->namespace_prefix, std::move(package)});
+ added = true;
+ }
+
+ Visitor::Visit(ns);
+
+ if (added) {
+ package_decls_.pop_back();
+ }
}
-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 {};
+Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(
+ const StringPiece& alias, const StringPiece& local_package) const {
+ if (alias.empty()) {
+ return ExtractedPackage{local_package.ToString(), false /* private */};
+ }
+
+ const auto rend = package_decls_.rend();
+ for (auto iter = package_decls_.rbegin(); iter != rend; ++iter) {
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{local_package.ToString(),
+ iter->package.private_namespace};
+ }
+ return iter->package;
+ }
+ }
+ return {};
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index b374d20039a5..720fe357bfa1 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -17,6 +17,11 @@
#ifndef AAPT_XML_DOM_H
#define AAPT_XML_DOM_H
+#include <istream>
+#include <memory>
+#include <string>
+#include <vector>
+
#include "Diagnostics.h"
#include "Resource.h"
#include "ResourceValues.h"
@@ -24,30 +29,28 @@
#include "util/Util.h"
#include "xml/XmlUtil.h"
-#include <istream>
-#include <memory>
-#include <string>
-#include <vector>
-
namespace aapt {
namespace xml {
-struct RawVisitor;
+class 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;
+class Node {
+ public:
+ Node* parent = nullptr;
+ size_t line_number = 0;
+ size_t column_number = 0;
+ std::string comment;
+ std::vector<std::unique_ptr<Node>> children;
+
+ virtual ~Node() = default;
+
+ void AppendChild(std::unique_ptr<Node> child);
+ void InsertChild(size_t index, std::unique_ptr<Node> child);
+ virtual void Accept(RawVisitor* visitor) = 0;
+ virtual std::unique_ptr<Node> Clone() = 0;
};
/**
@@ -55,166 +58,175 @@ struct Node {
* subclass of Node.
*/
template <typename Derived>
-struct BaseNode : public Node {
- virtual void accept(RawVisitor* visitor) override;
+class BaseNode : public Node {
+ public:
+ 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;
+class Namespace : public BaseNode<Namespace> {
+ public:
+ std::string namespace_prefix;
+ std::string namespace_uri;
+
+ std::unique_ptr<Node> Clone() override;
};
struct AaptAttribute {
- Maybe<ResourceId> id;
- aapt::Attribute attribute;
+ Maybe<ResourceId> id;
+ aapt::Attribute attribute;
};
/**
* An XML attribute.
*/
struct Attribute {
- std::u16string namespaceUri;
- std::u16string name;
- std::u16string value;
+ std::string namespace_uri;
+ std::string name;
+ std::string value;
- Maybe<AaptAttribute> compiledAttribute;
- std::unique_ptr<Item> compiledValue;
+ Maybe<AaptAttribute> compiled_attribute;
+ std::unique_ptr<Item> compiled_value;
};
/**
* 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();
+class Element : public BaseNode<Element> {
+ public:
+ std::string namespace_uri;
+ std::string name;
+ std::vector<Attribute> attributes;
+
+ Attribute* FindAttribute(const StringPiece& ns, const StringPiece& name);
+ xml::Element* FindChild(const StringPiece& ns, const StringPiece& name);
+ xml::Element* FindChildWithAttribute(const StringPiece& ns,
+ const StringPiece& name,
+ const StringPiece& attr_ns,
+ const StringPiece& attr_name,
+ const StringPiece& attr_value);
+ std::vector<xml::Element*> GetChildElements();
+ std::unique_ptr<Node> Clone() override;
};
/**
* A Text (CDATA) XML node. Can not have any children.
*/
-struct Text : public BaseNode<Text> {
- std::u16string text;
+class Text : public BaseNode<Text> {
+ public:
+ std::string text;
+
+ std::unique_ptr<Node> Clone() override;
};
/**
* An XML resource with a source, name, and XML tree.
*/
-struct XmlResource {
- ResourceFile file;
- std::unique_ptr<xml::Node> root;
+class XmlResource {
+ public:
+ 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);
+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);
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len,
+ IDiagnostics* diag, const Source& source);
-Element* findRootElement(XmlResource* doc);
-Element* findRootElement(Node* node);
+Element* FindRootElement(XmlResource* doc);
+Element* FindRootElement(Node* node);
/**
- * A visitor interface for the different XML Node subtypes. This will not traverse into
+ * A visitor interface for the different XML Node subtypes. This will not
+ * traverse into
* children. Use Visitor for that.
*/
-struct RawVisitor {
- virtual ~RawVisitor() = default;
+class RawVisitor {
+ public:
+ virtual ~RawVisitor() = default;
- virtual void visit(Namespace* node) {}
- virtual void visit(Element* node) {}
- virtual void visit(Text* text) {}
+ 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;
+class Visitor : public RawVisitor {
+ public:
+ using RawVisitor::Visit;
- void visit(Namespace* node) override {
- visitChildren(node);
- }
+ void Visit(Namespace* node) override { VisitChildren(node); }
- void visit(Element* node) override {
- visitChildren(node);
- }
+ void Visit(Element* node) override { VisitChildren(node); }
- void visit(Text* text) override {
- visitChildren(text);
- }
+ void Visit(Text* text) override { VisitChildren(text); }
- void visitChildren(Node* node) {
- for (auto& child : node->children) {
- child->accept(this);
- }
+ 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;
- };
+ public:
+ using Visitor::Visit;
- std::vector<PackageDecl> mPackageDecls;
+ void Visit(Namespace* ns) override;
+ Maybe<ExtractedPackage> TransformPackageAlias(
+ const StringPiece& alias,
+ const StringPiece& local_package) const override;
-public:
- using Visitor::visit;
+ private:
+ struct PackageDecl {
+ std::string prefix;
+ ExtractedPackage package;
+ };
- void visit(Namespace* ns) override;
- Maybe<ExtractedPackage> transformPackageAlias(
- const StringPiece16& alias, const StringPiece16& localPackage) const override;
+ std::vector<PackageDecl> package_decls_;
};
// Implementations
template <typename Derived>
-void BaseNode<Derived>::accept(RawVisitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
+void BaseNode<Derived>::Accept(RawVisitor* visitor) {
+ visitor->Visit(static_cast<Derived*>(this));
}
template <typename T>
-struct NodeCastImpl : public RawVisitor {
- using RawVisitor::visit;
+class NodeCastImpl : public RawVisitor {
+ public:
+ using RawVisitor::Visit;
- T* value = nullptr;
+ T* value = nullptr;
- void visit(T* v) override {
- value = v;
- }
+ void Visit(T* v) override { value = v; }
};
template <typename T>
-T* nodeCast(Node* node) {
- NodeCastImpl<T> visitor;
- node->accept(&visitor);
- return visitor.value;
+T* NodeCast(Node* node) {
+ NodeCastImpl<T> visitor;
+ node->Accept(&visitor);
+ return visitor.value;
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
-#endif // AAPT_XML_DOM_H
+#endif // AAPT_XML_DOM_H
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 431ee2c8fb46..a414afe92fc0 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -16,17 +16,19 @@
#include "xml/XmlDom.h"
-#include <gtest/gtest.h>
#include <sstream>
#include <string>
+#include "test/Test.h"
+
namespace aapt {
-constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+constexpr const char* kXmlPreamble =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
TEST(XmlDomTest, Inflate) {
- std::stringstream in(kXmlPreamble);
- in << R"EOF(
+ std::stringstream in(kXmlPreamble);
+ in << R"EOF(
<Layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@@ -36,15 +38,15 @@ TEST(XmlDomTest, Inflate) {
</Layout>
)EOF";
- const Source source = { "test.xml" };
- StdErrDiagnostics diag;
- std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source);
- ASSERT_NE(doc, nullptr);
+ const Source source = {"test.xml"};
+ StdErrDiagnostics diag;
+ std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source);
+ ASSERT_NE(doc, nullptr);
- 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");
+ xml::Namespace* ns = xml::NodeCast<xml::Namespace>(doc->root.get());
+ ASSERT_NE(ns, nullptr);
+ EXPECT_EQ(ns->namespace_uri, xml::kSchemaAndroid);
+ EXPECT_EQ(ns->namespace_prefix, "android");
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index 323ec05b5f2c..e59fa86788cd 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -14,295 +14,295 @@
* limitations under the License.
*/
+#include <iostream>
+#include <string>
+
#include "util/Maybe.h"
#include "util/Util.h"
#include "xml/XmlPullParser.h"
#include "xml/XmlUtil.h"
-#include <iostream>
-#include <string>
-
namespace aapt {
namespace xml {
constexpr char kXmlNamespaceSep = 1;
-XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
- mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
- XML_SetUserData(mParser, this);
- XML_SetElementHandler(mParser, startElementHandler, endElementHandler);
- XML_SetNamespaceDeclHandler(mParser, startNamespaceHandler, endNamespaceHandler);
- XML_SetCharacterDataHandler(mParser, characterDataHandler);
- XML_SetCommentHandler(mParser, commentDataHandler);
- mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ });
+XmlPullParser::XmlPullParser(std::istream& in) : in_(in), empty_(), depth_(0) {
+ parser_ = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
+ XML_SetUserData(parser_, this);
+ XML_SetElementHandler(parser_, StartElementHandler, EndElementHandler);
+ XML_SetNamespaceDeclHandler(parser_, StartNamespaceHandler,
+ EndNamespaceHandler);
+ XML_SetCharacterDataHandler(parser_, CharacterDataHandler);
+ XML_SetCommentHandler(parser_, CommentDataHandler);
+ event_queue_.push(EventData{Event::kStartDocument, 0, depth_++});
}
-XmlPullParser::~XmlPullParser() {
- XML_ParserFree(mParser);
-}
+XmlPullParser::~XmlPullParser() { XML_ParserFree(parser_); }
+
+XmlPullParser::Event XmlPullParser::Next() {
+ const Event currentEvent = event();
+ if (currentEvent == Event::kBadDocument ||
+ currentEvent == Event::kEndDocument) {
+ return currentEvent;
+ }
-XmlPullParser::Event XmlPullParser::next() {
- const Event currentEvent = getEvent();
- if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {
- return currentEvent;
+ event_queue_.pop();
+ while (event_queue_.empty()) {
+ in_.read(buffer_, sizeof(buffer_) / sizeof(*buffer_));
+
+ const bool done = in_.eof();
+ if (in_.bad() && !done) {
+ error_ = strerror(errno);
+ event_queue_.push(EventData{Event::kBadDocument});
+ continue;
}
- mEventQueue.pop();
- while (mEventQueue.empty()) {
- mIn.read(mBuffer, sizeof(mBuffer) / sizeof(*mBuffer));
-
- const bool done = mIn.eof();
- if (mIn.bad() && !done) {
- mLastError = strerror(errno);
- mEventQueue.push(EventData{ Event::kBadDocument });
- continue;
- }
-
- if (XML_Parse(mParser, mBuffer, mIn.gcount(), done) == XML_STATUS_ERROR) {
- mLastError = XML_ErrorString(XML_GetErrorCode(mParser));
- mEventQueue.push(EventData{ Event::kBadDocument });
- continue;
- }
-
- if (done) {
- mEventQueue.push(EventData{ Event::kEndDocument, 0, 0 });
- }
+ if (XML_Parse(parser_, buffer_, in_.gcount(), done) == XML_STATUS_ERROR) {
+ error_ = XML_ErrorString(XML_GetErrorCode(parser_));
+ event_queue_.push(EventData{Event::kBadDocument});
+ continue;
}
- Event event = getEvent();
-
- // 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<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri());
- if (event == Event::kStartNamespace) {
- if (result) {
- mPackageAliases.emplace_back(
- PackageDecl{ getNamespacePrefix(), std::move(result.value()) });
- }
- } else {
- if (result) {
- mPackageAliases.pop_back();
- }
- }
+ if (done) {
+ event_queue_.push(EventData{Event::kEndDocument, 0, 0});
+ }
+ }
+
+ Event next_event = event();
+
+ // Record namespace prefixes and package names so that we can do our own
+ // handling of references that use namespace aliases.
+ if (next_event == Event::kStartNamespace ||
+ next_event == Event::kEndNamespace) {
+ Maybe<ExtractedPackage> result =
+ ExtractPackageFromNamespace(namespace_uri());
+ if (next_event == Event::kStartNamespace) {
+ if (result) {
+ package_aliases_.emplace_back(
+ PackageDecl{namespace_prefix(), std::move(result.value())});
+ }
+ } else {
+ if (result) {
+ package_aliases_.pop_back();
+ }
}
+ }
- return event;
+ return next_event;
}
-XmlPullParser::Event XmlPullParser::getEvent() const {
- return mEventQueue.front().event;
+XmlPullParser::Event XmlPullParser::event() const {
+ return event_queue_.front().event;
}
-const std::string& XmlPullParser::getLastError() const {
- return mLastError;
-}
+const std::string& XmlPullParser::error() const { return error_; }
-const std::u16string& XmlPullParser::getComment() const {
- return mEventQueue.front().data1;
+const std::string& XmlPullParser::comment() const {
+ return event_queue_.front().data1;
}
-size_t XmlPullParser::getLineNumber() const {
- return mEventQueue.front().lineNumber;
+size_t XmlPullParser::line_number() const {
+ return event_queue_.front().line_number;
}
-size_t XmlPullParser::getDepth() const {
- return mEventQueue.front().depth;
-}
+size_t XmlPullParser::depth() const { return event_queue_.front().depth; }
-const std::u16string& XmlPullParser::getText() const {
- if (getEvent() != Event::kText) {
- return mEmpty;
- }
- return mEventQueue.front().data1;
+const std::string& XmlPullParser::text() const {
+ if (event() != Event::kText) {
+ return empty_;
+ }
+ return event_queue_.front().data1;
}
-const std::u16string& XmlPullParser::getNamespacePrefix() const {
- const Event currentEvent = getEvent();
- if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
- return mEmpty;
- }
- return mEventQueue.front().data1;
+const std::string& XmlPullParser::namespace_prefix() const {
+ const Event current_event = event();
+ if (current_event != Event::kStartNamespace &&
+ current_event != Event::kEndNamespace) {
+ return empty_;
+ }
+ return event_queue_.front().data1;
}
-const std::u16string& XmlPullParser::getNamespaceUri() const {
- const Event currentEvent = getEvent();
- if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
- return mEmpty;
- }
- return mEventQueue.front().data2;
+const std::string& XmlPullParser::namespace_uri() const {
+ const Event current_event = event();
+ if (current_event != Event::kStartNamespace &&
+ current_event != Event::kEndNamespace) {
+ return empty_;
+ }
+ return event_queue_.front().data2;
}
-Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias(
- const StringPiece16& alias, const StringPiece16& localPackage) const {
- if (alias.empty()) {
- return ExtractedPackage{ localPackage.toString(), false /* private */ };
+Maybe<ExtractedPackage> XmlPullParser::TransformPackageAlias(
+ const StringPiece& alias, const StringPiece& local_package) const {
+ if (alias.empty()) {
+ return ExtractedPackage{local_package.ToString(), false /* private */};
+ }
+
+ const auto end_iter = package_aliases_.rend();
+ for (auto iter = package_aliases_.rbegin(); iter != end_iter; ++iter) {
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{local_package.ToString(),
+ iter->package.private_namespace};
+ }
+ return iter->package;
}
-
- const auto endIter = mPackageAliases.rend();
- for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
- if (alias == iter->prefix) {
- if (iter->package.package.empty()) {
- return ExtractedPackage{ localPackage.toString(),
- iter->package.privateNamespace };
- }
- return iter->package;
- }
- }
- return {};
+ }
+ return {};
}
-const std::u16string& XmlPullParser::getElementNamespace() const {
- const Event currentEvent = getEvent();
- if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
- return mEmpty;
- }
- return mEventQueue.front().data1;
+const std::string& XmlPullParser::element_namespace() const {
+ const Event current_event = event();
+ if (current_event != Event::kStartElement &&
+ current_event != Event::kEndElement) {
+ return empty_;
+ }
+ return event_queue_.front().data1;
}
-const std::u16string& XmlPullParser::getElementName() const {
- const Event currentEvent = getEvent();
- if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
- return mEmpty;
- }
- return mEventQueue.front().data2;
+const std::string& XmlPullParser::element_name() const {
+ const Event current_event = event();
+ if (current_event != Event::kStartElement &&
+ current_event != Event::kEndElement) {
+ return empty_;
+ }
+ return event_queue_.front().data2;
}
-XmlPullParser::const_iterator XmlPullParser::beginAttributes() const {
- return mEventQueue.front().attributes.begin();
+XmlPullParser::const_iterator XmlPullParser::begin_attributes() const {
+ return event_queue_.front().attributes.begin();
}
-XmlPullParser::const_iterator XmlPullParser::endAttributes() const {
- return mEventQueue.front().attributes.end();
+XmlPullParser::const_iterator XmlPullParser::end_attributes() const {
+ return event_queue_.front().attributes.end();
}
-size_t XmlPullParser::getAttributeCount() const {
- if (getEvent() != Event::kStartElement) {
- return 0;
- }
- return mEventQueue.front().attributes.size();
+size_t XmlPullParser::attribute_count() const {
+ if (event() != Event::kStartElement) {
+ return 0;
+ }
+ return event_queue_.front().attributes.size();
}
/**
* Extracts the namespace and name of an expanded element or attribute name.
*/
-static void splitName(const char* name, std::u16string& outNs, std::u16string& outName) {
- const char* p = name;
- while (*p != 0 && *p != kXmlNamespaceSep) {
- p++;
- }
-
- if (*p == 0) {
- outNs = std::u16string();
- outName = util::utf8ToUtf16(name);
- } else {
- outNs = util::utf8ToUtf16(StringPiece(name, (p - name)));
- outName = util::utf8ToUtf16(p + 1);
- }
+static void SplitName(const char* name, std::string& out_ns,
+ std::string& out_name) {
+ const char* p = name;
+ while (*p != 0 && *p != kXmlNamespaceSep) {
+ p++;
+ }
+
+ if (*p == 0) {
+ out_ns = std::string();
+ out_name = name;
+ } else {
+ out_ns = StringPiece(name, (p - name)).ToString();
+ out_name = p + 1;
+ }
}
-void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
- const char* uri) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
- std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string();
- parser->mNamespaceUris.push(namespaceUri);
- parser->mEventQueue.push(EventData{
- Event::kStartNamespace,
- XML_GetCurrentLineNumber(parser->mParser),
- parser->mDepth++,
- prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(),
- namespaceUri
- });
+void XMLCALL XmlPullParser::StartNamespaceHandler(void* user_data,
+ const char* prefix,
+ const char* uri) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+ std::string namespace_uri = uri != nullptr ? uri : std::string();
+ parser->namespace_uris_.push(namespace_uri);
+ parser->event_queue_.push(
+ EventData{Event::kStartNamespace,
+ XML_GetCurrentLineNumber(parser->parser_), parser->depth_++,
+ prefix != nullptr ? prefix : std::string(), namespace_uri});
}
-void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name,
- const char** attrs) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
-
- EventData data = {
- Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++
- };
- splitName(name, data.data1, data.data2);
-
- while (*attrs) {
- Attribute attribute;
- splitName(*attrs++, attribute.namespaceUri, attribute.name);
- attribute.value = util::utf8ToUtf16(*attrs++);
-
- // Insert in sorted order.
- auto iter = std::lower_bound(data.attributes.begin(), data.attributes.end(), attribute);
- data.attributes.insert(iter, std::move(attribute));
- }
-
- // Move the structure into the queue (no copy).
- parser->mEventQueue.push(std::move(data));
+void XMLCALL XmlPullParser::StartElementHandler(void* user_data,
+ const char* name,
+ const char** attrs) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+
+ EventData data = {Event::kStartElement,
+ XML_GetCurrentLineNumber(parser->parser_),
+ parser->depth_++};
+ SplitName(name, data.data1, data.data2);
+
+ while (*attrs) {
+ Attribute attribute;
+ SplitName(*attrs++, attribute.namespace_uri, attribute.name);
+ attribute.value = *attrs++;
+
+ // Insert in sorted order.
+ auto iter = std::lower_bound(data.attributes.begin(), data.attributes.end(),
+ attribute);
+ data.attributes.insert(iter, std::move(attribute));
+ }
+
+ // Move the structure into the queue (no copy).
+ parser->event_queue_.push(std::move(data));
}
-void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
+void XMLCALL XmlPullParser::CharacterDataHandler(void* user_data, const char* s,
+ int len) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
- parser->mEventQueue.push(EventData{
- Event::kText,
- XML_GetCurrentLineNumber(parser->mParser),
- parser->mDepth,
- util::utf8ToUtf16(StringPiece(s, len))
- });
+ parser->event_queue_.push(
+ EventData{Event::kText, XML_GetCurrentLineNumber(parser->parser_),
+ parser->depth_, StringPiece(s, len).ToString()});
}
-void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
+void XMLCALL XmlPullParser::EndElementHandler(void* user_data,
+ const char* name) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
- EventData data = {
- Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth)
- };
- splitName(name, data.data1, data.data2);
+ EventData data = {Event::kEndElement,
+ XML_GetCurrentLineNumber(parser->parser_),
+ --(parser->depth_)};
+ SplitName(name, data.data1, data.data2);
- // Move the data into the queue (no copy).
- parser->mEventQueue.push(std::move(data));
+ // Move the data into the queue (no copy).
+ parser->event_queue_.push(std::move(data));
}
-void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
-
- parser->mEventQueue.push(EventData{
- Event::kEndNamespace,
- XML_GetCurrentLineNumber(parser->mParser),
- --(parser->mDepth),
- prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(),
- parser->mNamespaceUris.top()
- });
- parser->mNamespaceUris.pop();
+void XMLCALL XmlPullParser::EndNamespaceHandler(void* user_data,
+ const char* prefix) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+
+ parser->event_queue_.push(
+ EventData{Event::kEndNamespace, XML_GetCurrentLineNumber(parser->parser_),
+ --(parser->depth_), prefix != nullptr ? prefix : std::string(),
+ parser->namespace_uris_.top()});
+ parser->namespace_uris_.pop();
}
-void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
+void XMLCALL XmlPullParser::CommentDataHandler(void* user_data,
+ const char* comment) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
- parser->mEventQueue.push(EventData{
- Event::kComment,
- XML_GetCurrentLineNumber(parser->mParser),
- parser->mDepth,
- util::utf8ToUtf16(comment)
- });
+ parser->event_queue_.push(EventData{Event::kComment,
+ XML_GetCurrentLineNumber(parser->parser_),
+ parser->depth_, comment});
}
-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<StringPiece> FindAttribute(const XmlPullParser* parser,
+ const StringPiece& name) {
+ auto iter = parser->FindAttribute("", name);
+ if (iter != parser->end_attributes()) {
+ return StringPiece(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;
- }
+Maybe<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
+ const StringPiece& name) {
+ auto iter = parser->FindAttribute("", name);
+ if (iter != parser->end_attributes()) {
+ StringPiece trimmed = util::TrimWhitespace(iter->value);
+ if (!trimmed.empty()) {
+ return trimmed;
}
- return {};
+ }
+ return {};
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index 7e7070e5e5ea..ff58d604e35e 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -17,14 +17,9 @@
#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 <expat.h>
#include <algorithm>
-#include <expat.h>
#include <istream>
#include <ostream>
#include <queue>
@@ -32,267 +27,303 @@
#include <string>
#include <vector>
+#include "android-base/macros.h"
+
+#include "Resource.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "xml/XmlUtil.h"
+
namespace aapt {
namespace xml {
class XmlPullParser : public IPackageDeclStack {
-public:
- enum class Event {
- kBadDocument,
- kStartDocument,
- kEndDocument,
-
- kStartNamespace,
- kEndNamespace,
- kStartElement,
- kEndElement,
- kText,
- kComment,
- };
-
- /**
- * 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);
-
- XmlPullParser(std::istream& in);
- ~XmlPullParser();
-
- /**
- * Returns the current event that is being processed.
- */
- Event getEvent() const;
-
- const std::string& getLastError() const;
-
- /**
- * Note, unlike XmlPullParser, the first call to next() will return
- * StartElement of the first element.
- */
- Event next();
-
- //
- // These are available for all nodes.
- //
-
- const std::u16string& getComment() const;
- size_t getLineNumber() const;
- size_t getDepth() const;
-
- /**
- * Returns the character data for a Text event.
- */
- const std::u16string& getText() const;
-
- //
- // Namespace prefix and URI are available for StartNamespace and EndNamespace.
- //
-
- 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:
- * xmlns:app = "http://schemas.android.com/apk/res/com.android.app"
- * ...
- * android:text="@app:string/message"
- *
- * In this case, 'app' will be converted to 'com.android.app'.
- *
- * If xmlns:app="http://schemas.android.com/apk/res-auto", then
- * 'package' will be set to 'defaultPackage'.
- */
- Maybe<ExtractedPackage> transformPackageAlias(
- const StringPiece16& alias, const StringPiece16& localPackage) const override;
-
- //
- // Remaining methods are for retrieving information about attributes
- // associated with a StartElement.
- //
- // Attributes must be in sorted order (according to the less than operator
- // of struct Attribute).
- //
-
- struct Attribute {
- std::u16string namespaceUri;
- std::u16string name;
- std::u16string value;
-
- int compare(const Attribute& rhs) const;
- bool operator<(const Attribute& rhs) const;
- bool operator==(const Attribute& rhs) const;
- bool operator!=(const Attribute& rhs) const;
- };
-
- using const_iterator = std::vector<Attribute>::const_iterator;
-
- 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;
+ public:
+ enum class Event {
+ kBadDocument,
+ kStartDocument,
+ kEndDocument,
+
+ kStartNamespace,
+ kEndNamespace,
+ kStartElement,
+ kEndElement,
+ kText,
+ kComment,
+ };
+
+ /**
+ * Skips to the next direct descendant node of the given start_depth,
+ * skipping namespace nodes.
+ *
+ * When NextChildNode() returns true, you can expect Comments, Text, and
+ * StartElement events.
+ */
+ static bool NextChildNode(XmlPullParser* parser, size_t start_depth);
+ static bool SkipCurrentElement(XmlPullParser* parser);
+ static bool IsGoodEvent(Event event);
+
+ explicit XmlPullParser(std::istream& in);
+ ~XmlPullParser();
+
+ /**
+ * Returns the current event that is being processed.
+ */
+ Event event() const;
+
+ const std::string& error() const;
+
+ /**
+ * Note, unlike XmlPullParser, the first call to next() will return
+ * StartElement of the first element.
+ */
+ Event Next();
+
+ //
+ // These are available for all nodes.
+ //
+
+ const std::string& comment() const;
+ size_t line_number() const;
+ size_t depth() const;
+
+ /**
+ * Returns the character data for a Text event.
+ */
+ const std::string& text() const;
+
+ //
+ // Namespace prefix and URI are available for StartNamespace and EndNamespace.
+ //
+
+ const std::string& namespace_prefix() const;
+ const std::string& namespace_uri() const;
+
+ //
+ // These are available for StartElement and EndElement.
+ //
+
+ const std::string& element_namespace() const;
+ const std::string& element_name() const;
+
+ /*
+ * Uses the current stack of namespaces to resolve the package. Eg:
+ * xmlns:app = "http://schemas.android.com/apk/res/com.android.app"
+ * ...
+ * android:text="@app:string/message"
+ *
+ * In this case, 'app' will be converted to 'com.android.app'.
+ *
+ * If xmlns:app="http://schemas.android.com/apk/res-auto", then
+ * 'package' will be set to 'defaultPackage'.
+ */
+ Maybe<ExtractedPackage> TransformPackageAlias(
+ const StringPiece& alias,
+ const StringPiece& local_package) const override;
+
+ //
+ // Remaining methods are for retrieving information about attributes
+ // associated with a StartElement.
+ //
+ // Attributes must be in sorted order (according to the less than operator
+ // of struct Attribute).
+ //
+
+ struct Attribute {
+ std::string namespace_uri;
+ std::string name;
+ std::string value;
+
+ int compare(const Attribute& rhs) const;
+ bool operator<(const Attribute& rhs) const;
+ bool operator==(const Attribute& rhs) const;
+ bool operator!=(const Attribute& rhs) const;
+ };
+
+ using const_iterator = std::vector<Attribute>::const_iterator;
+
+ const_iterator begin_attributes() const;
+ const_iterator end_attributes() const;
+ size_t attribute_count() const;
+ const_iterator FindAttribute(StringPiece namespace_uri,
+ StringPiece name) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlPullParser);
+
+ static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix,
+ const char* uri);
+ static void XMLCALL StartElementHandler(void* user_data, const char* name,
+ const char** attrs);
+ static void XMLCALL CharacterDataHandler(void* user_data, const char* s,
+ int len);
+ static void XMLCALL EndElementHandler(void* user_data, const char* name);
+ static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix);
+ static void XMLCALL CommentDataHandler(void* user_data, const char* comment);
+
+ struct EventData {
+ Event event;
+ size_t line_number;
+ size_t depth;
+ std::string data1;
+ std::string data2;
+ std::vector<Attribute> attributes;
+ };
+
+ std::istream& in_;
+ XML_Parser parser_;
+ char buffer_[16384];
+ std::queue<EventData> event_queue_;
+ std::string error_;
+ const std::string empty_;
+ size_t depth_;
+ std::stack<std::string> namespace_uris_;
+
+ struct PackageDecl {
+ std::string prefix;
+ ExtractedPackage package;
+ };
+ std::vector<PackageDecl> package_aliases_;
};
/**
* Finds the attribute in the current element within the global namespace.
*/
-Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name);
+Maybe<StringPiece> FindAttribute(const XmlPullParser* parser,
+ const StringPiece& name);
/**
- * Finds the attribute in the current element within the global namespace. The attribute's value
+ * 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);
+Maybe<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
+ const StringPiece& name);
//
// Implementation
//
-inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event event) {
- switch (event) {
- case XmlPullParser::Event::kBadDocument: return out << "BadDocument";
- case XmlPullParser::Event::kStartDocument: return out << "StartDocument";
- case XmlPullParser::Event::kEndDocument: return out << "EndDocument";
- case XmlPullParser::Event::kStartNamespace: return out << "StartNamespace";
- case XmlPullParser::Event::kEndNamespace: return out << "EndNamespace";
- case XmlPullParser::Event::kStartElement: return out << "StartElement";
- case XmlPullParser::Event::kEndElement: return out << "EndElement";
- case XmlPullParser::Event::kText: return out << "Text";
- case XmlPullParser::Event::kComment: return out << "Comment";
- }
- return out;
+inline ::std::ostream& operator<<(::std::ostream& out,
+ XmlPullParser::Event event) {
+ switch (event) {
+ case XmlPullParser::Event::kBadDocument:
+ return out << "BadDocument";
+ case XmlPullParser::Event::kStartDocument:
+ return out << "StartDocument";
+ case XmlPullParser::Event::kEndDocument:
+ return out << "EndDocument";
+ case XmlPullParser::Event::kStartNamespace:
+ return out << "StartNamespace";
+ case XmlPullParser::Event::kEndNamespace:
+ return out << "EndNamespace";
+ case XmlPullParser::Event::kStartElement:
+ return out << "StartElement";
+ case XmlPullParser::Event::kEndElement:
+ return out << "EndElement";
+ case XmlPullParser::Event::kText:
+ return out << "Text";
+ case XmlPullParser::Event::kComment:
+ return out << "Comment";
+ }
+ return out;
}
-inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) {
- Event event;
+inline bool XmlPullParser::NextChildNode(XmlPullParser* parser,
+ size_t start_depth) {
+ Event event;
+
+ // First get back to the start depth.
+ while (IsGoodEvent(event = parser->Next()) &&
+ parser->depth() > start_depth + 1) {
+ }
- // 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();
+ // Now look for the first good node.
+ while ((event != Event::kEndElement || parser->depth() > start_depth) &&
+ IsGoodEvent(event)) {
+ switch (event) {
+ case Event::kText:
+ case Event::kComment:
+ case Event::kStartElement:
+ return true;
+ default:
+ break;
}
- return false;
+ 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 false;
- case Event::kStartElement:
- depth++;
- break;
- case Event::kEndElement:
- depth--;
- break;
- default:
- break;
- }
+inline bool XmlPullParser::SkipCurrentElement(XmlPullParser* parser) {
+ int depth = 1;
+ while (depth > 0) {
+ switch (parser->Next()) {
+ case Event::kEndDocument:
+ return true;
+ case Event::kBadDocument:
+ return false;
+ case Event::kStartElement:
+ depth++;
+ break;
+ case Event::kEndElement:
+ depth--;
+ break;
+ default:
+ break;
}
- return true;
+ }
+ return true;
}
-inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) {
- return event != Event::kBadDocument && event != Event::kEndDocument;
+inline bool XmlPullParser::IsGoodEvent(XmlPullParser::Event event) {
+ return event != Event::kBadDocument && event != Event::kEndDocument;
}
inline int XmlPullParser::Attribute::compare(const Attribute& rhs) const {
- int cmp = namespaceUri.compare(rhs.namespaceUri);
- if (cmp != 0) return cmp;
- return name.compare(rhs.name);
+ int cmp = namespace_uri.compare(rhs.namespace_uri);
+ if (cmp != 0) return cmp;
+ return name.compare(rhs.name);
}
inline bool XmlPullParser::Attribute::operator<(const Attribute& rhs) const {
- return compare(rhs) < 0;
+ return compare(rhs) < 0;
}
inline bool XmlPullParser::Attribute::operator==(const Attribute& rhs) const {
- return compare(rhs) == 0;
+ return compare(rhs) == 0;
}
inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const {
- return compare(rhs) != 0;
+ return compare(rhs) != 0;
}
-inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece16 namespaceUri,
- StringPiece16 name) const {
- const auto endIter = endAttributes();
- const auto iter = std::lower_bound(beginAttributes(), endIter,
- std::pair<StringPiece16, StringPiece16>(namespaceUri, name),
- [](const Attribute& attr, const std::pair<StringPiece16, StringPiece16>& rhs) -> bool {
- int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(),
- rhs.first.data(), rhs.first.size());
- if (cmp < 0) return true;
- if (cmp > 0) return false;
- cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), rhs.second.size());
- if (cmp < 0) return true;
- return false;
- }
- );
-
- if (iter != endIter && namespaceUri == iter->namespaceUri && name == iter->name) {
- return iter;
- }
- return endIter;
+inline XmlPullParser::const_iterator XmlPullParser::FindAttribute(
+ StringPiece namespace_uri, StringPiece name) const {
+ const auto end_iter = end_attributes();
+ const auto iter = std::lower_bound(
+ begin_attributes(), end_iter,
+ std::pair<StringPiece, StringPiece>(namespace_uri, name),
+ [](const Attribute& attr,
+ const std::pair<StringPiece, StringPiece>& rhs) -> bool {
+ int cmp = attr.namespace_uri.compare(
+ 0, attr.namespace_uri.size(), rhs.first.data(), rhs.first.size());
+ if (cmp < 0) return true;
+ if (cmp > 0) return false;
+ cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(),
+ rhs.second.size());
+ if (cmp < 0) return true;
+ return false;
+ });
+
+ if (iter != end_iter && namespace_uri == iter->namespace_uri &&
+ name == iter->name) {
+ return iter;
+ }
+ return end_iter;
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
-#endif // AAPT_XML_PULL_PARSER_H
+#endif // AAPT_XML_PULL_PARSER_H
diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
index 8fa2c6d274c8..4f18cd218cd9 100644
--- a/tools/aapt2/xml/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -14,42 +14,43 @@
* limitations under the License.
*/
-#include "util/StringPiece.h"
#include "xml/XmlPullParser.h"
-#include <gtest/gtest.h>
#include <sstream>
+#include "test/Test.h"
+#include "util/StringPiece.h"
+
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);
+ 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));
+ const size_t depth_outer = parser.depth();
+ ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
- EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName()));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
+ EXPECT_EQ(StringPiece("a"), StringPiece(parser.element_name()));
- 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 depth_a = parser.depth();
+ ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_a));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
+ EXPECT_EQ(StringPiece("b"), StringPiece(parser.element_name()));
- 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()));
+ const size_t depth_b = parser.depth();
+ ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
+ EXPECT_EQ(StringPiece("c"), StringPiece(parser.element_name()));
- ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
- EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName()));
+ ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
+ EXPECT_EQ(StringPiece("e"), StringPiece(parser.element_name()));
- ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
- EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent());
+ ASSERT_FALSE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
+ EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.event());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index ab9f544d67ea..d00f7f2fe0aa 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -14,54 +14,69 @@
* limitations under the License.
*/
-#include "util/Maybe.h"
-#include "util/Util.h"
#include "xml/XmlUtil.h"
#include <string>
+#include "util/Maybe.h"
+#include "util/Util.h"
+
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 */ };
+std::string BuildPackageNamespace(const StringPiece& package,
+ bool private_reference) {
+ std::string result =
+ private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
+ result.append(package.data(), package.size());
+ return result;
+}
- } 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 */ };
+Maybe<ExtractedPackage> ExtractPackageFromNamespace(
+ const std::string& namespace_uri) {
+ if (util::StartsWith(namespace_uri, kSchemaPublicPrefix)) {
+ StringPiece schema_prefix = kSchemaPublicPrefix;
+ StringPiece package = namespace_uri;
+ package = package.substr(schema_prefix.size(),
+ package.size() - schema_prefix.size());
+ if (package.empty()) {
+ return {};
+ }
+ return ExtractedPackage{package.ToString(), false /* is_private */};
- } else if (namespaceUri == kSchemaAuto) {
- return ExtractedPackage{ std::u16string(), true /* isPrivate */ };
+ } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
+ StringPiece schema_prefix = kSchemaPrivatePrefix;
+ StringPiece package = namespace_uri;
+ package = package.substr(schema_prefix.size(),
+ package.size() - schema_prefix.size());
+ if (package.empty()) {
+ return {};
}
- return {};
+ return ExtractedPackage{package.ToString(), true /* is_private */};
+
+ } else if (namespace_uri == kSchemaAuto) {
+ return ExtractedPackage{std::string(), true /* is_private */};
+ }
+ 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);
+void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack,
+ const StringPiece& local_package,
+ Reference* in_ref) {
+ if (in_ref->name) {
+ if (Maybe<ExtractedPackage> transformed_package =
+ decl_stack->TransformPackageAlias(in_ref->name.value().package,
+ local_package)) {
+ ExtractedPackage& extracted_package = transformed_package.value();
+ in_ref->name.value().package = std::move(extracted_package.package);
- // If the reference was already private (with a * prefix) and the namespace is public,
- // we keep the reference private.
- inRef->privateReference |= extractedPackage.privateNamespace;
- }
+ // If the reference was already private (with a * prefix) and the
+ // namespace is public,
+ // we keep the reference private.
+ in_ref->private_reference |= extracted_package.private_namespace;
}
+ }
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 98e5520a6ea2..536540162d07 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -17,34 +17,41 @@
#ifndef AAPT_XML_XMLUTIL_H
#define AAPT_XML_XMLUTIL_H
+#include <string>
+
#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";
+constexpr const char* kSchemaAuto = "http://schemas.android.com/apk/res-auto";
+constexpr const char* kSchemaPublicPrefix =
+ "http://schemas.android.com/apk/res/";
+constexpr const char* kSchemaPrivatePrefix =
+ "http://schemas.android.com/apk/prv/res/";
+constexpr const char* kSchemaAndroid =
+ "http://schemas.android.com/apk/res/android";
+constexpr const char* kSchemaTools = "http://schemas.android.com/tools";
+constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt";
/**
* 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;
+ /**
+ * 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::string package;
- /**
- * True if the package's private namespace was declared. This means that private resources
- * are made visible.
- */
- bool privateNamespace;
+ /**
+ * True if the package's private namespace was declared. This means that
+ * private resources
+ * are made visible.
+ */
+ bool private_namespace;
};
/**
@@ -55,31 +62,48 @@ struct ExtractedPackage {
* Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
* returns an empty package name.
*/
-Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri);
+Maybe<ExtractedPackage> ExtractPackageFromNamespace(
+ const std::string& namespace_uri);
+
+/**
+ * Returns an XML Android namespace for the given package of the form:
+ *
+ * http://schemas.android.com/apk/res/<package>
+ *
+ * If privateReference == true, the package will be of the form:
+ *
+ * http://schemas.android.com/apk/prv/res/<package>
+ */
+std::string BuildPackageNamespace(const StringPiece& package,
+ bool private_reference = false);
/**
- * Interface representing a stack of XML namespace declarations. When looking up the package
+ * 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;
+ 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;
+ /**
+ * Returns an ExtractedPackage struct if the alias given corresponds with a
+ * package declaration.
+ */
+ virtual Maybe<ExtractedPackage> TransformPackageAlias(
+ const StringPiece& alias, const StringPiece& local_package) 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.
+ * 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);
+void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack,
+ const StringPiece& local_package,
+ Reference* in_ref);
-} // namespace xml
-} // namespace aapt
+} // 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
index 319e7707d874..5eecc8f5fb20 100644
--- a/tools/aapt2/xml/XmlUtil_test.cpp
+++ b/tools/aapt2/xml/XmlUtil_test.cpp
@@ -14,41 +14,46 @@
* limitations under the License.
*/
-#include "test/Common.h"
#include "xml/XmlUtil.h"
-#include <gtest/gtest.h>
+#include "test/Test.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);
+ AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace("com.android"));
+ AAPT_ASSERT_FALSE(
+ xml::ExtractPackageFromNamespace("http://schemas.android.com/apk"));
+ AAPT_ASSERT_FALSE(
+ xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res"));
+ AAPT_ASSERT_FALSE(
+ xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/"));
+ AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace(
+ "http://schemas.android.com/apk/prv/res/"));
+
+ Maybe<xml::ExtractedPackage> p =
+ xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/a");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::string("a"), p.value().package);
+ EXPECT_FALSE(p.value().private_namespace);
+
+ p = xml::ExtractPackageFromNamespace(
+ "http://schemas.android.com/apk/prv/res/android");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::string("android"), p.value().package);
+ EXPECT_TRUE(p.value().private_namespace);
+
+ p = xml::ExtractPackageFromNamespace(
+ "http://schemas.android.com/apk/prv/res/com.test");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::string("com.test"), p.value().package);
+ EXPECT_TRUE(p.value().private_namespace);
+
+ p = xml::ExtractPackageFromNamespace(
+ "http://schemas.android.com/apk/res-auto");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::string(), p.value().package);
+ EXPECT_TRUE(p.value().private_namespace);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index ca2d2e75ee89..6ca59b0fb93b 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -91,7 +91,7 @@ class Method():
while r in raw: raw.remove(r)
self.split = list(raw)
- for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract"]:
+ for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default"]:
while r in raw: raw.remove(r)
self.typ = raw[0]
diff --git a/tools/bit/Android.mk b/tools/bit/Android.mk
new file mode 100644
index 000000000000..1c1291f07d2e
--- /dev/null
+++ b/tools/bit/Android.mk
@@ -0,0 +1,46 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+# ==========================================================
+# Build the host executable: protoc-gen-javastream
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bit
+
+# This tool doesn't build on darwin.
+LOCAL_MODULE_HOST_OS := linux
+
+LOCAL_SRC_FILES := \
+ aapt.cpp \
+ adb.cpp \
+ command.cpp \
+ main.cpp \
+ make.cpp \
+ print.cpp \
+ util.cpp
+
+LOCAL_STATIC_LIBRARIES := \
+ libexpat \
+ libinstrumentation \
+ libjsoncpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libprotobuf-cpp-full
+
+include $(BUILD_HOST_EXECUTABLE)
+
diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp
new file mode 100644
index 000000000000..961b47cdfecd
--- /dev/null
+++ b/tools/bit/aapt.cpp
@@ -0,0 +1,270 @@
+/*
+ * 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 "aapt.h"
+
+#include "command.h"
+#include "print.h"
+#include "util.h"
+
+#include <regex>
+
+const regex NS_REGEX("( *)N: ([^=]+)=(.*)");
+const regex ELEMENT_REGEX("( *)E: ([^ ]+) \\(line=(\\d+)\\)");
+const regex ATTR_REGEX("( *)A: ([^\\(=]+)[^=]*=\"([^\"]+)\".*");
+
+const string ANDROID_NS("http://schemas.android.com/apk/res/android");
+
+bool
+Apk::HasActivity(const string& className)
+{
+ string fullClassName = full_class_name(package, className);
+ const size_t N = activities.size();
+ for (size_t i=0; i<N; i++) {
+ if (activities[i] == fullClassName) {
+ return true;
+ }
+ }
+ return false;
+}
+
+struct Attribute {
+ string ns;
+ string name;
+ string value;
+};
+
+struct Element {
+ Element* parent;
+ string ns;
+ string name;
+ int lineno;
+ vector<Attribute> attributes;
+ vector<Element*> children;
+
+ /**
+ * Indentation in the xmltree dump. Might not be equal to the distance
+ * from the root because namespace rows (scopes) have their own indentation.
+ */
+ int depth;
+
+ Element();
+ ~Element();
+
+ string GetAttr(const string& ns, const string& name) const;
+ void FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse);
+
+};
+
+Element::Element()
+{
+}
+
+Element::~Element()
+{
+ const size_t N = children.size();
+ for (size_t i=0; i<N; i++) {
+ delete children[i];
+ }
+}
+
+string
+Element::GetAttr(const string& ns, const string& name) const
+{
+ const size_t N = attributes.size();
+ for (size_t i=0; i<N; i++) {
+ const Attribute& attr = attributes[i];
+ if (attr.ns == ns && attr.name == name) {
+ return attr.value;
+ }
+ }
+ return string();
+}
+
+void
+Element::FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse)
+{
+ const size_t N = children.size();
+ for (size_t i=0; i<N; i++) {
+ Element* child = children[i];
+ if (child->ns == ns && child->name == name) {
+ result->push_back(child);
+ }
+ if (recurse) {
+ child->FindElements(ns, name, result, recurse);
+ }
+ }
+}
+
+struct Scope {
+ Scope* parent;
+ int depth;
+ map<string,string> namespaces;
+
+ Scope(Scope* parent, int depth);
+};
+
+Scope::Scope(Scope* p, int d)
+ :parent(p),
+ depth(d)
+{
+ if (p != NULL) {
+ namespaces = p->namespaces;
+ }
+}
+
+
+string
+full_class_name(const string& packageName, const string& className)
+{
+ if (className.length() == 0) {
+ return "";
+ }
+ if (className[0] == '.') {
+ return packageName + className;
+ }
+ if (className.find('.') == string::npos) {
+ return packageName + "." + className;
+ }
+ return className;
+}
+
+string
+pretty_component_name(const string& packageName, const string& className)
+{
+ if (starts_with(packageName, className)) {
+ size_t pn = packageName.length();
+ size_t cn = className.length();
+ if (cn > pn && className[pn] == '.') {
+ return packageName + "/" + string(className, pn, string::npos);
+ }
+ }
+ return packageName + "/" + className;
+}
+
+int
+inspect_apk(Apk* apk, const string& filename)
+{
+ // Load the manifest xml
+ Command cmd("aapt");
+ cmd.AddArg("dump");
+ cmd.AddArg("xmltree");
+ cmd.AddArg(filename);
+ cmd.AddArg("AndroidManifest.xml");
+
+ int err;
+
+ string output = get_command_output(cmd, &err, false);
+ check_error(err);
+
+ // Parse the manifest xml
+ Scope* scope = new Scope(NULL, -1);
+ Element* root = NULL;
+ Element* current = NULL;
+ vector<string> lines;
+ split_lines(&lines, output);
+ for (size_t i=0; i<lines.size(); i++) {
+ const string& line = lines[i];
+ smatch match;
+ if (regex_match(line, match, NS_REGEX)) {
+ int depth = match[1].length() / 2;
+ while (depth < scope->depth) {
+ Scope* tmp = scope;
+ scope = scope->parent;
+ delete tmp;
+ }
+ scope = new Scope(scope, depth);
+ scope->namespaces[match[2]] = match[3];
+ } else if (regex_match(line, match, ELEMENT_REGEX)) {
+ Element* element = new Element();
+
+ string str = match[2];
+ size_t colon = str.find(':');
+ if (colon == string::npos) {
+ element->name = str;
+ } else {
+ element->ns = scope->namespaces[string(str, 0, colon)];
+ element->name.assign(str, colon+1, string::npos);
+ }
+ element->lineno = atoi(match[3].str().c_str());
+ element->depth = match[1].length() / 2;
+
+ if (root == NULL) {
+ current = element;
+ root = element;
+ } else {
+ while (element->depth <= current->depth && current->parent != NULL) {
+ current = current->parent;
+ }
+ element->parent = current;
+ current->children.push_back(element);
+ current = element;
+ }
+ } else if (regex_match(line, match, ATTR_REGEX)) {
+ if (current != NULL) {
+ Attribute attr;
+ string str = match[2];
+ size_t colon = str.find(':');
+ if (colon == string::npos) {
+ attr.name = str;
+ } else {
+ attr.ns = scope->namespaces[string(str, 0, colon)];
+ attr.name.assign(str, colon+1, string::npos);
+ }
+ attr.value = match[3];
+ current->attributes.push_back(attr);
+ }
+ }
+ }
+ while (scope != NULL) {
+ Scope* tmp = scope;
+ scope = scope->parent;
+ delete tmp;
+ }
+
+ // Package name
+ apk->package = root->GetAttr("", "package");
+ if (apk->package.size() == 0) {
+ print_error("%s:%d: Manifest root element doesn't contain a package attribute",
+ filename.c_str(), root->lineno);
+ delete root;
+ return 1;
+ }
+
+ // Instrumentation runner
+ vector<Element*> instrumentation;
+ root->FindElements("", "instrumentation", &instrumentation, true);
+ if (instrumentation.size() > 0) {
+ // TODO: How could we deal with multiple instrumentation tags?
+ // We'll just pick the first one.
+ apk->runner = instrumentation[0]->GetAttr(ANDROID_NS, "name");
+ }
+
+ // Activities
+ vector<Element*> activities;
+ root->FindElements("", "activity", &activities, true);
+ for (size_t i=0; i<activities.size(); i++) {
+ string name = activities[i]->GetAttr(ANDROID_NS, "name");
+ if (name.size() == 0) {
+ continue;
+ }
+ apk->activities.push_back(full_class_name(apk->package, name));
+ }
+
+ delete root;
+ return 0;
+}
+
diff --git a/tools/bit/aapt.h b/tools/bit/aapt.h
new file mode 100644
index 000000000000..6aeb03f18744
--- /dev/null
+++ b/tools/bit/aapt.h
@@ -0,0 +1,39 @@
+/*
+ * 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_H
+#define AAPT_H
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Apk
+{
+ string package;
+ string runner;
+ vector<string> activities;
+
+ bool HasActivity(const string& className);
+};
+
+string full_class_name(const string& packageName, const string& className);
+string pretty_component_name(const string& packageName, const string& className);
+
+int inspect_apk(Apk* apk, const string& filename);
+
+#endif // AAPT_H
diff --git a/tools/bit/adb.cpp b/tools/bit/adb.cpp
new file mode 100644
index 000000000000..0c8424de566d
--- /dev/null
+++ b/tools/bit/adb.cpp
@@ -0,0 +1,463 @@
+/*
+ * 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 "adb.h"
+
+#include "command.h"
+#include "print.h"
+#include "util.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <limits.h>
+
+#include <iostream>
+#include <istream>
+#include <streambuf>
+
+using namespace std;
+
+struct Buffer: public streambuf
+{
+ Buffer(char* begin, size_t size);
+};
+
+Buffer::Buffer(char* begin, size_t size)
+{
+ this->setg(begin, begin, begin + size);
+}
+
+int
+run_adb(const char* first, ...)
+{
+ Command cmd("adb");
+
+ if (first == NULL) {
+ return 0;
+ }
+
+ cmd.AddArg(first);
+
+ va_list args;
+ va_start(args, first);
+ while (true) {
+ const char* arg = va_arg(args, char*);
+ if (arg == NULL) {
+ break;
+ }
+ cmd.AddArg(arg);
+ }
+ va_end(args);
+
+ return run_command(cmd);
+}
+
+string
+get_system_property(const string& name, int* err)
+{
+ Command cmd("adb");
+ cmd.AddArg("shell");
+ cmd.AddArg("getprop");
+ cmd.AddArg(name);
+
+ return trim(get_command_output(cmd, err, false));
+}
+
+
+static uint64_t
+read_varint(int fd, int* err, bool* done)
+{
+ uint32_t bits = 0;
+ uint64_t result = 0;
+ while (true) {
+ uint8_t byte;
+ ssize_t amt = read(fd, &byte, 1);
+ if (amt == 0) {
+ *done = true;
+ return result;
+ } else if (amt < 0) {
+ return *err = errno;
+ }
+ result |= uint64_t(byte & 0x7F) << bits;
+ if ((byte & 0x80) == 0) {
+ return result;
+ }
+ bits += 7;
+ if (bits > 64) {
+ *err = -1;
+ return 0;
+ }
+ }
+}
+
+static char*
+read_sized_buffer(int fd, int* err, size_t* resultSize)
+{
+ bool done = false;
+ uint64_t size = read_varint(fd, err, &done);
+ if (*err != 0 || done) {
+ return NULL;
+ }
+ if (size == 0) {
+ *resultSize = 0;
+ return NULL;
+ }
+ // 10 MB seems like a reasonable limit.
+ if (size > 10*1024*1024) {
+ print_error("result buffer too large: %llu", size);
+ return NULL;
+ }
+ char* buf = (char*)malloc(size);
+ if (buf == NULL) {
+ print_error("Can't allocate a buffer of size for test results: %llu", size);
+ return NULL;
+ }
+ int pos = 0;
+ while (size - pos > 0) {
+ ssize_t amt = read(fd, buf+pos, size-pos);
+ if (amt == 0) {
+ // early end of pipe
+ print_error("Early end of pipe.");
+ *err = -1;
+ free(buf);
+ return NULL;
+ } else if (amt < 0) {
+ // error
+ *err = errno;
+ free(buf);
+ return NULL;
+ }
+ pos += amt;
+ }
+ *resultSize = (size_t)size;
+ return buf;
+}
+
+static int
+read_sized_proto(int fd, Message* message)
+{
+ int err = 0;
+ size_t size;
+ char* buf = read_sized_buffer(fd, &err, &size);
+ if (err != 0) {
+ if (buf != NULL) {
+ free(buf);
+ }
+ return err;
+ } else if (size == 0) {
+ if (buf != NULL) {
+ free(buf);
+ }
+ return 0;
+ } else if (buf == NULL) {
+ return -1;
+ }
+ Buffer buffer(buf, size);
+ istream in(&buffer);
+
+ err = message->ParseFromIstream(&in) ? 0 : -1;
+
+ free(buf);
+ return err;
+}
+
+static int
+skip_bytes(int fd, ssize_t size, char* scratch, int scratchSize)
+{
+ while (size > 0) {
+ ssize_t amt = size < scratchSize ? size : scratchSize;
+ fprintf(stderr, "skipping %lu/%ld bytes\n", size, amt);
+ amt = read(fd, scratch, amt);
+ if (amt == 0) {
+ // early end of pipe
+ print_error("Early end of pipe.");
+ return -1;
+ } else if (amt < 0) {
+ // error
+ return errno;
+ }
+ size -= amt;
+ }
+ return 0;
+}
+
+static int
+skip_unknown_field(int fd, uint64_t tag, char* scratch, int scratchSize) {
+ bool done;
+ int err;
+ uint64_t size;
+ switch (tag & 0x7) {
+ case 0: // varint
+ read_varint(fd, &err, &done);
+ if (err != 0) {
+ return err;
+ } else if (done) {
+ return -1;
+ } else {
+ return 0;
+ }
+ case 1:
+ return skip_bytes(fd, 8, scratch, scratchSize);
+ case 2:
+ size = read_varint(fd, &err, &done);
+ if (err != 0) {
+ return err;
+ } else if (done) {
+ return -1;
+ }
+ if (size > INT_MAX) {
+ // we'll be here a long time but this keeps it from overflowing
+ return -1;
+ }
+ return skip_bytes(fd, (ssize_t)size, scratch, scratchSize);
+ case 5:
+ return skip_bytes(fd, 4, scratch, scratchSize);
+ default:
+ print_error("bad wire type for tag 0x%lx\n", tag);
+ return -1;
+ }
+}
+
+static int
+read_instrumentation_results(int fd, char* scratch, int scratchSize,
+ InstrumentationCallbacks* callbacks)
+{
+ bool done = false;
+ int err = 0;
+ string result;
+ while (true) {
+ uint64_t tag = read_varint(fd, &err, &done);
+ if (done) {
+ // Done reading input (this is the only place that a stream end isn't an error).
+ return 0;
+ } else if (err != 0) {
+ return err;
+ } else if (tag == 0xa) { // test_status
+ TestStatus status;
+ err = read_sized_proto(fd, &status);
+ if (err != 0) {
+ return err;
+ }
+ callbacks->OnTestStatus(status);
+ } else if (tag == 0x12) { // session_status
+ SessionStatus status;
+ err = read_sized_proto(fd, &status);
+ if (err != 0) {
+ return err;
+ }
+ callbacks->OnSessionStatus(status);
+ } else {
+ err = skip_unknown_field(fd, tag, scratch, scratchSize);
+ if (err != 0) {
+ return err;
+ }
+ }
+ }
+ return 0;
+}
+
+int
+run_instrumentation_test(const string& packageName, const string& runner, const string& className,
+ InstrumentationCallbacks* callbacks)
+{
+ Command cmd("adb");
+ cmd.AddArg("shell");
+ cmd.AddArg("am");
+ cmd.AddArg("instrument");
+ cmd.AddArg("-w");
+ cmd.AddArg("-m");
+ if (className.length() > 0) {
+ cmd.AddArg("-e");
+ cmd.AddArg("class");
+ cmd.AddArg(className);
+ }
+ cmd.AddArg(packageName + "/" + runner);
+
+ print_command(cmd);
+
+ int fds[2];
+ pipe(fds);
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ // fork error
+ return errno;
+ } else if (pid == 0) {
+ // child
+ while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
+ close(fds[1]);
+ close(fds[0]);
+ const char* prog = cmd.GetProg();
+ char* const* argv = cmd.GetArgv();
+ char* const* env = cmd.GetEnv();
+ exec_with_path_search(prog, argv, env);
+ print_error("Unable to run command: %s", prog);
+ exit(1);
+ } else {
+ // parent
+ close(fds[1]);
+ string result;
+ const int size = 16*1024;
+ char* buf = (char*)malloc(size);
+ int err = read_instrumentation_results(fds[0], buf, size, callbacks);
+ free(buf);
+ int status;
+ waitpid(pid, &status, 0);
+ if (err != 0) {
+ return err;
+ }
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ } else {
+ return -1;
+ }
+ }
+}
+
+/**
+ * Get the second to last bundle in the args list. Stores the last name found
+ * in last. If the path is not found or if the args list is empty, returns NULL.
+ */
+static const ResultsBundleEntry *
+find_penultimate_entry(const ResultsBundle& bundle, va_list args)
+{
+ const ResultsBundle* b = &bundle;
+ const char* arg = va_arg(args, char*);
+ while (arg) {
+ string last = arg;
+ arg = va_arg(args, char*);
+ bool found = false;
+ for (int i=0; i<b->entries_size(); i++) {
+ const ResultsBundleEntry& e = b->entries(i);
+ if (e.key() == last) {
+ if (arg == NULL) {
+ return &e;
+ } else if (e.has_value_bundle()) {
+ b = &e.value_bundle();
+ found = true;
+ }
+ }
+ }
+ if (!found) {
+ return NULL;
+ }
+ if (arg == NULL) {
+ return NULL;
+ }
+ }
+ return NULL;
+}
+
+string
+get_bundle_string(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return string();
+ }
+ if (entry->has_value_string()) {
+ *found = true;
+ return entry->value_string();
+ }
+ *found = false;
+ return string();
+}
+
+int32_t
+get_bundle_int(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_int()) {
+ *found = true;
+ return entry->value_int();
+ }
+ *found = false;
+ return 0;
+}
+
+float
+get_bundle_float(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_float()) {
+ *found = true;
+ return entry->value_float();
+ }
+ *found = false;
+ return 0;
+}
+
+double
+get_bundle_double(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_double()) {
+ *found = true;
+ return entry->value_double();
+ }
+ *found = false;
+ return 0;
+}
+
+int64_t
+get_bundle_long(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_long()) {
+ *found = true;
+ return entry->value_long();
+ }
+ *found = false;
+ return 0;
+}
+
diff --git a/tools/bit/adb.h b/tools/bit/adb.h
new file mode 100644
index 000000000000..dca80c853b45
--- /dev/null
+++ b/tools/bit/adb.h
@@ -0,0 +1,47 @@
+/*
+ * 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 ADB_H
+#define ADB_H
+
+#include "instrumentation_data.pb.h"
+
+#include <string>
+
+using namespace android::am;
+using namespace google::protobuf;
+using namespace std;
+
+class InstrumentationCallbacks {
+public:
+ virtual void OnTestStatus(TestStatus& status) = 0;
+ virtual void OnSessionStatus(SessionStatus& status) = 0;
+};
+
+int run_adb(const char* first, ...);
+
+string get_system_property(const string& name, int* err);
+
+int run_instrumentation_test(const string& packageName, const string& runner,
+ const string& className, InstrumentationCallbacks* callbacks);
+
+string get_bundle_string(const ResultsBundle& bundle, bool* found, ...);
+int32_t get_bundle_int(const ResultsBundle& bundle, bool* found, ...);
+float get_bundle_float(const ResultsBundle& bundle, bool* found, ...);
+double get_bundle_double(const ResultsBundle& bundle, bool* found, ...);
+int64_t get_bundle_long(const ResultsBundle& bundle, bool* found, ...);
+
+#endif // ADB_H
diff --git a/tools/bit/command.cpp b/tools/bit/command.cpp
new file mode 100644
index 000000000000..9a8449bf9356
--- /dev/null
+++ b/tools/bit/command.cpp
@@ -0,0 +1,217 @@
+/*
+ * 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 "command.h"
+
+#include "print.h"
+#include "util.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+extern char **environ;
+
+Command::Command(const string& prog)
+ :prog(prog)
+{
+}
+
+Command::~Command()
+{
+}
+
+void
+Command::AddArg(const string& arg)
+{
+ args.push_back(arg);
+}
+
+void
+Command::AddEnv(const string& name, const string& value)
+{
+ env[name] = value;
+}
+
+const char*
+Command::GetProg() const
+{
+ return prog.c_str();
+}
+
+char *const *
+Command::GetArgv() const
+{
+ const int N = args.size();
+ char** result = (char**)malloc(sizeof(char*)*(N+2));
+ result[0] = strdup(prog.c_str());
+ for (int i=0; i<N; i++) {
+ result[i+1] = strdup(args[i].c_str());
+ }
+ result[N+1] = 0;
+ return result;
+}
+
+char *const *
+Command::GetEnv() const
+{
+ map<string,string> copy;
+ for (const char** p=(const char**)environ; *p != NULL; p++) {
+ char* name = strdup(*p);
+ char* value = strchr(name, '=');
+ *value = '\0';
+ value++;
+ copy[name] = value;
+ free(name);
+ }
+ for (map<string,string>::const_iterator it=env.begin(); it!=env.end(); it++) {
+ copy[it->first] = it->second;
+ }
+ char** result = (char**)malloc(sizeof(char*)*(copy.size()+1));
+ char** row = result;
+ for (map<string,string>::const_iterator it=copy.begin(); it!=copy.end(); it++) {
+ *row = (char*)malloc(it->first.size() + it->second.size() + 2);
+ strcpy(*row, it->first.c_str());
+ strcat(*row, "=");
+ strcat(*row, it->second.c_str());
+ row++;
+ }
+ *row = NULL;
+ return result;
+}
+
+string
+get_command_output(const Command& command, int* err, bool quiet)
+{
+ if (!quiet) {
+ print_command(command);
+ }
+
+ int fds[2];
+ pipe(fds);
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ // fork error
+ *err = errno;
+ return string();
+ } else if (pid == 0) {
+ // child
+ while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
+ close(fds[1]);
+ close(fds[0]);
+ const char* prog = command.GetProg();
+ char* const* argv = command.GetArgv();
+ char* const* env = command.GetEnv();
+ exec_with_path_search(prog, argv, env);
+ if (!quiet) {
+ print_error("Unable to run command: %s", prog);
+ }
+ exit(1);
+ } else {
+ // parent
+ close(fds[1]);
+ string result;
+ const int size = 16*1024;
+ char* buf = (char*)malloc(size);
+ while (true) {
+ ssize_t amt = read(fds[0], buf, size);
+ if (amt <= 0) {
+ break;
+ } else if (amt > 0) {
+ result.append(buf, amt);
+ }
+ }
+ free(buf);
+ int status;
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ *err = WEXITSTATUS(status);
+ return result;
+ } else {
+ *err = -1;
+ return string();
+ }
+ }
+}
+
+
+int
+run_command(const Command& command)
+{
+ print_command(command);
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ // fork error
+ return errno;
+ } else if (pid == 0) {
+ // child
+ const char* prog = command.GetProg();
+ char* const* argv = command.GetArgv();
+ char* const* env = command.GetEnv();
+ exec_with_path_search(prog, argv, env);
+ print_error("Unable to run command: %s", prog);
+ exit(1);
+ } else {
+ // parent
+ int status;
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ } else {
+ return -1;
+ }
+ }
+}
+
+int
+exec_with_path_search(const char* prog, char const* const* argv, char const* const* envp)
+{
+ if (prog[0] == '/') {
+ return execve(prog, (char*const*)argv, (char*const*)envp);
+ } else {
+ char* pathEnv = strdup(getenv("PATH"));
+ if (pathEnv == NULL) {
+ return 1;
+ }
+ char* dir = pathEnv;
+ while (dir) {
+ char* next = strchr(dir, ':');
+ if (next != NULL) {
+ *next = '\0';
+ next++;
+ }
+ if (dir[0] == '/') {
+ struct stat st;
+ string executable = string(dir) + "/" + prog;
+ if (stat(executable.c_str(), &st) == 0) {
+ execve(executable.c_str(), (char*const*)argv, (char*const*)envp);
+ }
+ }
+ dir = next;
+ }
+ free(pathEnv);
+ return 1;
+ }
+}
+
diff --git a/tools/bit/command.h b/tools/bit/command.h
new file mode 100644
index 000000000000..fb44900b0806
--- /dev/null
+++ b/tools/bit/command.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 COMMAND_H
+#define COMMAND_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Command
+{
+ Command(const string& prog);
+ ~Command();
+
+ void AddArg(const string& arg);
+ void AddEnv(const string& name, const string& value);
+
+ const char* GetProg() const;
+ char* const* GetArgv() const;
+ char* const* GetEnv() const;
+
+ string GetCommandline() const;
+
+ string prog;
+ vector<string> args;
+ map<string,string> env;
+};
+
+/**
+ * Run the command and collect stdout.
+ * Returns the exit code.
+ */
+string get_command_output(const Command& command, int* err, bool quiet=false);
+
+/**
+ * Run the command.
+ * Returns the exit code.
+ */
+int run_command(const Command& command);
+
+// Mac OS doesn't have execvpe. This is the same as execvpe.
+int exec_with_path_search(const char* prog, char const* const* argv, char const* const* envp);
+
+#endif // COMMAND_H
+
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
new file mode 100644
index 000000000000..868d90a1188d
--- /dev/null
+++ b/tools/bit/main.cpp
@@ -0,0 +1,994 @@
+/*
+ * 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 "aapt.h"
+#include "adb.h"
+#include "make.h"
+#include "print.h"
+#include "util.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <google/protobuf/stubs/common.h>
+
+using namespace std;
+
+/**
+ * An entry from the command line for something that will be built, installed,
+ * and/or tested.
+ */
+struct Target {
+ bool build;
+ bool install;
+ bool test;
+ string pattern;
+ string name;
+ vector<string> actions;
+ Module module;
+
+ int testActionCount;
+
+ int testPassCount;
+ int testFailCount;
+ bool actionsWithNoTests;
+
+ Target(bool b, bool i, bool t, const string& p);
+};
+
+Target::Target(bool b, bool i, bool t, const string& p)
+ :build(b),
+ install(i),
+ test(t),
+ pattern(p),
+ testActionCount(0),
+ testPassCount(0),
+ testFailCount(0),
+ actionsWithNoTests(false)
+{
+}
+
+/**
+ * Command line options.
+ */
+struct Options {
+ // For help
+ bool runHelp;
+
+ // For tab completion
+ bool runTab;
+ string tabPattern;
+
+ // For build/install/test
+ bool noRestart;
+ bool reboot;
+ vector<Target*> targets;
+
+ Options();
+ ~Options();
+};
+
+Options::Options()
+ :runHelp(false),
+ runTab(false),
+ noRestart(false),
+ reboot(false),
+ targets()
+{
+}
+
+Options::~Options()
+{
+}
+
+struct InstallApk
+{
+ TrackedFile file;
+ bool alwaysInstall;
+ bool installed;
+
+ InstallApk();
+ InstallApk(const InstallApk& that);
+ InstallApk(const string& filename, bool always);
+ ~InstallApk() {};
+};
+
+InstallApk::InstallApk()
+{
+}
+
+InstallApk::InstallApk(const InstallApk& that)
+ :file(that.file),
+ alwaysInstall(that.alwaysInstall),
+ installed(that.installed)
+{
+}
+
+InstallApk::InstallApk(const string& filename, bool always)
+ :file(filename),
+ alwaysInstall(always),
+ installed(false)
+{
+}
+
+
+/**
+ * Record for an test that is going to be launched.
+ */
+struct TestAction {
+ TestAction();
+
+ // The package name from the apk
+ string packageName;
+
+ // The test runner class
+ string runner;
+
+ // The test class, or none if all tests should be run
+ string className;
+
+ // The original target that requested this action
+ Target* target;
+
+ // The number of tests that passed
+ int passCount;
+
+ // The number of tests that failed
+ int failCount;
+};
+
+TestAction::TestAction()
+ :passCount(0),
+ failCount(0)
+{
+}
+
+/**
+ * Record for an activity that is going to be launched.
+ */
+struct ActivityAction {
+ // The package name from the apk
+ string packageName;
+
+ // The test class, or none if all tests should be run
+ string className;
+};
+
+/**
+ * Callback class for the am instrument command.
+ */
+class TestResults: public InstrumentationCallbacks
+{
+public:
+ virtual void OnTestStatus(TestStatus& status);
+ virtual void OnSessionStatus(SessionStatus& status);
+
+ /**
+ * Set the TestAction that the tests are for.
+ * It will be updated with statistics as the tests run.
+ */
+ void SetCurrentAction(TestAction* action);
+
+private:
+ TestAction* m_currentAction;
+};
+
+void
+TestResults::OnTestStatus(TestStatus& status)
+{
+ bool found;
+// printf("OnTestStatus\n");
+// status.PrintDebugString();
+ int32_t resultCode = status.has_results() ? status.result_code() : 0;
+
+ if (!status.has_results()) {
+ return;
+ }
+ const ResultsBundle &results = status.results();
+
+ int32_t currentTestNum = get_bundle_int(results, &found, "current", NULL);
+ if (!found) {
+ currentTestNum = -1;
+ }
+
+ int32_t testCount = get_bundle_int(results, &found, "numtests", NULL);
+ if (!found) {
+ testCount = -1;
+ }
+
+ string className = get_bundle_string(results, &found, "class", NULL);
+ if (!found) {
+ return;
+ }
+
+ string testName = get_bundle_string(results, &found, "test", NULL);
+ if (!found) {
+ return;
+ }
+
+ if (resultCode == 0) {
+ // test passed
+ m_currentAction->passCount++;
+ m_currentAction->target->testPassCount++;
+ } else if (resultCode == 1) {
+ // test starting
+ ostringstream line;
+ line << "Running";
+ if (currentTestNum > 0) {
+ line << ": " << currentTestNum;
+ if (testCount > 0) {
+ line << " of " << testCount;
+ }
+ }
+ line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName;
+ print_one_line("%s", line.str().c_str());
+ } else if (resultCode == -2) {
+ // test failed
+ m_currentAction->failCount++;
+ m_currentAction->target->testFailCount++;
+ printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold,
+ m_currentAction->target->name.c_str(), className.c_str(),
+ testName.c_str(), g_escapeEndColor);
+
+ string stack = get_bundle_string(results, &found, "stack", NULL);
+ if (found) {
+ printf("%s\n", stack.c_str());
+ }
+ }
+}
+
+void
+TestResults::OnSessionStatus(SessionStatus& /*status*/)
+{
+ //status.PrintDebugString();
+}
+
+void
+TestResults::SetCurrentAction(TestAction* action)
+{
+ m_currentAction = action;
+}
+
+/**
+ * Prints the usage statement / help text.
+ */
+static void
+print_usage(FILE* out) {
+ fprintf(out, "usage: bit OPTIONS PATTERN\n");
+ fprintf(out, "\n");
+ fprintf(out, " Build, sync and test android code.\n");
+ fprintf(out, "\n");
+ fprintf(out, " The -b -i and -t options allow you to specify which phases\n");
+ fprintf(out, " you want to run. If none of those options are given, then\n");
+ fprintf(out, " all phases are run. If any of these options are provided\n");
+ fprintf(out, " then only the listed phases are run.\n");
+ fprintf(out, "\n");
+ fprintf(out, " OPTIONS\n");
+ fprintf(out, " -b Run a build\n");
+ fprintf(out, " -i Install the targets\n");
+ fprintf(out, " -t Run the tests\n");
+ fprintf(out, "\n");
+ fprintf(out, " -n Don't reboot or restart\n");
+ fprintf(out, " -r If the runtime needs to be restarted, do a full reboot\n");
+ fprintf(out, " instead\n");
+ fprintf(out, "\n");
+ fprintf(out, " PATTERN\n");
+ fprintf(out, " One or more targets to build, install and test. The target\n");
+ fprintf(out, " names are the names that appear in the LOCAL_MODULE or\n");
+ fprintf(out, " LOCAL_PACKAGE_NAME variables in Android.mk or Android.bp files.\n");
+ fprintf(out, "\n");
+ fprintf(out, " Building and installing\n");
+ fprintf(out, " -----------------------\n");
+ fprintf(out, " The modules specified will be built and then installed. If the\n");
+ fprintf(out, " files are on the system partition, they will be synced and the\n");
+ fprintf(out, " attached device rebooted. If they are APKs that aren't on the\n");
+ fprintf(out, " system partition they are installed with adb install.\n");
+ fprintf(out, "\n");
+ fprintf(out, " For example:\n");
+ fprintf(out, " bit framework\n");
+ fprintf(out, " Builds framework.jar, syncs the system partition and reboots.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit SystemUI\n");
+ fprintf(out, " Builds SystemUI.apk, syncs the system partition and reboots.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases\n");
+ fprintf(out, " Builds this CTS apk, adb installs it, but does not run any\n");
+ fprintf(out, " tests.\n");
+ fprintf(out, "\n");
+ fprintf(out, " Running Unit Tests\n");
+ fprintf(out, " ------------------\n");
+ fprintf(out, " To run a unit test, list the test class names and optionally the\n");
+ fprintf(out, " test method after the module.\n");
+ fprintf(out, "\n");
+ fprintf(out, " For example:\n");
+ fprintf(out, " bit CtsProtoTestCases:*\n");
+ fprintf(out, " Builds this CTS apk, adb installs it, and runs all the tests\n");
+ fprintf(out, " contained in that apk.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit framework CtsProtoTestCases:*\n");
+ fprintf(out, " Builds the framework and the apk, syncs and reboots, then\n");
+ fprintf(out, " adb installs CtsProtoTestCases.apk, and runs all tests \n");
+ fprintf(out, " contained in that apk.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\n");
+ fprintf(out, " bit CtsProtoTestCases:android.util.proto.cts.ProtoOutputStreamBoolTest\n");
+ fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs all the\n");
+ fprintf(out, " tests in the ProtoOutputStreamBoolTest class.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite\n");
+ fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n");
+ fprintf(out, " test method on that class.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite,.ProtoOutputStreamBoolTest\\#testRepeated\n");
+ fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n");
+ fprintf(out, " and testRepeated test methods on that class.\n");
+ fprintf(out, "\n");
+ fprintf(out, " Launching an Activity\n");
+ fprintf(out, " ---------------------\n");
+ fprintf(out, " To launch an activity, specify the activity class name after\n");
+ fprintf(out, " the module name.\n");
+ fprintf(out, "\n");
+ fprintf(out, " For example:\n");
+ fprintf(out, " bit StatusBarTest:NotificationBuilderTest\n");
+ fprintf(out, " bit StatusBarTest:.NotificationBuilderTest\n");
+ fprintf(out, " bit StatusBarTest:com.android.statusbartest.NotificationBuilderTest\n");
+ fprintf(out, " Builds and installs StatusBarTest.apk, launches the\n");
+ fprintf(out, " com.android.statusbartest/.NotificationBuilderTest activity.\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: bit --tab ...\n");
+ fprintf(out, "\n");
+ fprintf(out, " Lists the targets in a format for tab completion. To get tab\n");
+ fprintf(out, " completion, add this to your bash environment:\n");
+ fprintf(out, "\n");
+ fprintf(out, " complete -C \"bit --tab\" bit\n");
+ fprintf(out, "\n");
+ fprintf(out, " Sourcing android's build/envsetup.sh will do this for you\n");
+ fprintf(out, " automatically.\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: bit --help\n");
+ fprintf(out, "usage: bit -h\n");
+ fprintf(out, "\n");
+ fprintf(out, " Print this help message\n");
+ fprintf(out, "\n");
+}
+
+
+/**
+ * Sets the appropriate flag* variables. If there is a problem with the
+ * commandline arguments, prints the help message and exits with an error.
+ */
+static void
+parse_args(Options* options, int argc, const char** argv)
+{
+ // Help
+ if (argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
+ options->runHelp = true;
+ return;
+ }
+
+ // Tab
+ if (argc >= 4 && strcmp(argv[1], "--tab") == 0) {
+ options->runTab = true;
+ options->tabPattern = argv[3];
+ return;
+ }
+
+ // Normal usage
+ bool anyPhases = false;
+ bool gotPattern = false;
+ bool flagBuild = false;
+ bool flagInstall = false;
+ bool flagTest = false;
+ for (int i=1; i < argc; i++) {
+ string arg(argv[i]);
+ if (arg[0] == '-') {
+ for (size_t j=1; j<arg.size(); j++) {
+ switch (arg[j]) {
+ case '-':
+ break;
+ case 'b':
+ if (gotPattern) {
+ gotPattern = false;
+ flagInstall = false;
+ flagTest = false;
+ }
+ flagBuild = true;
+ anyPhases = true;
+ break;
+ case 'i':
+ if (gotPattern) {
+ gotPattern = false;
+ flagBuild = false;
+ flagTest = false;
+ }
+ flagInstall = true;
+ anyPhases = true;
+ break;
+ case 't':
+ if (gotPattern) {
+ gotPattern = false;
+ flagBuild = false;
+ flagInstall = false;
+ }
+ flagTest = true;
+ anyPhases = true;
+ break;
+ case 'n':
+ options->noRestart = true;
+ break;
+ case 'r':
+ options->reboot = true;
+ break;
+ default:
+ fprintf(stderr, "Unrecognized option '%c'\n", arg[j]);
+ print_usage(stderr);
+ exit(1);
+ break;
+ }
+ }
+ } else {
+ Target* target = new Target(flagBuild || !anyPhases, flagInstall || !anyPhases,
+ flagTest || !anyPhases, arg);
+ size_t colonPos = arg.find(':');
+ if (colonPos == 0) {
+ fprintf(stderr, "Test / activity supplied without a module to build: %s\n",
+ arg.c_str());
+ print_usage(stderr);
+ exit(1);
+ } else if (colonPos == string::npos) {
+ target->name = arg;
+ } else {
+ target->name.assign(arg, 0, colonPos);
+ size_t beginPos = colonPos+1;
+ size_t commaPos;
+ while (true) {
+ commaPos = arg.find(',', beginPos);
+ if (commaPos == string::npos) {
+ if (beginPos != arg.size()) {
+ target->actions.push_back(string(arg, beginPos, commaPos));
+ }
+ break;
+ } else {
+ if (commaPos != beginPos) {
+ target->actions.push_back(string(arg, beginPos, commaPos-beginPos));
+ }
+ beginPos = commaPos+1;
+ }
+ }
+ }
+ options->targets.push_back(target);
+ gotPattern = true;
+ }
+ }
+ // If no pattern was supplied, give an error
+ if (options->targets.size() == 0) {
+ fprintf(stderr, "No PATTERN supplied.\n\n");
+ print_usage(stderr);
+ exit(1);
+ }
+}
+
+/**
+ * Get an environment variable.
+ * Exits with an error if it is unset or the empty string.
+ */
+static string
+get_required_env(const char* name, bool quiet)
+{
+ const char* value = getenv(name);
+ if (value == NULL || value[0] == '\0') {
+ if (!quiet) {
+ fprintf(stderr, "%s not set. Did you source build/envsetup.sh,"
+ " run lunch and do a build?\n", name);
+ }
+ exit(1);
+ }
+ return string(value);
+}
+
+/**
+ * Get the out directory.
+ *
+ * This duplicates the logic in build/make/core/envsetup.mk (which hasn't changed since 2011)
+ * so that we don't have to wait for get_build_var make invocation.
+ */
+string
+get_out_dir()
+{
+ const char* out_dir = getenv("OUT_DIR");
+ if (out_dir == NULL || out_dir[0] == '\0') {
+ const char* common_base = getenv("OUT_DIR_COMMON_BASE");
+ if (common_base == NULL || common_base[0] == '\0') {
+ // We don't prefix with buildTop because we cd there and it
+ // makes all the filenames long when being pretty printed.
+ return "out";
+ } else {
+ char pwd[PATH_MAX];
+ if (getcwd(pwd, PATH_MAX) == NULL) {
+ fprintf(stderr, "Your pwd is too long.\n");
+ exit(1);
+ }
+ const char* slash = strrchr(pwd, '/');
+ if (slash == NULL) {
+ slash = "";
+ }
+ string result(common_base);
+ result += slash;
+ return result;
+ }
+ }
+ return string(out_dir);
+}
+
+/**
+ * Check that a system property on the device matches the expected value.
+ * Exits with an error if they don't.
+ */
+static void
+check_device_property(const string& property, const string& expected)
+{
+ int err;
+ string deviceValue = get_system_property(property, &err);
+ check_error(err);
+ if (deviceValue != expected) {
+ print_error("There is a mismatch between the build you just did and the device you");
+ print_error("are trying to sync it to in the %s system property", property.c_str());
+ print_error(" build: %s", expected.c_str());
+ print_error(" device: %s", deviceValue.c_str());
+ exit(1);
+ }
+}
+
+/**
+ * Run the build, install, and test actions.
+ */
+void
+run_phases(vector<Target*> targets, const Options& options)
+{
+ int err = 0;
+
+ //
+ // Initialization
+ //
+
+ print_status("Initializing");
+
+ const string buildTop = get_required_env("ANDROID_BUILD_TOP", false);
+ const string buildProduct = get_required_env("TARGET_PRODUCT", false);
+ const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
+ const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
+ const string buildDevice = get_build_var(buildTop, "TARGET_DEVICE", false);
+ const string buildId = get_build_var(buildTop, "BUILD_ID", false);
+ const string buildOut = get_out_dir();
+
+ // TODO: print_command("cd", buildTop.c_str());
+ chdir(buildTop.c_str());
+
+ // Get the modules for the targets
+ map<string,Module> modules;
+ read_modules(buildOut, buildDevice, &modules, false);
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ map<string,Module>::iterator mod = modules.find(target->name);
+ if (mod != modules.end()) {
+ target->module = mod->second;
+ } else {
+ print_error("Error: Could not find module: %s", target->name.c_str());
+ err = 1;
+ }
+ }
+ if (err != 0) {
+ exit(1);
+ }
+
+ // Choose the goals
+ vector<string> goals;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->build) {
+ goals.push_back(target->name);
+ }
+ }
+
+ // Figure out whether we need to sync the system and which apks to install
+ string systemPath = buildOut + "/target/product/" + buildDevice + "/system/";
+ string dataPath = buildOut + "/target/product/" + buildDevice + "/data/";
+ bool syncSystem = false;
+ bool alwaysSyncSystem = false;
+ vector<InstallApk> installApks;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->install) {
+ for (size_t j=0; j<target->module.installed.size(); j++) {
+ const string& file = target->module.installed[j];
+ // System partition
+ if (starts_with(file, systemPath)) {
+ syncSystem = true;
+ if (!target->build) {
+ // If a system partition target didn't get built then
+ // it won't change we will always need to do adb sync
+ alwaysSyncSystem = true;
+ }
+ continue;
+ }
+ // Apk in the data partition
+ if (starts_with(file, dataPath) && ends_with(file, ".apk")) {
+ // Always install it if we didn't build it because otherwise
+ // it will never have changed.
+ installApks.push_back(InstallApk(file, !target->build));
+ continue;
+ }
+ }
+ }
+ }
+ map<string,FileInfo> systemFilesBefore;
+ if (syncSystem && !alwaysSyncSystem) {
+ get_directory_contents(systemPath, &systemFilesBefore);
+ }
+
+ //
+ // Build
+ //
+
+ // Run the build
+ if (goals.size() > 0) {
+ print_status("Building");
+ err = build_goals(goals);
+ check_error(err);
+ }
+
+ //
+ // Install
+ //
+
+ // Sync the system partition and reboot
+ bool skipSync = false;
+ if (syncSystem) {
+ print_status("Syncing /system");
+
+ if (!alwaysSyncSystem) {
+ // If nothing changed and we weren't forced to sync, skip the reboot for speed.
+ map<string,FileInfo> systemFilesAfter;
+ get_directory_contents(systemPath, &systemFilesAfter);
+ skipSync = !directory_contents_differ(systemFilesBefore, systemFilesAfter);
+ }
+ if (skipSync) {
+ printf("Skipping sync because no files changed.\n");
+ } else {
+ // Do some sanity checks
+ check_device_property("ro.build.product", buildProduct);
+ check_device_property("ro.build.type", buildVariant);
+ check_device_property("ro.build.id", buildId);
+
+ // Stop & Sync
+ if (!options.noRestart) {
+ err = run_adb("shell", "stop", NULL);
+ check_error(err);
+ }
+ err = run_adb("remount", NULL);
+ check_error(err);
+ err = run_adb("sync", "system", NULL);
+ check_error(err);
+
+ if (!options.noRestart) {
+ if (options.reboot) {
+ print_status("Rebooting");
+
+ err = run_adb("reboot", NULL);
+ check_error(err);
+ err = run_adb("wait-for-device", NULL);
+ check_error(err);
+ } else {
+ print_status("Restarting the runtime");
+
+ err = run_adb("shell", "setprop", "sys.boot_completed", "0", NULL);
+ check_error(err);
+ err = run_adb("shell", "start", NULL);
+ check_error(err);
+ }
+
+ while (true) {
+ string completed = get_system_property("sys.boot_completed", &err);
+ check_error(err);
+ if (completed == "1") {
+ break;
+ }
+ sleep(2);
+ }
+ sleep(1);
+ err = run_adb("shell", "wm", "dismiss-keyguard", NULL);
+ check_error(err);
+ }
+ }
+ }
+
+ // Install APKs
+ if (installApks.size() > 0) {
+ print_status("Installing APKs");
+ for (size_t i=0; i<installApks.size(); i++) {
+ InstallApk& apk = installApks[i];
+ if (!apk.file.fileInfo.exists || apk.file.HasChanged()) {
+ // It didn't exist before or it changed, so int needs install
+ err = run_adb("install", "-r", apk.file.filename.c_str(), NULL);
+ check_error(err);
+ apk.installed = true;
+ } else {
+ printf("APK didn't change. Skipping install of %s\n", apk.file.filename.c_str());
+ }
+ }
+ }
+
+ //
+ // Actions
+ //
+
+ // Inspect the apks, and figure out what is an activity and what needs a test runner
+ bool printedInspecting = false;
+ vector<TestAction> testActions;
+ vector<ActivityAction> activityActions;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->test) {
+ for (size_t j=0; j<target->module.installed.size(); j++) {
+ string filename = target->module.installed[j];
+
+ if (!ends_with(filename, ".apk")) {
+ continue;
+ }
+
+ if (!printedInspecting) {
+ printedInspecting = true;
+ print_status("Inspecting APKs");
+ }
+
+ Apk apk;
+ err = inspect_apk(&apk, filename);
+ check_error(err);
+
+ for (size_t k=0; k<target->actions.size(); k++) {
+ string actionString = target->actions[k];
+ if (actionString == "*") {
+ if (apk.runner.length() == 0) {
+ print_error("Error: Test requested for apk that doesn't"
+ " have an <instrumentation> tag: %s\n",
+ target->module.name.c_str());
+ exit(1);
+ }
+ TestAction action;
+ action.packageName = apk.package;
+ action.runner = apk.runner;
+ action.target = target;
+ testActions.push_back(action);
+ target->testActionCount++;
+ } else if (apk.HasActivity(actionString)) {
+ ActivityAction action;
+ action.packageName = apk.package;
+ action.className = full_class_name(apk.package, actionString);
+ activityActions.push_back(action);
+ } else {
+ if (apk.runner.length() == 0) {
+ print_error("Error: Test requested for apk that doesn't"
+ " have an <instrumentation> tag: %s\n",
+ target->module.name.c_str());
+ exit(1);
+ }
+ TestAction action;
+ action.packageName = apk.package;
+ action.runner = apk.runner;
+ action.className = full_class_name(apk.package, actionString);
+ action.target = target;
+ testActions.push_back(action);
+ target->testActionCount++;
+ }
+ }
+ }
+ }
+ }
+
+ // Run the instrumentation tests
+ TestResults testResults;
+ if (testActions.size() > 0) {
+ print_status("Running tests");
+ for (size_t i=0; i<testActions.size(); i++) {
+ TestAction& action = testActions[i];
+ testResults.SetCurrentAction(&action);
+ err = run_instrumentation_test(action.packageName, action.runner, action.className,
+ &testResults);
+ check_error(err);
+ if (action.passCount == 0 && action.failCount == 0) {
+ action.target->actionsWithNoTests = true;
+ }
+ int total = action.passCount + action.failCount;
+ printf("%sRan %d test%s for %s. ", g_escapeClearLine,
+ total, total > 1 ? "s" : "", action.target->name.c_str());
+ if (action.passCount == 0 && action.failCount == 0) {
+ printf("%s%d passed, %d failed%s\n", g_escapeYellowBold, action.passCount,
+ action.failCount, g_escapeEndColor);
+ } else if (action.failCount > 0) {
+ printf("%d passed, %s%d failed%s\n", action.passCount, g_escapeRedBold,
+ action.failCount, g_escapeEndColor);
+ } else {
+ printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount,
+ g_escapeEndColor, action.failCount);
+ }
+ }
+ }
+
+ // Launch the activity
+ if (activityActions.size() > 0) {
+ print_status("Starting activity");
+
+ if (activityActions.size() > 1) {
+ print_warning("Multiple activities specified. Will only start the first one:");
+ for (size_t i=0; i<activityActions.size(); i++) {
+ ActivityAction& action = activityActions[i];
+ print_warning(" %s",
+ pretty_component_name(action.packageName, action.className).c_str());
+ }
+ }
+
+ const ActivityAction& action = activityActions[0];
+ string componentName = action.packageName + "/" + action.className;
+ err = run_adb("shell", "am", "start", componentName.c_str(), NULL);
+ check_error(err);
+ }
+
+ //
+ // Print summary
+ //
+
+ printf("\n%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor);
+
+ // Build
+ if (goals.size() > 0) {
+ printf("%sBuilt:%s\n", g_escapeBold, g_escapeEndColor);
+ for (size_t i=0; i<goals.size(); i++) {
+ printf(" %s\n", goals[i].c_str());
+ }
+ }
+
+ // Install
+ if (syncSystem) {
+ if (skipSync) {
+ printf("%sSkipped syncing /system partition%s\n", g_escapeBold, g_escapeEndColor);
+ } else {
+ printf("%sSynced /system partition%s\n", g_escapeBold, g_escapeEndColor);
+ }
+ }
+ if (installApks.size() > 0) {
+ bool printedTitle = false;
+ for (size_t i=0; i<installApks.size(); i++) {
+ const InstallApk& apk = installApks[i];
+ if (apk.installed) {
+ if (!printedTitle) {
+ printf("%sInstalled:%s\n", g_escapeBold, g_escapeEndColor);
+ printedTitle = true;
+ }
+ printf(" %s\n", apk.file.filename.c_str());
+ }
+ }
+ printedTitle = false;
+ for (size_t i=0; i<installApks.size(); i++) {
+ const InstallApk& apk = installApks[i];
+ if (!apk.installed) {
+ if (!printedTitle) {
+ printf("%sSkipped install:%s\n", g_escapeBold, g_escapeEndColor);
+ printedTitle = true;
+ }
+ printf(" %s\n", apk.file.filename.c_str());
+ }
+ }
+ }
+
+ // Tests
+ if (testActions.size() > 0) {
+ printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor);
+ size_t maxNameLength = 0;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->test) {
+ size_t len = target->name.length();
+ if (len > maxNameLength) {
+ maxNameLength = len;
+ }
+ }
+ }
+ string padding(maxNameLength, ' ');
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->testActionCount > 0) {
+ printf(" %s%s", target->name.c_str(), padding.c_str() + target->name.length());
+ if (target->actionsWithNoTests) {
+ printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold,
+ target->testPassCount, target->testFailCount, g_escapeEndColor);
+ } else if (target->testFailCount > 0) {
+ printf(" %d passed, %s%d failed%s\n", target->testPassCount,
+ g_escapeRedBold, target->testFailCount, g_escapeEndColor);
+ } else {
+ printf(" %s%d passed%s, %d failed\n", g_escapeGreenBold,
+ target->testPassCount, g_escapeEndColor, target->testFailCount);
+ }
+ }
+ }
+ }
+ if (activityActions.size() > 1) {
+ printf("%sStarted Activity:%s\n", g_escapeBold, g_escapeEndColor);
+ const ActivityAction& action = activityActions[0];
+ printf(" %s\n", pretty_component_name(action.packageName, action.className).c_str());
+ }
+
+ printf("%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor);
+}
+
+/**
+ * Implement tab completion of the target names from the all modules file.
+ */
+void
+run_tab_completion(const string& word)
+{
+ const string buildTop = get_required_env("ANDROID_BUILD_TOP", true);
+ const string buildProduct = get_required_env("TARGET_PRODUCT", false);
+ const string buildOut = get_out_dir();
+
+ chdir(buildTop.c_str());
+
+ string buildDevice = sniff_device_name(buildOut, buildProduct);
+
+ map<string,Module> modules;
+ read_modules(buildOut, buildDevice, &modules, true);
+
+ for (map<string,Module>::const_iterator it = modules.begin(); it != modules.end(); it++) {
+ if (starts_with(it->first, word)) {
+ printf("%s\n", it->first.c_str());
+ }
+ }
+}
+
+/**
+ * Main entry point.
+ */
+int
+main(int argc, const char** argv)
+{
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ init_print();
+
+ Options options;
+ parse_args(&options, argc, argv);
+
+ if (options.runHelp) {
+ // Help
+ print_usage(stdout);
+ exit(0);
+ } else if (options.runTab) {
+ run_tab_completion(options.tabPattern);
+ exit(0);
+ } else {
+ // Normal run
+ run_phases(options.targets, options);
+ }
+
+ return 0;
+}
+
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
new file mode 100644
index 000000000000..60b5687bb313
--- /dev/null
+++ b/tools/bit/make.cpp
@@ -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.
+ */
+
+#include "make.h"
+
+#include "command.h"
+#include "print.h"
+#include "util.h"
+
+#include <json/reader.h>
+#include <json/value.h>
+
+#include <fstream>
+#include <string>
+#include <map>
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+using namespace std;
+
+map<string,string> g_buildVars;
+
+string
+get_build_var(const string& buildTop, const string& name, bool quiet)
+{
+ int err;
+
+ map<string,string>::iterator it = g_buildVars.find(name);
+ if (it == g_buildVars.end()) {
+ Command cmd("make");
+ cmd.AddArg("--no-print-directory");
+ cmd.AddArg("-C");
+ cmd.AddArg(buildTop);
+ cmd.AddArg("-f");
+ cmd.AddArg("build/core/config.mk");
+ cmd.AddArg(string("dumpvar-") + name);
+ cmd.AddEnv("CALLED_FROM_SETUP", "true");
+ cmd.AddEnv("BUILD_SYSTEM", "build/core");
+
+ string output = trim(get_command_output(cmd, &err, quiet));
+ if (err == 0) {
+ g_buildVars[name] = output;
+ return output;
+ } else {
+ return string();
+ }
+ } else {
+ return it->second;
+ }
+}
+
+string
+sniff_device_name(const string& buildOut, const string& product)
+{
+ string match("ro.build.product=" + product);
+
+ string base(buildOut + "/target/product");
+ DIR* dir = opendir(base.c_str());
+ if (dir == NULL) {
+ return string();
+ }
+
+ dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ if (entry->d_type == DT_DIR) {
+ string filename(base + "/" + entry->d_name + "/system/build.prop");
+ vector<string> lines;
+ split_lines(&lines, read_file(filename));
+ for (size_t i=0; i<lines.size(); i++) {
+ if (lines[i] == match) {
+ return entry->d_name;
+ }
+ }
+ }
+ }
+
+ closedir(dir);
+ return string();
+}
+
+void
+json_error(const string& filename, const char* error, bool quiet)
+{
+ if (!quiet) {
+ print_error("Unable to parse module info file (%s): %s", error, filename.c_str());
+ print_error("Have you done a full build?");
+ }
+ exit(1);
+}
+
+static void
+get_values(const Json::Value& json, const string& name, vector<string>* result)
+{
+ Json::Value nullValue;
+
+ const Json::Value& value = json.get(name, nullValue);
+ if (!value.isArray()) {
+ return;
+ }
+
+ const int N = value.size();
+ for (int i=0; i<N; i++) {
+ const Json::Value& child = value[i];
+ if (child.isString()) {
+ result->push_back(child.asString());
+ }
+ }
+}
+
+void
+read_modules(const string& buildOut, const string& device, map<string,Module>* result, bool quiet)
+{
+ string filename(string(buildOut + "/target/product/") + device + "/module-info.json");
+ std::ifstream stream(filename, std::ifstream::binary);
+
+ if (stream.fail()) {
+ if (!quiet) {
+ print_error("Unable to open module info file: %s", filename.c_str());
+ print_error("Have you done a full build?");
+ }
+ exit(1);
+ }
+
+ Json::Value json;
+ Json::Reader reader;
+ if (!reader.parse(stream, json)) {
+ json_error(filename, "can't parse json format", quiet);
+ return;
+ }
+
+ if (!json.isObject()) {
+ json_error(filename, "root element not an object", quiet);
+ return;
+ }
+
+ vector<string> names = json.getMemberNames();
+ const int N = names.size();
+ for (int i=0; i<N; i++) {
+ const string& name = names[i];
+
+ const Json::Value& value = json[name];
+ if (!value.isObject()) {
+ continue;
+ }
+
+ Module module;
+
+ module.name = name;
+ get_values(value, "class", &module.classes);
+ get_values(value, "path", &module.paths);
+ get_values(value, "installed", &module.installed);
+
+ // Only keep classes we can handle
+ for (ssize_t i = module.classes.size() - 1; i >= 0; i--) {
+ string cl = module.classes[i];
+ if (!(cl == "JAVA_LIBRARIES" || cl == "EXECUTABLES" || cl == "SHARED_LIBRARIES"
+ || cl == "APPS")) {
+ module.classes.erase(module.classes.begin() + i);
+ }
+ }
+ if (module.classes.size() == 0) {
+ continue;
+ }
+
+ // Only target modules (not host)
+ for (ssize_t i = module.installed.size() - 1; i >= 0; i--) {
+ string fn = module.installed[i];
+ if (!starts_with(fn, buildOut + "/target/")) {
+ module.installed.erase(module.installed.begin() + i);
+ }
+ }
+ if (module.installed.size() == 0) {
+ continue;
+ }
+
+ (*result)[name] = module;
+ }
+}
+
+int
+build_goals(const vector<string>& goals)
+{
+ Command cmd("make");
+ cmd.AddArg("-f");
+ cmd.AddArg("build/core/main.mk");
+ for (size_t i=0; i<goals.size(); i++) {
+ cmd.AddArg(goals[i]);
+ }
+
+ return run_command(cmd);
+}
+
diff --git a/tools/bit/make.h b/tools/bit/make.h
new file mode 100644
index 000000000000..bb83c6e14226
--- /dev/null
+++ b/tools/bit/make.h
@@ -0,0 +1,50 @@
+/*
+ * 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 MAKE_H
+#define MAKE_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Module
+{
+ string name;
+ vector<string> classes;
+ vector<string> paths;
+ vector<string> installed;
+};
+
+string get_build_var(const string& buildTop, const string& name, bool quiet);
+
+/**
+ * Poke around in the out directory and try to find a device name that matches
+ * our product. This is faster than running get_build_var and good enough for
+ * tab completion.
+ *
+ * Returns the empty string if we can't find one.
+ */
+string sniff_device_name(const string& buildOut, const string& product);
+
+void read_modules(const string& buildOut, const string& buildDevice,
+ map<string,Module>* modules, bool quiet);
+
+int build_goals(const vector<string>& goals);
+
+#endif // MAKE_H
diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp
new file mode 100644
index 000000000000..790e0b4b227e
--- /dev/null
+++ b/tools/bit/print.cpp
@@ -0,0 +1,155 @@
+/*
+ * 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 "print.h"
+
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "util.h"
+
+bool g_stdoutIsTty;
+char const* g_escapeBold;
+char const* g_escapeRedBold;
+char const* g_escapeGreenBold;
+char const* g_escapeYellowBold;
+char const* g_escapeUnderline;
+char const* g_escapeEndColor;
+char const* g_escapeClearLine;
+
+void
+init_print()
+{
+ if (isatty(fileno(stdout))) {
+ g_stdoutIsTty = true;
+ g_escapeBold = "\033[1m";
+ g_escapeRedBold = "\033[91m\033[1m";
+ g_escapeGreenBold = "\033[92m\033[1m";
+ g_escapeYellowBold = "\033[93m\033[1m";
+ g_escapeUnderline = "\033[4m";
+ g_escapeEndColor = "\033[0m";
+ g_escapeClearLine = "\033[K";
+ } else {
+ g_stdoutIsTty = false;
+ g_escapeBold = "";
+ g_escapeRedBold = "";
+ g_escapeGreenBold = "";
+ g_escapeYellowBold = "";
+ g_escapeUnderline = "";
+ g_escapeEndColor = "";
+ g_escapeClearLine = "";
+ }
+}
+
+void
+print_status(const char* format, ...)
+{
+ printf("\n%s%s", g_escapeBold, g_escapeUnderline);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+
+ printf("%s\n", g_escapeEndColor);
+}
+
+void
+print_command(const Command& command)
+{
+ fputs(g_escapeBold, stdout);
+ for (map<string,string>::const_iterator it=command.env.begin(); it!=command.env.end(); it++) {
+ fputs(it->first.c_str(), stdout);
+ fputc('=', stdout);
+ fputs(escape_for_commandline(it->second.c_str()).c_str(), stdout);
+ putc(' ', stdout);
+ }
+ fputs(command.prog.c_str(), stdout);
+ for (vector<string>::const_iterator it=command.args.begin(); it!=command.args.end(); it++) {
+ putc(' ', stdout);
+ fputs(escape_for_commandline(it->c_str()).c_str(), stdout);
+ }
+ fputs(g_escapeEndColor, stdout);
+ fputc('\n', stdout);
+}
+
+void
+print_error(const char* format, ...)
+{
+ fputs(g_escapeRedBold, stderr);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+
+ fputs(g_escapeEndColor, stderr);
+ fputc('\n', stderr);
+}
+
+void
+print_warning(const char* format, ...)
+{
+ fputs(g_escapeYellowBold, stderr);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+
+ fputs(g_escapeEndColor, stderr);
+ fputc('\n', stderr);
+}
+
+void
+print_one_line(const char* format, ...)
+{
+ if (g_stdoutIsTty) {
+ struct winsize ws;
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
+ int size = ws.ws_col + 1;
+ char* buf = (char*)malloc(size);
+
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buf, size, format, args);
+ va_end(args);
+
+ printf("%s%s\r", buf, g_escapeClearLine);
+ free(buf);
+
+ fflush(stdout);
+ } else {
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+ printf("\n");
+ }
+}
+
+void
+check_error(int err)
+{
+ if (err != 0) {
+ fputc('\n', stderr);
+ print_error("Stopping due to errors.");
+ exit(1);
+ }
+}
+
+
diff --git a/tools/bit/print.h b/tools/bit/print.h
new file mode 100644
index 000000000000..b6c3e9aa27fa
--- /dev/null
+++ b/tools/bit/print.h
@@ -0,0 +1,39 @@
+/*
+ * 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 PRINT_H
+#define PRINT_H
+
+#include "command.h"
+
+extern bool g_stdoutIsTty;
+extern char const* g_escapeBold;
+extern char const* g_escapeRedBold;
+extern char const* g_escapeGreenBold;
+extern char const* g_escapeYellowBold;
+extern char const* g_escapeUnderline;
+extern char const* g_escapeEndColor;
+extern char const* g_escapeClearLine;
+
+void init_print();
+void print_status(const char* format, ...);
+void print_command(const Command& command);
+void print_error(const char* format, ...);
+void print_warning(const char* format, ...);
+void print_one_line(const char* format, ...);
+void check_error(int err);
+
+#endif // PRINT_H
diff --git a/tools/bit/util.cpp b/tools/bit/util.cpp
new file mode 100644
index 000000000000..fc93bcb8c935
--- /dev/null
+++ b/tools/bit/util.cpp
@@ -0,0 +1,254 @@
+/*
+ * 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 "util.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <string.h>
+#include <unistd.h>
+
+
+FileInfo::FileInfo()
+{
+ memset(this, 0, sizeof(FileInfo));
+}
+
+FileInfo::FileInfo(const FileInfo& that)
+{
+ memcpy(this, &that, sizeof(FileInfo));
+}
+
+FileInfo::FileInfo(const string& filename)
+{
+ struct stat st;
+ int err = stat(filename.c_str(), &st);
+ if (err != 0) {
+ memset(this, 0, sizeof(FileInfo));
+ } else {
+ exists = true;
+ mtime = st.st_mtime;
+ ctime = st.st_ctime;
+ size = st.st_size;
+ }
+}
+
+bool
+FileInfo::operator==(const FileInfo& that) const
+{
+ return exists == that.exists
+ && mtime == that.mtime
+ && ctime == that.ctime
+ && size == that.size;
+}
+
+bool
+FileInfo::operator!=(const FileInfo& that) const
+{
+ return exists != that.exists
+ || mtime != that.mtime
+ || ctime != that.ctime
+ || size != that.size;
+}
+
+FileInfo::~FileInfo()
+{
+}
+
+TrackedFile::TrackedFile()
+ :filename(),
+ fileInfo()
+{
+}
+
+TrackedFile::TrackedFile(const TrackedFile& that)
+{
+ filename = that.filename;
+ fileInfo = that.fileInfo;
+}
+
+TrackedFile::TrackedFile(const string& file)
+ :filename(file),
+ fileInfo(file)
+{
+}
+
+TrackedFile::~TrackedFile()
+{
+}
+
+bool
+TrackedFile::HasChanged() const
+{
+ FileInfo updated(filename);
+ return !updated.exists || fileInfo != updated;
+}
+
+void
+get_directory_contents(const string& name, map<string,FileInfo>* results)
+{
+ int err;
+ DIR* dir = opendir(name.c_str());
+ if (dir == NULL) {
+ return;
+ }
+
+ dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
+ continue;
+ }
+ if (entry->d_type == DT_DIR) {
+ string subdir = name + "/" + entry->d_name;
+ get_directory_contents(subdir, results);
+ } else if (entry->d_type == DT_LNK || entry->d_type == DT_REG) {
+ string filename(name + "/" + entry->d_name);
+ (*results)[filename] = FileInfo(filename);
+ }
+ }
+
+ closedir(dir);
+}
+
+bool
+directory_contents_differ(const map<string,FileInfo>& before, const map<string,FileInfo>& after)
+{
+ if (before.size() != after.size()) {
+ return true;
+ }
+ map<string,FileInfo>::const_iterator b = before.begin();
+ map<string,FileInfo>::const_iterator a = after.begin();
+ while (b != before.end() && a != after.end()) {
+ if (b->first != a->first) {
+ return true;
+ }
+ if (a->second != b->second) {
+ return true;
+ }
+ a++;
+ b++;
+ }
+ return false;
+}
+
+string
+escape_quotes(const char* str)
+{
+ string result;
+ while (*str) {
+ if (*str == '"') {
+ result += '\\';
+ result += '"';
+ } else {
+ result += *str;
+ }
+ }
+ return result;
+}
+
+string
+escape_for_commandline(const char* str)
+{
+ if (strchr(str, '"') != NULL || strchr(str, ' ') != NULL
+ || strchr(str, '\t') != NULL) {
+ return escape_quotes(str);
+ } else {
+ return str;
+ }
+}
+
+static bool
+spacechr(char c)
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+string
+trim(const string& str)
+{
+ const ssize_t N = (ssize_t)str.size();
+ ssize_t begin = 0;
+ while (begin < N && spacechr(str[begin])) {
+ begin++;
+ }
+ ssize_t end = N - 1;
+ while (end >= begin && spacechr(str[end])) {
+ end--;
+ }
+ return string(str, begin, end-begin+1);
+}
+
+bool
+starts_with(const string& str, const string& prefix)
+{
+ return str.compare(0, prefix.length(), prefix) == 0;
+}
+
+bool
+ends_with(const string& str, const string& suffix)
+{
+ if (str.length() < suffix.length()) {
+ return false;
+ } else {
+ return str.compare(str.length()-suffix.length(), suffix.length(), suffix) == 0;
+ }
+}
+
+void
+split_lines(vector<string>* result, const string& str)
+{
+ const int N = str.length();
+ int begin = 0;
+ int end = 0;
+ for (; end < N; end++) {
+ const char c = str[end];
+ if (c == '\r' || c == '\n') {
+ if (begin != end) {
+ result->push_back(string(str, begin, end-begin));
+ }
+ begin = end+1;
+ }
+ }
+ if (begin != end) {
+ result->push_back(string(str, begin, end-begin));
+ }
+}
+
+string
+read_file(const string& filename)
+{
+ FILE* file = fopen(filename.c_str(), "r");
+ if (file == NULL) {
+ return string();
+ }
+
+ fseek(file, 0, SEEK_END);
+ int size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ char* buf = (char*)malloc(size);
+ fread(buf, 1, size, file);
+
+ string result(buf, size);
+
+ free(buf);
+ fclose(file);
+
+ return result;
+}
+
+
diff --git a/tools/bit/util.h b/tools/bit/util.h
new file mode 100644
index 000000000000..718f1474a969
--- /dev/null
+++ b/tools/bit/util.h
@@ -0,0 +1,83 @@
+/*
+ * 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 UTIL_H
+#define UTIL_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct FileInfo
+{
+ bool exists;
+ time_t mtime;
+ time_t ctime;
+ off_t size;
+
+ FileInfo();
+ FileInfo(const FileInfo& that);
+ explicit FileInfo(const string& filename);
+ ~FileInfo();
+
+ bool operator==(const FileInfo& that) const;
+ bool operator!=(const FileInfo& that) const;
+};
+
+
+/**
+ * Record for a file that we are watching
+ */
+struct TrackedFile {
+ string filename;
+ FileInfo fileInfo;
+
+ TrackedFile();
+ TrackedFile(const TrackedFile& that);
+ explicit TrackedFile(const string& filename);
+ ~TrackedFile();
+
+ // Returns if the file has changed. If it doesn't currently exist, returns true.
+ bool HasChanged() const;
+};
+
+/**
+ * Get FileInfo structures recursively for all the files and symlinks in a directory.
+ * Does not traverse symlinks, but it does record them.
+ */
+void get_directory_contents(const string& dir, map<string,FileInfo>* results);
+
+bool directory_contents_differ(const map<string,FileInfo>& before,
+ const map<string,FileInfo>& after);
+
+string escape_quotes(const char* str);
+
+string escape_for_commandline(const char* str);
+
+string trim(const string& trim);
+
+bool starts_with(const string& str, const string& prefix);
+
+bool ends_with(const string& str, const string& suffix);
+
+void split_lines(vector<string>* result, const string& str);
+
+string read_file(const string& filename);
+
+#endif // UTIL_H
+
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py
index 7ec46a3ee86b..219fa2de7e50 100755
--- a/tools/fonts/fontchain_lint.py
+++ b/tools/fonts/fontchain_lint.py
@@ -314,8 +314,11 @@ def check_emoji_defaults(default_emoji):
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:
+ # score even if they appear after the emoji font. However,
+ # we should skip checking the text symbols font, since
+ # symbol fonts should be able to override the emoji display
+ # style when 'Zsym' is explicitly specified by the user.
+ if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts):
continue
# Check default emoji-style characters
@@ -588,6 +591,19 @@ def compute_expected_emoji():
return all_emoji, default_emoji, equivalent_emoji
+def check_vertical_metrics():
+ for record in _fallback_chain:
+ if record.name in ['sans-serif', 'sans-serif-condensed']:
+ font = open_font(record.font)
+ assert font['head'].yMax == 2163 and font['head'].yMin == -555, (
+ 'yMax and yMin of %s do not match expected values.' % (record.font,))
+
+ if record.name in ['sans-serif', 'sans-serif-condensed', 'serif', 'monospace']:
+ font = open_font(record.font)
+ assert font['hhea'].ascent == 1900 and font['hhea'].descent == -500, (
+ 'ascent and descent of %s do not match expected values.' % (record.font,))
+
+
def main():
global _fonts_dir
target_out = sys.argv[1]
@@ -596,6 +612,8 @@ def main():
fonts_xml_path = path.join(target_out, 'etc', 'fonts.xml')
parse_fonts_xml(fonts_xml_path)
+ check_vertical_metrics()
+
hyphens_dir = path.join(target_out, 'usr', 'hyphen-data')
check_hyphens(hyphens_dir)
diff --git a/tools/layoutlib/.gitignore b/tools/layoutlib/.gitignore
index eb52b64fd32c..819103db9d99 100644
--- a/tools/layoutlib/.gitignore
+++ b/tools/layoutlib/.gitignore
@@ -1,3 +1,4 @@
bin
/.idea/workspace.xml
/out
+/bridge/out
diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
index 3681f2aaf3f1..74fa549f66d4 100644
--- a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
+++ b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
@@ -11,7 +11,6 @@
<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" />
diff --git a/tools/layoutlib/.idea/libraries/junit.xml b/tools/layoutlib/.idea/libraries/junit.xml
index c889f5ff6c97..ba46ebff7d16 100644
--- a/tools/layoutlib/.idea/libraries/junit.xml
+++ b/tools/layoutlib/.idea/libraries/junit.xml
@@ -1,7 +1,7 @@
<component name="libraryTable">
<library name="junit">
<CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/junit_intermediates/javalib.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/junit-host_intermediates/javalib.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
diff --git a/tools/layoutlib/bridge/bridge.iml b/tools/layoutlib/bridge/bridge.iml
index 57d08cb22c7e..fbaed520fff9 100644
--- a/tools/layoutlib/bridge/bridge.iml
+++ b/tools/layoutlib/bridge/bridge.iml
@@ -7,6 +7,7 @@
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/tests/src" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/src/main/myapplication.widgets" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.settings" />
<excludeFolder url="file://$MODULE_DIR$/bin" />
<excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/.gradle" />
diff --git a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
index e0d3b8cd4de4..b4d5288bc925 100644
--- a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
@@ -18,6 +18,8 @@ package android.content.res;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.util.SparseArray;
+
/**
* Delegate used to provide implementation of a select few native methods of {@link AssetManager}
* <p/>
@@ -38,4 +40,8 @@ public class AssetManager_Delegate {
Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme);
}
+ @LayoutlibDelegate
+ /*package*/ static SparseArray<String> getAssignedPackageIdentifiers(AssetManager manager) {
+ return new SparseArray<>();
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index d0e431acadff..35cf9038f9ae 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -80,7 +80,7 @@ public final class BridgeTypedArray extends TypedArray {
public BridgeTypedArray(Resources resources, BridgeContext context, int len,
boolean platformFile) {
- super(resources, null, null, 0);
+ super(resources);
mBridgeResources = resources;
mContext = context;
mPlatformFile = platformFile;
@@ -584,6 +584,7 @@ public final class BridgeTypedArray extends TypedArray {
if (value == null) {
return defValue;
}
+ value = value.trim();
// if the value is just an integer, return it.
try {
@@ -595,6 +596,11 @@ public final class BridgeTypedArray extends TypedArray {
// pass
}
+ if (value.startsWith("#")) {
+ // this looks like a color, do not try to parse it
+ return defValue;
+ }
+
// Handle the @id/<name>, @+id/<name> and @android:id/<name>
// We need to return the exact value that was compiled (from the various R classes),
// as these values can be reused internally with calls to findViewById().
@@ -632,7 +638,15 @@ public final class BridgeTypedArray extends TypedArray {
}
}
- // not a direct id valid reference? resolve it
+ // not a direct id valid reference. First check if it's an enum (this is a corner case
+ // for attributes that have a reference|enum type), then fallback to resolve
+ // as an ID without prefix.
+ Integer enumValue = resolveEnumAttribute(index);
+ if (enumValue != null) {
+ return enumValue;
+ }
+
+ // Ok, not an enum, resolve as an ID
Integer idValue;
if (resValue.isFramework()) {
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 ea320c701c24..c3d4cef61b35 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -45,6 +45,7 @@ import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
+import android.util.LruCache;
import android.util.TypedValue;
import android.view.ViewGroup.LayoutParams;
@@ -58,6 +59,9 @@ import java.util.Iterator;
public class Resources_Delegate {
private static boolean[] mPlatformResourceFlag = new boolean[1];
+ // TODO: This cache is cleared every time a render session is disposed. Look into making this
+ // more long lived.
+ private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50);
public static Resources initSystem(BridgeContext context,
AssetManager assets,
@@ -75,6 +79,7 @@ public class Resources_Delegate {
* would prevent us from unloading the library.
*/
public static void disposeSystem() {
+ sDrawableCache.evictAll();
Resources.mSystem.mContext = null;
Resources.mSystem.mLayoutlibCallback = null;
Resources.mSystem = null;
@@ -137,9 +142,23 @@ public class Resources_Delegate {
@LayoutlibDelegate
static Drawable getDrawable(Resources resources, int id, Theme theme) {
Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
if (value != null) {
- return ResourceHelper.getDrawable(value.getSecond(), resources.mContext, theme);
+ String key = value.getSecond().getValue();
+
+ Drawable.ConstantState constantState = key != null ? sDrawableCache.get(key) : null;
+ Drawable drawable;
+ if (constantState != null) {
+ drawable = constantState.newDrawable(resources, theme);
+ } else {
+ drawable =
+ ResourceHelper.getDrawable(value.getSecond(), resources.mContext, theme);
+
+ if (key != null) {
+ sDrawableCache.put(key, drawable.getConstantState());
+ }
+ }
+
+ return drawable;
}
// id was not found or not resolved. Throw a NotFoundException.
diff --git a/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java
new file mode 100644
index 000000000000..df858067ba04
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java
@@ -0,0 +1,727 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+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.text.TextUtils;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+
+public class BaseCanvas_Delegate {
+ // ---- delegate manager ----
+ protected static DelegateManager<BaseCanvas_Delegate> sManager =
+ new DelegateManager<>(BaseCanvas_Delegate.class);
+
+ // ---- delegate helper data ----
+ private final static boolean[] sBoolOut = new boolean[1];
+
+
+ // ---- delegate data ----
+ protected Bitmap_Delegate mBitmap;
+ protected GcSnapshot mSnapshot;
+
+ // ---- Public Helper methods ----
+
+ protected BaseCanvas_Delegate(Bitmap_Delegate bitmap) {
+ mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
+ }
+
+ protected BaseCanvas_Delegate() {
+ mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
+ }
+
+ /**
+ * Disposes of the {@link Graphics2D} stack.
+ */
+ protected void dispose() {
+ mSnapshot.dispose();
+ }
+
+ /**
+ * Returns the current {@link Graphics2D} used to draw.
+ */
+ public GcSnapshot getSnapshot() {
+ return mSnapshot;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
+ long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) {
+ // get the delegate from the native int.
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+ if (bitmapDelegate == null) {
+ return;
+ }
+
+ BufferedImage image = bitmapDelegate.getImage();
+ float right = left + image.getWidth();
+ float bottom = top + image.getHeight();
+
+ drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
+ 0, 0, image.getWidth(), image.getHeight(),
+ (int)left, (int)top, (int)right, (int)bottom);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawBitmap(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) {
+ // get the delegate from the native int.
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+ if (bitmapDelegate == null) {
+ return;
+ }
+
+ drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, (int) srcLeft, (int) srcTop,
+ (int) srcRight, (int) srcBottom, (int) dstLeft, (int) dstTop, (int) dstRight,
+ (int) dstBottom);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
+ final float x, final float y, int width, int height, boolean hasAlpha,
+ long nativePaintOrZero) {
+ // create a temp BufferedImage containing the content.
+ final BufferedImage image = new BufferedImage(width, height,
+ hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
+ image.setRGB(0, 0, width, height, colors, offset, stride);
+
+ draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
+ (graphics, paint) -> {
+ if (paint != null && paint.isFilterBitmap()) {
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ }
+
+ graphics.drawImage(image, (int) x, (int) y, null);
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawColor(long nativeCanvas, final int color, final int mode) {
+ // get the delegate from the native int.
+ BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ final int w = canvasDelegate.mBitmap.getImage().getWidth();
+ final int h = canvasDelegate.mBitmap.getImage().getHeight();
+ draw(nativeCanvas, (graphics, paint) -> {
+ // reset its transform just in case
+ graphics.setTransform(new AffineTransform());
+
+ // set the color
+ graphics.setColor(new java.awt.Color(color, true /*alpha*/));
+
+ Composite composite = PorterDuffUtility.getComposite(
+ PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
+ if (composite != null) {
+ graphics.setComposite(composite);
+ }
+
+ graphics.fillRect(0, 0, w, h);
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawPaint(long nativeCanvas, long paint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawPaint is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawPoint(long nativeCanvas, float x, float y,
+ long nativePaint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawPoint is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count,
+ long nativePaint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawPoint is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawLine(long nativeCanvas,
+ final float startX, final float startY, final float stopX, final float stopY,
+ long paint) {
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ (graphics, paintDelegate) -> graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawLines(long nativeCanvas,
+ final float[] pts, final int offset, final int count,
+ long nativePaint) {
+ draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
+ false /*forceSrcMode*/, (graphics, paintDelegate) -> {
+ for (int i = 0; i < count; i += 4) {
+ graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1],
+ (int) pts[i + offset + 2], (int) pts[i + offset + 3]);
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawRect(long nativeCanvas,
+ final float left, final float top, final float right, final float bottom, long paint) {
+
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ (graphics, paintDelegate) -> {
+ int style = paintDelegate.getStyle();
+
+ // draw
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fillRect((int)left, (int)top,
+ (int)(right-left), (int)(bottom-top));
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.drawRect((int)left, (int)top,
+ (int)(right-left), (int)(bottom-top));
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawOval(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*/,
+ (graphics, paintDelegate) -> {
+ int style = paintDelegate.getStyle();
+
+ // draw
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fillOval((int)left, (int)top,
+ (int)(right - left), (int)(bottom - top));
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.drawOval((int)left, (int)top,
+ (int)(right - left), (int)(bottom - top));
+ }
+ });
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawCircle(long nativeCanvas,
+ float cx, float cy, float radius, long paint) {
+ nDrawOval(nativeCanvas,
+ cx - radius, cy - radius, cx + radius, cy + radius,
+ paint);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawArc(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) {
+ if (right > left && bottom > top) {
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ (graphics, paintDelegate) -> {
+ int style = paintDelegate.getStyle();
+
+ Arc2D.Float arc = new Arc2D.Float(
+ left, top, right - left, bottom - top,
+ -startAngle, -sweep,
+ useCenter ? Arc2D.PIE : Arc2D.OPEN);
+
+ // draw
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fill(arc);
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.draw(arc);
+ }
+ });
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawRoundRect(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*/,
+ (graphics, paintDelegate) -> {
+ int style = paintDelegate.getStyle();
+
+ // draw
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fillRoundRect(
+ (int)left, (int)top,
+ (int)(right - left), (int)(bottom - top),
+ 2 * (int)rx, 2 * (int)ry);
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.drawRoundRect(
+ (int)left, (int)top,
+ (int)(right - left), (int)(bottom - top),
+ 2 * (int)rx, 2 * (int)ry);
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ public static void nDrawPath(long nativeCanvas, long path, long paint) {
+ final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ (graphics, paintDelegate) -> {
+ Shape shape = pathDelegate.getJavaShape();
+ Rectangle2D bounds = shape.getBounds2D();
+ if (bounds.isEmpty()) {
+ // Apple JRE 1.6 doesn't like drawing empty shapes.
+ // http://b.android.com/178278
+
+ if (pathDelegate.isEmpty()) {
+ // This means that the path doesn't have any lines or curves so
+ // nothing to draw.
+ return;
+ }
+
+ // The stroke width is not consider for the size of the bounds so,
+ // for example, a horizontal line, would be considered as an empty
+ // rectangle.
+ // If the strokeWidth is not 0, we use it to consider the size of the
+ // path as well.
+ float strokeWidth = paintDelegate.getStrokeWidth();
+ if (strokeWidth <= 0.0f) {
+ return;
+ }
+ bounds.setRect(bounds.getX(), bounds.getY(),
+ Math.max(strokeWidth, bounds.getWidth()),
+ Math.max(strokeWidth, bounds.getHeight()));
+ }
+
+ int style = paintDelegate.getStyle();
+
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fill(shape);
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.draw(shape);
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawRegion(long nativeCanvas, long nativeRegion,
+ long nativePaint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Some canvas paths may not be drawn", null, null);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawNinePatch(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
+ /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
+ long nMatrix, long nPaint) {
+ // get the delegate from the native int.
+ BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ // get the delegate from the native int, which can be null
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
+
+ // get the delegate from the native int.
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+ if (bitmapDelegate == null) {
+ return;
+ }
+
+ final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+ if (matrixDelegate == null) {
+ return;
+ }
+
+ final AffineTransform mtx = matrixDelegate.getAffineTransform();
+
+ canvasDelegate.getSnapshot().draw((graphics, paint) -> {
+ if (paint != null && paint.isFilterBitmap()) {
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ }
+
+ //FIXME add support for canvas, screen and bitmap densities.
+ graphics.drawImage(image, mtx, null);
+ }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap,
+ int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
+ int colorOffset, long nPaint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawVertices(long nCanvas, int mode, int n,
+ float[] verts, int vertOffset,
+ float[] texs, int texOffset,
+ int[] colors, int colorOffset,
+ short[] indices, int indexOffset,
+ int indexCount, long nPaint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawVertices is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawText(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 nDrawText(long nativeCanvas, String text,
+ int start, int end, float x, float y, final int flags, long paint,
+ long typeface) {
+ int count = end - start;
+ char[] buffer = TemporaryBuffer.obtain(count);
+ TextUtils.getChars(text, start, end, buffer, 0);
+
+ nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawTextRun(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;
+ char[] buffer = TemporaryBuffer.obtain(count);
+ TextUtils.getChars(text, start, end, buffer, 0);
+
+ drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawTextRun(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 nDrawTextOnPath(long nativeCanvas,
+ char[] text, int index,
+ int count, long path,
+ float hOffset,
+ float vOffset, int bidiFlags,
+ long paint, long typeface) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawTextOnPath(long nativeCanvas,
+ String text, long path,
+ float hOffset,
+ float vOffset,
+ int bidiFlags, long paint,
+ long typeface) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ /**
+ * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
+ * <p>Note that the drawable may actually be executed several times if there are
+ * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
+ */
+ private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode,
+ GcSnapshot.Drawable drawable) {
+ // get the delegate from the native int.
+ BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ // get the paint which can be null if nPaint is 0;
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
+
+ canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
+ }
+
+ /**
+ * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
+ * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
+ * <p>Note that the drawable may actually be executed several times if there are
+ * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
+ */
+ private static void draw(long nCanvas, GcSnapshot.Drawable drawable) {
+ // get the delegate from the native int.
+ BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ canvasDelegate.mSnapshot.draw(drawable);
+ }
+
+ private static void drawText(long nativeCanvas, final char[] text, final int index,
+ final int count, final float startX, final float startY, final boolean isRtl,
+ long paint, final long typeface) {
+
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ (graphics, paintDelegate) -> {
+ // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
+ // Any change to this method should be reflected in Paint.measureText
+
+ // assert that the typeface passed is actually the one stored in paint.
+ assert (typeface == paintDelegate.mNativeTypeface);
+
+ // Paint.TextAlign indicates how the text is positioned relative to X.
+ // LEFT is the default and there's nothing to do.
+ float x = startX;
+ int limit = index + count;
+ if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
+ RectF bounds =
+ paintDelegate.measureText(text, index, count, null, 0, isRtl);
+ float m = bounds.right - bounds.left;
+ if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
+ x -= m / 2;
+ } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
+ x -= m;
+ }
+ }
+
+ new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x,
+ startY).renderText(index, limit, isRtl, null, 0, true);
+ });
+ }
+
+ private static void drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap,
+ long nativePaintOrZero, final int sleft, final int stop, final int sright,
+ final int sbottom, final int dleft, final int dtop, final int dright,
+ final int dbottom) {
+ // get the delegate from the native int.
+ BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ // get the paint, which could be null if the int is 0
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
+
+ final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
+
+ draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
+ (graphics, paint) -> {
+ if (paint != null && paint.isFilterBitmap()) {
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ }
+
+ //FIXME add support for canvas, screen and bitmap densities.
+ graphics.drawImage(image, dleft, dtop, dright, dbottom, sleft, stop, sright,
+ sbottom, null);
+ });
+ }
+
+ /**
+ * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
+ * The image returns, through a 1-size boolean array, whether the drawing code should
+ * use a SRC composite no matter what the paint says.
+ *
+ * @param bitmap the bitmap
+ * @param paint the paint that will be used to draw
+ * @param forceSrcMode whether the composite will have to be SRC
+ * @return the image to draw
+ */
+ private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
+ boolean[] forceSrcMode) {
+ BufferedImage image = bitmap.getImage();
+ forceSrcMode[0] = false;
+
+ // if the bitmap config is alpha_8, then we erase all color value from it
+ // before drawing it.
+ if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
+ fixAlpha8Bitmap(image);
+ } else if (!bitmap.hasAlpha()) {
+ // hasAlpha is merely a rendering hint. There can in fact be alpha values
+ // in the bitmap but it should be ignored at drawing time.
+ // There is two ways to do this:
+ // - override the composite to be SRC. This can only be used if the composite
+ // was going to be SRC or SRC_OVER in the first place
+ // - Create a different bitmap to draw in which all the alpha channel values is set
+ // to 0xFF.
+ if (paint != null) {
+ PorterDuff.Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
+
+ forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || mode == PorterDuff.Mode.SRC;
+ }
+
+ // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
+ if (!forceSrcMode[0]) {
+ image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
+ }
+ }
+
+ return image;
+ }
+
+ private static void fixAlpha8Bitmap(final BufferedImage image) {
+ int w = image.getWidth();
+ int h = image.getHeight();
+ int[] argb = new int[w * h];
+ image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+
+ final int length = argb.length;
+ for (int i = 0 ; i < length; i++) {
+ argb[i] &= 0xFF000000;
+ }
+ image.setRGB(0, 0, w, h, argb, 0, w);
+ }
+
+ protected int save(int saveFlags) {
+ // get the current save count
+ int count = mSnapshot.size();
+
+ mSnapshot = mSnapshot.save(saveFlags);
+
+ // return the old save count
+ return count;
+ }
+
+ protected int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
+ Paint_Delegate paint = new Paint_Delegate();
+ paint.setAlpha(alpha);
+ return saveLayer(rect, paint, saveFlags);
+ }
+
+ protected int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
+ // get the current save count
+ int count = mSnapshot.size();
+
+ mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
+
+ // return the old save count
+ return count;
+ }
+
+ /**
+ * Restores the {@link GcSnapshot} to <var>saveCount</var>
+ * @param saveCount the saveCount
+ */
+ protected void restoreTo(int saveCount) {
+ mSnapshot = mSnapshot.restoreTo(saveCount);
+ }
+
+ /**
+ * Restores the top {@link GcSnapshot}
+ */
+ protected void restore() {
+ mSnapshot = mSnapshot.restore();
+ }
+
+ protected boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
+ return mSnapshot.clipRect(left, top, right, bottom, regionOp);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index f1da3a266448..2ae46540674f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -92,8 +92,7 @@ public final class Bitmap_Delegate {
@Nullable
public static Bitmap_Delegate getDelegate(@Nullable Bitmap bitmap) {
- // refSkPixelRef is a hack to get the native pointer: see #nativeRefPixelRef()
- return bitmap == null ? null : getDelegate(bitmap.refSkPixelRef());
+ return bitmap == null ? null : getDelegate(bitmap.getNativeInstance());
}
/**
@@ -327,7 +326,7 @@ public final class Bitmap_Delegate {
@LayoutlibDelegate
/*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height,
- int config, int allocSize, boolean isPremultiplied) {
+ int config, boolean isPremultiplied) {
Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
"Bitmap.reconfigure() is not supported", null /*data*/);
}
@@ -601,12 +600,20 @@ public final class Bitmap_Delegate {
return Arrays.equals(argb1, argb2);
}
- // Only used by AssetAtlasService, which we don't care about.
@LayoutlibDelegate
- /*package*/ static long nativeRefPixelRef(long nativeBitmap) {
- // Hack: This is called by Bitmap.refSkPixelRef() and LayoutLib uses that method to get
- // the native pointer from a Bitmap. So, we return nativeBitmap here.
- return nativeBitmap;
+ /*package*/ static int nativeGetAllocationByteCount(long nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return 0;
+ }
+ return nativeRowBytes(nativeBitmap) * delegate.mImage.getHeight();
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativePrepareToDraw(long nativeBitmap) {
+ // do nothing as Bitmap_Delegate does not have caches
}
// ---- Private delegate/helper methods ----
@@ -627,7 +634,7 @@ public final class Bitmap_Delegate {
boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
// and create/return a new Bitmap with it
- return new Bitmap(nativeInt, null /* buffer */, width, height, density, isMutable,
+ return new Bitmap(nativeInt, width, height, density, isMutable,
isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index fa880f0710c4..43a0ff5a23dc 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -55,40 +55,27 @@ import libcore.util.NativeAllocationRegistry_Delegate;
* @see DelegateManager
*
*/
-public final class Canvas_Delegate {
+public final class Canvas_Delegate extends BaseCanvas_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 ----
-
- private final static boolean[] sBoolOut = new boolean[1];
-
-
- // ---- delegate data ----
- private Bitmap_Delegate mBitmap;
- private GcSnapshot mSnapshot;
-
private DrawFilter_Delegate mDrawFilter = null;
-
// ---- Public Helper methods ----
/**
* Returns the native delegate associated to a given {@link Canvas} object.
*/
public static Canvas_Delegate getDelegate(Canvas canvas) {
- return sManager.getDelegate(canvas.getNativeCanvasWrapper());
+ return (Canvas_Delegate) sManager.getDelegate(canvas.getNativeCanvasWrapper());
}
/**
* Returns the native delegate associated to a given an int referencing a {@link Canvas} object.
*/
public static Canvas_Delegate getDelegate(long native_canvas) {
- return sManager.getDelegate(native_canvas);
+ return (Canvas_Delegate) sManager.getDelegate(native_canvas);
}
/**
@@ -110,20 +97,20 @@ public final class Canvas_Delegate {
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static void freeCaches() {
+ /*package*/ static void nFreeCaches() {
// nothing to be done here.
}
@LayoutlibDelegate
- /*package*/ static void freeTextLayoutCaches() {
+ /*package*/ static void nFreeTextLayoutCaches() {
// nothing to be done here yet.
}
@LayoutlibDelegate
- /*package*/ static long initRaster(@Nullable Bitmap bitmap) {
+ /*package*/ static long nInitRaster(@Nullable Bitmap bitmap) {
long nativeBitmapOrZero = 0;
if (bitmap != null) {
- nativeBitmapOrZero = bitmap.refSkPixelRef();
+ nativeBitmapOrZero = bitmap.getNativeInstance();
}
if (nativeBitmapOrZero > 0) {
// get the Bitmap from the int
@@ -142,8 +129,8 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_setBitmap(long canvas, Bitmap bitmap) {
- Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
+ public static void nSetBitmap(long canvas, Bitmap bitmap) {
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
if (canvasDelegate == null || bitmapDelegate==null) {
return;
@@ -153,9 +140,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static boolean native_isOpaque(long nativeCanvas) {
+ public static boolean nIsOpaque(long nativeCanvas) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return false;
}
@@ -164,12 +151,12 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_setHighContrastText(long nativeCanvas, boolean highContrastText){}
+ public static void nSetHighContrastText(long nativeCanvas, boolean highContrastText){}
@LayoutlibDelegate
- public static int native_getWidth(long nativeCanvas) {
+ public static int nGetWidth(long nativeCanvas) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
@@ -178,9 +165,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static int native_getHeight(long nativeCanvas) {
+ public static int nGetHeight(long nativeCanvas) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
@@ -189,9 +176,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static int native_save(long nativeCanvas, int saveFlags) {
+ public static int nSave(long nativeCanvas, int saveFlags) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
@@ -200,11 +187,11 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static int native_saveLayer(long nativeCanvas, float l,
+ public static int nSaveLayer(long nativeCanvas, float l,
float t, float r, float b,
long paint, int layerFlags) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
@@ -219,11 +206,11 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static int native_saveLayerAlpha(long nativeCanvas, float l,
+ public static int nSaveLayerAlpha(long nativeCanvas, float l,
float t, float r, float b,
int alpha, int layerFlags) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
@@ -232,10 +219,10 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_restore(long nativeCanvas, boolean throwOnUnderflow) {
+ public static void nRestore(long nativeCanvas, boolean throwOnUnderflow) {
// FIXME: implement throwOnUnderflow.
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
@@ -244,11 +231,11 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_restoreToCount(long nativeCanvas, int saveCount,
+ public static void nRestoreToCount(long nativeCanvas, int saveCount,
boolean throwOnUnderflow) {
// FIXME: implement throwOnUnderflow.
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
@@ -257,9 +244,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static int native_getSaveCount(long nativeCanvas) {
+ public static int nGetSaveCount(long nativeCanvas) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
@@ -268,9 +255,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_translate(long nativeCanvas, float dx, float dy) {
+ public static void nTranslate(long nativeCanvas, float dx, float dy) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
@@ -279,9 +266,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_scale(long nativeCanvas, float sx, float sy) {
+ public static void nScale(long nativeCanvas, float sx, float sy) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
@@ -290,9 +277,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_rotate(long nativeCanvas, float degrees) {
+ public static void nRotate(long nativeCanvas, float degrees) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
@@ -301,9 +288,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_skew(long nativeCanvas, float kx, float ky) {
+ public static void nSkew(long nativeCanvas, float kx, float ky) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
@@ -325,9 +312,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_concat(long nCanvas, long nMatrix) {
+ public static void nConcat(long nCanvas, long nMatrix) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
if (canvasDelegate == null) {
return;
}
@@ -353,9 +340,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_setMatrix(long nCanvas, long nMatrix) {
+ public static void nSetMatrix(long nCanvas, long nMatrix) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
if (canvasDelegate == null) {
return;
}
@@ -383,12 +370,12 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static boolean native_clipRect(long nCanvas,
+ public static boolean nClipRect(long nCanvas,
float left, float top,
float right, float bottom,
int regionOp) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
if (canvasDelegate == null) {
return false;
}
@@ -397,10 +384,10 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static boolean native_clipPath(long nativeCanvas,
+ public static boolean nClipPath(long nativeCanvas,
long nativePath,
int regionOp) {
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return true;
}
@@ -414,10 +401,10 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static boolean native_clipRegion(long nativeCanvas,
+ public static boolean nClipRegion(long nativeCanvas,
long nativeRegion,
int regionOp) {
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return true;
}
@@ -431,8 +418,8 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void nativeSetDrawFilter(long nativeCanvas, long nativeFilter) {
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
@@ -446,10 +433,10 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static boolean native_getClipBounds(long nativeCanvas,
+ public static boolean nGetClipBounds(long nativeCanvas,
Rect bounds) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return false;
}
@@ -467,9 +454,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static void native_getCTM(long canvas, long matrix) {
+ public static void nGetCTM(long canvas, long matrix) {
// get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
if (canvasDelegate == null) {
return;
}
@@ -484,13 +471,13 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- public static boolean native_quickReject(long nativeCanvas, long path) {
+ public static boolean nQuickReject(long nativeCanvas, long path) {
// FIXME properly implement quickReject
return false;
}
@LayoutlibDelegate
- public static boolean native_quickReject(long nativeCanvas,
+ public static boolean nQuickReject(long nativeCanvas,
float left, float top,
float right, float bottom) {
// FIXME properly implement quickReject
@@ -498,509 +485,11 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- 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) {
- return;
- }
-
- final int w = canvasDelegate.mBitmap.getImage().getWidth();
- final int h = canvasDelegate.mBitmap.getImage().getHeight();
- draw(nativeCanvas, new GcSnapshot.Drawable() {
-
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paint) {
- // reset its transform just in case
- graphics.setTransform(new AffineTransform());
-
- // set the color
- graphics.setColor(new Color(color, true /*alpha*/));
-
- Composite composite = PorterDuffUtility.getComposite(
- PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
- if (composite != null) {
- graphics.setComposite(composite);
- }
-
- graphics.fillRect(0, 0, w, h);
- }
- });
- }
-
- @LayoutlibDelegate
- 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
- public static void native_drawPoint(long nativeCanvas, float x, float y,
- long nativePaint) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawPoint is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- public static void native_drawPoints(long nativeCanvas, float[] pts, int offset, int count,
- long nativePaint) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawPoint is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- 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*/,
- new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
- graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
- }
- });
- }
-
- @LayoutlibDelegate
- public static void native_drawLines(long nativeCanvas,
- final float[] pts, final int offset, final int count,
- long nativePaint) {
- draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
- false /*forceSrcMode*/, new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
- for (int i = 0; i < count; i += 4) {
- graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1],
- (int) pts[i + offset + 2], (int) pts[i + offset + 3]);
- }
- }
- });
- }
-
- @LayoutlibDelegate
- 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*/,
- new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
- int style = paintDelegate.getStyle();
-
- // draw
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fillRect((int)left, (int)top,
- (int)(right-left), (int)(bottom-top));
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.drawRect((int)left, (int)top,
- (int)(right-left), (int)(bottom-top));
- }
- }
- });
- }
-
- @LayoutlibDelegate
- 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*/,
- new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
- int style = paintDelegate.getStyle();
-
- // draw
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fillOval((int)left, (int)top,
- (int)(right - left), (int)(bottom - top));
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.drawOval((int)left, (int)top,
- (int)(right - left), (int)(bottom - top));
- }
- }
- });
- }
- }
-
- @LayoutlibDelegate
- 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,
- paint);
- }
-
- @LayoutlibDelegate
- 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) {
- if (right > left && bottom > top) {
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
- int style = paintDelegate.getStyle();
-
- Arc2D.Float arc = new Arc2D.Float(
- left, top, right - left, bottom - top,
- -startAngle, -sweep,
- useCenter ? Arc2D.PIE : Arc2D.OPEN);
-
- // draw
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fill(arc);
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.draw(arc);
- }
- }
- });
- }
- }
-
- @LayoutlibDelegate
- 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*/,
- new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
- int style = paintDelegate.getStyle();
-
- // draw
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fillRoundRect(
- (int)left, (int)top,
- (int)(right - left), (int)(bottom - top),
- 2 * (int)rx, 2 * (int)ry);
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.drawRoundRect(
- (int)left, (int)top,
- (int)(right - left), (int)(bottom - top),
- 2 * (int)rx, 2 * (int)ry);
- }
- }
- });
- }
-
- @LayoutlibDelegate
- public static void native_drawPath(long nativeCanvas, long path, long paint) {
- final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
- if (pathDelegate == null) {
- return;
- }
-
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
- Shape shape = pathDelegate.getJavaShape();
- Rectangle2D bounds = shape.getBounds2D();
- if (bounds.isEmpty()) {
- // Apple JRE 1.6 doesn't like drawing empty shapes.
- // http://b.android.com/178278
-
- if (pathDelegate.isEmpty()) {
- // This means that the path doesn't have any lines or curves so
- // nothing to draw.
- return;
- }
-
- // The stroke width is not consider for the size of the bounds so,
- // for example, a horizontal line, would be considered as an empty
- // rectangle.
- // If the strokeWidth is not 0, we use it to consider the size of the
- // path as well.
- float strokeWidth = paintDelegate.getStrokeWidth();
- if (strokeWidth <= 0.0f) {
- return;
- }
- bounds.setRect(bounds.getX(), bounds.getY(),
- Math.max(strokeWidth, bounds.getWidth()),
- Math.max(strokeWidth, bounds.getHeight()));
- }
-
- int style = paintDelegate.getStyle();
-
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fill(shape);
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.draw(shape);
- }
- }
- });
- }
-
- @LayoutlibDelegate
- 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,
- int screenDensity,
- int bitmapDensity) {
- // get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
- if (bitmapDelegate == null) {
- return;
- }
-
- BufferedImage image = bitmapDelegate.getImage();
- float right = left + image.getWidth();
- float bottom = top + image.getHeight();
-
- drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
- 0, 0, image.getWidth(), image.getHeight(),
- (int)left, (int)top, (int)right, (int)bottom);
- }
-
- @LayoutlibDelegate
- 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) {
- // get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
- if (bitmapDelegate == null) {
- return;
- }
-
- drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
- (int)srcLeft, (int)srcTop, (int)srcRight, (int)srcBottom,
- (int)dstLeft, (int)dstTop, (int)dstRight, (int)dstBottom);
- }
-
- @LayoutlibDelegate
- 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,
- long nativePaintOrZero) {
- // create a temp BufferedImage containing the content.
- final BufferedImage image = new BufferedImage(width, height,
- hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
- image.setRGB(0, 0, width, height, colors, offset, stride);
-
- draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
- new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paint) {
- if (paint != null && paint.isFilterBitmap()) {
- graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- }
-
- graphics.drawImage(image, (int) x, (int) y, null);
- }
- });
- }
-
- @LayoutlibDelegate
- 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);
- if (canvasDelegate == null) {
- return;
- }
-
- // get the delegate from the native int, which can be null
- Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
-
- // get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
- if (bitmapDelegate == null) {
- return;
- }
-
- final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
-
- Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
- if (matrixDelegate == null) {
- return;
- }
-
- final AffineTransform mtx = matrixDelegate.getAffineTransform();
-
- canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paint) {
- if (paint != null && paint.isFilterBitmap()) {
- graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- }
-
- //FIXME add support for canvas, screen and bitmap densities.
- graphics.drawImage(image, mtx, null);
- }
- }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
- }
-
- @LayoutlibDelegate
- public static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap,
- int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
- int colorOffset, long nPaint) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- public static void nativeDrawVertices(long nCanvas, int mode, int n,
- float[] verts, int vertOffset,
- float[] texs, int texOffset,
- int[] colors, int colorOffset,
- short[] indices, int indexOffset,
- int indexCount, long nPaint) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawVertices is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- 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
- 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;
- char[] buffer = TemporaryBuffer.obtain(count);
- TextUtils.getChars(text, start, end, buffer, 0);
-
- native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
- }
-
- @LayoutlibDelegate
- 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;
- char[] buffer = TemporaryBuffer.obtain(count);
- TextUtils.getChars(text, start, end, buffer, 0);
-
- drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface);
- }
-
- @LayoutlibDelegate
- 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
- public static void native_drawTextOnPath(long nativeCanvas,
- char[] text, int index,
- int count, long path,
- float hOffset,
- float vOffset, int bidiFlags,
- long paint, long typeface) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- public static void native_drawTextOnPath(long nativeCanvas,
- String text, long path,
- float hOffset,
- float vOffset,
- int bidiFlags, long paint,
- long typeface) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static long getNativeFinalizer() {
+ /*package*/ static long nGetNativeFinalizer() {
synchronized (Canvas_Delegate.class) {
if (sFinalizer == -1) {
sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
- Canvas_Delegate delegate = sManager.getDelegate(nativePtr);
+ Canvas_Delegate delegate = Canvas_Delegate.getDelegate(nativePtr);
if (delegate != null) {
delegate.dispose();
}
@@ -1011,230 +500,12 @@ public final class Canvas_Delegate {
return sFinalizer;
}
- // ---- Private delegate/helper methods ----
-
- /**
- * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
- * <p>Note that the drawable may actually be executed several times if there are
- * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
- */
- private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode,
- GcSnapshot.Drawable drawable) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- // get the paint which can be null if nPaint is 0;
- Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
-
- canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
- }
-
- /**
- * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
- * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
- * <p>Note that the drawable may actually be executed several times if there are
- * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
- */
- private static void draw(long nCanvas, GcSnapshot.Drawable drawable) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- canvasDelegate.mSnapshot.draw(drawable);
- }
-
- private static void drawText(long nativeCanvas, final char[] text, final int index,
- final int count, final float startX, final float startY, final boolean isRtl,
- long paint, final long typeface) {
-
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
- // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
- // Any change to this method should be reflected in Paint.measureText
-
- // assert that the typeface passed is actually the one stored in paint.
- assert (typeface == paintDelegate.mNativeTypeface);
-
- // Paint.TextAlign indicates how the text is positioned relative to X.
- // LEFT is the default and there's nothing to do.
- float x = startX;
- int limit = index + count;
- if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
- RectF bounds = paintDelegate.measureText(text, index, count, null, 0,
- isRtl);
- float m = bounds.right - bounds.left;
- if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
- x -= m / 2;
- } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
- x -= m;
- }
- }
-
- new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, startY)
- .renderText(index, limit, isRtl, null, 0, true);
- }
- });
- }
-
private Canvas_Delegate(Bitmap_Delegate bitmap) {
- mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
+ super(bitmap);
}
private Canvas_Delegate() {
- mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
- }
-
- /**
- * Disposes of the {@link Graphics2D} stack.
- */
- private void dispose() {
- mSnapshot.dispose();
- }
-
- private int save(int saveFlags) {
- // get the current save count
- int count = mSnapshot.size();
-
- mSnapshot = mSnapshot.save(saveFlags);
-
- // return the old save count
- return count;
- }
-
- private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
- Paint_Delegate paint = new Paint_Delegate();
- paint.setAlpha(alpha);
- return saveLayer(rect, paint, saveFlags);
- }
-
- private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
- // get the current save count
- int count = mSnapshot.size();
-
- mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
-
- // return the old save count
- return count;
- }
-
- /**
- * Restores the {@link GcSnapshot} to <var>saveCount</var>
- * @param saveCount the saveCount
- */
- private void restoreTo(int saveCount) {
- mSnapshot = mSnapshot.restoreTo(saveCount);
- }
-
- /**
- * Restores the top {@link GcSnapshot}
- */
- private void restore() {
- mSnapshot = mSnapshot.restore();
- }
-
- private boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
- return mSnapshot.clipRect(left, top, right, bottom, regionOp);
- }
-
- private static void drawBitmap(
- long nativeCanvas,
- Bitmap_Delegate bitmap,
- long nativePaintOrZero,
- final int sleft, final int stop, final int sright, final int sbottom,
- final int dleft, final int dtop, final int dright, final int dbottom) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- // get the paint, which could be null if the int is 0
- Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
-
- final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
-
- draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
- new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paint) {
- if (paint != null && paint.isFilterBitmap()) {
- graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- }
-
- //FIXME add support for canvas, screen and bitmap densities.
- graphics.drawImage(image, dleft, dtop, dright, dbottom,
- sleft, stop, sright, sbottom, null);
- }
- });
- }
-
-
- /**
- * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
- * The image returns, through a 1-size boolean array, whether the drawing code should
- * use a SRC composite no matter what the paint says.
- *
- * @param bitmap the bitmap
- * @param paint the paint that will be used to draw
- * @param forceSrcMode whether the composite will have to be SRC
- * @return the image to draw
- */
- private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
- boolean[] forceSrcMode) {
- BufferedImage image = bitmap.getImage();
- forceSrcMode[0] = false;
-
- // if the bitmap config is alpha_8, then we erase all color value from it
- // before drawing it.
- if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
- fixAlpha8Bitmap(image);
- } else if (!bitmap.hasAlpha()) {
- // hasAlpha is merely a rendering hint. There can in fact be alpha values
- // in the bitmap but it should be ignored at drawing time.
- // There is two ways to do this:
- // - override the composite to be SRC. This can only be used if the composite
- // was going to be SRC or SRC_OVER in the first place
- // - Create a different bitmap to draw in which all the alpha channel values is set
- // to 0xFF.
- if (paint != null) {
- Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
- if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) {
- PorterDuff.Mode mode =
- ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode();
-
- forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER ||
- mode == PorterDuff.Mode.SRC;
- }
- }
-
- // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
- if (!forceSrcMode[0]) {
- image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
- }
- }
-
- return image;
- }
-
- private static void fixAlpha8Bitmap(final BufferedImage image) {
- int w = image.getWidth();
- int h = image.getHeight();
- int[] argb = new int[w * h];
- image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
-
- final int length = argb.length;
- for (int i = 0 ; i < length; i++) {
- argb[i] &= 0xFF000000;
- }
- image.setRGB(0, 0, w, h, argb, 0, w);
+ super();
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
index 59ddcc6a6ca8..a459734e805f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
@@ -63,16 +63,8 @@ public class ComposeShader_Delegate extends Shader_Delegate {
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long nativeCreate1(long native_shaderA, long native_shaderB,
- long native_mode) {
- // FIXME not supported yet.
- ComposeShader_Delegate newDelegate = new ComposeShader_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate2(long native_shaderA, long native_shaderB,
- int porterDuffMode) {
+ /*package*/ static long nativeCreate(long native_shaderA, long native_shaderB,
+ int native_mode) {
// FIXME not supported yet.
ComposeShader_Delegate newDelegate = new ComposeShader_Delegate();
return sManager.addNewDelegate(newDelegate);
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
index a503e50407ed..354f9191beda 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
@@ -27,6 +27,8 @@ import android.graphics.Matrix.ScaleToFit;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
/**
* Delegate implementing the native methods of android.graphics.Matrix
*
@@ -47,6 +49,7 @@ public final class Matrix_Delegate {
// ---- delegate manager ----
private static final DelegateManager<Matrix_Delegate> sManager =
new DelegateManager<Matrix_Delegate>(Matrix_Delegate.class);
+ private static long sFinalizer = -1;
// ---- delegate data ----
private float mValues[] = new float[MATRIX_SIZE];
@@ -174,7 +177,7 @@ public final class Matrix_Delegate {
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long native_create(long native_src_or_zero) {
+ /*package*/ static long nCreate(long native_src_or_zero) {
// create the delegate
Matrix_Delegate newDelegate = new Matrix_Delegate();
@@ -193,7 +196,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_isIdentity(long native_object) {
+ /*package*/ static boolean nIsIdentity(long native_object) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return false;
@@ -203,7 +206,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_isAffine(long native_object) {
+ /*package*/ static boolean nIsAffine(long native_object) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return true;
@@ -213,7 +216,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_rectStaysRect(long native_object) {
+ /*package*/ static boolean nRectStaysRect(long native_object) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return true;
@@ -223,7 +226,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_reset(long native_object) {
+ /*package*/ static void nReset(long native_object) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -233,7 +236,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_set(long native_object, long other) {
+ /*package*/ static void nSet(long native_object, long other) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -248,7 +251,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setTranslate(long native_object, float dx, float dy) {
+ /*package*/ static void nSetTranslate(long native_object, float dx, float dy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -258,7 +261,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setScale(long native_object, float sx, float sy,
+ /*package*/ static void nSetScale(long native_object, float sx, float sy,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
@@ -269,7 +272,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setScale(long native_object, float sx, float sy) {
+ /*package*/ static void nSetScale(long native_object, float sx, float sy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -287,7 +290,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setRotate(long native_object, float degrees, float px, float py) {
+ /*package*/ static void nSetRotate(long native_object, float degrees, float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -297,7 +300,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setRotate(long native_object, float degrees) {
+ /*package*/ static void nSetRotate(long native_object, float degrees) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -307,7 +310,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setSinCos(long native_object, float sinValue, float cosValue,
+ /*package*/ static void nSetSinCos(long native_object, float sinValue, float cosValue,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
@@ -326,7 +329,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setSinCos(long native_object, float sinValue, float cosValue) {
+ /*package*/ static void nSetSinCos(long native_object, float sinValue, float cosValue) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -336,7 +339,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setSkew(long native_object, float kx, float ky,
+ /*package*/ static void nSetSkew(long native_object, float kx, float ky,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
@@ -347,7 +350,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setSkew(long native_object, float kx, float ky) {
+ /*package*/ static void nSetSkew(long native_object, float kx, float ky) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -365,12 +368,12 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setConcat(long native_object, long a, long b) {
+ /*package*/ static void nSetConcat(long native_object, long a, long b) {
if (a == native_object) {
- native_preConcat(native_object, b);
+ nPreConcat(native_object, b);
return;
} else if (b == native_object) {
- native_postConcat(native_object, a);
+ nPostConcat(native_object, a);
return;
}
@@ -383,7 +386,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_preTranslate(long native_object, float dx, float dy) {
+ /*package*/ static void nPreTranslate(long native_object, float dx, float dy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
d.preTransform(getTranslate(dx, dy));
@@ -391,7 +394,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_preScale(long native_object, float sx, float sy,
+ /*package*/ static void nPreScale(long native_object, float sx, float sy,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
@@ -400,7 +403,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_preScale(long native_object, float sx, float sy) {
+ /*package*/ static void nPreScale(long native_object, float sx, float sy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
d.preTransform(getScale(sx, sy));
@@ -408,7 +411,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_preRotate(long native_object, float degrees,
+ /*package*/ static void nPreRotate(long native_object, float degrees,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
@@ -417,7 +420,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_preRotate(long native_object, float degrees) {
+ /*package*/ static void nPreRotate(long native_object, float degrees) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
@@ -430,7 +433,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_preSkew(long native_object, float kx, float ky,
+ /*package*/ static void nPreSkew(long native_object, float kx, float ky,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
@@ -439,7 +442,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_preSkew(long native_object, float kx, float ky) {
+ /*package*/ static void nPreSkew(long native_object, float kx, float ky) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
d.preTransform(getSkew(kx, ky));
@@ -447,7 +450,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_preConcat(long native_object, long other_matrix) {
+ /*package*/ static void nPreConcat(long native_object, long other_matrix) {
Matrix_Delegate d = sManager.getDelegate(native_object);
Matrix_Delegate other = sManager.getDelegate(other_matrix);
if (d != null && other != null) {
@@ -456,7 +459,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_postTranslate(long native_object, float dx, float dy) {
+ /*package*/ static void nPostTranslate(long native_object, float dx, float dy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
d.postTransform(getTranslate(dx, dy));
@@ -464,7 +467,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_postScale(long native_object, float sx, float sy,
+ /*package*/ static void nPostScale(long native_object, float sx, float sy,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
@@ -473,7 +476,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_postScale(long native_object, float sx, float sy) {
+ /*package*/ static void nPostScale(long native_object, float sx, float sy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
d.postTransform(getScale(sx, sy));
@@ -481,7 +484,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_postRotate(long native_object, float degrees,
+ /*package*/ static void nPostRotate(long native_object, float degrees,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
@@ -490,7 +493,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_postRotate(long native_object, float degrees) {
+ /*package*/ static void nPostRotate(long native_object, float degrees) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
d.postTransform(getRotate(degrees));
@@ -498,7 +501,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_postSkew(long native_object, float kx, float ky,
+ /*package*/ static void nPostSkew(long native_object, float kx, float ky,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
@@ -507,7 +510,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_postSkew(long native_object, float kx, float ky) {
+ /*package*/ static void nPostSkew(long native_object, float kx, float ky) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d != null) {
d.postTransform(getSkew(kx, ky));
@@ -515,7 +518,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_postConcat(long native_object, long other_matrix) {
+ /*package*/ static void nPostConcat(long native_object, long other_matrix) {
Matrix_Delegate d = sManager.getDelegate(native_object);
Matrix_Delegate other = sManager.getDelegate(other_matrix);
if (d != null && other != null) {
@@ -524,7 +527,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_setRectToRect(long native_object, RectF src,
+ /*package*/ static boolean nSetRectToRect(long native_object, RectF src,
RectF dst, int stf) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
@@ -589,7 +592,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_setPolyToPoly(long native_object, float[] src, int srcIndex,
+ /*package*/ static boolean nSetPolyToPoly(long native_object, float[] src, int srcIndex,
float[] dst, int dstIndex, int pointCount) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -599,7 +602,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_invert(long native_object, long inverse) {
+ /*package*/ static boolean nInvert(long native_object, long inverse) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return false;
@@ -627,7 +630,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_mapPoints(long native_object, float[] dst, int dstIndex,
+ /*package*/ static void nMapPoints(long native_object, float[] dst, int dstIndex,
float[] src, int srcIndex, int ptCount, boolean isPts) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
@@ -642,7 +645,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_mapRect(long native_object, RectF dst, RectF src) {
+ /*package*/ static boolean nMapRect(long native_object, RectF dst, RectF src) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return false;
@@ -652,7 +655,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float native_mapRadius(long native_object, float radius) {
+ /*package*/ static float nMapRadius(long native_object, float radius) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return 0.f;
@@ -667,7 +670,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_getValues(long native_object, float[] values) {
+ /*package*/ static void nGetValues(long native_object, float[] values) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -677,7 +680,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_setValues(long native_object, float[] values) {
+ /*package*/ static void nSetValues(long native_object, float[] values) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return;
@@ -687,7 +690,7 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean native_equals(long native_a, long native_b) {
+ /*package*/ static boolean nEquals(long native_a, long native_b) {
Matrix_Delegate a = sManager.getDelegate(native_a);
if (a == null) {
return false;
@@ -708,8 +711,13 @@ public final class Matrix_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void finalizer(long native_instance) {
- sManager.removeJavaReferenceFor(native_instance);
+ /*package*/ static long nGetNativeFinalizer() {
+ synchronized (Matrix_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
}
// ---- Private helper methods ----
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index 33296e1abdc9..e68d8b3e9f93 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -90,10 +90,11 @@ public class Paint_Delegate {
private int mHintingMode = Paint.HINTING_ON;
private int mHyphenEdit;
private float mLetterSpacing; // not used in actual text rendering.
+ private float mWordSpacing; // not used in actual text rendering.
// Variant of the font. A paint's variant can only be compact or elegant.
private FontVariant mFontVariant = FontVariant.COMPACT;
- private Xfermode_Delegate mXfermode;
+ private int mPorterDuffMode = Xfermode.DEFAULT;
private ColorFilter_Delegate mColorFilter;
private Shader_Delegate mShader;
private PathEffect_Delegate mPathEffect;
@@ -206,12 +207,10 @@ public class Paint_Delegate {
}
/**
- * Returns the {@link Xfermode} delegate or null if none have been set
- *
- * @return the delegate or null.
+ * Returns the {@link PorterDuff.Mode} as an int
*/
- public Xfermode_Delegate getXfermode() {
- return mXfermode;
+ public int getPorterDuffMode() {
+ return mPorterDuffMode;
}
/**
@@ -261,7 +260,7 @@ public class Paint_Delegate {
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static int nGetFlags(Paint thisPaint, long nativePaint) {
+ /*package*/ static int nGetFlags(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -274,7 +273,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
- /*package*/ static void nSetFlags(Paint thisPaint, long nativePaint, int flags) {
+ /*package*/ static void nSetFlags(long nativePaint, int flags) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -285,12 +284,12 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetFilterBitmap(Paint thisPaint, long nativePaint, boolean filter) {
+ /*package*/ static void nSetFilterBitmap(long nativePaint, boolean filter) {
setFlag(nativePaint, Paint.FILTER_BITMAP_FLAG, filter);
}
@LayoutlibDelegate
- /*package*/ static int nGetHinting(Paint thisPaint, long nativePaint) {
+ /*package*/ static int nGetHinting(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -301,7 +300,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetHinting(Paint thisPaint, long nativePaint, int mode) {
+ /*package*/ static void nSetHinting(long nativePaint, int mode) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -312,46 +311,46 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetAntiAlias(Paint thisPaint, long nativePaint, boolean aa) {
+ /*package*/ static void nSetAntiAlias(long nativePaint, boolean aa) {
setFlag(nativePaint, Paint.ANTI_ALIAS_FLAG, aa);
}
@LayoutlibDelegate
- /*package*/ static void nSetSubpixelText(Paint thisPaint, long nativePaint,
+ /*package*/ static void nSetSubpixelText(long nativePaint,
boolean subpixelText) {
setFlag(nativePaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
}
@LayoutlibDelegate
- /*package*/ static void nSetUnderlineText(Paint thisPaint, long nativePaint,
+ /*package*/ static void nSetUnderlineText(long nativePaint,
boolean underlineText) {
setFlag(nativePaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
}
@LayoutlibDelegate
- /*package*/ static void nSetStrikeThruText(Paint thisPaint, long nativePaint,
+ /*package*/ static void nSetStrikeThruText(long nativePaint,
boolean strikeThruText) {
setFlag(nativePaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
}
@LayoutlibDelegate
- /*package*/ static void nSetFakeBoldText(Paint thisPaint, long nativePaint,
+ /*package*/ static void nSetFakeBoldText(long nativePaint,
boolean fakeBoldText) {
setFlag(nativePaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
}
@LayoutlibDelegate
- /*package*/ static void nSetDither(Paint thisPaint, long nativePaint, boolean dither) {
+ /*package*/ static void nSetDither(long nativePaint, boolean dither) {
setFlag(nativePaint, Paint.DITHER_FLAG, dither);
}
@LayoutlibDelegate
- /*package*/ static void nSetLinearText(Paint thisPaint, long nativePaint, boolean linearText) {
+ /*package*/ static void nSetLinearText(long nativePaint, boolean linearText) {
setFlag(nativePaint, Paint.LINEAR_TEXT_FLAG, linearText);
}
@LayoutlibDelegate
- /*package*/ static int nGetColor(Paint thisPaint, long nativePaint) {
+ /*package*/ static int nGetColor(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -362,7 +361,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetColor(Paint thisPaint, long nativePaint, int color) {
+ /*package*/ static void nSetColor(long nativePaint, int color) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -373,7 +372,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int nGetAlpha(Paint thisPaint, long nativePaint) {
+ /*package*/ static int nGetAlpha(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -384,7 +383,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetAlpha(Paint thisPaint, long nativePaint, int a) {
+ /*package*/ static void nSetAlpha(long nativePaint, int a) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -395,7 +394,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float nGetStrokeWidth(Paint thisPaint, long nativePaint) {
+ /*package*/ static float nGetStrokeWidth(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -406,7 +405,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetStrokeWidth(Paint thisPaint, long nativePaint, float width) {
+ /*package*/ static void nSetStrokeWidth(long nativePaint, float width) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -417,7 +416,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float nGetStrokeMiter(Paint thisPaint, long nativePaint) {
+ /*package*/ static float nGetStrokeMiter(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -428,7 +427,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetStrokeMiter(Paint thisPaint, long nativePaint, float miter) {
+ /*package*/ static void nSetStrokeMiter(long nativePaint, float miter) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -455,14 +454,14 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static boolean nIsElegantTextHeight(Paint thisPaint, long nativePaint) {
+ /*package*/ static boolean nIsElegantTextHeight(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT;
}
@LayoutlibDelegate
- /*package*/ static void nSetElegantTextHeight(Paint thisPaint, long nativePaint,
+ /*package*/ static void nSetElegantTextHeight(long nativePaint,
boolean elegant) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -474,7 +473,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float nGetTextSize(Paint thisPaint, long nativePaint) {
+ /*package*/ static float nGetTextSize(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -485,7 +484,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetTextSize(Paint thisPaint, long nativePaint, float textSize) {
+ /*package*/ static void nSetTextSize(long nativePaint, float textSize) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -499,7 +498,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float nGetTextScaleX(Paint thisPaint, long nativePaint) {
+ /*package*/ static float nGetTextScaleX(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -510,7 +509,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetTextScaleX(Paint thisPaint, long nativePaint, float scaleX) {
+ /*package*/ static void nSetTextScaleX(long nativePaint, float scaleX) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -524,7 +523,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float nGetTextSkewX(Paint thisPaint, long nativePaint) {
+ /*package*/ static float nGetTextSkewX(long nativePaint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -535,7 +534,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetTextSkewX(Paint thisPaint, long nativePaint, float skewX) {
+ /*package*/ static void nSetTextSkewX(long nativePaint, float skewX) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -549,7 +548,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float nAscent(Paint thisPaint, long nativePaint, long nativeTypeface) {
+ /*package*/ static float nAscent(long nativePaint, long nativeTypeface) {
// get the delegate
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -566,7 +565,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float nDescent(Paint thisPaint, long nativePaint, long nativeTypeface) {
+ /*package*/ static float nDescent(long nativePaint, long nativeTypeface) {
// get the delegate
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -583,7 +582,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float nGetFontMetrics(Paint thisPaint, long nativePaint, long nativeTypeface,
+ /*package*/ static float nGetFontMetrics(long nativePaint, long nativeTypeface,
FontMetrics metrics) {
// get the delegate
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -595,7 +594,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int nGetFontMetricsInt(Paint thisPaint, long nativePaint,
+ /*package*/ static int nGetFontMetricsInt(long nativePaint,
long nativeTypeface, FontMetricsInt fmi) {
// get the delegate
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -841,16 +840,12 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long nSetXfermode(long native_object, long xfermode) {
- // get the delegate from the native int.
+ /*package*/ static void nSetXfermode(long native_object, int xfermode) {
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
- return xfermode;
+ return;
}
-
- delegate.mXfermode = Xfermode_Delegate.getDelegate(xfermode);
-
- return xfermode;
+ delegate.mPorterDuffMode = xfermode;
}
@LayoutlibDelegate
@@ -998,7 +993,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, char[] text,
+ /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, char[] text,
int contextStart, int contextLength, int flags, int offset, int cursorOpt) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1007,7 +1002,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, String text,
+ /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, String text,
int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1086,6 +1081,26 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
+ /*package*/ static float nGetWordSpacing(long nativePaint) {
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+ return delegate.mWordSpacing;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetWordSpacing(long nativePaint, float wordSpacing) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
+ "Paint.setWordSpacing() not supported.", null, null);
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null) {
+ return;
+ }
+ delegate.mWordSpacing = wordSpacing;
+ }
+
+ @LayoutlibDelegate
/*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
"Paint.setFontFeatureSettings() not supported.", null, null);
@@ -1215,7 +1230,7 @@ public class Paint_Delegate {
mStrokeWidth = paint.mStrokeWidth;
mStrokeMiter = paint.mStrokeMiter;
- mXfermode = paint.mXfermode;
+ mPorterDuffMode = paint.mPorterDuffMode;
mColorFilter = paint.mColorFilter;
mShader = paint.mShader;
mPathEffect = paint.mPathEffect;
@@ -1242,7 +1257,7 @@ public class Paint_Delegate {
mTextSize = 20.f;
mTextScaleX = 1.f;
mTextSkewX = 0.f;
- mXfermode = null;
+ mPorterDuffMode = Xfermode.DEFAULT;
mColorFilter = null;
mShader = null;
mPathEffect = null;
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index 265ebd1755e3..219c487cac53 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -86,6 +86,8 @@ public final class Path_Delegate {
public void reset() {
mPath.reset();
+ mLastX = 0;
+ mLastY = 0;
}
public void setPathIterator(PathIterator iterator) {
@@ -124,7 +126,7 @@ public final class Path_Delegate {
return;
}
- pathDelegate.mPath.reset();
+ pathDelegate.reset();
}
@LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
deleted file mode 100644
index 8825f84995c8..000000000000
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
+++ /dev/null
@@ -1,87 +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.layoutlib.bridge.impl.PorterDuffUtility;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.PorterDuff.Mode;
-
-import java.awt.Composite;
-
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
-
-/**
- * Delegate implementing the native methods of android.graphics.PorterDuffXfermode
- *
- * Through the layoutlib_create tool, the original native methods of PorterDuffXfermode 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 PorterDuffXfermode 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 PorterDuffXfermode_Delegate extends Xfermode_Delegate {
-
- // ---- delegate data ----
-
- private final Mode mMode;
-
- // ---- Public Helper methods ----
-
- public Mode getMode() {
- return mMode;
- }
-
- @Override
- public Composite getComposite(int alpha) {
- return PorterDuffUtility.getComposite(mMode, alpha);
- }
-
- @Override
- public boolean isSupported() {
- return true;
- }
-
- @Override
- public String getSupportMessage() {
- // no message since isSupported returns true;
- return null;
- }
-
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreateXfermode(int mode) {
- PorterDuffXfermode_Delegate newDelegate = new PorterDuffXfermode_Delegate(mode);
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-
- private PorterDuffXfermode_Delegate(int mode) {
- mMode = getPorterDuffMode(mode);
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java
deleted file mode 100644
index 94a6d76fe8dc..000000000000
--- a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java
+++ /dev/null
@@ -1,69 +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.Xfermode
- *
- * Through the layoutlib_create tool, the original native methods of Xfermode 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 Xfermode class.
- *
- * This also serve as a base class for all Xfermode delegate classes.
- *
- * @see DelegateManager
- *
- */
-public abstract class Xfermode_Delegate {
-
- // ---- delegate manager ----
- protected static final DelegateManager<Xfermode_Delegate> sManager =
- new DelegateManager<Xfermode_Delegate>(Xfermode_Delegate.class);
-
- // ---- delegate helper data ----
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- public static Xfermode_Delegate getDelegate(long native_instance) {
- return sManager.getDelegate(native_instance);
- }
-
- public abstract Composite getComposite(int alpha);
- public abstract boolean isSupported();
- public abstract String getSupportMessage();
-
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static void finalizer(long native_instance) {
- sManager.removeJavaReferenceFor(native_instance);
- }
-
- // ---- Private delegate/helper methods ----
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
index 200fe3b1d192..ad2c5647eef2 100644
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
@@ -58,8 +58,13 @@ public class AnimatedVectorDrawable_Delegate {
}
@LayoutlibDelegate
+ /*package*/ static void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr) {
+ // TODO: implement
+ }
+ @LayoutlibDelegate
/*package*/ static void nAddAnimator(long setPtr, long propertyValuesHolder,
- long nativeInterpolator, long startDelay, long duration, int repeatCount) {
+ long nativeInterpolator, long startDelay, long duration, int repeatCount,
+ int repeatMode) {
PropertySetter holder = sHolders.getDelegate(propertyValuesHolder);
if (holder == null || holder.getValues() == null) {
return;
@@ -72,6 +77,7 @@ public class AnimatedVectorDrawable_Delegate {
animator.setStartDelay(startDelay);
animator.setDuration(duration);
animator.setRepeatCount(repeatCount);
+ animator.setRepeatMode(repeatMode);
animator.setTarget(holder);
animator.setPropertyName(holder.getValues().getPropertyName());
@@ -137,6 +143,14 @@ public class AnimatedVectorDrawable_Delegate {
}
@LayoutlibDelegate
+ /*package*/ static void nSetPropertyHolderData(long nativePtr, int[] 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;
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
index 3d78931a152c..fc848d9af956 100644
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
@@ -18,6 +18,7 @@ package android.graphics.drawable;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.graphics.Canvas;
import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate {
@@ -25,4 +26,9 @@ public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate {
/*package*/ static boolean useLastSeenTarget(VectorDrawableAnimatorRT thisDrawableAnimator) {
return true;
}
+
+ @LayoutlibDelegate
+ /*package*/ static void onDraw(VectorDrawableAnimatorRT thisDrawableAnimator, Canvas canvas) {
+ // Do not attempt to record as we are not using a DisplayListCanvas
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
index 49f8691986be..cee679a1d469 100644
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.BaseCanvas_Delegate;
import android.graphics.Canvas_Delegate;
import android.graphics.Color;
import android.graphics.Matrix;
@@ -70,6 +71,13 @@ public class VectorDrawable_Delegate {
private static final DelegateManager<VNativeObject> sPathManager =
new DelegateManager<>(VNativeObject.class);
+ private static long addNativeObject(VNativeObject object) {
+ long ptr = sPathManager.addNewDelegate(object);
+ object.setNativePtr(ptr);
+
+ return ptr;
+ }
+
/**
* Obtains styled attributes from the theme, if available, or unstyled resources if the theme is
* null.
@@ -91,8 +99,14 @@ public class VectorDrawable_Delegate {
@LayoutlibDelegate
static long nCreateTree(long rootGroupPtr) {
- VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr);
- return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroup));
+ return addNativeObject(new VPathRenderer_Delegate(rootGroupPtr));
+ }
+
+ @LayoutlibDelegate
+ static long nCreateTreeFromCopy(long rendererToCopyPtr, long rootGroupPtr) {
+ VPathRenderer_Delegate rendererToCopy = VNativeObject.getDelegate(rendererToCopyPtr);
+ return addNativeObject(new VPathRenderer_Delegate(rendererToCopy,
+ rootGroupPtr));
}
@LayoutlibDelegate
@@ -128,12 +142,12 @@ public class VectorDrawable_Delegate {
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);
+ Canvas_Delegate.nSave(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+ Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.left, bounds.top);
if (needsMirroring) {
- Canvas_Delegate.native_translate(canvasWrapperPtr, bounds.width(), 0);
- Canvas_Delegate.native_scale(canvasWrapperPtr, -1.0f, 1.0f);
+ Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.width(), 0);
+ Canvas_Delegate.nScale(canvasWrapperPtr, -1.0f, 1.0f);
}
// At this point, canvas has been translated to the right position.
@@ -142,21 +156,20 @@ public class VectorDrawable_Delegate {
bounds.offsetTo(0, 0);
nativePathRenderer.draw(canvasWrapperPtr, colorFilterPtr, bounds.width(), bounds.height());
- Canvas_Delegate.native_restore(canvasWrapperPtr, true);
+ Canvas_Delegate.nRestore(canvasWrapperPtr, true);
return bounds.width() * bounds.height();
}
@LayoutlibDelegate
static long nCreateFullPath() {
- return sPathManager.addNewDelegate(new VFullPath_Delegate());
+ return addNativeObject(new VFullPath_Delegate());
}
@LayoutlibDelegate
static long nCreateFullPath(long nativeFullPathPtr) {
VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr);
-
- return sPathManager.addNewDelegate(new VFullPath_Delegate(original));
+ return addNativeObject(new VFullPath_Delegate(original));
}
@LayoutlibDelegate
@@ -222,25 +235,24 @@ public class VectorDrawable_Delegate {
@LayoutlibDelegate
static long nCreateClipPath() {
- return sPathManager.addNewDelegate(new VClipPath_Delegate());
+ return addNativeObject(new VClipPath_Delegate());
}
@LayoutlibDelegate
static long nCreateClipPath(long clipPathPtr) {
VClipPath_Delegate original = VNativeObject.getDelegate(clipPathPtr);
- return sPathManager.addNewDelegate(new VClipPath_Delegate(original));
+ return addNativeObject(new VClipPath_Delegate(original));
}
@LayoutlibDelegate
static long nCreateGroup() {
- return sPathManager.addNewDelegate(new VGroup_Delegate());
+ return addNativeObject(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>()));
+ return addNativeObject(new VGroup_Delegate(original, new ArrayMap<>()));
}
@LayoutlibDelegate
@@ -493,7 +505,9 @@ public class VectorDrawable_Delegate {
* not need it
* </ol>
*/
- interface VNativeObject {
+ abstract static class VNativeObject {
+ long mNativePtr = 0;
+
@NonNull
static <T> T getDelegate(long nativePtr) {
//noinspection unchecked
@@ -503,7 +517,17 @@ public class VectorDrawable_Delegate {
return vNativeObject;
}
- void setName(String name);
+ abstract void setName(String name);
+
+ void setNativePtr(long nativePtr) {
+ mNativePtr = nativePtr;
+ }
+
+ /**
+ * Method to explicitly dispose native objects
+ */
+ void dispose() {
+ }
}
private static class VClipPath_Delegate extends VPath_Delegate {
@@ -773,7 +797,7 @@ public class VectorDrawable_Delegate {
}
}
- static class VGroup_Delegate implements VNativeObject {
+ static class VGroup_Delegate extends 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;
@@ -959,9 +983,28 @@ public class VectorDrawable_Delegate {
public void setName(String name) {
mGroupName = name;
}
+
+ @Override
+ protected void dispose() {
+ mChildren.stream().filter(child -> child instanceof VNativeObject).forEach(child
+ -> {
+ VNativeObject nativeObject = (VNativeObject) child;
+ if (nativeObject.mNativePtr != 0) {
+ sPathManager.removeJavaReferenceFor(nativeObject.mNativePtr);
+ nativeObject.mNativePtr = 0;
+ }
+ nativeObject.dispose();
+ });
+ mChildren.clear();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ }
}
- public static class VPath_Delegate implements VNativeObject {
+ public static class VPath_Delegate extends VNativeObject {
protected PathParser_Delegate.PathDataNode[] mNodes = null;
String mPathName;
int mChangingConfigurations;
@@ -973,7 +1016,7 @@ public class VectorDrawable_Delegate {
public VPath_Delegate(VPath_Delegate copy) {
mPathName = copy.mPathName;
mChangingConfigurations = copy.mChangingConfigurations;
- mNodes = PathParser_Delegate.deepCopyNodes(copy.mNodes);
+ mNodes = copy.mNodes != null ? PathParser_Delegate.deepCopyNodes(copy.mNodes) : null;
}
public void toPath(Path path) {
@@ -1001,9 +1044,14 @@ public class VectorDrawable_Delegate {
PathParser_Delegate.updateNodes(mNodes, nodes);
}
}
+
+ @Override
+ void dispose() {
+ mNodes = null;
+ }
}
- static class VPathRenderer_Delegate implements VNativeObject {
+ static class VPathRenderer_Delegate extends 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
@@ -1021,7 +1069,7 @@ public class VectorDrawable_Delegate {
private final Path mPath;
private final Path mRenderPath;
private final Matrix mFinalPathMatrix = new Matrix();
- private final VGroup_Delegate mRootGroup;
+ private final long mRootGroupPtr;
private float mViewportWidth = 0;
private float mViewportHeight = 0;
private float mRootAlpha = 1.0f;
@@ -1029,12 +1077,20 @@ public class VectorDrawable_Delegate {
private Paint mFillPaint;
private PathMeasure mPathMeasure;
- private VPathRenderer_Delegate(VGroup_Delegate rootGroup) {
- mRootGroup = rootGroup;
+ private VPathRenderer_Delegate(long rootGroupPtr) {
+ mRootGroupPtr = rootGroupPtr;
mPath = new Path();
mRenderPath = new Path();
}
+ private VPathRenderer_Delegate(VPathRenderer_Delegate rendererToCopy,
+ long rootGroupPtr) {
+ this(rootGroupPtr);
+ mViewportWidth = rendererToCopy.mViewportWidth;
+ mViewportHeight = rendererToCopy.mViewportHeight;
+ mRootAlpha = rendererToCopy.mRootAlpha;
+ }
+
private float getRootAlpha() {
return mRootAlpha;
}
@@ -1053,7 +1109,7 @@ public class VectorDrawable_Delegate {
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);
+ Canvas_Delegate.nSave(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);
@@ -1066,12 +1122,12 @@ public class VectorDrawable_Delegate {
drawPath(currentGroup, childPath, canvasPtr, w, h, filterPtr);
}
}
- Canvas_Delegate.native_restore(canvasPtr, true);
+ Canvas_Delegate.nRestore(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);
+ drawGroupTree(VNativeObject.getDelegate(mRootGroupPtr), Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr);
}
private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr,
@@ -1098,7 +1154,7 @@ public class VectorDrawable_Delegate {
if (VPath.isClipPath()) {
mRenderPath.addPath(path, mFinalPathMatrix);
- Canvas_Delegate.native_clipPath(canvasPtr, mRenderPath.mNativePath, Op
+ Canvas_Delegate.nClipPath(canvasPtr, mRenderPath.mNativePath, Op
.INTERSECT.nativeInt);
} else {
VFullPath_Delegate fullPath = (VFullPath_Delegate) VPath;
@@ -1133,7 +1189,8 @@ public class VectorDrawable_Delegate {
}
final Paint fillPaint = mFillPaint;
- fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
+ fillPaint.setColor(applyAlpha(applyAlpha(fullPath.mFillColor, fullPath
+ .mFillAlpha), getRootAlpha()));
Paint_Delegate fillPaintDelegate = Paint_Delegate.getDelegate(fillPaint
.getNativeInstance());
// mFillPaint can not be null at this point so we will have a delegate
@@ -1141,7 +1198,7 @@ public class VectorDrawable_Delegate {
fillPaintDelegate.setColorFilter(filterPtr);
fillPaintDelegate.setShader(fullPath.mFillGradient);
Path_Delegate.native_setFillType(mRenderPath.mNativePath, fullPath.mFillType);
- Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
+ BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
.getNativeInstance());
}
@@ -1162,7 +1219,8 @@ public class VectorDrawable_Delegate {
}
strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
- strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
+ strokePaint.setColor(applyAlpha(applyAlpha(fullPath.mStrokeColor, fullPath
+ .mStrokeAlpha), getRootAlpha()));
Paint_Delegate strokePaintDelegate = Paint_Delegate.getDelegate(strokePaint
.getNativeInstance());
// mStrokePaint can not be null at this point so we will have a delegate
@@ -1171,7 +1229,7 @@ public class VectorDrawable_Delegate {
final float finalStrokeScale = minScale * matrixScale;
strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
strokePaintDelegate.setShader(fullPath.mStrokeGradient);
- Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
+ BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
.getNativeInstance());
}
}
@@ -1209,5 +1267,17 @@ public class VectorDrawable_Delegate {
@Override
public void setName(String name) {
}
+
+ @Override
+ protected void finalize() throws Throwable {
+ // The mRootGroupPtr is not explicitly freed by anything in the VectorDrawable so we
+ // need to free it here.
+ VNativeObject nativeObject = sPathManager.getDelegate(mRootGroupPtr);
+ sPathManager.removeJavaReferenceFor(mRootGroupPtr);
+ assert nativeObject != null;
+ nativeObject.dispose();
+
+ super.finalize();
+ }
}
}
diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
index 549074d15757..34c78455f85d 100644
--- a/tools/layoutlib/bridge/src/android/os/ServiceManager.java
+++ b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
@@ -31,6 +31,13 @@ public final class ServiceManager {
}
/**
+ * Is not supposed to return null, but that is fine for layoutlib.
+ */
+ public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
+ throw new ServiceNotFoundException(name);
+ }
+
+ /**
* Place a new @a service called @a name into the service
* manager.
*
@@ -71,4 +78,18 @@ public final class ServiceManager {
public static void initServiceCache(Map<String, IBinder> cache) {
// pass
}
+
+ /**
+ * Exception thrown when no service published for given name. This might be
+ * thrown early during boot before certain services have published
+ * themselves.
+ *
+ * @hide
+ */
+ public static class ServiceNotFoundException extends Exception {
+ // identical to the original implementation
+ public ServiceNotFoundException(String name) {
+ super("No service published for: " + name);
+ }
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
index af0c4569b08b..d299add05eab 100644
--- a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
@@ -102,4 +102,9 @@ public class SystemProperties_Delegate {
/*package*/ static void native_add_change_callback() {
// pass.
}
+
+ @LayoutlibDelegate
+ /*package*/ static void native_report_sysprop_change() {
+ // pass.
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java b/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java
index 4f00b5da08a8..aa393a976d12 100644
--- a/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java
+++ b/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java
@@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.InflateException;
public class BridgePreferenceInflater extends PreferenceInflater {
@@ -42,7 +43,15 @@ public class BridgePreferenceInflater extends PreferenceInflater {
viewKey = ((BridgeXmlBlockParser) attrs).getViewCookie();
}
- Preference preference = super.onCreateItem(name, attrs);
+ Preference preference = null;
+ try {
+ preference = super.onCreateItem(name, attrs);
+ } catch (ClassNotFoundException | InflateException exception) {
+ // name is probably not a valid preference type
+ if ("SwitchPreferenceCompat".equals(name)) {
+ preference = super.onCreateItem("SwitchPreference", attrs);
+ }
+ }
if (viewKey != null && bc != null) {
bc.addCookie(preference, viewKey);
diff --git a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
index 94f3f546d0c3..85584d3ed2cc 100644
--- a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
+++ b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
@@ -34,7 +34,7 @@ public class AttachInfo_Accessor {
Display display = wm.getDefaultDisplay();
ViewRootImpl root = new ViewRootImpl(context, display);
AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(),
- display, root, new Handler(), null);
+ display, root, new Handler(), null, context);
info.mHasWindowFocus = true;
info.mWindowVisibility = View.VISIBLE;
info.mInTouchMode = false; // this is so that we can display selections.
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index bdddfd8d8ba3..b19cb5860a63 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -39,11 +39,28 @@ import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.widget.NumberPicker;
import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
-
+import java.util.Set;
+
+import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW;
+import static com.android.SdkConstants.BUTTON;
+import static com.android.SdkConstants.CHECKED_TEXT_VIEW;
+import static com.android.SdkConstants.CHECK_BOX;
+import static com.android.SdkConstants.EDIT_TEXT;
+import static com.android.SdkConstants.IMAGE_BUTTON;
+import static com.android.SdkConstants.IMAGE_VIEW;
+import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW;
+import static com.android.SdkConstants.RADIO_BUTTON;
+import static com.android.SdkConstants.SEEK_BAR;
+import static com.android.SdkConstants.SPINNER;
+import static com.android.SdkConstants.TEXT_VIEW;
import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
/**
@@ -52,6 +69,21 @@ import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
public final class BridgeInflater extends LayoutInflater {
private final LayoutlibCallback mLayoutlibCallback;
+ /**
+ * If true, the inflater will try to replace the framework widgets with the AppCompat versions.
+ * Ideally, this should be based on the activity being an AppCompat activity but since that is
+ * not trivial to check from layoutlib, we currently base the decision on the current theme
+ * being an AppCompat theme.
+ */
+ private boolean mLoadAppCompatViews;
+ /**
+ * This set contains the framework views that have an AppCompat version but failed to load.
+ * This might happen because not all widgets are contained in all versions of the support
+ * library.
+ * This will help us to avoid trying to load the AppCompat version multiple times if it
+ * doesn't exist.
+ */
+ private Set<String> mFailedAppCompatViews = new HashSet<>();
private boolean mIsInMerge = false;
private ResourceReference mResourceReference;
private Map<View, String> mOpenDrawerLayouts;
@@ -59,6 +91,15 @@ public final class BridgeInflater extends LayoutInflater {
// Keep in sync with the same value in LayoutInflater.
private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme };
+ private static final String APPCOMPAT_WIDGET_PREFIX = "android.support.v7.widget.AppCompat";
+ /** List of platform widgets that have an AppCompat version */
+ private static final Set<String> APPCOMPAT_VIEWS = Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(TEXT_VIEW, IMAGE_VIEW, BUTTON, EDIT_TEXT, SPINNER,
+ IMAGE_BUTTON, CHECK_BOX, RADIO_BUTTON, CHECKED_TEXT_VIEW,
+ AUTO_COMPLETE_TEXT_VIEW, MULTI_AUTO_COMPLETE_TEXT_VIEW, "RatingBar",
+ SEEK_BAR)));
+
/**
* List of class prefixes which are tried first by default.
* <p/>
@@ -74,13 +115,15 @@ public final class BridgeInflater extends LayoutInflater {
return sClassPrefixList;
}
- protected BridgeInflater(LayoutInflater original, Context newContext) {
+ private BridgeInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
newContext = getBaseContext(newContext);
if (newContext instanceof BridgeContext) {
mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback();
+ mLoadAppCompatViews = ((BridgeContext) newContext).isAppCompatTheme();
} else {
mLayoutlibCallback = null;
+ mLoadAppCompatViews = false;
}
}
@@ -90,10 +133,11 @@ public final class BridgeInflater extends LayoutInflater {
* @param context The Android application context.
* @param layoutlibCallback the {@link LayoutlibCallback} object.
*/
- public BridgeInflater(Context context, LayoutlibCallback layoutlibCallback) {
+ public BridgeInflater(BridgeContext context, LayoutlibCallback layoutlibCallback) {
super(context);
mLayoutlibCallback = layoutlibCallback;
mConstructorArgs[0] = context;
+ mLoadAppCompatViews = context.isAppCompatTheme();
}
@Override
@@ -101,26 +145,39 @@ public final class BridgeInflater extends LayoutInflater {
View view = null;
try {
- // First try to find a class using the default Android prefixes
- for (String prefix : sClassPrefixList) {
- try {
- view = createView(name, prefix, attrs);
- if (view != null) {
- break;
- }
- } catch (ClassNotFoundException e) {
- // Ignore. We'll try again using the base class below.
+ if (mLoadAppCompatViews
+ && APPCOMPAT_VIEWS.contains(name)
+ && !mFailedAppCompatViews.contains(name)) {
+ // We are using an AppCompat theme so try to load the appcompat views
+ view = loadCustomView(APPCOMPAT_WIDGET_PREFIX + name, attrs, true);
+
+ if (view == null) {
+ mFailedAppCompatViews.add(name); // Do not try this one anymore
}
}
- // Next try using the parent loader. This will most likely only work for
- // fully-qualified class names.
- try {
- if (view == null) {
- view = super.onCreateView(name, attrs);
+ if (view == null) {
+ // First try to find a class using the default Android prefixes
+ for (String prefix : sClassPrefixList) {
+ try {
+ view = createView(name, prefix, attrs);
+ if (view != null) {
+ break;
+ }
+ } catch (ClassNotFoundException e) {
+ // Ignore. We'll try again using the base class below.
+ }
+ }
+
+ // Next try using the parent loader. This will most likely only work for
+ // fully-qualified class names.
+ try {
+ if (view == null) {
+ view = super.onCreateView(name, attrs);
+ }
+ } catch (ClassNotFoundException e) {
+ // Ignore. We'll try again using the custom view loader below.
}
- } catch (ClassNotFoundException e) {
- // Ignore. We'll try again using the custom view loader below.
}
// Finally try again using the custom view loader
@@ -144,9 +201,26 @@ public final class BridgeInflater extends LayoutInflater {
@Override
public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
- View view;
+ View view = null;
+ if (name.equals("view")) {
+ // This is usually done by the superclass but this allows us catching the error and
+ // reporting something useful.
+ name = attrs.getAttributeValue(null, "class");
+
+ if (name == null) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to inflate view tag without " +
+ "class attribute", null);
+ // We weren't able to resolve the view so we just pass a mock View to be able to
+ // continue rendering.
+ view = new MockView(context, attrs);
+ ((MockView) view).setText("view");
+ }
+ }
+
try {
- view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr);
+ if (view == null) {
+ view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr);
+ }
} catch (InflateException e) {
// Creation of ContextThemeWrapper code is same as in the super method.
// Apply a theme wrapper, if allowed and one is specified.
@@ -235,17 +309,29 @@ public final class BridgeInflater extends LayoutInflater {
return null;
}
- private View loadCustomView(String name, AttributeSet attrs) throws Exception {
+ /**
+ * Instantiates the given view name and returns the instance. If the view doesn't exist, a
+ * MockView or null might be returned.
+ * @param name the custom view name
+ * @param attrs the {@link AttributeSet} to be passed to the view constructor
+ * @param silent if true, errors while loading the view won't be reported and, if the view
+ * doesn't exist, null will be returned.
+ */
+ private View loadCustomView(String name, AttributeSet attrs, boolean silent) throws Exception {
if (mLayoutlibCallback != null) {
// first get the classname in case it's not the node name
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
+ if (name == null) {
+ return null;
+ }
}
mConstructorArgs[1] = attrs;
- Object customView = mLayoutlibCallback.loadView(name, mConstructorSignature,
- mConstructorArgs);
+ Object customView = silent ?
+ mLayoutlibCallback.loadClass(name, mConstructorSignature, mConstructorArgs)
+ : mLayoutlibCallback.loadView(name, mConstructorSignature, mConstructorArgs);
if (customView instanceof View) {
return (View)customView;
@@ -255,6 +341,10 @@ public final class BridgeInflater extends LayoutInflater {
return null;
}
+ private View loadCustomView(String name, AttributeSet attrs) throws Exception {
+ return loadCustomView(name, attrs, false);
+ }
+
private void setupViewInContext(View view, AttributeSet attrs) {
Context context = getContext();
context = getBaseContext(context);
@@ -300,6 +390,17 @@ public final class BridgeInflater extends LayoutInflater {
getDrawerLayoutMap().put(view, attrVal);
}
}
+ else if (view instanceof NumberPicker) {
+ NumberPicker numberPicker = (NumberPicker) view;
+ String minValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "minValue");
+ if (minValue != null) {
+ numberPicker.setMinValue(Integer.parseInt(minValue));
+ }
+ String maxValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "maxValue");
+ if (maxValue != null) {
+ numberPicker.setMaxValue(Integer.parseInt(maxValue));
+ }
+ }
}
}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 0c3231bcde60..4b9815d93ac7 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -20,6 +20,7 @@ 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.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -30,9 +31,9 @@ import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.DisplayMetrics;
-import android.view.AppTransitionAnimationSpec;
import java.lang.Override;
@@ -76,16 +77,15 @@ public class IWindowManagerImpl implements IWindowManager {
// ---- unused implementation of 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,
- Rect arg11, Configuration arg12, int arg13, boolean arg14, boolean arg15, int arg16,
- int arg17)
- throws RemoteException {
+ public void addAppToken(int addPos, IApplicationToken token, int taskId,
+ int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int configChanges,
+ boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
+ int targetSdkVersion, int rotationAnimationHint) throws RemoteException {
// TODO Auto-generated method stub
}
@Override
- public void addWindowToken(IBinder arg0, int arg1) throws RemoteException {
+ public void addWindowToken(IBinder arg0, int arg1, int arg2) throws RemoteException {
// TODO Auto-generated method stub
}
@@ -276,13 +276,13 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void removeAppToken(IBinder arg0) throws RemoteException {
+ public void removeAppToken(IBinder arg0, int arg1) throws RemoteException {
// TODO Auto-generated method stub
}
@Override
- public void removeWindowToken(IBinder arg0) throws RemoteException {
+ public void removeWindowToken(IBinder arg0, int arg1) throws RemoteException {
// TODO Auto-generated method stub
}
@@ -325,9 +325,7 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void setAppTask(IBinder arg0, int arg1, int arg2, Rect arg3, Configuration arg4,
- int arg5, boolean arg6)
- throws RemoteException {
+ public void addAppToTask(IBinder arg0, int arg1) throws RemoteException {
// TODO Auto-generated method stub
}
@@ -412,7 +410,8 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public int[] setNewConfiguration(Configuration arg0) throws RemoteException {
+ public int[] setNewDisplayOverrideConfiguration(Configuration arg0, int displayId)
+ throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -486,7 +485,7 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1)
+ public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1, int arg2)
throws RemoteException {
// TODO Auto-generated method stub
return null;
@@ -514,11 +513,11 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void dismissKeyguard() {
+ public void dismissKeyguard(IKeyguardDismissCallback callback) throws RemoteException {
}
@Override
- public void keyguardGoingAway(int flags) throws RemoteException {
+ public void setSwitchingUser(boolean switching) throws RemoteException {
}
@Override
@@ -581,6 +580,20 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
+ public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) throws RemoteException {
+ }
+
+ @Override
+ public Rect getPictureInPictureDefaultBounds(int displayId) {
+ return null;
+ }
+
+ @Override
+ public Rect getPictureInPictureMovementBounds(int displayId) {
+ return null;
+ }
+
+ @Override
public void setResizeDimLayer(boolean visible, int targetStackId, float alpha)
throws RemoteException {
}
@@ -595,7 +608,7 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void getStableInsets(Rect outInsets) throws RemoteException {
+ public void getStableInsets(int displayId, Rect outInsets) throws RemoteException {
}
@Override
@@ -603,13 +616,24 @@ public class IWindowManagerImpl implements IWindowManager {
throws RemoteException {}
@Override
- public void createWallpaperInputConsumer(InputChannel inputChannel) throws RemoteException {}
+ public void createInputConsumer(String name, InputChannel inputChannel)
+ throws RemoteException {}
@Override
- public void removeWallpaperInputConsumer() throws RemoteException {}
+ public boolean destroyInputConsumer(String name) throws RemoteException {
+ return false;
+ }
@Override
public Bitmap screenshotWallpaper() throws RemoteException {
return null;
}
+
+ @Override
+ public void enableSurfaceTrace(ParcelFileDescriptor fd) throws RemoteException {
+ }
+
+ @Override
+ public void disableSurfaceTrace() throws RemoteException {
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index ea9a255e8561..fad35d2102d8 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -16,6 +16,7 @@
package android.view;
+import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import android.graphics.Canvas;
@@ -32,6 +33,8 @@ import android.graphics.RectF;
import android.graphics.Region.Op;
import android.graphics.Shader.TileMode;
+import java.awt.Rectangle;
+
/**
* Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly,
* since it modifies the size of the content, that we can't do.
@@ -54,12 +57,18 @@ public class RectShadowPainter {
if (saved == -1) {
return;
}
+
+ float radius = viewOutline.getRadius();
+ if (radius <= 0) {
+ // We can not paint a shadow with radius 0
+ return;
+ }
+
try {
Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
cornerPaint.setStyle(Style.FILL);
Paint edgePaint = new Paint(cornerPaint);
edgePaint.setAntiAlias(false);
- float radius = viewOutline.getRadius();
float outerArcRadius = radius + shadowSize;
int[] colors = {START_COLOR, START_COLOR, END_COLOR};
cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
@@ -121,9 +130,16 @@ public class RectShadowPainter {
int saved = canvas.save();
// Usually canvas has been translated to the top left corner of the view when this is
// called. So, setting a clip rect at 0,0 will clip the top left part of the shadow.
- // Thus, we just expand in each direction by width and height of the canvas.
- canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(),
- canvas.getHeight(), Op.REPLACE);
+ // Thus, we just expand in each direction by width and height of the canvas, while staying
+ // inside the original drawing region.
+ GcSnapshot snapshot = Canvas_Delegate.getDelegate(canvas).getSnapshot();
+ Rectangle originalClip = snapshot.getOriginalClip();
+ if (originalClip != null) {
+ canvas.clipRect(originalClip.x, originalClip.y, originalClip.x + originalClip.width,
+ originalClip.y + originalClip.height, Op.REPLACE);
+ canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(),
+ canvas.getHeight(), Op.INTERSECT);
+ }
canvas.translate(0, shadowSize / 2f);
return saved;
}
diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
index 24f788766cc9..a801cb0606e0 100644
--- a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
@@ -21,6 +21,8 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.graphics.Matrix;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
/**
* Delegate implementing the native methods of {@link RenderNode}
* <p/>
@@ -35,7 +37,7 @@ public class RenderNode_Delegate {
// ---- delegate manager ----
private static final DelegateManager<RenderNode_Delegate> sManager =
new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class);
-
+ private static long sFinalizer = -1;
private float mLift;
private float mTranslationX;
@@ -62,8 +64,13 @@ public class RenderNode_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nDestroyRenderNode(long renderNode) {
- sManager.removeJavaReferenceFor(renderNode);
+ /*package*/ static long nGetNativeFinalizer() {
+ synchronized (RenderNode_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
}
@LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/view/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
index 1e7dfbe0aed9..ebb2af45320e 100644
--- a/tools/layoutlib/bridge/src/android/view/SurfaceView.java
+++ b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
@@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.MockView;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.graphics.Region;
import android.util.AttributeSet;
/**
@@ -49,6 +50,19 @@ public class SurfaceView extends MockView {
super(context, attrs, defStyleAttr, defStyleRes);
}
+ public boolean gatherTransparentRegion(Region region) {
+ return false;
+ }
+
+ public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+ }
+
+ public void setZOrderOnTop(boolean onTop) {
+ }
+
+ public void setSecure(boolean isSecure) {
+ }
+
public SurfaceHolder getHolder() {
return mSurfaceHolder;
}
diff --git a/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java
new file mode 100644
index 000000000000..06874bd57d2b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java
@@ -0,0 +1,342 @@
+/*
+ * 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.view.textservice;
+
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesManager;
+import com.android.internal.textservice.ITextServicesSessionListener;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
+
+import java.util.Locale;
+
+/**
+ * System API to the overall text services, which arbitrates interaction between applications
+ * and text services. You can retrieve an instance of this interface with
+ * {@link Context#getSystemService(String) Context.getSystemService()}.
+ *
+ * The user can change the current text services in Settings. And also applications can specify
+ * the target text services.
+ *
+ * <h3>Architecture Overview</h3>
+ *
+ * <p>There are three primary parties involved in the text services
+ * framework (TSF) architecture:</p>
+ *
+ * <ul>
+ * <li> The <strong>text services manager</strong> as expressed by this class
+ * is the central point of the system that manages interaction between all
+ * other parts. It is expressed as the client-side API here which exists
+ * in each application context and communicates with a global system service
+ * that manages the interaction across all processes.
+ * <li> A <strong>text service</strong> implements a particular
+ * interaction model allowing the client application to retrieve information of text.
+ * The system binds to the current text service that is in use, causing it to be created and run.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the text service
+ * manager for connections to text services.
+ * </ul>
+ *
+ * <h3>Text services sessions</h3>
+ * <ul>
+ * <li>The <strong>spell checker session</strong> is one of the text services.
+ * {@link android.view.textservice.SpellCheckerSession}</li>
+ * </ul>
+ *
+ */
+public final class TextServicesManager {
+ private static final String TAG = TextServicesManager.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ private static TextServicesManager sInstance;
+
+ private final ITextServicesManager mService;
+
+ private TextServicesManager() {
+ mService = new FakeTextServicesManager();
+ }
+
+ /**
+ * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
+ * @hide
+ */
+ public static TextServicesManager getInstance() {
+ synchronized (TextServicesManager.class) {
+ if (sInstance == null) {
+ sInstance = new TextServicesManager();
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Returns the language component of a given locale string.
+ */
+ private static String parseLanguageFromLocaleString(String locale) {
+ final int idx = locale.indexOf('_');
+ if (idx < 0) {
+ return locale;
+ } else {
+ return locale.substring(0, idx);
+ }
+ }
+
+ /**
+ * Get a spell checker session for the specified spell checker
+ * @param locale the locale for the spell checker. If {@code locale} is null and
+ * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
+ * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
+ * the locale specified in Settings will be returned only when it is same as {@code locale}.
+ * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
+ * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
+ * selected.
+ * @param listener a spell checker session lister for getting results from a spell checker.
+ * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
+ * languages in settings will be returned.
+ * @return the spell checker session of the spell checker
+ */
+ public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
+ SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
+ if (listener == null) {
+ throw new NullPointerException();
+ }
+ if (!referToSpellCheckerLanguageSettings && locale == null) {
+ throw new IllegalArgumentException("Locale should not be null if you don't refer"
+ + " settings.");
+ }
+
+ if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
+ return null;
+ }
+
+ final SpellCheckerInfo sci;
+ try {
+ sci = mService.getCurrentSpellChecker(null);
+ } catch (RemoteException e) {
+ return null;
+ }
+ if (sci == null) {
+ return null;
+ }
+ SpellCheckerSubtype subtypeInUse = null;
+ if (referToSpellCheckerLanguageSettings) {
+ subtypeInUse = getCurrentSpellCheckerSubtype(true);
+ if (subtypeInUse == null) {
+ return null;
+ }
+ if (locale != null) {
+ final String subtypeLocale = subtypeInUse.getLocale();
+ final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+ if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
+ return null;
+ }
+ }
+ } else {
+ final String localeStr = locale.toString();
+ for (int i = 0; i < sci.getSubtypeCount(); ++i) {
+ final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
+ final String tempSubtypeLocale = subtype.getLocale();
+ final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
+ if (tempSubtypeLocale.equals(localeStr)) {
+ subtypeInUse = subtype;
+ break;
+ } else if (tempSubtypeLanguage.length() >= 2 &&
+ locale.getLanguage().equals(tempSubtypeLanguage)) {
+ subtypeInUse = subtype;
+ }
+ }
+ }
+ if (subtypeInUse == null) {
+ return null;
+ }
+ final SpellCheckerSession session = new SpellCheckerSession(
+ sci, mService, listener, subtypeInUse);
+ try {
+ mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
+ session.getTextServicesSessionListener(),
+ session.getSpellCheckerSessionListener(), bundle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return session;
+ }
+
+ /**
+ * @hide
+ */
+ public SpellCheckerInfo[] getEnabledSpellCheckers() {
+ try {
+ final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
+ if (DBG) {
+ Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
+ }
+ return retval;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public SpellCheckerInfo getCurrentSpellChecker() {
+ try {
+ // Passing null as a locale for ICS
+ return mService.getCurrentSpellChecker(null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setCurrentSpellChecker(SpellCheckerInfo sci) {
+ try {
+ if (sci == null) {
+ throw new NullPointerException("SpellCheckerInfo is null.");
+ }
+ mService.setCurrentSpellChecker(null, sci.getId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
+ boolean allowImplicitlySelectedSubtype) {
+ try {
+ // Passing null as a locale until we support multiple enabled spell checker subtypes.
+ return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setSpellCheckerSubtype(SpellCheckerSubtype subtype) {
+ try {
+ final int hashCode;
+ if (subtype == null) {
+ hashCode = 0;
+ } else {
+ hashCode = subtype.hashCode();
+ }
+ mService.setCurrentSpellCheckerSubtype(null, hashCode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setSpellCheckerEnabled(boolean enabled) {
+ try {
+ mService.setSpellCheckerEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isSpellCheckerEnabled() {
+ try {
+ return mService.isSpellCheckerEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class FakeTextServicesManager implements ITextServicesManager {
+
+ @Override
+ public void finishSpellCheckerService(ISpellCheckerSessionListener arg0)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void getSpellCheckerService(String arg0, String arg1,
+ ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean isSpellCheckerEnabled() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setSpellCheckerEnabled(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java
deleted file mode 100644
index 3017292d8337..000000000000
--- a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.textservice;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.view.textservice.SpellCheckerInfo;
-import android.view.textservice.SpellCheckerSubtype;
-
-
-/**
- * Delegate used to provide new implementation of a select few methods of
- * {@link ITextServicesManager$Stub}
- *
- * Through the layoutlib_create tool, the original methods of Stub have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class ITextServicesManager_Stub_Delegate {
-
- @LayoutlibDelegate
- public static ITextServicesManager asInterface(IBinder obj) {
- // ignore the obj and return a fake interface implementation
- return new FakeTextServicesManager();
- }
-
- private static class FakeTextServicesManager implements ITextServicesManager {
-
- @Override
- public void finishSpellCheckerService(ISpellCheckerSessionListener arg0)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void getSpellCheckerService(String arg0, String arg1,
- ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean isSpellCheckerEnabled() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setSpellCheckerEnabled(boolean arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public IBinder asBinder() {
- // TODO Auto-generated method stub
- return null;
- }
-
- }
- }
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
index ffce1a0496d9..da1ab27b6ff2 100644
--- 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
@@ -19,6 +19,7 @@ package com.android.internal.view.animation;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.graphics.Path;
import android.util.MathUtils;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
@@ -31,6 +32,7 @@ import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
+import android.view.animation.PathInterpolator;
/**
* Delegate used to provide new implementation of a select few methods of {@link
@@ -93,6 +95,16 @@ public class NativeInterpolatorFactoryHelper_Delegate {
return sManager.addNewDelegate(new OvershootInterpolator(tension));
}
+ @LayoutlibDelegate
+ /*package*/ static long createPathInterpolator(float[] x, float[] y) {
+ Path path = new Path();
+ path.moveTo(x[0], y[0]);
+ for (int i = 1; i < x.length; i++) {
+ path.lineTo(x[i], y[i]);
+ }
+ return sManager.addNewDelegate(new PathInterpolator(path));
+ }
+
private static class LutInterpolator extends BaseInterpolator {
private final float[] mValues;
private final int mSize;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
index e4cbb2f4c02d..c827f178279e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
@@ -97,8 +97,8 @@ public final class BridgeContentProvider implements IContentProvider {
}
@Override
- public Cursor query(String callingPackage, Uri arg0, String[] arg1, String arg2, String[] arg3,
- String arg4, ICancellationSignal arg5) throws RemoteException {
+ public Cursor query(String callingPackage, Uri arg0, String[] arg1,
+ Bundle arg3, ICancellationSignal arg4) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -145,4 +145,10 @@ public final class BridgeContentProvider implements IContentProvider {
public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
return null;
}
+
+ @Override
+ public boolean refresh(String callingPkg, Uri url, Bundle args,
+ ICancellationSignal cancellationSignal) throws RemoteException {
+ return false;
+ }
}
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 616cb5761402..1b3b563fb51e 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
@@ -76,6 +76,7 @@ import android.os.Parcel;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -110,6 +111,7 @@ import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_AP
*/
@SuppressWarnings("deprecation") // For use of Pair.
public final class BridgeContext extends Context {
+ private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";
/** The map adds cookies to each view so that IDE can link xml tags to views. */
private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
@@ -153,6 +155,7 @@ public final class BridgeContext extends Context {
private ClassLoader mClassLoader;
private IBinder mBinder;
private PackageManager mPackageManager;
+ private Boolean mIsThemeAppCompat;
/**
* Some applications that target both pre API 17 and post API 17, set the newer attrs to
@@ -371,7 +374,9 @@ public final class BridgeContext extends Context {
return true;
}
- return false;
+ // If the value is not a valid reference, fallback to pass the value as a string.
+ outValue.string = value.getValue();
+ return true;
}
@@ -479,6 +484,36 @@ public final class BridgeContext extends Context {
return Pair.of(null, Boolean.FALSE);
}
+ /**
+ * Returns whether the current selected theme is based on AppCompat
+ */
+ public boolean isAppCompatTheme() {
+ // If a cached value exists, return it.
+ if (mIsThemeAppCompat != null) {
+ return mIsThemeAppCompat;
+ }
+ // Ideally, we should check if the corresponding activity extends
+ // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
+ StyleResourceValue defaultTheme = mRenderResources.getDefaultTheme();
+ // We can't simply check for parent using resources.themeIsParentOf() since the
+ // inheritance structure isn't really what one would expect. The first common parent
+ // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
+ boolean isThemeAppCompat = false;
+ for (int i = 0; i < 50; i++) {
+ if (defaultTheme == null) {
+ break;
+ }
+ // for loop ensures that we don't run into cyclic theme inheritance.
+ if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
+ isThemeAppCompat = true;
+ break;
+ }
+ defaultTheme = mRenderResources.getParent(defaultTheme);
+ }
+ mIsThemeAppCompat = isThemeAppCompat;
+ return isThemeAppCompat;
+ }
+
@SuppressWarnings("deprecation")
private ILayoutPullParser getParser(ResourceReference resource) {
ILayoutPullParser parser;
@@ -831,45 +866,55 @@ public final class BridgeContext extends Context {
}
}
- // if there's no direct value for this attribute in the XML, we look for default
- // values in the widget defStyle, and then in the theme.
- if (value == null) {
- ResourceValue resValue = null;
-
+ // Calculate the default value from the Theme in two cases:
+ // - If defaultPropMap is not null, get the default value to add it to the list
+ // of default values of properties.
+ // - If value is null, it means that the attribute is not directly set as an
+ // attribute in the XML so try to get the default value.
+ ResourceValue defaultValue = null;
+ if (defaultPropMap != null || value == null) {
// look for the value in the custom style first (and its parent if needed)
if (customStyleValues != null) {
- resValue = mRenderResources.findItemInStyle(customStyleValues,
- attrName, frameworkAttr);
+ defaultValue = mRenderResources.findItemInStyle(customStyleValues, attrName,
+ frameworkAttr);
}
// then look for the value in the default Style (and its parent if needed)
- if (resValue == null && defStyleValues != null) {
- resValue = mRenderResources.findItemInStyle(defStyleValues,
- attrName, frameworkAttr);
+ if (defaultValue == null && defStyleValues != null) {
+ defaultValue = mRenderResources.findItemInStyle(defStyleValues, attrName,
+ frameworkAttr);
}
// if the item is not present in the defStyle, we look in the main theme (and
// its parent themes)
- if (resValue == null) {
- resValue = mRenderResources.findItemInTheme(attrName, frameworkAttr);
+ if (defaultValue == null) {
+ defaultValue = mRenderResources.findItemInTheme(attrName, frameworkAttr);
}
// if we found a value, we make sure this doesn't reference another value.
// So we resolve it.
- if (resValue != null) {
- String preResolve = resValue.getValue();
- resValue = mRenderResources.resolveResValue(resValue);
+ if (defaultValue != null) {
+ String preResolve = defaultValue.getValue();
+ defaultValue = mRenderResources.resolveResValue(defaultValue);
if (defaultPropMap != null) {
defaultPropMap.put(
frameworkAttr ? SdkConstants.PREFIX_ANDROID + attrName :
- attrName,
- new Property(preResolve, resValue.getValue()));
+ attrName, new Property(preResolve, defaultValue.getValue()));
}
+ }
+ }
+ // Done calculating the defaultValue
+ // if there's no direct value for this attribute in the XML, we look for default
+ // values in the widget defStyle, and then in the theme.
+ if (value == null) {
+ // if we found a value, we make sure this doesn't reference another value.
+ // So we resolve it.
+ if (defaultValue != null) {
// 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();
+ String val = defaultValue.getValue();
if (val != null && val.startsWith(SdkConstants.PREFIX_THEME_REF)) {
if (!attrName.equals(RTL_ATTRS.get(val)) ||
getApplicationInfo().targetSdkVersion <
@@ -880,11 +925,11 @@ public final class BridgeContext extends Context {
String.format("Failed to find '%s' in current theme.", val),
val);
}
- resValue = null;
+ defaultValue = null;
}
}
- ta.bridgeSetValue(index, attrName, frameworkAttr, resValue);
+ ta.bridgeSetValue(index, attrName, frameworkAttr, defaultValue);
} else {
// there is a value in the XML, but we need to resolve it in case it's
// referencing another resource or a theme value.
@@ -1138,7 +1183,7 @@ public final class BridgeContext extends Context {
@Override
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) {
+ String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) {
}
};
}
@@ -1615,6 +1660,12 @@ public final class BridgeContext extends Context {
// pass
}
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, Bundle options) {
+ // pass
+ }
+
public void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, int appOp) {
// pass
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 b3ed9e1a0164..f47b1050ca73 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
@@ -272,6 +272,11 @@ public class BridgePackageManager extends PackageManager {
}
@Override
+ public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
+ return null;
+ }
+
+ @Override
public List<EphemeralApplicationInfo> getEphemeralApplications() {
return null;
}
@@ -490,12 +495,6 @@ 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;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
index d432120ccb6f..ab278195f328 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
@@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.LayoutlibCallback;
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.util.ReflectionUtils;
import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
import android.annotation.NonNull;
@@ -116,7 +117,7 @@ public class RecyclerViewUtil {
private static void setProperty(@NonNull Object object, @NonNull String propertyClassName,
@NonNull Object propertyValue, @NonNull String propertySetter)
throws ReflectionException {
- Class<?> propertyClass = getClassInstance(propertyValue, propertyClassName);
+ Class<?> propertyClass = ReflectionUtils.getClassInstance(propertyValue, propertyClassName);
setProperty(object, propertyClass, propertyValue, propertySetter);
}
@@ -126,22 +127,4 @@ public class RecyclerViewUtil {
invoke(getMethod(object.getClass(), propertySetter, propertyClass), object, propertyValue);
}
- /**
- * Looks through the class hierarchy of {@code object} at runtime and returns the class matching
- * the name {@code className}.
- * <p/>
- * This is used when we cannot use Class.forName() since the class we want was loaded from a
- * different ClassLoader.
- */
- @NonNull
- private static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) {
- Class<?> superClass = object.getClass();
- while (superClass != null) {
- if (className.equals(superClass.getName())) {
- return superClass;
- }
- superClass = superClass.getSuperclass();
- }
- throw new RuntimeException("invalid object/classname combination.");
- }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
new file mode 100644
index 000000000000..6ad9efc81afa
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
@@ -0,0 +1,282 @@
+/*
+ * 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.support;
+
+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.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ScrollView;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getAccessibleMethod;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getClassInstance;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
+
+/**
+ * Class with utility methods to instantiate Preferences provided by the support library.
+ * This class uses reflection to access the support preference objects so it heavily depends on
+ * the API being stable.
+ */
+public class SupportPreferencesUtil {
+ private static final String PREFERENCE_PKG = "android.support.v7.preference";
+ private static final String PREFERENCE_MANAGER = PREFERENCE_PKG + ".PreferenceManager";
+ private static final String PREFERENCE_GROUP = PREFERENCE_PKG + ".PreferenceGroup";
+ private static final String PREFERENCE_GROUP_ADAPTER =
+ PREFERENCE_PKG + ".PreferenceGroupAdapter";
+ private static final String PREFERENCE_INFLATER = PREFERENCE_PKG + ".PreferenceInflater";
+
+ private SupportPreferencesUtil() {
+ }
+
+ @NonNull
+ private static Object instantiateClass(@NonNull LayoutlibCallback callback,
+ @NonNull String className, @Nullable Class[] constructorSignature,
+ @Nullable Object[] constructorArgs) throws ReflectionException {
+ try {
+ Object instance = callback.loadClass(className, constructorSignature, constructorArgs);
+ if (instance == null) {
+ throw new ClassNotFoundException(className + " class not found");
+ }
+ return instance;
+ } catch (ClassNotFoundException e) {
+ throw new ReflectionException(e);
+ }
+ }
+
+ @NonNull
+ private static Object createPreferenceGroupAdapter(@NonNull LayoutlibCallback callback,
+ @NonNull Object preferenceScreen) throws ReflectionException {
+ Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP);
+
+ return instantiateClass(callback, PREFERENCE_GROUP_ADAPTER,
+ new Class[]{preferenceGroupClass}, new Object[]{preferenceScreen});
+ }
+
+ @NonNull
+ private static Object createInflatedPreference(@NonNull LayoutlibCallback callback,
+ @NonNull Context context, @NonNull XmlPullParser parser, @NonNull Object preferenceScreen,
+ @NonNull Object preferenceManager) throws ReflectionException {
+ Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP);
+ Object preferenceInflater = instantiateClass(callback, PREFERENCE_INFLATER,
+ new Class[]{Context.class, preferenceManager.getClass()},
+ new Object[]{context, preferenceManager});
+ Object inflatedPreference =
+ invoke(getAccessibleMethod(preferenceInflater.getClass(), "inflate",
+ XmlPullParser.class, preferenceGroupClass), preferenceInflater, parser,
+ null);
+
+ if (inflatedPreference == null) {
+ throw new ReflectionException("inflate method returned null");
+ }
+
+ return inflatedPreference;
+ }
+
+ /**
+ * Returns a themed wrapper context of {@link BridgeContext} with the theme specified in
+ * ?attr/preferenceTheme applied to it.
+ */
+ @Nullable
+ private static Context getThemedContext(@NonNull BridgeContext bridgeContext) {
+ RenderResources resources = bridgeContext.getRenderResources();
+ ResourceValue preferenceTheme = resources.findItemInTheme("preferenceTheme", false);
+
+ if (preferenceTheme != null) {
+ // resolve it, if needed.
+ preferenceTheme = resources.resolveResValue(preferenceTheme);
+ }
+ if (preferenceTheme instanceof StyleResourceValue) {
+ int styleId = bridgeContext.getDynamicIdByStyle(((StyleResourceValue) preferenceTheme));
+ if (styleId != 0) {
+ return new ContextThemeWrapper(bridgeContext, styleId);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a {@link LinearLayout} containing all the UI widgets representing the preferences
+ * passed in the group adapter.
+ */
+ @Nullable
+ private static LinearLayout setUpPreferencesListView(@NonNull BridgeContext bridgeContext,
+ @NonNull Context themedContext, @NonNull ArrayList<Object> viewCookie,
+ @NonNull Object preferenceGroupAdapter) throws ReflectionException {
+ // Setup the LinearLayout that will contain the preferences
+ LinearLayout listView = new LinearLayout(themedContext);
+ listView.setOrientation(LinearLayout.VERTICAL);
+ listView.setLayoutParams(
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+ if (!viewCookie.isEmpty()) {
+ bridgeContext.addViewKey(listView, viewCookie.get(0));
+ }
+
+ // Get all the preferences and add them to the LinearLayout
+ Integer preferencesCount =
+ (Integer) invoke(getMethod(preferenceGroupAdapter.getClass(), "getItemCount"),
+ preferenceGroupAdapter);
+ if (preferencesCount == null) {
+ return listView;
+ }
+
+ Method getItemId = getMethod(preferenceGroupAdapter.getClass(), "getItemId", int.class);
+ Method getItemViewType =
+ getMethod(preferenceGroupAdapter.getClass(), "getItemViewType", int.class);
+ Method onCreateViewHolder =
+ getMethod(preferenceGroupAdapter.getClass(), "onCreateViewHolder", ViewGroup.class,
+ int.class);
+ for (int i = 0; i < preferencesCount; i++) {
+ Long id = (Long) invoke(getItemId, preferenceGroupAdapter, i);
+ if (id == null) {
+ continue;
+ }
+
+ // Get the type of the preference layout and bind it to a newly created view holder
+ Integer type = (Integer) invoke(getItemViewType, preferenceGroupAdapter, i);
+ Object viewHolder =
+ invoke(onCreateViewHolder, preferenceGroupAdapter, listView, type);
+ if (viewHolder == null) {
+ continue;
+ }
+ invoke(getMethod(preferenceGroupAdapter.getClass(), "onBindViewHolder",
+ viewHolder.getClass(), int.class), preferenceGroupAdapter, viewHolder, i);
+
+ try {
+ // Get the view from the view holder and add it to our layout
+ View itemView =
+ (View) viewHolder.getClass().getField("itemView").get(viewHolder);
+
+ int arrayPosition = id.intValue() - 1; // IDs are 1 based
+ if (arrayPosition >= 0 && arrayPosition < viewCookie.size()) {
+ bridgeContext.addViewKey(itemView, viewCookie.get(arrayPosition));
+ }
+ listView.addView(itemView);
+ } catch (IllegalAccessException | NoSuchFieldException ignored) {
+ }
+ }
+
+ return listView;
+ }
+
+ /**
+ * Inflates a preferences layout using the support library. If the support library is not
+ * available, this method will return null without advancing the parsers.
+ */
+ @Nullable
+ public static View inflatePreference(@NonNull BridgeContext bridgeContext,
+ @NonNull XmlPullParser parser, @Nullable ViewGroup root) {
+ try {
+ LayoutlibCallback callback = bridgeContext.getLayoutlibCallback();
+
+ Context context = getThemedContext(bridgeContext);
+ if (context == null) {
+ // Probably we couldn't find the "preferenceTheme" in the theme
+ return null;
+ }
+
+ // Create PreferenceManager
+ Object preferenceManager =
+ instantiateClass(callback, PREFERENCE_MANAGER, new Class[]{Context.class},
+ new Object[]{context});
+
+ // From this moment on, we can assume that we found the support library and that
+ // nothing should fail
+
+ // Create PreferenceScreen
+ Object preferenceScreen =
+ invoke(getMethod(preferenceManager.getClass(), "createPreferenceScreen",
+ Context.class), preferenceManager, context);
+ if (preferenceScreen == null) {
+ return null;
+ }
+
+ // Setup a parser that stores the list of cookies in the same order as the preferences
+ // are inflated. That way we can later reconstruct the list using the preference id
+ // since they are sequential and start in 1.
+ ArrayList<Object> viewCookie = new ArrayList<>();
+ if (parser instanceof BridgeXmlBlockParser) {
+ // Setup a parser that stores the XmlTag
+ parser = new BridgeXmlBlockParser(parser, null, false) {
+ @Override
+ public Object getViewCookie() {
+ return ((BridgeXmlBlockParser) getParser()).getViewCookie();
+ }
+
+ @Override
+ public int next() throws XmlPullParserException, IOException {
+ int ev = super.next();
+ if (ev == XmlPullParser.START_TAG) {
+ viewCookie.add(this.getViewCookie());
+ }
+
+ return ev;
+ }
+ };
+ }
+
+ // Create the PreferenceInflater
+ Object inflatedPreference =
+ createInflatedPreference(callback, context, parser, preferenceScreen,
+ preferenceManager);
+
+ // Setup the RecyclerView (set adapter and layout manager)
+ Object preferenceGroupAdapter =
+ createPreferenceGroupAdapter(callback, inflatedPreference);
+
+ // Instead of just setting the group adapter as adapter for a RecyclerView, we manually
+ // get all the items and add them to a LinearLayout. This allows us to set the view
+ // cookies so the preferences are correctly linked to their XML.
+ LinearLayout listView = setUpPreferencesListView(bridgeContext, context, viewCookie,
+ preferenceGroupAdapter);
+
+ ScrollView scrollView = new ScrollView(context);
+ scrollView.setLayoutParams(
+ new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ scrollView.addView(listView);
+
+ if (root != null) {
+ root.addView(scrollView);
+ }
+
+ return scrollView;
+ } catch (ReflectionException e) {
+ return null;
+ }
+ }
+}
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 c59b1a66bb02..11da44583451 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
@@ -24,8 +24,8 @@ import android.util.SparseArray;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -33,7 +33,7 @@ import java.util.concurrent.atomic.AtomicLong;
*
* This is used in conjunction with layoublib_create: certain Android java classes are mere
* wrappers around a heavily native based implementation, and we need a way to run these classes
- * in our Eclipse rendering framework without bringing all the native code from the Android
+ * in our Android Studio rendering framework without bringing all the native code from the Android
* platform.
*
* Thus we instruct layoutlib_create to modify the bytecode of these classes to replace their
@@ -61,7 +61,7 @@ import java.util.concurrent.atomic.AtomicLong;
* following mechanism:
*
* - {@link #addNewDelegate(Object)} and {@link #removeJavaReferenceFor(long)} adds and removes
- * the delegate to/from a list. This list hold the reference and prevents the GC from reclaiming
+ * the delegate to/from a set. This set holds the reference and prevents the GC from reclaiming
* the delegate.
*
* - {@link #addNewDelegate(Object)} also adds the delegate to a {@link SparseArray} that holds a
@@ -76,12 +76,12 @@ public final class DelegateManager<T> {
@SuppressWarnings("FieldCanBeLocal")
private final Class<T> mClass;
private static final SparseWeakArray<Object> sDelegates = new SparseWeakArray<>();
- /** list used to store delegates when their main object holds a reference to them.
+ /** Set 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 static final List<Object> sJavaReferences = new ArrayList<>();
+ private static final Set<Object> sJavaReferences = new HashSet<>();
private static final AtomicLong sDelegateCounter = new AtomicLong(1);
public DelegateManager(Class<T> theClass) {
@@ -129,7 +129,7 @@ public final class DelegateManager<T> {
long native_object = sDelegateCounter.getAndIncrement();
synchronized (DelegateManager.class) {
sDelegates.put(native_object, newDelegate);
- assert !sJavaReferences.contains(newDelegate);
+ // Only for development: assert !sJavaReferences.contains(newDelegate);
sJavaReferences.add(newDelegate);
}
@@ -165,5 +165,6 @@ public final class DelegateManager<T> {
int idx = sDelegates.indexOfValue(reference);
out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName());
}
+ out.printf("\nTotal number of objects: %d\n", sJavaReferences.size());
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
index a39eb4d45ca5..7526e090be50 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -24,12 +24,13 @@ import android.graphics.Canvas;
import android.graphics.ColorFilter_Delegate;
import android.graphics.Paint;
import android.graphics.Paint_Delegate;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Region_Delegate;
import android.graphics.Shader_Delegate;
-import android.graphics.Xfermode_Delegate;
import java.awt.AlphaComposite;
import java.awt.Color;
@@ -40,6 +41,7 @@ import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
+import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
@@ -620,7 +622,8 @@ public class GcSnapshot {
int y = 0;
int width;
int height;
- Rectangle clipBounds = originalGraphics.getClipBounds();
+ Rectangle clipBounds = originalGraphics.getClip() != null ? originalGraphics
+ .getClipBounds() : null;
if (clipBounds != null) {
if (clipBounds.width == 0 || clipBounds.height == 0) {
// Clip is 0 so no need to paint anything.
@@ -825,28 +828,9 @@ public class GcSnapshot {
g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
return;
}
- Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
- if (xfermodeDelegate != null) {
- if (xfermodeDelegate.isSupported()) {
- Composite composite = xfermodeDelegate.getComposite(alpha);
- assert composite != null;
- if (composite != null) {
- g.setComposite(composite);
- return;
- }
- } else {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
- xfermodeDelegate.getSupportMessage(),
- null /*throwable*/, null /*data*/);
- }
- }
- // if there was no custom xfermode, but we have alpha (due to a shader and a non
- // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
- // that will handle the alpha.
- if (alpha != 0xFF) {
- g.setComposite(AlphaComposite.getInstance(
- AlphaComposite.SRC_OVER, (float) alpha / 255.f));
- }
+ Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
+ Composite composite = PorterDuffUtility.getComposite(mode, alpha);
+ g.setComposite(composite);
}
private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
@@ -869,4 +853,33 @@ public class GcSnapshot {
dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
}
+ /**
+ * Returns the clip of the oldest snapshot of the stack, appropriately translated to be
+ * expressed in the coordinate system of the latest snapshot.
+ */
+ public Rectangle getOriginalClip() {
+ GcSnapshot originalSnapshot = this;
+ while (originalSnapshot.mPrevious != null) {
+ originalSnapshot = originalSnapshot.mPrevious;
+ }
+ if (originalSnapshot.mLayers.isEmpty()) {
+ return null;
+ }
+ Graphics2D graphics2D = originalSnapshot.mLayers.get(0).getGraphics();
+ Rectangle bounds = graphics2D.getClipBounds();
+ if (bounds == null) {
+ return null;
+ }
+ try {
+ AffineTransform originalTransform =
+ ((Graphics2D) graphics2D.create()).getTransform().createInverse();
+ AffineTransform latestTransform = getTransform().createInverse();
+ bounds.x += latestTransform.getTranslateX() - originalTransform.getTranslateX();
+ bounds.y += latestTransform.getTranslateY() - originalTransform.getTranslateY();
+ } catch (NoninvertibleTransformException e) {
+ Bridge.getLog().warning(null, "Non invertible transformation", null);
+ }
+ return bounds;
+ }
+
}
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 1afd90d39f31..726ff223bd8f 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
@@ -20,7 +20,6 @@ import com.android.ide.common.rendering.api.HardwareConfig;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
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;
@@ -94,7 +93,6 @@ class Layout extends RelativeLayout {
private static final String ATTR_WINDOW_TITLE_SIZE = "windowTitleSize";
private static final String ATTR_WINDOW_TRANSLUCENT_STATUS = StatusBar.ATTR_TRANSLUCENT;
private static final String ATTR_WINDOW_TRANSLUCENT_NAV = NavigationBar.ATTR_TRANSLUCENT;
- private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";
// Default sizes
private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
@@ -168,13 +166,13 @@ class Layout extends RelativeLayout {
FrameLayout contentRoot = new FrameLayout(getContext());
LayoutParams params = createLayoutParams(MATCH_PARENT, MATCH_PARENT);
int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
- if (mBuilder.hasNavBar() && mBuilder.solidBars()) {
+ if (mBuilder.hasSolidNavBar()) {
params.addRule(rule, getId(ID_NAV_BAR));
}
int below = -1;
if (mBuilder.mActionBarSize <= 0 && mBuilder.mTitleBarSize > 0) {
below = getId(ID_TITLE_BAR);
- } else if (mBuilder.hasStatusBar() && mBuilder.solidBars()) {
+ } else if (mBuilder.hasSolidStatusBar()) {
below = getId(ID_STATUS_BAR);
}
if (below != -1) {
@@ -236,17 +234,17 @@ class Layout extends RelativeLayout {
boolean isMenu = "menu".equals(params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG));
BridgeActionBar actionBar;
- if (mBuilder.isThemeAppCompat() && !isMenu) {
+ if (context.isAppCompatTheme() && !isMenu) {
actionBar = new AppCompatActionBar(context, params);
} else {
actionBar = new FrameworkActionBar(context, params);
}
LayoutParams layoutParams = createLayoutParams(MATCH_PARENT, MATCH_PARENT);
int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
- if (mBuilder.hasNavBar() && mBuilder.solidBars()) {
+ if (mBuilder.hasSolidNavBar()) {
layoutParams.addRule(rule, getId(ID_NAV_BAR));
}
- if (mBuilder.hasStatusBar() && mBuilder.solidBars()) {
+ if (mBuilder.hasSolidStatusBar()) {
layoutParams.addRule(BELOW, getId(ID_STATUS_BAR));
}
actionBar.getRootView().setLayoutParams(layoutParams);
@@ -259,10 +257,10 @@ class Layout extends RelativeLayout {
int simulatedPlatformVersion) {
TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion);
LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mTitleBarSize);
- if (mBuilder.hasStatusBar() && mBuilder.solidBars()) {
+ if (mBuilder.hasSolidStatusBar()) {
params.addRule(BELOW, getId(ID_STATUS_BAR));
}
- if (mBuilder.isNavBarVertical() && mBuilder.solidBars()) {
+ if (mBuilder.isNavBarVertical() && mBuilder.hasSolidNavBar()) {
params.addRule(START_OF, getId(ID_NAV_BAR));
}
titleBar.setLayoutParams(params);
@@ -324,8 +322,6 @@ class Layout extends RelativeLayout {
private boolean mTranslucentStatus;
private boolean mTranslucentNav;
- private Boolean mIsThemeAppCompat;
-
public Builder(@NonNull SessionParams params, @NonNull BridgeContext context) {
mParams = params;
mContext = context;
@@ -364,8 +360,10 @@ class Layout extends RelativeLayout {
return;
}
// Check if an actionbar is needed
- boolean windowActionBar = getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR,
- !isThemeAppCompat(), true);
+ boolean isMenu = "menu".equals(mParams.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG));
+ boolean windowActionBar = isMenu ||
+ getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR,
+ !mContext.isAppCompatTheme(), true);
if (windowActionBar) {
mActionBarSize = getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
} else {
@@ -420,39 +418,18 @@ class Layout extends RelativeLayout {
return mParams.getHardwareConfig().hasSoftwareButtons();
}
- private boolean isThemeAppCompat() {
- // If a cached value exists, return it.
- if (mIsThemeAppCompat != null) {
- return mIsThemeAppCompat;
- }
- // Ideally, we should check if the corresponding activity extends
- // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
- StyleResourceValue defaultTheme = mResources.getDefaultTheme();
- // We can't simply check for parent using resources.themeIsParentOf() since the
- // inheritance structure isn't really what one would expect. The first common parent
- // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
- boolean isThemeAppCompat = false;
- for (int i = 0; i < 50; i++) {
- if (defaultTheme == null) {
- break;
- }
- // for loop ensures that we don't run into cyclic theme inheritance.
- if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
- isThemeAppCompat = true;
- break;
- }
- defaultTheme = mResources.getParent(defaultTheme);
- }
- mIsThemeAppCompat = isThemeAppCompat;
- return isThemeAppCompat;
+ /**
+ * Return true if the nav bar is present and not translucent
+ */
+ private boolean hasSolidNavBar() {
+ return hasNavBar() && !mTranslucentNav;
}
/**
- * Return true if the status bar or nav bar are present, they are not translucent (i.e
- * content doesn't overlap with them).
+ * Return true if the status bar is present and not translucent
*/
- private boolean solidBars() {
- return !(hasNavBar() && mTranslucentNav) && !(hasStatusBar() && mTranslucentStatus);
+ private boolean hasSolidStatusBar() {
+ return hasStatusBar() && !mTranslucentStatus;
}
private boolean hasNavBar() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
index e273b2cd75cc..1ae9cb646cf3 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
@@ -39,8 +39,6 @@ public class ParserFactory {
public final static boolean LOG_PARSER = false;
- private final static String ENCODING = "UTF-8"; //$NON-NLS-1$
-
// Used to get a new XmlPullParser from the client.
@Nullable
private static com.android.ide.common.rendering.api.ParserFactory sParserFactory;
@@ -74,7 +72,7 @@ public class ParserFactory {
stream = readAndClose(stream, name, size);
- parser.setInput(stream, ENCODING);
+ parser.setInput(stream, null);
if (isLayout) {
try {
return new LayoutParserWrapper(parser).peekTillLayoutStart();
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
index 80d7c68bcf06..70e2eb17794a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
@@ -24,14 +24,12 @@ import android.graphics.BlendComposite.BlendingMode;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter_Delegate;
-import android.graphics.PorterDuffXfermode_Delegate;
import java.awt.AlphaComposite;
import java.awt.Composite;
/**
- * Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link
- * PorterDuffXfermode_Delegate}.
+ * Provides various utility methods for {@link PorterDuffColorFilter_Delegate}.
*/
public final class PorterDuffUtility {
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 a8077ccae01a..91a783ae7efc 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
@@ -20,6 +20,7 @@ import com.android.ide.common.rendering.api.AdapterBinding;
import com.android.ide.common.rendering.api.HardwareConfig;
import com.android.ide.common.rendering.api.IAnimationListener;
import com.android.ide.common.rendering.api.ILayoutPullParser;
+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.RenderSession;
@@ -44,6 +45,7 @@ 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.android.support.SupportPreferencesUtil;
import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
import com.android.resources.ResourceType;
@@ -303,6 +305,20 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
SessionParams params = getParams();
BridgeContext context = getContext();
+ if (Bridge.isLocaleRtl(params.getLocale())) {
+ if (!params.isRtlSupported()) {
+ Bridge.getLog().warning(LayoutLog.TAG_RTL_NOT_ENABLED,
+ "You are using a right-to-left " +
+ "(RTL) locale but RTL is not enabled", null);
+ } else if (params.getSimulatedPlatformVersion() < 17) {
+ // This will render ok because we are using the latest layoutlib but at least
+ // warn the user that this might fail in a real device.
+ Bridge.getLog().warning(LayoutLog.TAG_RTL_NOT_SUPPORTED, "You are using a " +
+ "right-to-left " +
+ "(RTL) locale but RTL is not supported for API level < 17", null);
+ }
+ }
+
// Sets the project callback (custom view loader) to the fragment delegate so that
// it can instantiate the custom Fragment.
Fragment_Delegate.setLayoutlibCallback(params.getLayoutlibCallback());
@@ -311,8 +327,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
boolean isPreference = "PreferenceScreen".equals(rootTag);
View view;
if (isPreference) {
- view = Preference_Delegate.inflatePreference(getContext(), mBlockParser,
+ // First try to use the support library inflater. If something fails, fallback
+ // to the system preference inflater.
+ view = SupportPreferencesUtil.inflatePreference(getContext(), mBlockParser,
mContentRoot);
+ if (view == null) {
+ view = Preference_Delegate.inflatePreference(getContext(), mBlockParser,
+ mContentRoot);
+ }
} else {
view = mInflater.inflate(mBlockParser, mContentRoot);
}
@@ -336,7 +358,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
mMeasuredScreenWidth, MeasureSpec.EXACTLY,
mMeasuredScreenHeight, MeasureSpec.EXACTLY);
mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
- mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
+ mSystemViewInfoList =
+ visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(),
false);
return SUCCESS.createResult();
@@ -499,7 +522,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
mMeasuredScreenHeight);
}
- mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
+ mSystemViewInfoList =
+ visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(),
false);
// success!
@@ -1220,20 +1244,22 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
* bounds of all the views.
*
* @param view the root View
- * @param offset an offset for the view bounds.
+ * @param hOffset horizontal offset for the view bounds.
+ * @param vOffset vertical offset for the view bounds.
* @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
* @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
* content frame.
*
* @return {@code ViewInfo} containing the bounds of the view and it children otherwise.
*/
- private ViewInfo visit(View view, int offset, boolean setExtendedInfo,
+ private ViewInfo visit(View view, int hOffset, int vOffset, boolean setExtendedInfo,
boolean isContentFrame) {
- ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame);
+ ViewInfo result = createViewInfo(view, hOffset, vOffset, setExtendedInfo, isContentFrame);
if (view instanceof ViewGroup) {
ViewGroup group = ((ViewGroup) view);
- result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset,
+ result.setChildren(visitAllChildren(group, isContentFrame ? 0 : hOffset,
+ isContentFrame ? 0 : vOffset,
setExtendedInfo, isContentFrame));
}
return result;
@@ -1245,20 +1271,22 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
* the children of the {@code mContentRoot}.
*
* @param viewGroup the root View
- * @param offset an offset from the top for the content view frame.
+ * @param hOffset horizontal offset from the top for the content view frame.
+ * @param vOffset vertical offset from the top for the content view frame.
* @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
* @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
* content frame. {@code false} if the {@code ViewInfo} to be created is
* part of the system decor.
*/
- private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
+ private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int hOffset, int vOffset,
boolean setExtendedInfo, boolean isContentFrame) {
if (viewGroup == null) {
return null;
}
if (!isContentFrame) {
- offset += viewGroup.getTop();
+ vOffset += viewGroup.getTop();
+ hOffset += viewGroup.getLeft();
}
int childCount = viewGroup.getChildCount();
@@ -1266,7 +1294,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
for (int i = 0; i < childCount; i++) {
- ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset,
+ ViewInfo[] childViewInfo =
+ visitContentRoot(viewGroup.getChildAt(i), hOffset, vOffset,
setExtendedInfo);
childrenWithoutOffset.add(childViewInfo[0]);
childrenWithOffset.add(childViewInfo[1]);
@@ -1276,7 +1305,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
} else {
List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
for (int i = 0; i < childCount; i++) {
- children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo,
+ children.add(visit(viewGroup.getChildAt(i), hOffset, vOffset, setExtendedInfo,
isContentFrame));
}
return children;
@@ -1295,16 +1324,18 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
* index 1 is with the offset.
*/
@NonNull
- private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) {
+ private ViewInfo[] visitContentRoot(View view, int hOffset, int vOffset,
+ boolean setExtendedInfo) {
ViewInfo[] result = new ViewInfo[2];
if (view == null) {
return result;
}
- result[0] = createViewInfo(view, 0, setExtendedInfo, true);
- result[1] = createViewInfo(view, offset, setExtendedInfo, true);
+ result[0] = createViewInfo(view, 0, 0, setExtendedInfo, true);
+ result[1] = createViewInfo(view, hOffset, vOffset, setExtendedInfo, true);
if (view instanceof ViewGroup) {
- List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true);
+ List<ViewInfo> children =
+ visitAllChildren((ViewGroup) view, 0, 0, setExtendedInfo, true);
result[0].setChildren(children);
result[1].setChildren(children);
}
@@ -1315,9 +1346,12 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
* Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
* of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
* set.
- * @param offset an offset for the view bounds. Used only if view is part of the content frame.
+ * @param hOffset horizontal offset for the view bounds. Used only if view is part of the
+ * content frame.
+ * @param vOffset vertial an offset for the view bounds. Used only if view is part of the
+ * content frame.
*/
- private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo,
+ private ViewInfo createViewInfo(View view, int hOffset, int vOffset, boolean setExtendedInfo,
boolean isContentFrame) {
if (view == null) {
return null;
@@ -1333,9 +1367,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// 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),
- -scrollX + view.getLeft(), -scrollY + view.getTop() + offset,
- -scrollX + view.getRight(), -scrollY + view.getBottom() + offset,
+ getContext().getViewKey(view), -scrollX + view.getLeft() + hOffset,
+ -scrollY + view.getTop() + vOffset, -scrollX + view.getRight() + hOffset,
+ -scrollY + view.getBottom() + vOffset,
view, view.getLayoutParams());
} else {
// We are part of the system decor.
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 a21de56066cb..c197e40eb4cf 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
@@ -77,6 +77,7 @@ public final class ResourceHelper {
*/
public static int getColor(String value) {
if (value != null) {
+ value = value.trim();
if (!value.startsWith("#")) {
if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
throw new NumberFormatException(String.format(
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
index 7ce27b6a55fa..040191e859ba 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
@@ -37,6 +37,15 @@ public class ReflectionUtils {
}
}
+ @NonNull
+ public static Method getAccessibleMethod(@NonNull Class<?> clazz, @NonNull String name,
+ @Nullable Class<?>... params) throws ReflectionException {
+ Method method = getMethod(clazz, name, params);
+ method.setAccessible(true);
+
+ return method;
+ }
+
@Nullable
public static Object invoke(@NonNull Method method, @Nullable Object object,
@Nullable Object... args) throws ReflectionException {
@@ -74,6 +83,25 @@ public class ReflectionUtils {
}
/**
+ * Looks through the class hierarchy of {@code object} at runtime and returns the class matching
+ * the name {@code className}.
+ * <p>
+ * This is used when we cannot use Class.forName() since the class we want was loaded from a
+ * different ClassLoader.
+ */
+ @NonNull
+ public static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) {
+ Class<?> superClass = object.getClass();
+ while (superClass != null) {
+ if (className.equals(superClass.getName())) {
+ return superClass;
+ }
+ superClass = superClass.getSuperclass();
+ }
+ throw new RuntimeException("invalid object/classname combination.");
+ }
+
+ /**
* Wraps all reflection related exceptions. Created since ReflectiveOperationException was
* introduced in 1.7 and we are still on 1.6
*/
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
index 465647606e52..9ee416a3b4ca 100644
--- a/tools/layoutlib/bridge/tests/Android.mk
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -17,7 +17,9 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Only compile source java files in this lib.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, res/testApp/MyApplication/src/main/myapplication.widgets)
LOCAL_JAVA_RESOURCE_DIRS := res
LOCAL_MODULE := layoutlib-tests
diff --git a/tools/layoutlib/bridge/tests/res/empty.xml b/tools/layoutlib/bridge/tests/res/empty.xml
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/empty.xml
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle
index 4561e1b80125..478166022d73 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle
@@ -19,12 +19,12 @@ allprojects {
apply plugin: 'com.android.application'
android {
- compileSdkVersion 22
- buildToolsVersion '21.1.2'
+ compileSdkVersion 25
+ buildToolsVersion '25.0.0'
defaultConfig {
applicationId 'com.android.layoutlib.test.myapplication'
minSdkVersion 21
- targetSdkVersion 22
+ targetSdkVersion 25
versionCode 1
versionName '1.0'
}
@@ -34,6 +34,9 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+ lintOptions {
+ abortOnError false
+ }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
index e0373cba84a7..f73528a556e2 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomCalendar.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomCalendar.class
deleted file mode 100644
index c3630552c7fe..000000000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomCalendar.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomDate.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomDate.class
deleted file mode 100644
index edda3de57058..000000000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomDate.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
index ec42017bad67..5bb04fc88caa 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class
new file mode 100644
index 000000000000..ff699d1412dc
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
index 0e208f2dc7de..a3931b839c80 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
index 2b77af367a38..e2936771bfa4 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
index fd01b445df0d..d6268bf9601e 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
index 91cf5b6d824b..08b98fbb6f04 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
index 6c351da69d69..f9be1ca4583c 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
index aecbff6add3b..6874b49eb009 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
index fc3f23600d86..a4205a8c4e94 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
index 83ad35bd0ab1..4fb3b61bf9d4 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
index 6d7c71995eaf..dba67fd7efcc 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
index 9bf302ad6906..f274dbfbbdd6 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
Binary files differ
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 0e788e0c8264..ef95f83f8b24 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 bad296bf4a66..6eeb82c93735 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/animated_vector.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
index 9f266278c352..26aed6a7ed7c 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
index 89009be843e7..aaf1514ddc24 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners.png
new file mode 100644
index 000000000000..868cd51f8bb5
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png
new file mode 100644
index 000000000000..601f711f32d3
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png
new file mode 100644
index 000000000000..0b8f1a96791c
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png
new file mode 100644
index 000000000000..4e448c8f2efc
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png
new file mode 100644
index 000000000000..290018b6497d
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.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 55d6a20949a2..466eca8d1721 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/golden/vector_drawable_91383.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png
new file mode 100644
index 000000000000..940fe5bc44ba
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java
index 41d75de7ada7..e7f22bfba00a 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java
@@ -1,3 +1,19 @@
+/*
+ * 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.test.myapplication;
import android.content.Context;
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomCalendar.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomCalendar.java
index 80bbaf139b4c..3b819e58e45e 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomCalendar.java
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomCalendar.java
@@ -1,4 +1,20 @@
-package com.android.layoutlib.test.myapplication;
+/*
+ * 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.test.myapplication.widgets;
import android.content.Context;
import android.util.AttributeSet;
@@ -26,6 +42,6 @@ public class CustomCalendar extends CalendarView {
}
private void init() {
- setDate(871703200000L, false, true);
+ setDate(871732800000L, false, true);
}
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomDate.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomDate.java
index cb750f49322b..f3877f1b3d90 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomDate.java
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomDate.java
@@ -1,4 +1,20 @@
-package com.android.layoutlib.test.myapplication;
+/*
+ * 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.test.myapplication.widgets;
import android.content.Context;
import android.util.AttributeSet;
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java
new file mode 100644
index 000000000000..58ad46deec2a
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * This package contains custom widgets used to set a specific time for the DayTimePicker during
+ * testing.
+ * The classes here are both used from the Android project and from the Bridge test project.
+ */
+package com.android.layoutlib.test.myapplication.widgets; \ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml
new file mode 100644
index 000000000000..42e3beb50b81
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml
@@ -0,0 +1,65 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="1102"
+ android:viewportHeight="642"
+ android:width="1102px"
+ android:height="642px">
+
+ <group
+ android:translateX="-126.347"
+ android:translateY="6.7928655e-4">
+
+
+ <path
+ android:fillColor="#94c147"
+ android:pathData="
+ m 552.777,147.57
+ c -14.147,0 -25.622,11.652 -25.622,26.02
+ v 101.68
+ c 0,14.372 11.475,26.019 25.622,26.019 14.147,0 25.61,-11.646 25.61,-26.019
+ V 173.59
+ c 0.001,-14.368 -11.462,-26.02 -25.61,-26.02
+ z
+
+ m -309.011,0
+ c -14.155,0 -25.618,11.652 -25.618,26.02
+ v 101.68
+ c 0,14.372 11.462,26.019 25.618,26.019 14.153,0 25.623,-11.646 25.623,-26.019
+ V 173.59
+ c -0.008,-14.368 -11.475,-26.02 -25.623,-26.02
+ z" />
+
+
+ <path
+ android:fillColor="#94c147"
+ android:pathData="m 284.799,148.364 v 185.768 c 0,11.035 8.948,19.98 19.983,19.98 h 22.815 v 56.567 c 0,14.37 11.47,26.016 25.623,26.016 14.148,0 25.623,-11.646 25.623,-26.016 v -56.567 h 39.878 v 56.567 c 0,14.37 11.463,26.016 25.61,26.016 14.147,0 25.622,-11.646 25.622,-26.016 v -56.567 h 22.828 c 11.022,0 19.971,-8.953 19.971,-19.98 V 148.364 H 284.799 l 0,0 z" />
+
+ <group
+ android:name="head"
+ android:pivotX="400"
+ android:pivotY="131.105">
+
+ <path
+ android:fillColor="#94c147"
+ android:pathData="m 452.302,52.105 21.057,-30.572 c 1.245,-1.819 0.939,-4.199 -0.695,-5.329 -1.637,-1.123 -3.968,-0.568 -5.225,1.251 l -21.875,31.75 c -14.404,-5.682 -30.418,-8.844 -47.293,-8.844 -16.87,0 -32.893,3.162 -47.297,8.844 l -21.875,-31.75 c -1.257,-1.819 -3.589,-2.375 -5.225,-1.251 -1.636,1.124 -1.946,3.509 -0.696,5.329 l 21.057,30.572 c -33.464,15.57 -56.951,45.166 -59.941,79.706 H 512.25 C 509.259,97.271 485.772,67.676 452.302,52.105 z M 350.187,100.28 c -6.965,0 -12.617,-5.646 -12.617,-12.616 0,-6.958 5.647,-12.61 12.617,-12.61 6.97,0 12.603,5.652 12.603,12.61 0,6.965 -5.64,12.616 -12.603,12.616 z m 97.744,0 c -6.97,0 -12.609,-5.646 -12.609,-12.616 0,-6.958 5.64,-12.61 12.609,-12.61 6.971,0 12.61,5.652 12.61,12.61 0,6.965 -5.64,12.616 -12.61,12.616 z" />
+ </group>
+
+ </group>
+
+</vector> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml
new file mode 100644
index 000000000000..897c4113ead6
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml
@@ -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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="150dp"
+ android:height="150dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="m12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h4v1h-7v2h6c1.66,0 3,-1.34 3,-3V10c0,-4.97 -4.03,-9 -9,-9z"/>
+</vector> \ No newline at end of file
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 32e6e732bb5a..0998b25a221e 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
@@ -4,7 +4,8 @@
android:height="76dp"
android:width="76dp"
android:viewportHeight="48"
- android:viewportWidth="48">
+ android:viewportWidth="48"
+ android:alpha="0.6">
<group
android:name="root"
@@ -79,7 +80,7 @@
android:fillType="nonZero"
android:strokeWidth="1"
android:strokeColor="#AABBCC"
- android:fillColor="#AAEFCC"
+ android:fillColor="#40AAEFCC"
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>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml
index 50646ab4c388..e9aa9e1378fb 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<com.android.layoutlib.test.myapplication.ArraysCheckWidget xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.layoutlib.test.myapplication.ArraysCheckWidget
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/four_corners.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/four_corners.xml
new file mode 100644
index 000000000000..c42284a262ae
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/four_corners.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true"
+ android:textSize="40sp"
+ android:text="Bottom Text" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:textSize="40sp"
+ android:text="Top text" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:textSize="40sp"
+ android:text="Start text" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:textSize="40sp"
+ android:text="End text" />
+</RelativeLayout> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml
index c1f663e9fa8a..ff14ce0a6298 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml
@@ -15,10 +15,10 @@
android:checked="true"
android:layout_gravity="center"
/>
- <com.android.layoutlib.test.myapplication.CustomDate
+ <com.android.layoutlib.test.myapplication.widgets.CustomDate
android:layout_width="100dp"
android:layout_height="wrap_content"/>
- <com.android.layoutlib.test.myapplication.CustomCalendar
+ <com.android.layoutlib.test.myapplication.widgets.CustomCalendar
android:layout_width="200dp"
android:layout_gravity="center_horizontal"
android:layout_height="200dp"/>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml
new file mode 100644
index 000000000000..14b93f364a35
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ 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.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin">
+
+ <TextView
+ android:text="@string/hello_world"
+ android:layout_width="wrap_content"
+ android:layout_height="200dp"
+ android:background="#FF0000"
+ android:id="@+id/text1"/>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml
new file mode 100644
index 000000000000..3b01ea093122
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml
@@ -0,0 +1,34 @@
+<?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"
+ android:padding="16dp"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/android"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/headset"/>
+
+</LinearLayout>
+
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
index 88c9cbcb250a..c8a5fec71f09 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
@@ -3,7 +3,7 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<item name="myattr">@integer/ten</item>
- <!-- Customize your theme here. -->
+ <item name="android:animateFirstView">false</item>
</style>
</resources>
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 8f9fa8a2bcf6..d3f0f893f2ec 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
@@ -64,7 +64,7 @@ public class ImageUtils {
double scale = THUMBNAIL_SIZE / (double)maxDimension;
BufferedImage thumbnail = scale(image, scale, scale);
- InputStream is = ImageUtils.class.getResourceAsStream(relativePath);
+ InputStream is = ImageUtils.class.getClassLoader().getResourceAsStream(relativePath);
if (is == null) {
String message = "Unable to load golden thumbnail: " + relativePath + "\n";
message = saveImageAndAppendMessage(thumbnail, message, relativePath);
@@ -179,7 +179,7 @@ public class ImageUtils {
g.drawString("Actual", 2 * imageWidth + 10, 20);
}
- File output = new File(getTempDir(), "delta-" + imageName);
+ File output = new File(getFailureDir(), "delta-" + imageName);
if (output.exists()) {
boolean deleted = output.delete();
assertTrue(deleted);
@@ -302,15 +302,16 @@ public class ImageUtils {
}
/**
- * Temp directory where to write the thumbnails and deltas.
+ * Directory where to write the thumbnails and deltas.
*/
@NonNull
- private static File getTempDir() {
- if (System.getProperty("os.name").equals("Mac OS X")) {
- return new File("/tmp"); //$NON-NLS-1$
- }
+ private static File getFailureDir() {
+ String workingDirString = System.getProperty("user.dir");
+ File failureDir = new File(workingDirString, "out/failures");
- return new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
+ //noinspection ResultOfMethodCallIgnored
+ failureDir.mkdirs();
+ return failureDir; //$NON-NLS-1$
}
/**
@@ -319,7 +320,7 @@ public class ImageUtils {
@NonNull
private static String saveImageAndAppendMessage(@NonNull BufferedImage image,
@NonNull String initialMessage, @NonNull String relativePath) throws IOException {
- File output = new File(getTempDir(), getName(relativePath));
+ File output = new File(getFailureDir(), getName(relativePath));
if (output.exists()) {
boolean deleted = output.delete();
assertTrue(deleted);
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 8f570aee96b7..c813a1292318 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
@@ -39,6 +39,8 @@ 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.resources.ScreenOrientation;
+import com.android.tools.layoutlib.java.System_Delegate;
import com.android.utils.ILogger;
import org.junit.AfterClass;
@@ -51,6 +53,7 @@ import org.junit.runner.Description;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.PackageInstaller.Session;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -58,11 +61,28 @@ import android.util.DisplayMetrics;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.CopyOption;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiPredicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
import com.google.android.collect.Lists;
@@ -102,9 +122,11 @@ public class Main {
private static final String PLATFORM_DIR;
private static final String TEST_RES_DIR;
/** Location of the app to test inside {@link #TEST_RES_DIR}*/
- private static final String APP_TEST_DIR = "/testApp/MyApplication";
+ private static final String APP_TEST_DIR = "testApp/MyApplication";
/** Location of the app's res dir inside {@link #TEST_RES_DIR}*/
private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
+ private static final String APP_CLASSES_LOCATION =
+ APP_TEST_DIR + "/build/intermediates/classes/debug/";
private static LayoutLog sLayoutLibLog;
private static FrameworkResources sFrameworkRepo;
@@ -115,8 +137,12 @@ public class Main {
/** List of log messages generated by a render call. It can be used to find specific errors */
private static ArrayList<String> sRenderMessages = Lists.newArrayList();
+ // Default class loader with access to the app classes
+ private ClassLoader mDefaultClassLoader =
+ new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
+
@Rule
- public static TestWatcher sRenderMessageWatcher = new TestWatcher() {
+ public TestWatcher sRenderMessageWatcher = new TestWatcher() {
@Override
protected void succeeded(Description description) {
// We only check error messages if the rest of the test case was successful.
@@ -192,6 +218,7 @@ public class Main {
}
File[] hosts = host.listFiles(path -> path.isDirectory() &&
(path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
+ assert hosts != null;
for (File hostOut : hosts) {
String platformDir = getPlatformDirFromHostOut(hostOut);
if (platformDir != null) {
@@ -213,6 +240,7 @@ public class Main {
// We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
return path.isDirectory() && path.getName().startsWith("sdk");
});
+ assert sdkDirs != null;
for (File dir : sdkDirs) {
String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
if (platformDir != null) {
@@ -225,6 +253,7 @@ public class Main {
private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
File[] possibleSdks = sdkDir.listFiles(
path -> path.isDirectory() && path.getName().contains("android-sdk"));
+ assert possibleSdks != null;
for (File possibleSdk : possibleSdks) {
File platformsDir = new File(possibleSdk, "platforms");
File[] platforms = platformsDir.listFiles(
@@ -284,7 +313,8 @@ public class Main {
sFrameworkRepo.loadPublicResources(getLogger());
sProjectResources =
- new ResourceRepository(new FolderWrapper(TEST_RES_DIR + APP_TEST_RES), false) {
+ new ResourceRepository(new FolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES),
+ false) {
@NonNull
@Override
protected ResourceItem createResourceItem(@NonNull String name) {
@@ -312,6 +342,46 @@ public class Main {
renderAndVerify("activity.xml", "activity.png");
}
+ @Test
+ public void testTranslucentBars() throws ClassNotFoundException {
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ LayoutPullParser parser = createLayoutPullParser("four_corners.xml");
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
+ RenderingMode.NORMAL, 22);
+ renderAndVerify(params, "four_corners_translucent.png");
+
+ parser = createLayoutPullParser("four_corners.xml");
+ params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND,
+ layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
+ RenderingMode.NORMAL, 22);
+ renderAndVerify(params, "four_corners_translucent_land.png");
+
+ parser = createLayoutPullParser("four_corners.xml");
+ params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+ RenderingMode.NORMAL, 22);
+ renderAndVerify(params, "four_corners.png");
+ }
+
+ private static void gc() {
+ // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
+ Object obj = new Object();
+ WeakReference ref = new WeakReference<>(obj);
+ //noinspection UnusedAssignment
+ obj = null;
+ while(ref.get() != null) {
+ System.gc();
+ System.runFinalization();
+ }
+
+ System.gc();
+ System.runFinalization();
+ }
+
/** Test allwidgets.xml */
@Test
public void testAllWidgets() throws ClassNotFoundException {
@@ -334,14 +404,34 @@ public class Main {
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();
- }
+ @Test
+ public void testActivityActionBar() throws ClassNotFoundException {
+ LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+ RenderingMode.V_SCROLL, 22);
+
+ renderAndVerify(params, "simple_activity_noactionbar.png");
+
+ parser = createLayoutPullParser("simple_activity.xml");
+ params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.Light", false,
+ RenderingMode.V_SCROLL, 22);
+
+ renderAndVerify(params, "simple_activity.png");
+
+ // This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are
+ // displaying menus.
+ parser = createLayoutPullParser("simple_activity.xml");
+ params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+ RenderingMode.V_SCROLL, 22);
+ params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu");
+ renderAndVerify(params, "simple_activity.png");
}
@AfterClass
@@ -364,7 +454,8 @@ public class Main {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml");
// Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
ConfigGenerator customConfigGenerator = new ConfigGenerator()
@@ -398,7 +489,8 @@ public class Main {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml");
// Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
@@ -423,7 +515,8 @@ public class Main {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml");
// Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
@@ -433,13 +526,33 @@ public class Main {
renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
}
+ /**
+ * Regression test for http://b.android.com/91383 and http://b.android.com/203797
+ */
+ @Test
+ public void testVectorDrawable91383() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = createLayoutPullParser("vector_drawable_android.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+
+ renderAndVerify(params, "vector_drawable_91383.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());
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
@@ -450,6 +563,7 @@ public class Main {
RenderResult result = renderAndVerify(params, "scrolled.png");
assertNotNull(result);
+ assertNotNull(result.getResult());
assertTrue(result.getResult().isSuccess());
ViewInfo rootLayout = result.getRootViews().get(0);
@@ -469,7 +583,15 @@ public class Main {
@Test
public void testGetResourceNameVariants() throws Exception {
// Setup
- SessionParams params = createSessionParams("", ConfigGenerator.NEXUS_4);
+ // Create the layout pull parser for our resources (empty.xml can not be part of the test
+ // app as it won't compile).
+ LayoutPullParser parser = new LayoutPullParser("/empty.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
+ layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
AssetManager assetManager = AssetManager.getSystem();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
@@ -511,6 +633,8 @@ public class Main {
throws ClassNotFoundException {
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
+ System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
+ System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
RenderSession session = sBridge.createSession(params);
if (frameTimeNanos != -1) {
@@ -578,7 +702,8 @@ public class Main {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser(layoutFileName);
// Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java
new file mode 100644
index 000000000000..3fac7782b3ae
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java
@@ -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.
+ */
+
+package com.android.layoutlib.bridge.intensive;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import libcore.io.Streams;
+
+/**
+ * Module class loader that loads classes from the test project.
+ */
+public class ModuleClassLoader extends ClassLoader {
+ private final Map<String, Class<?>> mClasses = new HashMap<>();
+ private String myModuleRoot;
+
+ /**
+ * @param moduleRoot The path to the module root
+ * @param parent The parent class loader
+ */
+ public ModuleClassLoader(String moduleRoot, ClassLoader parent) {
+ super(parent);
+ myModuleRoot = moduleRoot + (moduleRoot.endsWith("/") ? "" : "/");
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ try {
+ return super.findClass(name);
+ } catch (ClassNotFoundException ignored) {
+ }
+
+ Class<?> clazz = mClasses.get(name);
+ if (clazz == null) {
+ String path = name.replace('.', '/').concat(".class");
+ try {
+ byte[] b = Streams.readFully(getResourceAsStream(myModuleRoot + path));
+ clazz = defineClass(name, b, 0, b.length);
+ mClasses.put(name, clazz);
+ } catch (IOException ignore) {
+ throw new ClassNotFoundException(name + " not found");
+ }
+ }
+
+ return clazz;
+ }
+}
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 96ae523006b3..bae2dc0c25f5 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
@@ -42,6 +42,9 @@ import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
import java.util.Map;
import com.google.android.collect.Maps;
@@ -59,10 +62,11 @@ public class LayoutLibTestCallback extends LayoutlibCallback {
private final Map<ResourceType, Map<String, Integer>> mResources = Maps.newHashMap();
private final ILogger mLog;
private final ActionBarCallback mActionBarCallback = new ActionBarCallback();
- private final ClassLoader mModuleClassLoader = new ModuleClassLoader(PROJECT_CLASSES_LOCATION);
+ private final ClassLoader mModuleClassLoader;
- public LayoutLibTestCallback(ILogger logger) {
+ public LayoutLibTestCallback(ILogger logger, ClassLoader classLoader) {
mLog = logger;
+ mModuleClassLoader = classLoader;
}
public void initResources() throws ClassNotFoundException {
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 111049474461..bc8083f9c40f 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
@@ -42,9 +42,11 @@ public class LayoutPullParser extends KXmlParser implements ILayoutPullParser{
* @param layoutPath Must start with '/' and be relative to test resources.
*/
public LayoutPullParser(String layoutPath) {
- assert layoutPath.startsWith("/");
+ if (layoutPath.startsWith("/")) {
+ layoutPath = layoutPath.substring(1);
+ }
try {
- init(getClass().getResourceAsStream(layoutPath));
+ init(getClass().getClassLoader().getResourceAsStream(layoutPath));
} catch (XmlPullParserException e) {
throw new IOError(e);
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java
deleted file mode 100644
index 110f4c8b9795..000000000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java
+++ /dev/null
@@ -1,67 +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.
- */
-
-package com.android.layoutlib.bridge.intensive.setup;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Map;
-
-import com.google.android.collect.Maps;
-
-/**
- * The ClassLoader to load the project's classes.
- */
-public class ModuleClassLoader extends ClassLoader {
-
- private final Map<String, Class<?>> mClasses = Maps.newHashMap();
- private final String mClassLocation;
-
- public ModuleClassLoader(String classLocation) {
- mClassLocation = classLocation;
- }
-
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- Class<?> aClass = mClasses.get(name);
- if (aClass != null) {
- return aClass;
- }
- String pathName = mClassLocation.concat(name.replace('.', '/')).concat(".class");
- InputStream classInputStream = getClass().getResourceAsStream(pathName);
- if (classInputStream == null) {
- throw new ClassNotFoundException("Unable to find class " + name + " at " + pathName);
- }
- byte[] data;
- try {
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- int nRead;
- data = new byte[16384]; // 16k
- while ((nRead = classInputStream.read(data, 0, data.length)) != -1) {
- buffer.write(data, 0, nRead);
- }
- buffer.flush();
- data = buffer.toByteArray();
- } catch (IOException e) {
- // Wrap the exception with ClassNotFoundException so that caller can deal with it.
- throw new ClassNotFoundException("Unable to load class " + name, e);
- }
- aClass = defineClass(name, data, 0, data.length);
- mClasses.put(name, aClass);
- return aClass;
- }
-}
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 3b376123daa4..a2f8372d6eb8 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
@@ -26,6 +26,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ListIterator;
@@ -48,8 +49,6 @@ public class AsmGenerator {
private final String mOsDestJar;
/** List of classes to inject in the final JAR from _this_ archive. */
private final Class<?>[] mInjectClasses;
- /** The set of methods to stub out. */
- private final Set<String> mStubMethods;
/** All classes to output as-is, except if they have native methods. */
private Map<String, ClassReader> mKeep;
/** All dependencies that must be completely stubbed. */
@@ -107,7 +106,6 @@ public class AsmGenerator {
}
}
mInjectClasses = injectedClasses.toArray(new Class<?>[0]);
- mStubMethods = new HashSet<>(Arrays.asList(createInfo.getOverriddenMethods()));
// Create the map/set of methods to change to delegates
mDelegateMethods = new HashMap<>();
@@ -384,7 +382,7 @@ public class AsmGenerator {
if (mInjectedMethodsMap.keySet().contains(binaryNewName)) {
cv = new InjectMethodsAdapter(cv, mInjectedMethodsMap.get(binaryNewName));
}
- cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className),
+ cv = new TransformClassAdapter(mLog, Collections.emptySet(), mDeleteReturns.get(className),
newName, cv, stubNativesOnly);
Set<String> delegateMethods = mDelegateMethods.get(className);
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 061bed7b7740..a23bad6ed480 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
@@ -64,18 +64,6 @@ public final class CreateInfo implements ICreateInfo {
}
/**
- * Returns The list of methods to stub out. Each entry must be in the form
- * "package.package.OuterClass$InnerClass#MethodName".
- * The list can be empty but must not be null.
- * <p/>
- * This usage is deprecated. Please use method 'delegates' instead.
- */
- @Override
- public String[] getOverriddenMethods() {
- return OVERRIDDEN_METHODS;
- }
-
- /**
* Returns the list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
* The list can be empty but must not be null.
@@ -186,11 +174,13 @@ public final class CreateInfo implements ICreateInfo {
"android.content.res.Resources$Theme#resolveAttributes",
"android.content.res.AssetManager#newTheme",
"android.content.res.AssetManager#deleteTheme",
+ "android.content.res.AssetManager#getAssignedPackageIdentifiers",
"android.content.res.TypedArray#getValueAt",
"android.content.res.TypedArray#obtain",
"android.graphics.BitmapFactory#finishDecode",
"android.graphics.BitmapFactory#setDensityFromOptions",
"android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget",
+ "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#onDraw",
"android.graphics.drawable.GradientDrawable#buildRing",
"android.graphics.FontFamily#addFont",
"android.graphics.Typeface#getSystemFontConfigLocation",
@@ -217,7 +207,7 @@ public final class CreateInfo implements ICreateInfo {
"android.view.MenuInflater#registerMenu",
"android.view.RenderNode#getMatrix",
"android.view.RenderNode#nCreate",
- "android.view.RenderNode#nDestroyRenderNode",
+ "android.view.RenderNode#nGetNativeFinalizer",
"android.view.RenderNode#nSetElevation",
"android.view.RenderNode#nGetElevation",
"android.view.RenderNode#nSetTranslationX",
@@ -245,7 +235,6 @@ public final class CreateInfo implements ICreateInfo {
"android.view.ViewGroup#drawChild",
"com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
- "com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
"dalvik.system.VMRuntime#newUnpaddedArray",
"libcore.io.MemoryMappedFile#mmapRO",
"libcore.io.MemoryMappedFile#close",
@@ -258,6 +247,7 @@ public final class CreateInfo implements ICreateInfo {
*/
public final static String[] DELEGATE_CLASS_NATIVES = new String[] {
"android.animation.PropertyValuesHolder",
+ "android.graphics.BaseCanvas",
"android.graphics.Bitmap",
"android.graphics.BitmapFactory",
"android.graphics.BitmapShader",
@@ -286,7 +276,6 @@ public final class CreateInfo implements ICreateInfo {
"android.graphics.PathEffect",
"android.graphics.PathMeasure",
"android.graphics.PorterDuffColorFilter",
- "android.graphics.PorterDuffXfermode",
"android.graphics.RadialGradient",
"android.graphics.Rasterizer",
"android.graphics.Region",
@@ -294,7 +283,6 @@ public final class CreateInfo implements ICreateInfo {
"android.graphics.SumPathEffect",
"android.graphics.SweepGradient",
"android.graphics.Typeface",
- "android.graphics.Xfermode",
"android.graphics.drawable.AnimatedVectorDrawable",
"android.graphics.drawable.VectorDrawable",
"android.os.SystemClock",
@@ -309,20 +297,13 @@ public final class CreateInfo implements ICreateInfo {
};
/**
- * The list of methods to stub out. Each entry must be in the form
- * "package.package.OuterClass$InnerClass#MethodName".
- * This usage is deprecated. Please use method 'delegates' instead.
- */
- private final static String[] OVERRIDDEN_METHODS = new String[] {
- };
-
- /**
* The list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
*/
private final static String[] RENAMED_CLASSES =
new String[] {
"android.os.ServiceManager", "android.os._Original_ServiceManager",
+ "android.view.textservice.TextServicesManager", "android.view.textservice._Original_TextServicesManager",
"android.util.LruCache", "android.util._Original_LruCache",
"android.view.SurfaceView", "android.view._Original_SurfaceView",
"android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager",
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
index 6c62423a2a89..535a9a8c0b77 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -45,13 +45,6 @@ public interface ICreateInfo {
String[] getDelegateClassNatives();
/**
- * Returns The list of methods to stub out. Each entry must be in the form
- * "package.package.OuterClass$InnerClass#MethodName".
- * The list can be empty but must not be null.
- */
- String[] getOverriddenMethods();
-
- /**
* Returns the list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
* The list can be empty but must not be null.
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 9bb91e599d77..4b6cf4354d91 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
@@ -124,6 +124,7 @@ public class Main {
"android.annotation.NonNull", // annotations
"android.annotation.Nullable", // annotations
"com.android.internal.transition.EpicenterTranslateClipReveal",
+ "com.android.internal.graphics.drawable.AnimationScaleListDrawable",
},
excludeClasses,
new String[] {
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 c4dd7eeafbba..0560d8aca1bd 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
@@ -107,12 +107,6 @@ public class AsmGeneratorTest {
}
@Override
- public String[] getOverriddenMethods() {
- // methods to force override
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
return new String[] {
@@ -187,12 +181,6 @@ public class AsmGeneratorTest {
}
@Override
- public String[] getOverriddenMethods() {
- // methods to force override
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
return EMPTY_STRING_ARRAY;
@@ -274,12 +262,6 @@ public class AsmGeneratorTest {
}
@Override
- public String[] getOverriddenMethods() {
- // methods to force override
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
return EMPTY_STRING_ARRAY;
@@ -360,12 +342,6 @@ public class AsmGeneratorTest {
}
@Override
- public String[] getOverriddenMethods() {
- // methods to force override
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
return EMPTY_STRING_ARRAY;
diff --git a/tools/streaming_proto/Android.mk b/tools/streaming_proto/Android.mk
new file mode 100644
index 000000000000..5a54fd10415d
--- /dev/null
+++ b/tools/streaming_proto/Android.mk
@@ -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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+# ==========================================================
+# Build the host executable: protoc-gen-javastream
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := protoc-gen-javastream
+LOCAL_SRC_FILES := \
+ Errors.cpp \
+ string_utils.cpp \
+ main.cpp
+LOCAL_SHARED_LIBRARIES := \
+ libprotoc
+include $(BUILD_HOST_EXECUTABLE)
+
+# ==========================================================
+# Build the java test
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, test) \
+ $(call all-proto-files-under, test)
+LOCAL_MODULE := StreamingProtoTest
+LOCAL_PROTOC_OPTIMIZE_TYPE := stream
+include $(BUILD_JAVA_LIBRARY)
+
diff --git a/tools/streaming_proto/Errors.cpp b/tools/streaming_proto/Errors.cpp
new file mode 100644
index 000000000000..91c6b9245de0
--- /dev/null
+++ b/tools/streaming_proto/Errors.cpp
@@ -0,0 +1,87 @@
+#include "Errors.h"
+
+#include <stdlib.h>
+
+namespace android {
+namespace javastream_proto {
+
+Errors ERRORS;
+
+const string UNKNOWN_FILE;
+const int UNKNOWN_LINE = 0;
+
+Error::Error()
+{
+}
+
+Error::Error(const Error& that)
+ :filename(that.filename),
+ lineno(that.lineno),
+ message(that.message)
+{
+}
+
+Error::Error(const string& f, int l, const char* m)
+ :filename(f),
+ lineno(l),
+ message(m)
+{
+}
+
+Errors::Errors()
+ :m_errors()
+{
+}
+
+Errors::~Errors()
+{
+}
+
+void
+Errors::Add(const string& filename, int lineno, const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ AddImpl(filename, lineno, format, args);
+ va_end(args);
+}
+
+void
+Errors::AddImpl(const string& filename, int lineno, const char* format, va_list args)
+{
+ va_list args2;
+ va_copy(args2, args);
+ int message_size = vsnprintf((char*)NULL, 0, format, args2);
+ va_end(args2);
+
+ char* buffer = new char[message_size+1];
+ vsnprintf(buffer, message_size, format, args);
+ Error error(filename, lineno, buffer);
+ delete[] buffer;
+
+ m_errors.push_back(error);
+}
+
+void
+Errors::Print() const
+{
+ for (vector<Error>::const_iterator it = m_errors.begin(); it != m_errors.end(); it++) {
+ if (it->filename == UNKNOWN_FILE) {
+ fprintf(stderr, "%s", it->message.c_str());
+ } else if (it->lineno == UNKNOWN_LINE) {
+ fprintf(stderr, "%s:%s", it->filename.c_str(), it->message.c_str());
+ } else {
+ fprintf(stderr, "%s:%d:%s", it->filename.c_str(), it->lineno, it->message.c_str());
+ }
+ }
+}
+
+bool
+Errors::HasErrors() const
+{
+ return m_errors.size() > 0;
+}
+
+} // namespace javastream_proto
+} // namespace android
+
diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h
new file mode 100644
index 000000000000..109195a20b06
--- /dev/null
+++ b/tools/streaming_proto/Errors.h
@@ -0,0 +1,48 @@
+#include <stdio.h>
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace javastream_proto {
+
+using namespace std;
+
+struct Error
+{
+ Error();
+ explicit Error(const Error& that);
+ Error(const string& filename, int lineno, const char* message);
+
+ string filename;
+ int lineno;
+ string message;
+};
+
+class Errors
+{
+public:
+ Errors();
+ ~Errors();
+
+ // Add an error
+ void Add(const string& filename, int lineno, const char* format, ...);
+
+ // Print the errors to stderr if there are any.
+ void Print() const;
+
+ bool HasErrors() const;
+
+private:
+ // The errors that have been added
+ vector<Error> m_errors;
+ void AddImpl(const string& filename, int lineno, const char* format, va_list ap);
+};
+
+extern Errors ERRORS;
+extern const string UNKNOWN_FILE;
+extern const int UNKNOWN_LINE;
+
+
+} // namespace javastream_proto
+} // namespace android
diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp
new file mode 100644
index 000000000000..5b4ba04b2b58
--- /dev/null
+++ b/tools/streaming_proto/main.cpp
@@ -0,0 +1,474 @@
+#include "Errors.h"
+
+#include "string_utils.h"
+
+#include "google/protobuf/compiler/plugin.pb.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+#include "google/protobuf/text_format.h"
+
+#include <stdio.h>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <map>
+
+using namespace android::javastream_proto;
+using namespace google::protobuf;
+using namespace google::protobuf::compiler;
+using namespace google::protobuf::io;
+using namespace std;
+
+const int FIELD_TYPE_SHIFT = 32;
+const uint64_t FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+
+const int FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
+
+
+/**
+ * See if this is the file for this request, and not one of the imported ones.
+ */
+static bool
+should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
+{
+ const int N = request.file_to_generate_size();
+ for (int i=0; i<N; i++) {
+ if (request.file_to_generate(i) == file) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string
+make_outer_class_name(const FileDescriptorProto& file_descriptor)
+{
+ string name = file_descriptor.options().java_outer_classname();
+ if (name.size() == 0) {
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ if (name.size() == 0) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+ "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+ }
+ return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string
+make_java_package(const FileDescriptorProto& file_descriptor) {
+ if (file_descriptor.options().has_java_package()) {
+ return file_descriptor.options().java_package();
+ } else {
+ return file_descriptor.package();
+ }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string
+make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
+{
+ string const package = make_java_package(file_descriptor);
+ string result;
+ if (package.size() > 0) {
+ result = replace_string(package, '.', '/');
+ result += '/';
+ }
+
+ result += class_name;
+ result += ".java";
+
+ return result;
+}
+
+static string
+indent_more(const string& indent)
+{
+ return indent + " ";
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void
+write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
+{
+ const int N = enu.value_size();
+ text << indent << "// enum " << enu.name() << endl;
+ for (int i=0; i<N; i++) {
+ const EnumValueDescriptorProto& value = enu.value(i);
+ text << indent << "public static final int "
+ << make_constant_name(value.name())
+ << " = " << value.number() << ";" << endl;
+ }
+ text << endl;
+}
+
+/**
+ * Get the string name for a field.
+ */
+static string
+get_proto_type(const FieldDescriptorProto& field)
+{
+ switch (field.type()) {
+ case FieldDescriptorProto::TYPE_DOUBLE:
+ return "double";
+ case FieldDescriptorProto::TYPE_FLOAT:
+ return "float";
+ case FieldDescriptorProto::TYPE_INT64:
+ return "int64";
+ case FieldDescriptorProto::TYPE_UINT64:
+ return "uint64";
+ case FieldDescriptorProto::TYPE_INT32:
+ return "int32";
+ case FieldDescriptorProto::TYPE_FIXED64:
+ return "fixed64";
+ case FieldDescriptorProto::TYPE_FIXED32:
+ return "fixed32";
+ case FieldDescriptorProto::TYPE_BOOL:
+ return "bool";
+ case FieldDescriptorProto::TYPE_STRING:
+ return "string";
+ case FieldDescriptorProto::TYPE_GROUP:
+ return "group<unsupported!>";
+ case FieldDescriptorProto::TYPE_MESSAGE:
+ return field.type_name();
+ case FieldDescriptorProto::TYPE_BYTES:
+ return "bytes";
+ case FieldDescriptorProto::TYPE_UINT32:
+ return "uint32";
+ case FieldDescriptorProto::TYPE_ENUM:
+ return field.type_name();
+ case FieldDescriptorProto::TYPE_SFIXED32:
+ return "sfixed32";
+ case FieldDescriptorProto::TYPE_SFIXED64:
+ return "sfixed64";
+ case FieldDescriptorProto::TYPE_SINT32:
+ return "sint32";
+ case FieldDescriptorProto::TYPE_SINT64:
+ return "sint64";
+ default:
+ // won't happen
+ return "void";
+ }
+}
+
+static uint64_t
+get_field_id(const FieldDescriptorProto& field)
+{
+ // Number
+ uint64_t result = (uint32_t)field.number();
+
+ // Type
+ switch (field.type()) {
+ case FieldDescriptorProto::TYPE_DOUBLE:
+ result |= FIELD_TYPE_DOUBLE;
+ break;
+ case FieldDescriptorProto::TYPE_FLOAT:
+ result |= FIELD_TYPE_FLOAT;
+ break;
+ case FieldDescriptorProto::TYPE_INT64:
+ result |= FIELD_TYPE_INT64;
+ break;
+ case FieldDescriptorProto::TYPE_UINT64:
+ result |= FIELD_TYPE_UINT64;
+ break;
+ case FieldDescriptorProto::TYPE_INT32:
+ result |= FIELD_TYPE_INT32;
+ break;
+ case FieldDescriptorProto::TYPE_FIXED64:
+ result |= FIELD_TYPE_FIXED64;
+ break;
+ case FieldDescriptorProto::TYPE_FIXED32:
+ result |= FIELD_TYPE_FIXED32;
+ break;
+ case FieldDescriptorProto::TYPE_BOOL:
+ result |= FIELD_TYPE_BOOL;
+ break;
+ case FieldDescriptorProto::TYPE_STRING:
+ result |= FIELD_TYPE_STRING;
+ break;
+ case FieldDescriptorProto::TYPE_MESSAGE:
+ result |= FIELD_TYPE_OBJECT;
+ break;
+ case FieldDescriptorProto::TYPE_BYTES:
+ result |= FIELD_TYPE_BYTES;
+ break;
+ case FieldDescriptorProto::TYPE_UINT32:
+ result |= FIELD_TYPE_UINT32;
+ break;
+ case FieldDescriptorProto::TYPE_ENUM:
+ result |= FIELD_TYPE_ENUM;
+ break;
+ case FieldDescriptorProto::TYPE_SFIXED32:
+ result |= FIELD_TYPE_SFIXED32;
+ break;
+ case FieldDescriptorProto::TYPE_SFIXED64:
+ result |= FIELD_TYPE_SFIXED64;
+ break;
+ case FieldDescriptorProto::TYPE_SINT32:
+ result |= FIELD_TYPE_SINT32;
+ break;
+ case FieldDescriptorProto::TYPE_SINT64:
+ result |= FIELD_TYPE_SINT64;
+ break;
+ default:
+ ;
+ }
+
+ // Count
+ if (field.options().packed()) {
+ result |= FIELD_COUNT_PACKED;
+ } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
+ result |= FIELD_COUNT_REPEATED;
+ } else {
+ result |= FIELD_COUNT_SINGLE;
+ }
+
+ return result;
+}
+
+/**
+ * Write a field.
+ */
+static void
+write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
+{
+ string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
+ ? "optional " : "";
+ string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
+ ? "repeated " : "";
+ string proto_type = get_proto_type(field);
+ string packed_comment = field.options().packed()
+ ? " [packed=true]" : "";
+ text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+ << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+ text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+ ios::fmtflags fmt(text.flags());
+ text << setfill('0') << setw(16) << hex << get_field_id(field);
+ text.flags(fmt);
+
+ text << "L;" << endl;
+
+ text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void
+write_message(stringstream& text, const DescriptorProto& message, const string& indent)
+{
+ int N;
+ const string indented = indent_more(indent);
+
+ text << indent << "// message " << message.name() << endl;
+ text << indent << "public final class " << message.name() << " {" << endl;
+ text << endl;
+
+ // Enums
+ N = message.enum_type_size();
+ for (int i=0; i<N; i++) {
+ write_enum(text, message.enum_type(i), indented);
+ }
+
+ // Nested classes
+ N = message.nested_type_size();
+ for (int i=0; i<N; i++) {
+ write_message(text, message.nested_type(i), indented);
+ }
+
+ // Fields
+ N = message.field_size();
+ for (int i=0; i<N; i++) {
+ write_field(text, message.field(i), indented);
+ }
+
+ text << indent << "}" << endl;
+ text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
+ */
+static void
+write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+ const string& filename, bool generate_outer,
+ const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
+{
+ stringstream text;
+
+ string const package_name = make_java_package(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor);
+
+ text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+ text << "// source: " << file_descriptor.name() << endl << endl;
+
+ if (package_name.size() > 0) {
+ if (package_name.size() > 0) {
+ text << "package " << package_name << ";" << endl;
+ text << endl;
+ }
+ }
+
+ // This bit of policy is android api rules specific: Raw proto classes
+ // must never be in the API
+ text << "/** @hide */" << endl;
+// text << "@android.annotation.TestApi" << endl;
+
+ if (generate_outer) {
+ text << "public final class " << outer_class_name << " {" << endl;
+ text << endl;
+ }
+
+ size_t N;
+ const string indented = generate_outer ? indent_more("") : string();
+
+ N = enums.size();
+ for (size_t i=0; i<N; i++) {
+ write_enum(text, enums[i], indented);
+ }
+
+ N = messages.size();
+ for (size_t i=0; i<N; i++) {
+ write_message(text, messages[i], indented);
+ }
+
+ if (generate_outer) {
+ text << "}" << endl;
+ }
+
+ CodeGeneratorResponse::File* file_response = response->add_file();
+ file_response->set_name(filename);
+ file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class. Put all of the enums into the "outer" class.
+ */
+static void
+write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+{
+ // If there is anything to put in the outer class file, create one
+ if (file_descriptor.enum_type_size() > 0) {
+ vector<EnumDescriptorProto> enums;
+ int N = file_descriptor.enum_type_size();
+ for (int i=0; i<N; i++) {
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ true, enums, messages);
+ }
+
+ // For each of the message types, make a file
+ int N = file_descriptor.message_type_size();
+ for (int i=0; i<N; i++) {
+ vector<EnumDescriptorProto> enums;
+
+ vector<DescriptorProto> messages;
+ messages.push_back(file_descriptor.message_type(i));
+
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+ false, enums, messages);
+ }
+}
+
+static void
+write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+{
+ int N;
+
+ vector<EnumDescriptorProto> enums;
+ N = file_descriptor.enum_type_size();
+ for (int i=0; i<N; i++) {
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+ N = file_descriptor.message_type_size();
+ for (int i=0; i<N; i++) {
+ messages.push_back(file_descriptor.message_type(i));
+ }
+
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ true, enums, messages);
+}
+
+/**
+ * Main.
+ */
+int
+main(int argc, char const*const* argv)
+{
+ (void)argc;
+ (void)argv;
+
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+ CodeGeneratorRequest request;
+ CodeGeneratorResponse response;
+
+ // Read the request
+ request.ParseFromIstream(&cin);
+
+ // Build the files we need.
+ const int N = request.proto_file_size();
+ for (int i=0; i<N; i++) {
+ const FileDescriptorProto& file_descriptor = request.proto_file(i);
+ if (should_generate_for_file(request, file_descriptor.name())) {
+ if (file_descriptor.options().java_multiple_files()) {
+ write_multiple_files(&response, file_descriptor);
+ } else {
+ write_single_file(&response, file_descriptor);
+ }
+ }
+ }
+
+ // If we had errors, don't write the response. Print the errors and exit.
+ if (ERRORS.HasErrors()) {
+ ERRORS.Print();
+ return 1;
+ }
+
+ // If we didn't have errors, write the response and exit happily.
+ response.SerializeToOstream(&cout);
+ return 0;
+}
diff --git a/tools/streaming_proto/string_utils.cpp b/tools/streaming_proto/string_utils.cpp
new file mode 100644
index 000000000000..cc738c4c108e
--- /dev/null
+++ b/tools/streaming_proto/string_utils.cpp
@@ -0,0 +1,95 @@
+
+#include "string_utils.h"
+#include <iostream>
+
+namespace android {
+namespace javastream_proto {
+
+using namespace std;
+
+string
+to_camel_case(const string& str)
+{
+ string result;
+ const int N = str.size();
+ result.reserve(N);
+ bool capitalize_next = true;
+ for (int i=0; i<N; i++) {
+ char c = str[i];
+ if (c == '_') {
+ capitalize_next = true;
+ } else {
+ if (capitalize_next && c >= 'a' && c <= 'z') {
+ c = 'A' + c - 'a';
+ capitalize_next = false;
+ } else if (c >= 'A' && c <= 'Z') {
+ capitalize_next = false;
+ } else if (c >= '0' && c <= '9') {
+ capitalize_next = true;
+ } else {
+ // All other characters (e.g. non-latin) count as capital.
+ capitalize_next = false;
+ }
+ result += c;
+ }
+ }
+ return result;
+}
+
+string
+make_constant_name(const string& str)
+{
+ string result;
+ const int N = str.size();
+ bool underscore_next = false;
+ for (int i=0; i<N; i++) {
+ char c = str[i];
+ if (c >= 'A' && c <= 'Z') {
+ if (underscore_next) {
+ result += '_';
+ underscore_next = false;
+ }
+ } else if (c >= 'a' && c <= 'z') {
+ c = 'A' + c - 'a';
+ underscore_next = true;
+ } else if (c == '_') {
+ underscore_next = false;
+ }
+ result += c;
+ }
+ return result;
+}
+
+string
+file_base_name(const string& str)
+{
+ size_t start = str.rfind('/');
+ if (start == string::npos) {
+ start = 0;
+ } else {
+ start++;
+ }
+ size_t end = str.find('.', start);
+ if (end == string::npos) {
+ end = str.size();
+ }
+ return str.substr(start, end-start);
+}
+
+string
+replace_string(const string& str, const char replace, const char with)
+{
+ string result(str);
+ const int N = result.size();
+ for (int i=0; i<N; i++) {
+ if (result[i] == replace) {
+ result[i] = with;
+ }
+ }
+ return result;
+}
+
+} // namespace javastream_proto
+} // namespace android
+
+
diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h
new file mode 100644
index 000000000000..ffe83ca99704
--- /dev/null
+++ b/tools/streaming_proto/string_utils.h
@@ -0,0 +1,32 @@
+#include <string>
+
+namespace android {
+namespace javastream_proto {
+
+using namespace std;
+
+/**
+ * Capitalizes the string, removes underscores and makes the next letter
+ * capitalized, and makes the letter following numbers capitalized.
+ */
+string to_camel_case(const string& str);
+
+/**
+ * Capitalize and insert underscores for CamelCase.
+ */
+string make_constant_name(const string& str);
+
+/**
+ * Returns the part of a file name that isn't a path and isn't a type suffix.
+ */
+string file_base_name(const string& str);
+
+/**
+ * Replace all occurances of 'replace' with 'with'.
+ */
+string replace_string(const string& str, const char replace, const char with);
+
+
+} // namespace javastream_proto
+} // namespace android
+
diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/imported.proto
new file mode 100644
index 000000000000..05c8f0c54fac
--- /dev/null
+++ b/tools/streaming_proto/test/imported.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package com.android.streaming_proto_test;
+
+/**
+ * Message that is used from the other file.
+ */
+message ImportedMessage {
+ optional int32 data = 1;
+};
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
new file mode 100644
index 000000000000..1246f539b44b
--- /dev/null
+++ b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
@@ -0,0 +1,7 @@
+package com.android.streaming_proto_test;
+
+public class Main {
+ public void main(String[] argv) {
+ System.out.println("hello world");
+ }
+}
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/test.proto
new file mode 100644
index 000000000000..de80ed6612fc
--- /dev/null
+++ b/tools/streaming_proto/test/test.proto
@@ -0,0 +1,124 @@
+/*
+ * 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";
+
+import "frameworks/base/tools/streaming_proto/test/imported.proto";
+
+package com.android.streaming_proto_test;
+
+/**
+ * Enum that outside the scope of any classes.
+ */
+enum Outside {
+ OUTSIDE_0 = 0;
+ OUTSIDE_1 = 1;
+};
+
+message Sibling {
+ optional int32 int32_field = 1;
+}
+
+/**
+ * Message with all of the field types.
+ */
+message All {
+ /**
+ * Enum that is inside the scope of a class.
+ */
+ enum Inside {
+ option allow_alias = true;
+ INSIDE_0 = 0;
+ INSIDE_1 = 1;
+ INSIDE_1A = 1;
+ };
+
+ /**
+ * Message that is recursive.
+ */
+ message Nested {
+ optional int32 data = 10001;
+ optional Nested nested = 10002;
+ };
+
+ optional double double_field = 10;
+ repeated double double_field_repeated = 11;
+ repeated double double_field_packed = 12 [packed=true];
+
+ optional float float_field = 20;
+ repeated float float_field_repeated = 21;
+ repeated float float_field_packed = 22 [packed=true];
+
+ optional int32 int32_field = 30;
+ repeated int32 int32_field_repeated = 31;
+ repeated int32 int32_field_packed = 32 [packed=true];
+
+ optional int64 int64_field = 40;
+ repeated int64 int64_field_repeated = 41;
+ repeated int64 int64_field_packed = 42 [packed=true];
+
+ optional uint32 uint32_field = 50;
+ repeated uint32 uint32_field_repeated = 51;
+ repeated uint32 uint32_field_packed = 52 [packed=true];
+
+ optional uint64 uint64_field = 60;
+ repeated uint64 uint64_field_repeated = 61;
+ repeated uint64 uint64_field_packed = 62 [packed=true];
+
+ optional sint32 sint32_field = 70;
+ repeated sint32 sint32_field_repeated = 71;
+ repeated sint32 sint32_field_packed = 72 [packed=true];
+
+ optional sint64 sint64_field = 80;
+ repeated sint64 sint64_field_repeated = 81;
+ repeated sint64 sint64_field_packed = 82 [packed=true];
+
+ optional fixed32 fixed32_field = 90;
+ repeated fixed32 fixed32_field_repeated = 91;
+ repeated fixed32 fixed32_field_packed = 92 [packed=true];
+
+ optional fixed64 fixed64_field = 100;
+ repeated fixed64 fixed64_field_repeated = 101;
+ repeated fixed64 fixed64_field_packed = 102 [packed=true];
+
+ optional sfixed32 sfixed32_field = 110;
+ repeated sfixed32 sfixed32_field_repeated = 111;
+ repeated sfixed32 sfixed32_field_packed = 112 [packed=true];
+
+ optional sfixed64 sfixed64_field = 120;
+ repeated sfixed64 sfixed64_field_repeated = 121;
+ repeated sfixed64 sfixed64_field_packed = 122 [packed=true];
+
+ optional bool bool_field = 130;
+ repeated bool bool_field_repeated = 131;
+ repeated bool bool_field_packed = 132 [packed=true];
+
+ optional string string_field = 140;
+ repeated string string_field_repeated = 141;
+
+ optional bytes bytes_field = 150;
+ repeated bytes bytes_field_repeated = 151;
+
+ optional Outside outside_field = 160;
+ repeated Outside outside_field_repeated = 161;
+ repeated Outside outside_field_packed = 162 [packed=true];
+
+ optional Nested nested_field = 170;
+ repeated Nested nested_field_repeated = 171;
+
+ optional ImportedMessage imported_field = 180;
+ repeated ImportedMessage imported_field_repeated = 181;
+};